Jekyll2022-11-14T16:01:32+00:00https://francisoliver.dev/feed.xmlFrancis Oliver AvanceñaHello! I am Francis and a Management Information Systems professional that currently works in the FinTech Industry in the Philippines.GRAMMYs 2021: Predictions for Pop and Major Categories2021-03-12T16:00:00+00:002021-03-12T16:00:00+00:00https://francisoliver.dev/blog/grammys-2021-predictions-for-pop-and-major-categories<p>Since the 2021 GRAMMY nominations came out last year in 2020, I wanted to try something new. In the past few years, I would look at the nominations and forget about it and would have like a few albums that I know, but this year I wanted to listen to all major categories and the few song nominations specifically in the pop music categories. So, with this year’s GRAMMYs coming up. I would like to share my predictions of who will win among the nominees for the following categories: Album of the Year, Record of the Year, Song of the Year, Best Pop Solo Performance, Best Pop Duo/Group Performance, and Best Pop Vocal Album.</p>
<p>I will rank them based on my enjoyment of the albums/song and provide a short explanation at the end for my picks/rankings.</p>
<h3 id="album-of-the-year">Album of the Year</h3>
<ol>
<li><strong>Black Pumas (Deluxe) by Black Pumas — Predicted Winner</strong></li>
<li>Women In Music Pt. III by HAIM</li>
<li>Everyday Life by Coldplay</li>
<li>Hollywood’s Bleeding by Post Malone</li>
<li>Future Nostalgia by Dua Lipa</li>
<li>Djesse Vol. 3 by Jacob Collier</li>
<li><em>folklore by Taylor Swift — Actual Winner</em></li>
<li>Chilombo by Jhene Aiko</li>
</ol>
<p>Out of all the albums in the nominees, this list Black Pumas’s album is the one I enjoyed the most. This pick might be a bit biased because I prefer music performed on instruments but the beat and the feel of the Black Pumas (Deluxe) is superb. I never really got into the eligibility of the Black Pumas album, some people say it’s not supposed to be on here because it was not released during the period of eligibility but I do not care about that, I’m just here for the music.</p>
<p>Few notes on the other nominees, Everyday Life by Coldplay may be cheesy for some listeners but it’s maybe one of their most inspired works. Jacob Collier’s Djesse Vol. 3 is what you expect from him: crazy harmonies and complex music theory, but for me, it gets old, like he’s saying: “Look at this music theory gimmick we used for this song.” The others are just well-produced pop albums or albums with good writing but never really gets going and keeps on playing the same-sounding song over and over again.</p>
<h3 id="record-of-the-year">Record of the Year</h3>
<ol>
<li><strong>Colors by Black Pumas — Predicted Winner</strong></li>
<li><em>everything i wanted by Billie Eilish — Actual Winner</em></li>
<li>Don’t Start Now by Dua Lipa</li>
<li>Say So by Doja Cat</li>
<li>Circles by Post Malone</li>
<li>Black Parade by Beyonce</li>
<li>Rockstar by DaBabby feat. Roddy Rich</li>
<li>Savage (Remix) by Megan Thee Stallion feat. Beyonce</li>
</ol>
<p>“Colors” by Black Pumas is my most enjoyed record out of all the nominees this year. This songs’ elements, the guitars, the bass, the keys, the vocals, and drums are recorded wonderfully, and it feels like it’s a live performance and it doesn’t sound like it comes from a laptop.</p>
<p>“Don’t Start Now,” “Say So,” and “Savage (Remix)” had a gigantic 2020. In terms of the recording aspect of this category, “Don’t Start Now” has a killer bassline, that propels not just this song, but the entirety of Dua Lipa’s sophomore release. “Say So” by Doja Cat is driven by the vocals and the masterful use of post-production effects on it (especially on the chorus). “Savage (Remix)” maybe my least enjoyed song (might be because of being overplayed, thanks to TikTok), but the chords and rhythm are unique.</p>
<h3 id="song-of-the-year">Song of the Year</h3>
<ol>
<li><strong>everything i wanted by Billie Eilish — Predicted Winner</strong></li>
<li>cardigan by Taylor Swift</li>
<li>Don’t Start Now by Dua Lipa</li>
<li>If The World Was Ending by JP Saxe feat. Julia Michaels</li>
<li><em>I Can’t Breathe by H.E.R. — Actual Winner</em></li>
<li>Black Parade by Beyonce</li>
<li>The Box by Roddy Rich</li>
</ol>
<p>This category is a two-horse race between Billie Eilish and Taylor Swift, if we are all being honest. I picked Billie Eilish because I feel like it’s a much stronger song, writing-wise, and the creative production (i.e. EQ change on the vocals to make it sound like Billie is singing underwater to accent a line). FINNEAS and her are on a streak of musically and sonically interesting songs but still palatable to the general public. Taylor may not be the best chord progression writer (it hasn’t been her strong suit), but her lyrics make up for it and this single from her first surprise quarantine album shows it. When you strip away all the production and focus on the lyrics, the detail that she goes into to get her point across in this track shows how easy it is for her.</p>
<h3 id="best-pop-solo-performance">Best Pop Solo Performance</h3>
<ol>
<li><strong>everything i wanted by Billie Eilish — Predicted Winner</strong></li>
<li>cardigan by Taylor Swift</li>
<li><em>Watermelon Sugar by Harry Styles — Actual Winner</em></li>
<li>Don’t Start Now by Dua Lipa</li>
<li>Say So by Doja Cat</li>
<li>Yummy by Justin Bieber</li>
</ol>
<p>Billie wins this category again based on the reasons laid out in the Song of the Year category of this article. I can see any song on this category winning this category except for “Yummy.” That song shouldn’t even be considered for any award but it’s here 🤷♂️.</p>
<h3 id="best-pop-duogroup-performance">Best Pop Duo/Group Performance</h3>
<ol>
<li><strong>exile by Tayor Swift feat. Bon Iver — Predicted Winner</strong></li>
<li>Dynamite by BTS</li>
<li><em>Rain On Me by Lady Gaga feat. Ariana Grande — Actual Winner</em></li>
<li>Intentions by Justin Bieber feat. Quavo</li>
<li>Un Dia (One Day) by J Balvin, Dua Lipa, Bad Bunny & Tainy</li>
</ol>
<p>“Exile” wins this category by a lot. Combine Taylor’s superb lyric writing and having Bon Iver for her to have a sort of dialogue in the song elevates it. “Dynamite” by BTS is just a catchy song with a hook in the chorus but has no main point but to have it ingrained in your ears for BTS to have a more international audience. “Intentions” by Justin Bieber is just poorly written, it is bad when Quavo says “asset” like “skkrt” to hype the song up because you have no engaging words in that line except for “asset” or “equity.”</p>
<h3 id="best-pop-vocal-album">Best Pop Vocal Album</h3>
<ol>
<li><strong>Fine Line by Harry Styles — Predicted Winner</strong></li>
<li><em>Future Nostalgia by Dua Lipa — Actual Winner</em></li>
<li>folklore by Taylor Swift</li>
<li>Chromatica by Lady Gaga</li>
<li>Changes by Justin Bieber</li>
</ol>
<p>Harry Styles’ album was a surprise during my listen, the project wasn’t just catchy but musically interesting. Songs like “Golden,” “She,” and “Falling” are the best on the record. It seems musically similar to Carly Rae Jepsen’s recent works but the type of music and vibe is different. Future Nostalgia and folklore have this problem of all the songs sounding the same, the former just being one disco track after another and your ears are tired of hearing the same tropes after four songs, and the latter has the opposite problems. Taylor’s folk songs never get into another gear like she’s stuck on a tempo and style of folk. “Chromatica” is Gaga’s return to form after “Joanne.” This release was hampered by the COVID pandemic, not having live shows for promotion, and people being stuck at home this year. It means that this wasn’t the album that the people needed during this time, so it hampered the success this album might have had culturally. Gaga goes full camp on some of the tracks on this record where it almost laughable (i.e. “Unwrap Me” line from “Sour Candy”). There are good tracks on there like “Enigma” which shows Gaga’s powerful vocals, and “Babylon” and the wordplay in that song (Babble-On, Babylon).</p>
<p>In all honesty, I am more excited about the performances this year. Especially from Silk Sonic (Bruno Mars and Anderson .Paak) and others. Let’s take the awards with a grain of salt, especially with The Weeknd’s boycott of the Recording Academy following the snub of his recent project “After Hours,” and simply enjoy the industry’s so-called “Biggest Night.”</p>Francis AvanceñaSince the 2021 GRAMMY nominations came out last year in 2020, I wanted to try something new. In the past few years, I would look at the nominations and forget about it and would have like a few albums that I know, but this year I wanted to listen to all major categories and the few song nominations specifically in the pop music categories. So, with this year’s GRAMMYs coming up. I would like to share my predictions of who will win among the nominees for the following categories: Album of the Year, Record of the Year, Song of the Year, Best Pop Solo Performance, Best Pop Duo/Group Performance, and Best Pop Vocal Album.Making Earthquake PH: Working with Tweets and Maps2020-01-15T00:00:00+00:002020-01-15T00:00:00+00:00https://francisoliver.dev/blog/making-earthquake-ph<p>The recent disaster in Batangas becuase of the Taal Volcano caused a spew of scientific terms and overflow of science communication to the public. It got me thinking on how we inform the general public regarding these terms and especially during these times where many volcanic earthquakes occur.</p>
<blockquote class="twitter-tweet tw-align-center">
<p lang="en" dir="ltr">Looks like <a href="https://twitter.com/phivolcs_dost?ref_src=twsrc%5Etfw">@phivolcs_dost</a>
needs a social media / digital comms manager to translate all of this info into human speak. <a href="https://twitter.com/hashtag/informationdissemination?src=hash&ref_src=twsrc%5Etfw">#informationdissemination</a>
<a href="https://t.co/9H2vqyleE7">https://t.co/9H2vqyleE7</a></p>— Gretchen Ho (@gretchenho) <a href="https://twitter.com/gretchenho/status/1216562934211416064?ref_src=twsrc%5Etfw">January 13, 2020</a>
</blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>As per Gretchen Ho’s tweet above how do we make PHIVOLCS’ tweets into human speak. That’s when I thought of making Earthquake PH.</p>
<h3 id="what-is-earthquake-ph">What is Earthquake PH?</h3>
<p>Earthquake PH is a website that gives details about earthquakes in the Philippines in an understandable manner through,descriptions and map visualizations of where earthquakes are. This site was created to make the general public informed about details about earthquakes making information understandable to the normal citizen.</p>
<h3 id="how-does-earthquake-ph-work">How does Earthquake PH work?</h3>
<p>The site uses the Twitter API to scrape the most recent tweets from the PHIVOLCS account every ten minutes. From the recent tweets, we filter them to get tweets specifically regarding earthquake information. Next, we parsed the information from the full text of the Tweet to get the following information using Regular Expressions:</p>
<ul>
<li>Date and Time</li>
<li>Location</li>
<li>Latitude</li>
<li>Longitude</li>
<li>Strength or Magnitude</li>
<li>Depth of Focus</li>
</ul>
<p>After this, we run the coordinates to a reverse Geocoder to give us an address of where the epicenter of the earthquake is and then save it to our MongoDB database.</p>
<h4 id="features-of-earthquake-ph">Features of Earthquake PH</h4>
<h4 id="earthquake-details">Earthquake Details</h4>
<p>The first feature is a general overview of the details regarding the earthquake. I included the general details from the tweet, a Google Map of the epicenter based on the longitude and latitude given and descriptions about how strong the earthquake was from <a href="https://www.gns.cri.nz/Home/Learning/Science-Topics/Earthquakes/Monitoring-Earthquakes/Other-earthquake-questions/What-is-the-Richter-Magnitude-Scale">this website.</a></p>
<table class="ui very simple table">
<tr>
<td> <b>Richter magnitude</b>
</td>
<td> <b>Description</b>
</td>
<td> <b>Earthquake effect</b>
</td>
</tr>
<tr>
<td> < 2.0
</td>
<td> Micro
</td>
<td> Micro earthquakes, not felt.
</td>
</tr>
<tr>
<td> 2.0-2.9
</td>
<td rowspan="2"> Minor
</td>
<td> Generally not felt, but recorded.
</td>
</tr>
<tr>
<td> 3.0-3.9
</td>
<td> Often felt, but rarely causes damage.
</td>
</tr>
<tr>
<td> 4.0-4.9
</td>
<td> Light
</td>
<td> Noticeable shaking of indoor items, rattling noises. Significant
damage unlikely.
</td>
</tr>
<tr>
<td> 5.0-5.9
</td>
<td> Moderate
</td>
<td> Can cause major damage to poorly constructed buildings over small
regions. At most slight damage to well-designed buildings.
</td>
</tr>
<tr>
<td> 6.0-6.9
</td>
<td> Strong
</td>
<td> Can be destructive in areas up to about 160 kilometres (100 mi)
across in populated areas.
</td>
</tr>
<tr>
<td> 7.0-7.9
</td>
<td> Major
</td>
<td> Can cause serious damage over larger areas.
</td>
</tr>
<tr>
<td> 8.0-8.9
</td>
<td rowspan="2"> Great
</td>
<td> Can cause serious damage in areas several hundred miles across.
</td>
</tr>
<tr>
<td> 9.0-9.9
</td>
<td> Devastating in areas several thousand miles across.
</td>
</tr>
<tr>
<td> 10.0+
</td>
<td> Epic
</td>
<td> Never recorded
</td>
</tr>
</table>
<div class="ui medium images" style="text-align:center;">
<img class="ui image blog-image" src="/assets/img/blog/making-earthquake-ph/homescreen.jpg" alt="Home Screen" />
<img class="ui image blog-image" src="/assets/img/blog/making-earthquake-ph/detail-1.jpg" alt="Detail Page Part 1" />
<img class="ui image blog-image" src="/assets/img/blog/making-earthquake-ph/detail-2.jpg" alt="Detail Page Part 2" />
</div>
<h4 id="quake-map">Quake Map</h4>
<p>I decided to add another feature which is called the <em>Quake Map</em> the Quake Map visualizes on the screen where the 10 latest earthquakes are, how strong they were and when and where exactly they took place.</p>
<div class="ui medium images" style="text-align:center;">
<img class="ui image blog-image" src="/assets/img/blog/making-earthquake-ph/quake-map.jpg" alt="Quake Map Page" />
</div>
<h3 id="technology-used">Technology Used</h3>
<p>Now for the technical details, the website is a monolith Express.js application, a MongoDB database from MongoDB Atlas, Several Map and Geocoding APIs and of course Twitter’s Developer API.</p>
<p>In the detail pages of the earthquakes, I used Google’s Map Embed API to visualize the location of the earthquake. On the other hand, I used Leaflet and OpenStreetMaps for the Quake Map since it’s open-source and free unlike Google’s API for multiple markers. Leaflet can also be used for more features down the line.</p>
<p>On updating the data on the site, I just do this by having a Node.js script that runs every 10 minutes and on this file, using the Twitter API, I get the 100 most recent tweets of PHIVOLCS not including RTs then get the information through RegEx, then I check the database if the Tweet ID is already included in the database if it’s not I used LocationIQ’s Reverse Geocoding API to get an address of where the earthquake occurred. Then insert it to the database.</p>
<h3 id="using-the-site">Using the site.</h3>
<p>You can visit the site by going to <a href="https://earthquakeph.francisoliver.dev/"><strong>https://earthquakeph.francisoliver.dev/</strong></a> or by clicking <a href="https://earthquakeph.francisoliver.dev/">here.</a> The site currently has more than 120 recorded earthquakes and counting.Feel free to suggest new features to the website and how I can improve it.</p>Francis AvanceñaThe recent disaster in Batangas becuase of the Taal Volcano caused a spew of scientific terms and overflow of science communication to the public. It got me thinking on how we inform the general public regarding these terms and especially during these times where many volcanic earthquakes occur.Side Projects: Telegram Food Bot using the Zomato API2019-10-19T00:00:00+00:002019-10-19T00:00:00+00:00https://francisoliver.dev/blog/telegram-food-bot-with-zomato-api<p>In work, we use Telegram as a main communication platform and being in a central business district, there are many food places to choose from that will end up in endless discussions on where to go out to eat for lunch outs and dinner catch-ups. When looking at fun APIs to use in a project I stumbled across Zomato’s API, so, as a side project, I decided to make a Telegram bot that narrows restaurant options and stop groups from being indecisive in choosing where to eat.</p>
<h3 id="what-is-zomato">What is Zomato?</h3>
<p><a href="https://www.zomato.com/">Zomato </a>is a restaurant aggregator and food delivery service startup company founded in India. It is now known today for its Zomato Gold product which allows for discounts and buy-1 take-1 deals in certain restaurants. The company localized into many countries, including here in the Philippines.</p>
<h3 id="what-is-telegram-and-telegram-bots">What is Telegram and Telegram Bots?</h3>
<p>Telegram is a cross-platform instant messaging app similar to WhatsApp and Viber, it is very popular due to its feature of end-to-end encryption in all conversations, customer stickers and chatbots (particularly the Werewolf, Uno and Quizarium chatbots) to name a few. In the industry, we mainly focus on Facebook’s Messenger Platform as a way for us to showcase services and improve a brand’s social presence but in the Telegram environment, anyone can make their own chatbot without creating a Facebook page and having the trouble of applying for a Facebook API Key. Telegram allows for developer creativity, in which developers can have a bot for every occasion and problems they deem they need to solve or another ‘side project’ that will never get pushed to production.</p>
<h3 id="how-will-the-chatbot-work">How will the chatbot work?</h3>
<p>Looking at the given endpoints of Zomato (check it out <a href="https://developers.zomato.com/api">here</a> for the full documentation), I planned to make it interactive as possible, here are the available endpoints from the documentation.</p>
<table class="ui table">
<tr>
<td><strong>Request</strong>
</td>
<td><strong>Endpoint</strong>
</td>
<td><strong>Description</strong>
</td>
</tr>
<tr>
<td colspan="3"><strong>Common</strong>
</td>
</tr>
<tr>
<td><code>GET</code>
</td>
<td><code>/categories</code>
</td>
<td>Get the list of Categories
</td>
</tr>
<tr>
<td><code>GET</code>
</td>
<td><code>/cities</code>
</td>
<td>Get city details
</td>
</tr>
<tr>
<td><code>GET</code>
</td>
<td><code>/collections</code>
</td>
<td>Get Zomato collections in a city
</td>
</tr>
<tr>
<td><code>GET</code>
</td>
<td><code>/cuisines</code>
</td>
<td>Get the list of all cuisines in a city
</td>
</tr>
<tr>
<td><code>GET</code>
</td>
<td><code>/establishments</code>
</td>
<td>Get a list of restaurant types in a city
</td>
</tr>
<tr>
<td><code>GET</code>
</td>
<td><code>/geocode</code>
</td>
<td>Get location details based on coordinates
</td>
</tr>
<tr>
<td colspan="3"><strong>Location</strong>
</td>
</tr>
<tr>
<td><code>GET</code>
</td>
<td><code>/location_details</code>
</td>
<td>Get Zomato location details
</td>
</tr>
<tr>
<td><code>GET</code>
</td>
<td><code>/locations</code>
</td>
<td>Search for Locations
</td>
</tr>
<tr>
<td colspan="3"><strong>Restaurant</strong>
</td>
</tr>
<tr>
<td><code>GET</code>
</td>
<td><code>/dailymenu</code>
</td>
<td>Get the daily menu of a restaurant
</td>
</tr>
<tr>
<td><code>GET</code>
</td>
<td><code>/restaurant</code>
</td>
<td>Get restaurant details
</td>
</tr>
<tr>
<td><code>GET</code>
</td>
<td><code>/reviews</code>
</td>
<td>Get restaurant reviews
</td>
</tr>
<tr>
<td><code>GET</code>
</td>
<td><code>/search</code>
</td>
<td>Search for restaurants
</td>
</tr>
</table>
<p>In order for an accurate search through the bot, we need to get three crucial points of information from the user first. First, where are they located (<code class="highlighter-rouge">/location</code>), next is the type of establishment they want to eat at (<code class="highlighter-rouge">/establishments</code>) and lastly the type of cuisine they would like to eat (<code class="highlighter-rouge">/cuisines</code>). After that, we search for the relevant restaurants through the search endpoint (<code class="highlighter-rouge">/search</code>) and display them to the user.</p>
<figure>
<img class="ui image blog-image" src="/assets/img/blog/telegram-food-bot-with-zomato-api/flowchart.jpg" alt="The chatbot's process flow" />
<figcaption>The chatbot's process flow</figcaption>
</figure>
<h3 id="implementation-and-technology-used">Implementation and Technology Used</h3>
<p>For this project, I used Node.js and NPM packages <a href="https://www.npmjs.com/package/telebot">Telebot</a> and <a href="https://www.npmjs.com/package/zomato.js">zomato.js</a>, a little RegEx knowledge is also useful here. These API wrappers make it easier for us to develop and abstracts the complexity making us focus on the experience of the chatbot and its implementation. I heavily used Telegram’s <code class="highlighter-rouge">inlineKeyboard</code> feature which allows for buttons to be pressed by the user which triggers the next part of the flow. Essentially, we call Zomato’s API based on different event handlers in the bot.</p>
<p>For the implementation, I created three different telegram commands as access to the bot’s services.</p>
<table class="ui very simple table">
<tr>
<td><strong>Command</strong>
</td>
<td><strong>Description</strong>
</td>
</tr>
<tr>
<td><code>/start</code>
</td>
<td>Start a conversation with the bot.
</td>
</tr>
<tr>
<td><code>/location</code>
</td>
<td>Tell the bot your location. Usage <code>/location <keyword>.</code>
</td>
</tr>
<tr>
<td><code>/search</code>
</td>
<td>A quick search for a restaurant. Usage <code>/search <keyword></code>, works best if you have already set a location with the <code>/location</code> command.
</td>
</tr>
</table>
<p>The bot’s flow is heavily reliant on the ‘<code class="highlighter-rouge">callbackQuery</code>’ event and parsing of that data to store for later usage in a case syntax. The bot does not use a database but instead uses a 2D array to store data unique per chat or conversation using Telegram’s <code class="highlighter-rouge">chat_id</code> as a unique identifier. As another kicker, I deployed the chatbot to Heroku in a free dyno, sending an HTTP request to the main URL every five minutes to avoid the chatbot from sleeping.</p>
<h3 id="the-chatbot-in-action">The chatbot in action</h3>
<h4 id="going-through-the-main-flow">Going through the main flow</h4>
<div class="ui small images" style="text-align:center;">
<img class="ui image blog-image" src="/assets/img/blog/telegram-food-bot-with-zomato-api/about.jpg" alt="About" />
<img class="ui image blog-image" src="/assets/img/blog/telegram-food-bot-with-zomato-api/get_started.jpg" alt="Get started" />
<img class="ui image blog-image" src="/assets/img/blog/telegram-food-bot-with-zomato-api/set_location.jpg" alt="Set location" />
<img class="ui image blog-image" src="/assets/img/blog/telegram-food-bot-with-zomato-api/set_establishment.jpg" alt="Set establishment" />
<img class="ui image blog-image" src="/assets/img/blog/telegram-food-bot-with-zomato-api/set_cuisine.jpg" alt="Set cuisine" />
<img class="ui image blog-image" src="/assets/img/blog/telegram-food-bot-with-zomato-api/restaurant_show.jpg" alt="Show resto" />
<img class="ui image blog-image" src="/assets/img/blog/telegram-food-bot-with-zomato-api/set_restaurant.jpg" alt="Set resto" />
<img class="ui image blog-image" src="/assets/img/blog/telegram-food-bot-with-zomato-api/show_map.jpg" alt="Show map" />
<img class="ui image blog-image" src="/assets/img/blog/telegram-food-bot-with-zomato-api/view_rating.jpg" alt="View Rating" />
<img class="ui image blog-image" src="/assets/img/blog/telegram-food-bot-with-zomato-api/view_highlights.jpg" alt="View highlights" />
</div>
<h4 id="doing-a-quick-search">Doing a quick search</h4>
<p>The quick search takes into consideration the set location through the <code class="highlighter-rouge">/location <KEYWORD></code> command using the bot and immediately going to the quick search may not yield the best results.</p>
<p>For this example, I put BGC Stopover Pavillion as my location so it takes into account that location when I search for a restaurant.</p>
<div class="ui medium images" style="text-align:center;">
<img class="ui image blog-image" src="/assets/img/blog/telegram-food-bot-with-zomato-api/quick_search.jpg" alt="Quick search" />
<img class="ui image blog-image" src="/assets/img/blog/telegram-food-bot-with-zomato-api/quick_search_select.jpg" alt="Quick search select" />
</div>
<h3 id="possible-improvements">Possible Improvements</h3>
<p>The possible improvements and features that can be added to the bot are as follows:</p>
<ol>
<li>Having the user control to control to sort via the cost and rating.</li>
<li>Having the user control how many restaurants in the search the bot will display as the results.</li>
<li>Having the user choose what payment method they want and displaying restaurants that only have that payment method.</li>
<li>Have suggested restaurants to try based on the user’s location via the <code class="highlighter-rouge">/location_details</code> endpoint.</li>
<li>Have a proper database to store the chat data.</li>
<li>Make the chatbot less reliant on <code class="highlighter-rouge">inlineKeyboards</code> but on a <code class="highlighter-rouge">ReplyKeyboardMarkup</code> to have a real conversation experience with the bot and to show which user did what if the chatbot is in the group chat.</li>
<li>And other general performance updates (having a database instead of an array to store data).</li>
</ol>
<h3 id="conclusion">Conclusion</h3>
<p>Telegram is one of the best channels for developers starting out with chatbots. It has a low barrier to entry and its usage can go to very simple to incredibly complex. It can be used for implementing games, implementing services through APIs such as a bot like this, a weather bot and even a bot that just sends jokes, web scraping to even handling payments with its integration with Stripe and other payment methods. It allows developers to have fun and just do that side project that you always wanted to do as a break from the usual jobs. Zomato’s API is just begging to be integrated into other apps, its huge repository of restaurant information across the globe can be used in not only chatbots but in websites and can be integrated with other services as well.</p>
<p>Making this chatbot was fun, doing something for ourselves once in a while that we can use in real life (me and my Management Trainee friends use this chatbot) without any pressure from deadlines is the ultimate goal of any side project. Sometimes we need that as developers, our own creation, a safe programming space, where we can just goof off and do what we want and where we have total control and learn new technologies at the same time.</p>Francis AvanceñaIn work, we use Telegram as a main communication platform and being in a central business district, there are many food places to choose from that will end up in endless discussions on where to go out to eat for lunch outs and dinner catch-ups. When looking at fun APIs to use in a project I stumbled across Zomato’s API, so, as a side project, I decided to make a Telegram bot that narrows restaurant options and stop groups from being indecisive in choosing where to eat.UAAP Real Time Sentiment Analysis of Fan Tweets2019-08-30T00:00:00+00:002019-08-30T00:00:00+00:00https://francisoliver.dev/blog/uaap-real-time-sentiment-analysis<p>It is UAAP season once again and this year’s season 82 is hosted by Ateneo it is a good time to take a look at one of my favorite projects I did during my senior year in college. This one is taken from the final project for CS 129.1: Special Topics in Software Engineering: Contemporary Database Technologies, more commonly known as “Contempo DB”. For this project, we did a real-time tweet analysis of tweets during the Final Four game of the Ateneo Blue Eagles and the FEU Tamaraws last November 25, 2018.</p>
<h3 id="what-we-wanted-to-find-out">What we wanted to find out.</h3>
<p>For the project, we wanted to find out through data analytics are three things.</p>
<ol>
<li>What percentage of the tweets regarding the UAAP game are toxic or not?</li>
<li>Which fan base or contingent is more toxic?</li>
<li>What are the most frequent words that fans use?</li>
</ol>
<p>If you look at Twitter during these crucial collegiate games you see that most of the Philippine Trends are taken up by topics that are related to the game. However, due to the action happening in the games, some of the fans become overly passionate about their tweets. This means there is good data velocity coming from Twitter’s API and there a lot of emotionally charged tweets that we can analyze.</p>
<h3 id="the-technology-we-used">The technology we used.</h3>
<p>The main technology that we used is mainly JavaScript based. NodeJS as a runtime and NPM Packages. For the data gathering we used the following:</p>
<ol>
<li><a href="https://www.mongodb.com/">MongoDB</a> with <a href="https://npmjs.org/package/mongojs">Mongojs</a> as a driver</li>
<li><a href="https://npmjs.org/package/twit">Twit</a> as a Twitter SDK wrapper for both the platform’s REST and Streaming APIs</li>
<li><a href="https://npmjs.org/package/sentiment">Sentiment</a> (NPM Package)</li>
<li><a href="https://www.npmjs.com/package/dotenv">Dotenv</a> to secure our API keys</li>
</ol>
<p>For data visualization we did it via an express web app and used the following:</p>
<ol>
<li><a href="https://expressjs.com/">Express</a></li>
<li><a href="https://www.npmjs.com/package/ejs">EJS</a> as a templating engine</li>
<li><a href="https://github.com/ankane/chartkick.js">Chartkick.js</a></li>
</ol>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Load environment variables</span>
<span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">dotenv</span><span class="dl">'</span><span class="p">).</span><span class="nx">config</span><span class="p">();</span>
<span class="c1">// Configure twitter API</span>
<span class="kd">const</span> <span class="nx">Twit</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">twit</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">config</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">./twitter_config</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">twitter</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Twit</span><span class="p">(</span><span class="nx">config</span><span class="p">);</span>
<span class="c1">// Load sentiment analysis package</span>
<span class="kd">const</span> <span class="nx">Sentiment</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">sentiment</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">sentiment</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Sentiment</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">filipinoWords</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">./filipino</span><span class="dl">'</span><span class="p">);</span>
<span class="c1">// Configure mongoDB</span>
<span class="kd">const</span> <span class="nx">mongojs</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">mongojs</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">db</span> <span class="o">=</span> <span class="nx">mongojs</span><span class="p">(</span><span class="dl">'</span><span class="s1">tweets</span><span class="dl">'</span><span class="p">,[</span><span class="dl">'</span><span class="s1">admu</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">feu</span><span class="dl">'</span><span class="p">])</span>
<span class="kd">const</span> <span class="nx">filipino</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">extras</span><span class="p">:</span> <span class="nx">filipinoWords</span>
<span class="p">};</span>
<span class="kd">const</span> <span class="nx">trackingWords</span> <span class="o">=</span> <span class="p">[</span>
<span class="dl">'</span><span class="s1">ADMU</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">FEU</span><span class="dl">'</span><span class="p">,</span>
<span class="c1">// Plus many more</span>
<span class="p">];</span>
</code></pre></div></div>
<h3 id="how-did-we-do-it">How did we do it?</h3>
<p>We followed this simple methodology:</p>
<ol>
<li>Using Twitter’s streaming API, we subscribed to the ‘statuses/filter’ endpoint.</li>
<li>Then we listened for tracking words related to the game and filter it to English (en) or Filipino (tl) words.</li>
<li>Analyzed the tweet using a sentiment analysis package.</li>
<li>Then we “classified” them to be either an Ateneo contingent or FEU contingent tweet.</li>
</ol>
<p>For the sentiment analysis, we simply thought of toxic and good words in Filipino and translated it to English and set their score as the same as their English score. This is not the best way to do it but for our case, it was the best we can do at that time. We managed to come up with around 50+ toxic words and around 40+ good words.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Listening for tweets . . .</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">stream</span> <span class="o">=</span> <span class="nx">twitter</span><span class="p">.</span><span class="nx">stream</span><span class="p">(</span><span class="dl">'</span><span class="s1">statuses/filter</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span><span class="na">track</span><span class="p">:</span> <span class="nx">trackingWords</span><span class="p">,</span> <span class="na">language</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">tl</span><span class="dl">'</span><span class="p">,</span><span class="dl">'</span><span class="s1">en</span><span class="dl">'</span><span class="p">]})</span>
<span class="nx">stream</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">tweet</span><span class="dl">'</span><span class="p">,(</span><span class="nx">tweet</span><span class="p">)</span><span class="o">=></span><span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">tweet</span><span class="p">.</span><span class="nx">retweeted_status</span> <span class="o">===</span> <span class="kc">undefined</span><span class="p">){</span>
<span class="kd">let</span> <span class="nx">tweetText</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">tweet</span><span class="p">.</span><span class="nx">extended_tweet</span> <span class="o">==</span> <span class="kc">undefined</span><span class="p">){</span>
<span class="nx">tweetText</span> <span class="o">=</span> <span class="nx">tweet</span><span class="p">.</span><span class="nx">text</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">tweetText</span> <span class="o">=</span> <span class="nx">tweet</span><span class="p">.</span><span class="nx">extended_tweet</span><span class="p">.</span><span class="nx">full_text</span>
<span class="p">}</span>
<span class="kd">let</span> <span class="nx">analysis</span> <span class="o">=</span> <span class="nx">sentiment</span><span class="p">.</span><span class="nx">analyze</span><span class="p">(</span><span class="nx">tweetText</span><span class="p">,</span> <span class="nx">filipino</span><span class="p">);</span>
<span class="kd">let</span> <span class="nx">tweetScore</span> <span class="o">=</span> <span class="nx">tweet</span><span class="p">;</span>
<span class="nx">tweetScore</span><span class="p">.</span><span class="nx">sentiment_analysis</span> <span class="o">=</span> <span class="nx">analysis</span><span class="p">;</span>
<span class="nx">classifyTweet</span><span class="p">(</span><span class="nx">tweetScore</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>
<p>We first gather and analyze the tweets coming in from the Twitter API and save it to a MongoDB collection, with the sentiment score and the tokenized tweet, based on a simple classification logic: For negative sentiment score tweets, if it talks about a certain school or team it is classified on the opposing side. On the other hand for positive and neutral scored tweets we classify it to them as fans of the team they are talking about. This classification is happening as the tweets come by.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">classifyTweet</span><span class="p">(</span><span class="nx">tweet</span><span class="p">){</span>
<span class="kd">let</span> <span class="nx">admuClassifiers</span> <span class="o">=</span><span class="p">[</span>
<span class="c1">// Classifiers for AdMU here.</span>
<span class="p">];</span>
<span class="kd">let</span> <span class="nx">feuClassifiers</span> <span class="o">=</span> <span class="p">[</span>
<span class="c1">// Classifiers for FEU here.</span>
<span class="p">];</span>
<span class="kd">let</span> <span class="nx">tweetText</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">tweet</span><span class="p">.</span><span class="nx">extended_tweet</span> <span class="o">==</span> <span class="kc">undefined</span><span class="p">){</span>
<span class="nx">regText</span> <span class="o">=</span> <span class="nx">tweet</span><span class="p">.</span><span class="nx">text</span><span class="p">;</span>
<span class="nx">tweetText</span> <span class="o">=</span> <span class="nx">tweet</span><span class="p">.</span><span class="nx">text</span><span class="p">.</span><span class="nx">toUpperCase</span><span class="p">();</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">regText</span> <span class="o">=</span> <span class="nx">tweet</span><span class="p">.</span><span class="nx">extended_tweet</span><span class="p">.</span><span class="nx">full_text</span><span class="p">;</span>
<span class="nx">tweetText</span> <span class="o">=</span> <span class="nx">tweet</span><span class="p">.</span><span class="nx">extended_tweet</span><span class="p">.</span><span class="nx">full_text</span><span class="p">.</span><span class="nx">toUpperCase</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">admu</span> <span class="k">of</span> <span class="nx">admuClassifiers</span><span class="p">){</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">tweetText</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">admu</span><span class="p">.</span><span class="nx">toUpperCase</span><span class="p">())){</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">tweet</span><span class="p">.</span><span class="nx">sentiment_analysis</span><span class="p">.</span><span class="nx">score</span> <span class="o">>=</span> <span class="mi">0</span><span class="p">){</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Inserted to ADMU: Score </span><span class="p">${</span><span class="nx">tweet</span><span class="p">.</span><span class="nx">sentiment_analysis</span><span class="p">.</span><span class="nx">score</span><span class="p">}</span><span class="s2"> - </span><span class="p">${</span><span class="nx">regText</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">db</span><span class="p">.</span><span class="nx">admu</span><span class="p">.</span><span class="nx">insert</span><span class="p">(</span><span class="nx">tweet</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Inserted to FEU: Score </span><span class="p">${</span><span class="nx">tweet</span><span class="p">.</span><span class="nx">sentiment_analysis</span><span class="p">.</span><span class="nx">score</span><span class="p">}</span><span class="s2"> - </span><span class="p">${</span><span class="nx">regText</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">db</span><span class="p">.</span><span class="nx">feu</span><span class="p">.</span><span class="nx">insert</span><span class="p">(</span><span class="nx">tweet</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">feu</span> <span class="k">of</span> <span class="nx">feuClassifiers</span><span class="p">){</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">tweetText</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">feu</span><span class="p">.</span><span class="nx">toUpperCase</span><span class="p">())){</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">tweet</span><span class="p">.</span><span class="nx">sentiment_analysis</span><span class="p">.</span><span class="nx">score</span> <span class="o">>=</span> <span class="mi">0</span><span class="p">){</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Inserted to FEU: Score </span><span class="p">${</span><span class="nx">tweet</span><span class="p">.</span><span class="nx">sentiment_analysis</span><span class="p">.</span><span class="nx">score</span><span class="p">}</span><span class="s2"> - </span><span class="p">${</span><span class="nx">regText</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">db</span><span class="p">.</span><span class="nx">feu</span><span class="p">.</span><span class="nx">insert</span><span class="p">(</span><span class="nx">tweet</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Inserted to ADMU: Score </span><span class="p">${</span><span class="nx">tweet</span><span class="p">.</span><span class="nx">sentiment_analysis</span><span class="p">.</span><span class="nx">score</span><span class="p">}</span><span class="s2"> - </span><span class="p">${</span><span class="nx">regText</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">db</span><span class="p">.</span><span class="nx">admu</span><span class="p">.</span><span class="nx">insert</span><span class="p">(</span><span class="nx">tweet</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>After the gathering we did a map-reduce on the tokenized tweets that we got, for three different use cases, we didn’t include stop words for both English and Filipino that we got from our professor, for the map-reduce phase.</p>
<ol>
<li>Most used words/emojis</li>
<li>Most used positive words/emojis.</li>
<li>Most used negative words/emojis.</li>
</ol>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Map functions</span>
<span class="kd">function</span> <span class="nx">getTokens1</span><span class="p">(){</span>
<span class="kd">var</span> <span class="nx">stopWords</span> <span class="o">=</span> <span class="p">[];</span>
<span class="kd">var</span> <span class="nx">tokens</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">sentiment_analysis</span><span class="p">.</span><span class="nx">tokens</span>
<span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">token</span> <span class="k">of</span> <span class="nx">tokens</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">token</span> <span class="o">!=</span> <span class="dl">""</span> <span class="o">&&</span> <span class="o">!</span><span class="nx">stopWords</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">token</span><span class="p">.</span><span class="nx">toLowerCase</span><span class="p">())){</span>
<span class="nx">emit</span><span class="p">(</span><span class="nx">token</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">getTokens2</span><span class="p">(){</span>
<span class="kd">var</span> <span class="nx">stopWords</span> <span class="o">=</span> <span class="p">[];</span>
<span class="kd">var</span> <span class="nx">tokens</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">sentiment_analysis</span><span class="p">.</span><span class="nx">positive</span>
<span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">token</span> <span class="k">of</span> <span class="nx">tokens</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">token</span> <span class="o">!=</span> <span class="dl">""</span> <span class="o">&&</span> <span class="o">!</span><span class="nx">stopWords</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">token</span><span class="p">.</span><span class="nx">toLowerCase</span><span class="p">())){</span>
<span class="nx">emit</span><span class="p">(</span><span class="nx">token</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">getTokens3</span><span class="p">(){</span>
<span class="kd">var</span> <span class="nx">stopWords</span> <span class="o">=</span> <span class="p">[];</span>
<span class="kd">var</span> <span class="nx">tokens</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">sentiment_analysis</span><span class="p">.</span><span class="nx">negative</span>
<span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">token</span> <span class="k">of</span> <span class="nx">tokens</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">token</span> <span class="o">!=</span> <span class="dl">""</span> <span class="o">&&</span> <span class="o">!</span><span class="nx">stopWords</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">token</span><span class="p">.</span><span class="nx">toLowerCase</span><span class="p">())){</span>
<span class="nx">emit</span><span class="p">(</span><span class="nx">token</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// Reduce Function</span>
<span class="kd">function</span> <span class="nx">aggregateCount</span><span class="p">(</span><span class="nx">key</span><span class="p">,</span> <span class="nx">values</span><span class="p">){</span>
<span class="kd">var</span> <span class="nx">count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">value</span> <span class="k">of</span> <span class="nx">values</span><span class="p">){</span>
<span class="nx">count</span> <span class="o">+=</span> <span class="nx">value</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">count</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Map Reduce Command Run in the MongoDB Shell</span>
<span class="nx">results</span> <span class="o">=</span> <span class="nx">db</span><span class="p">.</span><span class="nx">runCommand</span><span class="p">({</span>
<span class="na">mapReduce</span><span class="p">:</span> <span class="dl">'</span><span class="s1">admu</span><span class="dl">'</span><span class="p">,</span>
<span class="na">map</span><span class="p">:</span> <span class="nx">getTokens</span><span class="p">,</span>
<span class="na">reduce</span><span class="p">:</span> <span class="nx">aggregateCount</span><span class="p">,</span>
<span class="na">out</span><span class="p">:</span> <span class="dl">'</span><span class="s1">admu.wordcount</span><span class="dl">'</span>
<span class="p">});</span>
<span class="nx">results</span> <span class="o">=</span> <span class="nx">db</span><span class="p">.</span><span class="nx">runCommand</span><span class="p">({</span>
<span class="na">mapReduce</span><span class="p">:</span> <span class="dl">'</span><span class="s1">feu</span><span class="dl">'</span><span class="p">,</span>
<span class="na">map</span><span class="p">:</span> <span class="nx">getTokens</span><span class="p">,</span>
<span class="na">reduce</span><span class="p">:</span> <span class="nx">aggregateCount</span><span class="p">,</span>
<span class="na">out</span><span class="p">:</span> <span class="dl">'</span><span class="s1">feu.wordcount</span><span class="dl">'</span>
<span class="p">});</span>
<span class="nx">results</span> <span class="o">=</span> <span class="nx">db</span><span class="p">.</span><span class="nx">runCommand</span><span class="p">({</span>
<span class="na">mapReduce</span><span class="p">:</span> <span class="dl">'</span><span class="s1">admu</span><span class="dl">'</span><span class="p">,</span>
<span class="na">map</span><span class="p">:</span> <span class="nx">getTokens</span><span class="p">,</span>
<span class="na">reduce</span><span class="p">:</span> <span class="nx">aggregateCount</span><span class="p">,</span>
<span class="na">out</span><span class="p">:</span> <span class="dl">'</span><span class="s1">admu.positive</span><span class="dl">'</span>
<span class="p">});</span>
<span class="nx">results</span> <span class="o">=</span> <span class="nx">db</span><span class="p">.</span><span class="nx">runCommand</span><span class="p">({</span>
<span class="na">mapReduce</span><span class="p">:</span> <span class="dl">'</span><span class="s1">feu</span><span class="dl">'</span><span class="p">,</span>
<span class="na">map</span><span class="p">:</span> <span class="nx">getTokens</span><span class="p">,</span>
<span class="na">reduce</span><span class="p">:</span> <span class="nx">aggregateCount</span><span class="p">,</span>
<span class="na">out</span><span class="p">:</span> <span class="dl">'</span><span class="s1">feu.positive</span><span class="dl">'</span>
<span class="p">});</span>
<span class="nx">results</span> <span class="o">=</span> <span class="nx">db</span><span class="p">.</span><span class="nx">runCommand</span><span class="p">({</span>
<span class="na">mapReduce</span><span class="p">:</span> <span class="dl">'</span><span class="s1">admu</span><span class="dl">'</span><span class="p">,</span>
<span class="na">map</span><span class="p">:</span> <span class="nx">getTokens</span><span class="p">,</span>
<span class="na">reduce</span><span class="p">:</span> <span class="nx">aggregateCount</span><span class="p">,</span>
<span class="na">out</span><span class="p">:</span> <span class="dl">'</span><span class="s1">admu.negative</span><span class="dl">'</span>
<span class="p">});</span>
<span class="nx">results</span> <span class="o">=</span> <span class="nx">db</span><span class="p">.</span><span class="nx">runCommand</span><span class="p">({</span>
<span class="na">mapReduce</span><span class="p">:</span> <span class="dl">'</span><span class="s1">feu</span><span class="dl">'</span><span class="p">,</span>
<span class="na">map</span><span class="p">:</span> <span class="nx">getTokens</span><span class="p">,</span>
<span class="na">reduce</span><span class="p">:</span> <span class="nx">aggregateCount</span><span class="p">,</span>
<span class="na">out</span><span class="p">:</span> <span class="dl">'</span><span class="s1">feu.negative</span><span class="dl">'</span>
<span class="p">});</span>
</code></pre></div></div>
<h3 id="results">Results</h3>
<p>For our results in the web app we mainly looked at the following: using a bar graph we looked at the most frequently used words, most used positive words, most used negative words. Aside from that we also did Top 10 most positive tweets for both sides and a top 10 most negative tweets for both sides. Using chartkick.js we used an API endpoint to get the data for performance (gotta have that fast page load).</p>
<h4 id="general-results">General Results</h4>
<!-- Results Here -->
<figure>
<img class="ui image blog-image" src="/assets/img/blog/uaap-real-time-sentiment-analysis/general/count.jpg" alt="Total Count of Tweets" />
<figcaption>Total Count of Tweets</figcaption>
</figure>
<p><br /></p>
<figure>
<img class="ui image blog-image" src="/assets/img/blog/uaap-real-time-sentiment-analysis/general/count-positive.jpg" alt="Total Count of Positive Tweets" />
<figcaption>Total Count of Positive Tweets</figcaption>
</figure>
<p><br /></p>
<figure>
<img class="ui image blog-image" src="/assets/img/blog/uaap-real-time-sentiment-analysis/general/count-negative.jpg" alt="Total Count of Negative Tweets" />
<figcaption>Total Count of Negative Tweets</figcaption>
</figure>
<p><br /></p>
<figure>
<img class="ui image blog-image" src="/assets/img/blog/uaap-real-time-sentiment-analysis/general/percent-positive.jpg" alt="Total Percentage of Positive Tweets" />
<figcaption>Total Percentage of Positive Tweets</figcaption>
</figure>
<p><br /></p>
<figure>
<img class="ui image blog-image" src="/assets/img/blog/uaap-real-time-sentiment-analysis/general/percent-negative.jpg" alt="Total Percentage of Negative Tweets" />
<figcaption>Total Percentage of Negative Tweets</figcaption>
</figure>
<h4 id="looking-at-the-ateneo-tweets">Looking at the Ateneo Tweets</h4>
<figure>
<img class="ui image blog-image" src="/assets/img/blog/uaap-real-time-sentiment-analysis/ateneo/count.jpg" alt="Most Frequently Used Words/Emojis - Ateneo" />
<figcaption>Most Frequently Used Words/Emojis</figcaption>
</figure>
<p><br /></p>
<figure>
<img class="ui image blog-image" src="/assets/img/blog/uaap-real-time-sentiment-analysis/ateneo/positive.jpg" alt="Most Frequently Used Positive Words/Emojis - Ateneo" />
<figcaption>Most Frequently Used Positive Words/Emojis</figcaption>
</figure>
<p><br /></p>
<figure>
<img class="ui image blog-image" src="/assets/img/blog/uaap-real-time-sentiment-analysis/ateneo/negative.jpg" alt="Most Frequently Used Negative Words/Emojis - Ateneo" />
<figcaption>Most Frequently Used Negative Words/Emojis</figcaption>
</figure>
<h4 id="some-examples-of-top-positive-tweets-from-ateneo-fans">Some examples of top positive tweets from Ateneo fans</h4>
<table class="ui celled unstackable table">
<thead>
<tr>
<th>Tweet</th>
<th>Sentiment Score</th>
</tr>
</thead>
<tbody>
<tr>
<td>LOVE YOUR ENERGY, @ThirdyRavenaaa 💙 WOOHOO! Galing, galing!</td>
<td>14</td>
</tr>
<tr>
<td>Thirdy’s maturity every year since high school has been amazing! What’s more amazing is the maturity of not his hops but of the maturity of his biceps triceps and shoulders!!! 💪🏻 Am I right? Haha! Good job @ThirdyRavenaaa !!!</td>
<td>14</td>
</tr>
<tr>
<td>Finals here we come!! 💙 Congratulations, Ateneo Blue Eagles 😃 Good luck sa finals 😊💙 #BEBOB #UAAPFinalFour #OBF</td>
<td>13</td>
</tr>
</tbody>
</table>
<h4 id="now-some-top-negative-tweets-from-ateneo-fans">Now some top negative tweets from Ateneo fans</h4>
<table class="ui celled unstackable table">
<thead>
<tr>
<th>Tweet</th>
<th>Sentiment Score</th>
</tr>
</thead>
<tbody>
<tr>
<td>Sino yung #21 sa FEU? Halatang halata ang pagbunggo kay Isaac aba!! Gago ka?? Kitang kita sa replay pwede ka dumaan sa iba talagang ganun pa ha? Bullshit ka.</td>
<td>-9</td>
</tr>
<tr>
<td>Get yo shit in da basketball court Stockton. There’s no way that’s a basketball play. UAAP should ban that fool. #OBF</td>
<td>-9</td>
</tr>
<tr>
<td>@alecstockton2 how are you doing now in the dugout Mr. Ill tempered piece of shit</td>
<td>-6</td>
</tr>
</tbody>
</table>
<h4 id="looking-at-the-feu-tweets">Looking at the FEU Tweets</h4>
<figure>
<img class="ui image blog-image" src="/assets/img/blog/uaap-real-time-sentiment-analysis/feu/count.jpg" alt="Most Frequently Used Words/Emojis - FEU" />
<figcaption>Most Frequently Used Words/Emojis</figcaption>
</figure>
<p><br /></p>
<figure>
<img class="ui image blog-image" src="/assets/img/blog/uaap-real-time-sentiment-analysis/feu/positive.jpg" alt="Most Frequently Used Positive Words/Emojis - FEU" />
<figcaption>Most Frequently Used Positive Words/Emojis</figcaption>
</figure>
<p><br /></p>
<figure>
<img class="ui image blog-image" src="/assets/img/blog/uaap-real-time-sentiment-analysis/feu/negative.jpg" alt="Most Frequently Used Negative Words/Emojis - FEU" />
<figcaption>Most Frequently Used Negative Words/Emojis</figcaption>
</figure>
<h4 id="some-examples-of-top-positive-tweets-from-feu-fans">Some examples of top positive tweets from FEU fans</h4>
<table class="ui celled unstackable table">
<thead>
<tr>
<th>Tweet</th>
<th>Sentiment Score</th>
</tr>
</thead>
<tbody>
<tr>
<td>I will always be proud of you guys!! You have fought well! Let’s bounce back next year!! Braver!! 💪 Salute to all our graduating players 👏 You all have made the FEU Community so proud!! Thank you our brave Tams! Mahal namin kayo!! 💚💛</td>
<td>17</td>
</tr>
<tr>
<td>Though far from home, our feet may roam Our love will still be true Our voices shall unite to praise thy name anew We’ll treasure within our hearts the FEU! Horns up, Tamaraws! 💚💛🔰 Atleast we made it to the final 4. Not bad at all, Congrats Areneyow! 🤣</td>
<td>15</td>
</tr>
<tr>
<td>Nothing but love and respect to the FEU Men's Basketball team 💚💛 you guys did great! We'll bounce back strong next season.</td>
<td>10</td>
</tr>
</tbody>
</table>
<h4 id="now-some-top-negative-tweets-from-feu-fans">Now some top negative tweets from FEU fans</h4>
<p>We even got a Bisaya tweet in the mix.</p>
<table class="ui celled unstackable table">
<thead>
<tr>
<th>Tweet</th>
<th>Sentiment Score</th>
</tr>
</thead>
<tbody>
<tr>
<td>PUTANGINA MO KA WALA KANG MANNERS GAGO!!!! MGA FANS NG ATENEO BOO KAYO MGA QAQO</td>
<td>-9</td>
</tr>
<tr>
<td>thirdy ravena ayaw paawat sus</td>
<td>-7</td>
</tr>
<tr>
<td>Dili jud ni mawala ang BIASING pag magdula ang ATENEO ayy. Yawa mani si Thirdy Ravena. Playing victim pisteeee 🤬🤬🤬 di kayko ga watch ug basketball pero puta siya ✌🏼</td>
<td>-7</td>
</tr>
</tbody>
</table>
<h3 id="zipfs-law">Zipf’s Law</h3>
<p>The results and the curve that it shows reminds me of a VSauce video that I watched before. See the video here:</p>
<p style="text-align:center;">
<iframe width="100%" height="569" src="https://www.youtube.com/embed/fCn8zs912OE" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</p>
<p>In a nutshell, Zipf’s law just states that given a large sample of words used, the frequency of any word is inversely proportional to its rank in the frequency table. In mathematical terms, a word number n has a frequency proportional to 1/n.</p>
<h3 id="final-words">Final Words</h3>
<p>Me and my group are not data scientists, the methodology that we used is not perfect. We made this project specifically for a database class not necessarily a pattern recognition or data modeling class. The classification logic can be significantly improved and there are more things to analyze in tweets rather than sentiments. I encourage the use of Twitter’s excellent API to look into more possible data science use cases. I also included our presentation deck that has most of the points raised here and a video of our gatherer and classifier in action during the game itself.</p>
<p style="text-align:center;">
<iframe src="https://docs.google.com/presentation/d/e/2PACX-1vR-ozGHpy96TgR2rwHNDmMHsRF1sC3QV5ojjzUYoqP3-8eg_lgkXFUL3oYiyErHXTdcrxH5QhAwir8p/embed?start=false&loop=false&delayms=3000" frameborder="0" width="100%" height="569" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe>
</p>Francis AvanceñaIt is UAAP season once again and this year’s season 82 is hosted by Ateneo it is a good time to take a look at one of my favorite projects I did during my senior year in college. This one is taken from the final project for CS 129.1: Special Topics in Software Engineering: Contemporary Database Technologies, more commonly known as “Contempo DB”. For this project, we did a real-time tweet analysis of tweets during the Final Four game of the Ateneo Blue Eagles and the FEU Tamaraws last November 25, 2018.Implementing Dark Mode to Your Jekyll Site2019-07-08T00:00:00+00:002019-07-08T00:00:00+00:00https://francisoliver.dev/blog/dark-mode-in-jekyll<p><a href="https://jekyllrb.com" target="_blank" rel="noopener">Jekyll</a> is a static site generator powered by Ruby, that can use both HTML and Markdown for markup and Liquid as a template engine. It’s the technology that powers this site! And with the prevalence in web development of now having both a light and a dark theme, we will implement having this functionality in Jekyll sites.</p>
<p>Using native CSS variables and JavaScript we are going to implement a full blown toggled dark mode or dark theme in our Jekyll websites. First we must first define our CSS variables for our light theme. In CSS we define a variable by having two dashes prefixing the variable name like in the code example below.</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">html</span> <span class="p">{</span>
<span class="py">--bg</span><span class="p">:</span> <span class="m">#fff</span><span class="p">;</span>
<span class="py">--bg-secondary</span><span class="p">:</span> <span class="m">#fcfcfc</span><span class="p">;</span>
<span class="py">--headings</span><span class="p">:</span> <span class="m">#07f</span><span class="p">;</span>
<span class="py">--text</span><span class="p">:</span> <span class="m">#333</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>We can see that we have four CSS variables namely <code class="highlighter-rouge">--bg</code>, <code class="highlighter-rouge">--bg-secondary</code>, <code class="highlighter-rouge">--headings</code> and <code class="highlighter-rouge">--text</code>. Once we have all our CSS variables set up for the light theme we create another set for the dark theme retaining the variable names but appending it to the <code class="highlighter-rouge">html</code> element with the <code class="highlighter-rouge">data-theme='dark'</code> attribute like in the code below:</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">html</span><span class="o">[</span><span class="nt">data-theme</span><span class="o">=</span><span class="s2">'dark'</span><span class="o">]</span> <span class="p">{</span>
<span class="py">--bg</span><span class="p">:</span> <span class="m">#333</span><span class="p">;</span>
<span class="py">--bg-secondary</span><span class="p">:</span> <span class="m">#444</span><span class="p">;</span>
<span class="py">--headings</span><span class="p">:</span> <span class="m">#39f</span><span class="p">;</span>
<span class="py">--text</span><span class="p">:</span> <span class="m">#bbb</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>With those two steps out of the way we need only need to assign the CSS variables to the different HTML components.</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="nt">body</span> <span class="p">{</span>
<span class="nl">background-color</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--bg</span><span class="p">);</span>
<span class="p">}</span>
<span class="nc">.container</span> <span class="p">{</span>
<span class="nl">background-color</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--bg-secondary</span><span class="p">);</span>
<span class="p">}</span>
<span class="nt">h1</span><span class="o">,</span> <span class="nt">h2</span><span class="o">,</span> <span class="nt">h3</span><span class="o">,</span> <span class="nt">h4</span><span class="o">,</span> <span class="nt">h5</span><span class="o">,</span> <span class="nt">h6</span> <span class="p">{</span>
<span class="nl">color</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--headings</span><span class="p">);</span>
<span class="p">}</span>
<span class="nt">p</span><span class="o">,</span> <span class="nt">strong</span><span class="o">,</span> <span class="nt">b</span><span class="o">,</span> <span class="nt">em</span><span class="o">,</span> <span class="nt">span</span><span class="o">,</span> <span class="nt">code</span><span class="o">,</span> <span class="nt">small</span> <span class="p">{</span>
<span class="nl">color</span><span class="p">:</span> <span class="n">var</span><span class="p">(</span><span class="n">--text</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now in your <strong>main HTML layout file</strong> to check whether your dark mode works add the attribute <code class="highlighter-rouge">data-theme='dark'</code> to the <code class="highlighter-rouge">html</code> element.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><!DOCTYPE html></span>
<span class="nt"><html</span> <span class="na">lang=</span><span class="s">"en"</span> <span class="na">data-theme=</span><span class="s">"dark"</span><span class="nt">></span>
<span class="nt"><head></span>
<span class="nt"><meta</span> <span class="na">charset=</span><span class="s">"UTF-8"</span><span class="nt">></span>
<span class="nt"><meta</span> <span class="na">name=</span><span class="s">"viewport"</span> <span class="na">content=</span><span class="s">"width=device-width, initial-scale=1.0"</span><span class="nt">></span>
<span class="nt"><meta</span> <span class="na">http-equiv=</span><span class="s">"X-UA-Compatible"</span> <span class="na">content=</span><span class="s">"ie=edge"</span><span class="nt">></span>
<span class="nt"><script </span><span class="na">src=</span><span class="s">"/assets/js/application.js"</span><span class="nt">></script></span>
<span class="nt"><link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"/assets/css/application.css"</span><span class="nt">></span>
<span class="nt"><title></span>My Jekyll Site<span class="nt"></title></span>
<span class="nt"><head></span>
<span class="nt"><body></span>
<span class="c"><!-- Jekyll Template --></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</code></pre></div></div>
<p>For now we leave the site in its light mode set the <code class="highlighter-rouge">data-theme</code> attribute back to <code class="highlighter-rouge">light</code>. Now we must create a button to toggle between both modes and it give the class <code class="highlighter-rouge">theme-toggle</code> to be able to select it using JavaScript.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><button</span> <span class="na">class=</span><span class="s">"theme-toggle"</span><span class="nt">></span>Toggle Dark Mode<span class="nt"></button></span>
</code></pre></div></div>
<p>We will next write the JavaScript for the desired behavior of when clicking the button it must toggle between our two themes using native JavaScript we write.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">document</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">DOMContentLoaded</span><span class="dl">'</span><span class="p">,</span> <span class="nx">themeChange</span><span class="p">);</span>
<span class="kd">function</span> <span class="nx">themeChange</span><span class="p">(){</span>
<span class="c1">// Select our toggle button</span>
<span class="kd">let</span> <span class="nx">button</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">'</span><span class="s1">.theme-toggle</span><span class="dl">'</span><span class="p">);</span>
<span class="c1">// Add an event listener for a click</span>
<span class="nx">button</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">click</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">){</span>
<span class="c1">// Check the current data-theme value</span>
<span class="kd">let</span> <span class="nx">currentTheme</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">getAttribute</span><span class="p">(</span><span class="dl">'</span><span class="s1">data-theme</span><span class="dl">'</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="nx">currentTheme</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">light</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">transition</span><span class="p">();</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="dl">'</span><span class="s1">data-theme</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">dark</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">transition</span><span class="p">();</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="dl">'</span><span class="s1">data-theme</span><span class="dl">'</span><span class="p">,</span><span class="dl">'</span><span class="s1">light</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="c1">// Adds the 'transition' class to <html> for CSS fun</span>
<span class="kd">let</span> <span class="nx">transition</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span><span class="p">{</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="dl">'</span><span class="s1">transition</span><span class="dl">'</span><span class="p">);</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">setTimeout</span><span class="p">(()</span><span class="o">=></span><span class="p">{</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nx">remove</span><span class="p">(</span><span class="dl">'</span><span class="s1">transition</span><span class="dl">'</span><span class="p">);</span>
<span class="p">},</span> <span class="mi">1000</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Going back to our CSS file to add the CSS transition we add the following code below at the end of your CSS file.</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">html</span><span class="nc">.transition</span><span class="o">,</span>
<span class="nt">html</span><span class="nc">.transition</span> <span class="o">*,</span>
<span class="nt">html</span><span class="nc">.transition</span> <span class="o">*</span><span class="nd">:before</span><span class="o">,</span>
<span class="nt">html</span><span class="nc">.transition</span> <span class="o">*</span><span class="nd">:after</span> <span class="p">{</span>
<span class="nl">transition</span><span class="p">:</span> <span class="n">all</span> <span class="m">650ms</span> <span class="cp">!important</span><span class="p">;</span>
<span class="nl">transition-delay</span><span class="p">:</span> <span class="m">0</span> <span class="cp">!important</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>If you are using a single page application like React.js, Angular or Vue. This implementation works fine as you navigate the web app but for Jekyll in which the files are built into the <code class="highlighter-rouge">_sites</code> folder and technically has multiple HTML pages and files when deployed, the dark mode is immediately lost when we move from one HTML file to another. To remedy this we go to one special library known to Ruby Developers: <a href="https://github.com/turbolinks/turbolinks"><strong>turbolinks.js</strong></a>. Known for being shipped with the Ruby on Rails Framework its main implementation and the fundamentals of how turbolinks works is the solution on retaining the dark theme while navigating through our Jekyll site.</p>
<p>Turbolinks works by intercepting anchor tags or the <code class="highlighter-rouge"><a></code> tags in your site and instead of doing the regular <code class="highlighter-rouge">GET</code> request for it, turbolinks intercepts the request and makes an <code class="highlighter-rouge">XMLHttpRequest</code> or more commonly known as an <code class="highlighter-rouge">AJAX</code> request instead and only replacing the contents of the <code class="highlighter-rouge"><body></code> tag. So in technically it makes our Jekyll site like a single page application in a sense.</p>
<p>We only need to add the turbolinks.js library via CDN or locally through <code class="highlighter-rouge">npm</code> and add it to our header in the default HTML layout.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><!DOCTYPE html></span>
<span class="nt"><html</span> <span class="na">lang=</span><span class="s">"en"</span> <span class="na">data-theme=</span><span class="s">"light"</span><span class="nt">></span>
<span class="nt"><head></span>
<span class="nt"><meta</span> <span class="na">charset=</span><span class="s">"UTF-8"</span><span class="nt">></span>
<span class="nt"><meta</span> <span class="na">name=</span><span class="s">"viewport"</span> <span class="na">content=</span><span class="s">"width=device-width, initial-scale=1.0"</span><span class="nt">></span>
<span class="nt"><meta</span> <span class="na">http-equiv=</span><span class="s">"X-UA-Compatible"</span> <span class="na">content=</span><span class="s">"ie=edge"</span><span class="nt">></span>
<span class="nt"><script </span><span class="na">src=</span><span class="s">"https://cdnjs.cloudflare.com/ajax/libs/turbolinks/5.2.0/turbolinks.js"</span><span class="nt">></script></span>
<span class="nt"><script </span><span class="na">src=</span><span class="s">"/assets/js/application.js"</span><span class="nt">></script></span>
<span class="nt"><link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"/assets/css/application.css"</span><span class="nt">></span>
<span class="nt"><title></span>My Jekyll Site<span class="nt"></title></span>
<span class="nt"><head></span>
<span class="nt"><body></span>
<span class="c"><!-- Jekyll Template --></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</code></pre></div></div>
<p>In adding turbolinks we have to edit our JavaScript file, since we said that we want the code to run when <code class="highlighter-rouge">'DOMContentLoaded'</code> event has been triggered but due to turbolinks when we navigate our pages this event does not trigger thus leaving our code unable to execute. We must change the event handler to a turbolinks event namely the <code class="highlighter-rouge">'turbolinks:load'</code> event provided in the turbolinks documentation. Now our JavaScript file should look like the example below.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">document</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">turbolinks:load</span><span class="dl">'</span><span class="p">,</span> <span class="nx">themeChange</span><span class="p">);</span>
<span class="kd">function</span> <span class="nx">themeChange</span><span class="p">(){</span>
<span class="c1">// Select our toggle button</span>
<span class="kd">let</span> <span class="nx">button</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">'</span><span class="s1">.theme-toggle</span><span class="dl">'</span><span class="p">);</span>
<span class="c1">// Add an event listener for a click</span>
<span class="nx">button</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">click</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">){</span>
<span class="c1">// Check the current data-theme value</span>
<span class="kd">let</span> <span class="nx">currentTheme</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">getAttribute</span><span class="p">(</span><span class="dl">'</span><span class="s1">data-theme</span><span class="dl">'</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="nx">currentTheme</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">light</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">transition</span><span class="p">();</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="dl">'</span><span class="s1">data-theme</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">dark</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">transition</span><span class="p">();</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="dl">'</span><span class="s1">data-theme</span><span class="dl">'</span><span class="p">,</span><span class="dl">'</span><span class="s1">light</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="c1">// Adds the 'transition' class to <html> for CSS fun</span>
<span class="kd">let</span> <span class="nx">transition</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span><span class="p">{</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="dl">'</span><span class="s1">transition</span><span class="dl">'</span><span class="p">);</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">setTimeout</span><span class="p">(()</span><span class="o">=></span><span class="p">{</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nx">remove</span><span class="p">(</span><span class="dl">'</span><span class="s1">transition</span><span class="dl">'</span><span class="p">);</span>
<span class="p">},</span> <span class="mi">1000</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This enables our JavaScript files to execute when clicking our button toggler and to navigate without losing our chosen theme in the site. Remember that this only works in one session of navigating through the site, meaning that if another tab is created with the same site the default light theme will be shown, and if you visit the site again in the future it will revert back to the default theme.</p>
<p>Try using cookies to store some information on the browser to retain information on what theme the user prefers. It’s another implementation for another time.</p>Francis AvanceñaJekyll is a static site generator powered by Ruby, that can use both HTML and Markdown for markup and Liquid as a template engine. It’s the technology that powers this site! And with the prevalence in web development of now having both a light and a dark theme, we will implement having this functionality in Jekyll sites.