arnaucube
arnaucube/arnaucube.comPublic Self-hosted repository
arnaucube.com/blog/flock-botnet.html
blog/flock-botnet.htmlRaw
  1<!DOCTYPE html>
  2<html lang="en">
  3
  4  <head>
  5    <meta name="description" content="Developing a twitter botnet with autonomous bots replying tweets with text generated based on probabilities in Markov chains" />
  6    <meta charset="utf-8">
  7    <title> Auto generated tweets from Markov chains - arnaucube - blog</title>
  8    <meta name="title" content=" Auto generated tweets from Markov chains - arnaucube - blog">
  9    <meta name="description" content="Developing a twitter botnet with autonomous bots replying tweets with text generated based on probabilities in Markov chains">
 10
 11    <meta property="og:title" content=" Auto generated tweets from Markov chains - arnaucube - blog" />
 12    <meta property="og:description" content="Developing a twitter botnet with autonomous bots replying tweets with text generated based on probabilities in Markov chains" />
 13    <meta property="og:url" content="https://arnaucube.com/blog/flock-botnet.html" />
 14    <meta property="og:type" content="article" />
 15    <meta property="og:image" content="https://arnaucube.com/blog/img/posts/flock-botnet/markovchain.png" />
 16    <meta name="twitter:title" content=" Auto generated tweets from Markov chains - arnaucube - blog">
 17    <meta name="twitter:description" content="Developing a twitter botnet with autonomous bots replying tweets with text generated based on probabilities in Markov chains">
 18    <meta name="twitter:image" content="https://arnaucube.com/blog/img/posts/flock-botnet/markovchain.png">
 19    <meta name="twitter:card" content="summary_large_image">
 20    <meta name="author" content="arnaucube">
 21
 22    <link rel="icon" type="image/png" href="img/logoArnauCubeFavicon.png">
 23
 24    <meta name="viewport" content="width=device-width, initial-scale=1">
 25
 26    <link href="css/bootstrap.min.css" rel="stylesheet">
 27    <link rel="stylesheet" href="css/style.css">
 28
 29    <!-- highlightjs -->
 30    <!-- <link rel="stylesheet" href="js/highlightjs/atom-one-dark.css"> -->
 31    <link rel="stylesheet" href="js/highlightjs/atom-one-light.css">
 32    <!-- <link rel="stylesheet" href="js/highlightjs/gruvbox-dark.css"> -->
 33    <script src="js/highlightjs/highlight.pack.js"></script>
 34
 35    <!-- katex -->
 36    <link rel="stylesheet" href="js/katex/katex.min.css">
 37  <link rel="alternate" type="application/rss+xml" title="arnaucube - blog RSS" href="https://arnaucube.com/blog/rss.xml">
 38</head>
 39
 40  <body>
 41
 42    <!-- o_gradient_background" -->
 43    <nav id="mainNav" class="navbar navbar-default navbar-fixed-top"
 44                      style="height:50px;font-size:100%;">
 45      <div class="container">
 46        <div style="float:left;">
 47        <a href="/blog" style="color:#000;display:inline-block;">blog</a>
 48        <span style="margin-right:20px; margin-left:20px;">|</span>
 49        <a href="/blog/notes.html" style="font-size:90%;color:#000;display:inline-block;">notes</a>
 50        </div>
 51        <div style="float:right;">
 52          <a href="/" style="color:#000;display:inline-block;margin-right:10px;">arnaucube.com</a>
 53          <a href="/blog/rss.xml" target="_blank"
 54            type="application/rss+xml" title="subscribe via RSS" aria-label="subscribe via RSS">
 55              <img width="12px;" src="img/rss.png" />
 56          </a>
 57          <div class="onoffswitch" style="margin-left:0px;display:inline-block;" title="change theme">
 58            <input onclick="switchTheme()" type="checkbox" name="onoffswitch" class="onoffswitch-checkbox"
 59              id="themeSwitcher">
 60            <label class="onoffswitch-label" for="themeSwitcher"></label>
 61          </div>
 62        </div>
 63      </div>
 64      <!-- <img style="height:5px; width:100%; margin-top:8px;" src="img/gradient-line.jpg" /> -->
 65    </nav>
 66
 67
 68    <div class="container" style="max-width:800px;">
 69        <br>
 70        <div style="background:#cccccc; border:2px gray solid; padding:5px; width:80%;margin-left:auto; margin-right:auto; margin-bottom:5px;">
 71          <i>Note: this blog is human-written, no AI generated content.</i>
 72        </div>
 73
 74      <h1>Auto generated tweets from Markov chains</h1>
 75
 76<p><em>2017-12-29</em></p>
 77
 78<p>The main goal of this article is to explain how to develop a twitter botnet with autonomous bots replying tweets with text generated based on probabilities in Markov chains. As this is a project to learn, we have made everything from scratch.</p>
 79
 80<p>The idea of mixing twitter bots with Markov chains was in a Twitter conversation with <a href="https://twitter.com/x0rz">@x0rz</a></p>
 81
 82<h2>1. Markov chains</h2>
 83
 84<p>A Markov chain is a sequence of stochastic events (based on probabilities) where the current state of a variable or system is independent of all past states, except the current state.</p>
 85
 86<p><a href="https://en.wikipedia.org/wiki/Markov_chain">https://en.wikipedia.org/wiki/Markov_chain</a></p>
 87
 88<p>In our case, we will use Markov chains to analyze the probabilities that after a word comes another concrete word. So, we will generate some diagram like the following one, but with thousands of words.</p>
 89
 90<p><img src="img/posts/flock-botnet/markovchain.png" alt="flock-botnet" title="Markov chain" /></p>
 91
 92<p>In our case, we need as input some text document with thousands of words, to get a better input data. In this example we have made it with the book &ldquo;The Critique of Pure Reason&rdquo;, by Immanuel Kant (<a href="http://www.gutenberg.org/cache/epub/4280/pg4280.txt">http://www.gutenberg.org/cache/epub/4280/pg4280.txt</a>), just because is the first book that we have found in .txt format.</p>
 93
 94<h3>1.1 Calculating the Markov chains</h3>
 95
 96<p>First we need to read the text file:</p>
 97
 98<pre><code class="language-go">func readTxt(path string) (string, error) {
 99	data, err := ioutil.ReadFile(path)
100	if err != nil {
101		//Do something
102	}
103	dataClean := strings.Replace(string(data), &quot;\n&quot;, &quot; &quot;, -1)
104	content := string(dataClean)
105	return content, err
106}
107</code></pre>
108
109<p>To calculate the probabilities of the Markov states, we have made the following function that analyzes the full input text, and stores the Markov states:</p>
110
111<pre><code class="language-go">func calcMarkovStates(words []string) []State {
112	var states []State
113	//count words
114	for i := 0; i &lt; len(words)-1; i++ {
115		var iState int
116		states, iState = addWordToStates(states, words[i])
117		if iState &lt; len(words) {
118			states[iState].NextStates, _ = addWordToStates(states[iState].NextStates, words[i+1])
119		}
120
121		printLoading(i, len(words))
122	}
123
124	//count prob
125	for i := 0; i &lt; len(states); i++ {
126		states[i].Prob = (float64(states[i].Count) / float64(len(words)) * 100)
127		for j := 0; j &lt; len(states[i].NextStates); j++ {
128			states[i].NextStates[j].Prob = (float64(states[i].NextStates[j].Count) / float64(len(words)) * 100)
129		}
130	}
131	fmt.Println(&quot;\ntotal words computed: &quot; + strconv.Itoa(len(words)))
132	return states
133}
134</code></pre>
135
136<p>The <em>printLoading</em> function, is just a simple function to print in the terminal the % of the process done:</p>
137
138<pre><code class="language-go">func printLoading(n int, total int) {
139	var bar []string
140	tantPerFourty := int((float64(n) / float64(total)) * 40)
141	tantPerCent := int((float64(n) / float64(total)) * 100)
142	for i := 0; i &lt; tantPerFourty; i++ {
143		bar = append(bar, &quot;█&quot;)
144	}
145	progressBar := strings.Join(bar, &quot;&quot;)
146	fmt.Printf(&quot;\r &quot; + progressBar + &quot; - &quot; + strconv.Itoa(tantPerCent) + &quot;&quot;)
147}
148</code></pre>
149
150<p><img src="img/posts/flock-botnet/progressbarMarkov.gif" alt="flock-botnet" title="Markov chain" /></p>
151
152<h3>1.2 Generating text from the Markov chains</h3>
153
154<p>To generate the text, we will need a initializer word and the length of the output text to generate. Then, we perform a loop and get words based on the Markov chains probabilities calculated in the previous step.</p>
155
156<pre><code class="language-go">func (markov Markov) generateText(states []State, initWord string, count int) string {
157	var generatedText []string
158	word := initWord
159	generatedText = append(generatedText, word)
160	for i := 0; i &lt; count; i++ {
161		word = getNextMarkovState(states, word)
162		if word == &quot;word no exist on the memory&quot; {
163			return &quot;word no exist on the memory&quot;
164		}
165		generatedText = append(generatedText, word)
166	}
167	text := strings.Join(generatedText, &quot; &quot;)
168	return text
169}
170</code></pre>
171
172<p>To generate the text we need a function that given the markov chains and a word, returns a random probability based word to be the next word following the given word:</p>
173
174<pre><code class="language-go">func getNextMarkovState(states []State, word string) string {
175	iState := -1
176	for i := 0; i &lt; len(states); i++ {
177		if states[i].Word == word {
178			iState = i
179		}
180	}
181	if iState &lt; 0 {
182		return &quot;word no exist on the memory&quot;
183	}
184	var next State
185	next = states[iState].NextStates[0]
186	next.Prob = rand.Float64() * states[iState].Prob
187	for i := 0; i &lt; len(states[iState].NextStates); i++ {
188		if (rand.Float64()*states[iState].NextStates[i].Prob) &gt; next.Prob &amp;&amp; states[iState-1].Word != states[iState].NextStates[i].Word {
189			next = states[iState].NextStates[i]
190		}
191	}
192	return next.Word
193}
194</code></pre>
195
196<h2>2. Twitter API</h2>
197
198<p>To interact with the Twitter API, we will use <strong>go-twitter</strong> library <a href="https://github.com/dghubble/go-twitter">https://github.com/dghubble/go-twitter</a> .</p>
199
200<p>We setup a streaming connection with the Twitter API, we will filter tweets by some words related to our input dataset:</p>
201
202<pre><code class="language-go">func startStreaming(states []State, flock Flock, flockUser *twitter.Client, botScreenName string, keywords []string) {
203	// Convenience Demux demultiplexed stream messages
204	demux := twitter.NewSwitchDemux()
205	demux.Tweet = func(tweet *twitter.Tweet) {
206		if isRT(tweet) == false &amp;&amp; isFromBot(flock, tweet) == false {
207			processTweet(states, flockUser, botScreenName, keywords, tweet)
208		}
209	}
210	demux.DM = func(dm *twitter.DirectMessage) {
211		fmt.Println(dm.SenderID)
212	}
213	demux.Event = func(event *twitter.Event) {
214		fmt.Printf(&quot;%#v\n&quot;, event)
215	}
216
217	fmt.Println(&quot;Starting Stream...&quot;)
218	// FILTER
219	filterParams := &amp;twitter.StreamFilterParams{
220		Track:         keywords,
221		StallWarnings: twitter.Bool(true),
222	}
223	stream, err := flockUser.Streams.Filter(filterParams)
224	if err != nil {
225		log.Fatal(err)
226	}
227	// Receive messages until stopped or stream quits
228	demux.HandleChan(stream.Messages)
229}
230</code></pre>
231
232<p>Then, each time that a new tweet with some of our tracking words is tweeted, we process that tweet and then we generate a reply based on the Markov chains, and we post that reply:</p>
233
234<pre><code class="language-go">func processTweet(states []State, flockUser *twitter.Client, botScreenName string, keywords []string, tweet *twitter.Tweet) {
235	c.Yellow(&quot;bot @&quot; + botScreenName + &quot; - New tweet detected:&quot;)
236	fmt.Println(tweet.Text)
237
238	tweetWords := strings.Split(tweet.Text, &quot; &quot;)
239	generatedText := &quot;word no exist on the memory&quot;
240	for i := 0; i &lt; len(tweetWords) &amp;&amp; generatedText == &quot;word no exist on the memory&quot;; i++ {
241		fmt.Println(strconv.Itoa(i) + &quot; - &quot; + tweetWords[i])
242		generatedText = generateMarkovResponse(states, tweetWords[i])
243	}
244	c.Yellow(&quot;bot @&quot; + botScreenName + &quot; posting response&quot;)
245	fmt.Println(tweet.ID)
246	replyTweet(flockUser, &quot;@&quot;+tweet.User.ScreenName+&quot; &quot;+generatedText, tweet.ID)
247	waitTime(1)
248}
249</code></pre>
250
251<pre><code class="language-go">func replyTweet(client *twitter.Client, text string, inReplyToStatusID int64) {
252	tweet, httpResp, err := client.Statuses.Update(text, &amp;twitter.StatusUpdateParams{
253		InReplyToStatusID: inReplyToStatusID,
254	})
255	if err != nil {
256		fmt.Println(err)
257	}
258	if httpResp.Status != &quot;200 OK&quot; {
259		c.Red(&quot;error: &quot; + httpResp.Status)
260		c.Purple(&quot;maybe twitter has blocked the account, CTRL+C, wait 15 minutes and try again&quot;)
261	}
262	fmt.Print(&quot;tweet posted: &quot;)
263	c.Green(tweet.Text)
264}
265</code></pre>
266
267<h2>3. Flock-Botnet, or how to avoid the Twitter API limitations</h2>
268
269<p>If you ever played with the Twitter API, you will have seen that there are some restrictions and limitations. That means that if your bot have too much posting activity, the account will get blocked for some minutes.</p>
270
271<p>To avoid this limitation, we will deploy a botnet, where each bot will be replying tweets based on the Markov chains probabilities. In this way, when a bot post a tweet reply, the bot falls asleep for 1 minute. In the meantime, the other bots will be processing and replying the other tweets.</p>
272
273<p><img src="img/posts/flock-botnet/flock-botnet-scheme.png" alt="flock-botnet" title="01" /></p>
274
275<h2>3. Putting it all together</h2>
276
277<p>In this demo, we will use only 3 bots (twitter accounts).</p>
278
279<p>The botnet configuration will be in the config.json file:</p>
280
281<pre><code class="language-json">[{
282        &quot;title&quot;: &quot;bot1&quot;,
283        &quot;consumer_key&quot;: &quot;xxxxxxxxxxxxx&quot;,
284        &quot;consumer_secret&quot;: &quot;xxxxxxxxxxxxx&quot;,
285        &quot;access_token_key&quot;: &quot;xxxxxxxxxxxxx&quot;,
286        &quot;access_token_secret&quot;: &quot;xxxxxxxxxxxxx&quot;
287    },
288    {
289        &quot;title&quot;: &quot;bot2&quot;,
290        &quot;consumer_key&quot;: &quot;xxxxxxxxxxxxx&quot;,
291        &quot;consumer_secret&quot;: &quot;xxxxxxxxxxxxx&quot;,
292        &quot;access_token_key&quot;: &quot;xxxxxxxxxxxxx&quot;,
293        &quot;access_token_secret&quot;: &quot;xxxxxxxxxxxxx&quot;
294    },
295    {
296        &quot;title&quot;: &quot;bot3&quot;,
297        &quot;consumer_key&quot;: &quot;xxxxxxxxxxxxx&quot;,
298        &quot;consumer_secret&quot;: &quot;xxxxxxxxxxxxx&quot;,
299        &quot;access_token_key&quot;: &quot;xxxxxxxxxxxxx&quot;,
300        &quot;access_token_secret&quot;: &quot;xxxxxxxxxxxxx&quot;
301    }
302]
303</code></pre>
304
305<p>The complete process will be:</p>
306
307<p><img src="img/posts/flock-botnet/steps.png" alt="flock-botnet" title="01" /></p>
308
309<h2>4. Demo</h2>
310
311<p>We have setted up a small demo with 3 bots. As we have said in the beging of this post, we have used the <a href="http://www.gutenberg.org/cache/epub/4280/pg4280.txt">“The Critique of Pure Reason”, by Immanuel Kant </a> to generate the Markov chains.</p>
312
313<p>When the botnet is up working, the bots start streaming all the twitter new tweets containing the configured keywords.</p>
314
315<p>Each bot takes a tweet, analyzes the containing words, and generates a reply using the Markov chains previously calculated, and posts the tweet as reply.</p>
316
317<p>Example of terminal view during the flock-botnet execution:</p>
318
319<p><img src="img/posts/flock-botnet/terminal00.png" alt="flock-botnet" title="01" /></p>
320
321<p>Here is an example of the execution:</p>
322
323<p><img src="img/posts/flock-botnet/flock-botnet-demo.gif" alt="flock-botnet" title="Markov chain" /></p>
324
325<p>In the following examples, there are some screenshots that the bots (&ldquo;@andreimarkov&rdquo;, &ldquo;@dodecahedron&rdquo;, &ldquo;@projectNSA&rdquo;) have been replying to some people.</p>
326
327<p><img src="img/posts/flock-botnet/01.png" alt="flock-botnet" title="01" /></p>
328
329<hr>
330
331<p><img src="img/posts/flock-botnet/02.jpeg" alt="flock-botnet" title="02" /></p>
332
333<hr>
334
335<p><img src="img/posts/flock-botnet/03.jpeg" alt="flock-botnet" title="03" /></p>
336
337<hr>
338
339<p><img src="img/posts/flock-botnet/04.jpeg" alt="flock-botnet" title="04" /></p>
340
341<h2>Conclusion</h2>
342
343<p>In this article, we have seen how to build a Twitter botnet with the bots replying tweets with text generated based on Markov chains.</p>
344
345<p>As in this article we have used only 1 grade Markov chains, so the generated text is not really like humans text. But for future projects, a good choice would be combining more grades Markov chains with other text mining techniques.</p>
346
347<p>Twitter API have lots of uses, and in this post we have seen one of them. I hope to be able to write some more articles about other projects arround the Twitter API. For example some Twitter <a href="https://devpost.com/software/projectnsa">network nodes analysis</a>, or some <a href="https://arnaucube.com/hashtagsUsersNetworkPage.html">users &amp; hashtags analysis</a>.</p>
348
349<p>The complete code of this project is able in <a href="https://github.com/arnaucube/flock-botnet">https://github.com/arnaucube/flock-botnet</a></p>
350
351<p>Project page: <a href="http://arnaucube.com/flock-botnet/">http://arnaucube.com/flock-botnet/</a></p>
352
353    </div>
354
355    <footer style="text-align:center; margin-top:100px;margin-bottom:50px;">
356      <div class="container">
357        <br>
358        <a href="/blog">Go to main</a>
359        <br><br>
360        <div class="row">
361          <ul class="list-inline">
362            <li><a href="https://twitter.com/arnaucube"
363                   style="color:gray;text-decoration:none;"
364                   target="_blank">twitter.com/arnaucube</a>
365            </li>
366            <li><a href="https://github.com/arnaucube"
367                   style="color:gray;text-decoration:none;"
368                   target="_blank">github.com/arnaucube</a>
369            </li>
370          </ul>
371        </div>
372        <div class="row" style="display:inline-block;">
373          Blog made with <a href="http://github.com/arnaucube/blogo/"
374                            target="_blank" style="color: gray;text-decoration:none;">Blogo</a>
375        </div>
376      </div>
377    </footer>
378
379    <script>
380    </script>
381    <script src="js/external-links.js"></script>
382    <script>hljs.initHighlightingOnLoad();</script>
383    <script defer src="js/katex/katex.min.js"></script>
384    <script defer src="js/katex/auto-render.min.js"></script>
385    <script>
386      document.addEventListener("DOMContentLoaded", function() {
387        renderMathInElement(document.body, {
388          displayMode: false,
389          // customised options
390          // • auto-render specific keys, e.g.:
391          delimiters: [
392            {left: '$$', right: '$$', display: true},
393            {left: '$', right: '$', display: false},
394            {left: "\\[", right: "\\]", display: true},
395            {left: "\\(", right: "\\)", display: false},
396            {left: "\\begin{equation}", right: "\\end{equation}", display: true}
397          ],
398          // • rendering keys, e.g.:
399          throwOnError : true
400        });
401      });
402
403      ///
404      let theme = localStorage.getItem("theme");
405      if ((theme === "light-theme")||(theme==null)) {
406          theme = "light-theme";
407          document.getElementById("themeSwitcher").checked = false;
408      } else if (theme === "dark-theme") {
409          theme = "dark-theme";
410          document.getElementById("themeSwitcher").checked = true;
411      }
412      document.body.className = theme;
413      localStorage.setItem("theme", theme);
414
415      function switchTheme() {
416        theme = localStorage.getItem("theme");
417        if (theme === "light-theme") {
418          theme = "dark-theme";
419          document.getElementById("themeSwitcher").checked = true;
420        } else {
421          theme = "light-theme";
422          document.getElementById("themeSwitcher").checked = false;
423        }
424        document.body.className = theme;
425        localStorage.setItem("theme", theme);
426
427        console.log(theme);
428      }
429    </script>
430    <script>
431    function tagLinks(tagName) {
432      var tags = document.getElementsByTagName(tagName);
433      for (var i=0, hElem; hElem =  tags[i]; i++) {
434        if (hElem.parentNode.className=="row postThumb") {
435          continue;
436        }
437        hElem.id = hElem.innerHTML.toLowerCase().replace(" ", "-");
438        hElem.innerHTML = "<a style='color:black!important;' href='#"+hElem.id+"'>"+hElem.innerHTML+"</a>";
439      }
440    }
441    tagLinks("h2");
442    tagLinks("h3");
443    tagLinks("h4");
444    tagLinks("h5");
445    </script>
446    <script>
447function generateTOC(tocContainerId = "toc") {
448    const tocContainer = document.getElementById(tocContainerId);
449    if (!tocContainer) return;
450
451    const headings = document.querySelectorAll("h2, h3, h4, h5, h6");
452    if (!headings.length) return;
453
454    // Generate IDs if missing
455    headings.forEach((heading, index) => {
456        if (!heading.id) {
457            heading.id =
458                heading.textContent
459                    .trim()
460                    .toLowerCase()
461                    .replace(/[^\w\s-]/g, "")
462                    .replace(/\s+/g, "-") +
463                "-" +
464                index;
465        }
466    });
467
468    const toc = document.createElement("ul");
469    let currentLevel = 1;
470    let currentList = toc;
471    const stack = [toc];
472
473    headings.forEach((heading) => {
474        const level = parseInt(heading.tagName.substring(1), 10);
475
476        const li = document.createElement("li");
477        const a = document.createElement("a");
478
479        a.href = "#" + heading.id;
480        a.textContent = heading.textContent;
481
482        li.appendChild(a);
483
484        if (level > currentLevel) {
485            for (let i = currentLevel; i < level; i++) {
486                const nestedList = document.createElement("ul");
487
488                const lastLi =
489                    currentList.lastElementChild ||
490                    currentList.appendChild(document.createElement("ul"));
491
492                lastLi.appendChild(nestedList);
493
494                stack.push(nestedList);
495                currentList = nestedList;
496            }
497        } else if (level < currentLevel) {
498            for (let i = currentLevel; i > level; i--) {
499                stack.pop();
500            }
501            currentList = stack[stack.length - 1];
502        }
503
504        currentList.appendChild(li);
505        currentLevel = level;
506    });
507
508    tocContainer.innerHTML = "";
509    tocContainer.appendChild(toc);
510}
511
512document.addEventListener("DOMContentLoaded", () => {
513    generateTOC();
514});
515    </script>
516    <script src="js/mermaid.min.js"></script>
517
518  </body>
519</html>
520