<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: tjtanjin</title>
    <description>The latest articles on DEV Community by tjtanjin (@tjtanjin).</description>
    <link>https://dev.to/tjtanjin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1133177%2F4a18f3e4-c59c-4c3e-8bf1-18cc03143220.png</url>
      <title>DEV Community: tjtanjin</title>
      <link>https://dev.to/tjtanjin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tjtanjin"/>
    <language>en</language>
    <item>
      <title>Unveiling React ChatBotify v2: Plugins, Themes, Hooks, Events and More</title>
      <dc:creator>tjtanjin</dc:creator>
      <pubDate>Tue, 17 Jun 2025 17:18:04 +0000</pubDate>
      <link>https://dev.to/tjtanjin/unveiling-react-chatbotify-v2-plugins-themes-hooks-events-and-more-5gmn</link>
      <guid>https://dev.to/tjtanjin/unveiling-react-chatbotify-v2-plugins-themes-hooks-events-and-more-5gmn</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Chatbots have become indispensable tools for seeking meaningful, efficient customer interactions. However, creating sophisticated chatbots traditionally required &lt;strong&gt;complex development efforts&lt;/strong&gt; and &lt;strong&gt;challenging integrations&lt;/strong&gt;. About a year ago, &lt;strong&gt;&lt;a href="https://react-chatbotify.com" rel="noopener noreferrer"&gt;React ChatBotify&lt;/a&gt;&lt;/strong&gt; v2 commenced its beta phase, gathering invaluable insights from developers and users to gear up towards a stable release.&lt;/p&gt;

&lt;p&gt;Earlier last week, React ChatBotify v2 launched its &lt;strong&gt;&lt;a href="https://www.npmjs.com/package/react-chatbotify" rel="noopener noreferrer"&gt;stable release&lt;/a&gt;&lt;/strong&gt; - packed with advanced &lt;strong&gt;&lt;a href="https://react-chatbotify.com/plugins" rel="noopener noreferrer"&gt;plugin integrations&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href="https://react-chatbotify.com/themes" rel="noopener noreferrer"&gt;community themes&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href="https://react-chatbotify.com/docs/v2/api/events" rel="noopener noreferrer"&gt;chatbot events&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href="https://react-chatbotify.com/docs/v2/api/hooks/" rel="noopener noreferrer"&gt;intuitive hooks&lt;/a&gt;&lt;/strong&gt;, and extensive customization options. Whether you've followed along since the beta announcement or you're exploring React ChatBotify for the first time, this stable release offers &lt;strong&gt;transformative capabilities&lt;/strong&gt; for everyone.&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;&lt;a href="https://dev.to/tjtanjin/react-chatbotify-v2-beta-release-whats-changed-whats-new-and-whats-next-a6n"&gt;beta release article&lt;/a&gt;&lt;/strong&gt;, we covered some of the breaking changes introduced with v2. If you're interested, you'll find the full list of changes in the &lt;strong&gt;&lt;a href="https://react-chatbotify.com/docs/v2/introduction/migration_v2/" rel="noopener noreferrer"&gt;migration guide&lt;/a&gt;&lt;/strong&gt; helpful. In this article, we'll focus on some of the standout enhancements that will &lt;strong&gt;supercharge&lt;/strong&gt; your chatbot development!&lt;/p&gt;

&lt;h2&gt;
  
  
  Plugins: Extend &amp;amp; Modify Functionalities
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fakq1jthcotctgzn713s8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fakq1jthcotctgzn713s8.png" alt="Plugins Showcase" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In v1, it was incredibly &lt;strong&gt;tedious&lt;/strong&gt; to extend the chatbot with your own custom features, as functionalities of the chatbot were not exposed for integration. This limitation significantly hindered the ability for developers to create reusable behaviors that could be easily shared across projects - or even with others.&lt;/p&gt;

&lt;p&gt;Enter plugins, a powerful feature introduced in v2 which provided a &lt;strong&gt;straightforward and convenient&lt;/strong&gt; approach for developers to extend or even modify existing functionalities of the chatbot in the form of plug-and-play solutions. Built atop the &lt;strong&gt;&lt;a href="https://react-chatbotify.com/docs/v2/api/hooks/" rel="noopener noreferrer"&gt;hooks&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://react-chatbotify.com/docs/v2/api/events" rel="noopener noreferrer"&gt;events&lt;/a&gt;&lt;/strong&gt; API, there now exists countless plugins possibilities! In fact, alongside this v2 release, several &lt;strong&gt;&lt;a href="https://react-chatbotify.com/plugins" rel="noopener noreferrer"&gt;official plugins&lt;/a&gt;&lt;/strong&gt; have been released:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.npmjs.com/package/@rcb-plugins/llm-connector" rel="noopener noreferrer"&gt;LLM Connector Plugin&lt;/a&gt;&lt;/strong&gt;: A generic connector for integrating Large Language Models (LLMs) such as OpenAI ChatGPT &amp;amp; Google Gemini into your chatbot!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.npmjs.com/package/@rcb-plugins/markdown-renderer" rel="noopener noreferrer"&gt;Markdown Renderer Plugin&lt;/a&gt;&lt;/strong&gt;: A simple plugin for beautifully rendering markdown formatted messages in your chatbot!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.npmjs.com/package/@rcb-plugins/html-renderer" rel="noopener noreferrer"&gt;HTML Renderer Plugin&lt;/a&gt;&lt;/strong&gt;: A simple plugin for beautifully rendering html markup formatted messages in your chatbot!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.npmjs.com/package/@rcb-plugins/input-validator" rel="noopener noreferrer"&gt;Input Validator&lt;/a&gt;&lt;/strong&gt;: A lightweight plugin to validate user input with visual feedback in your chatbot!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Supporting the plugins feature was &lt;strong&gt;no trivial effort&lt;/strong&gt;, with the work on it leading to the development of the &lt;strong&gt;hooks&lt;/strong&gt; and &lt;strong&gt;events&lt;/strong&gt; API - both of which are also significant enhancements added in v2.&lt;/p&gt;

&lt;h2&gt;
  
  
  Themes: Styling With Ease
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Froaqnc4ohsbfudu1l7lv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Froaqnc4ohsbfudu1l7lv.png" alt="Base Appearance vs Terminal Theme" width="800" height="556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First introduced in the &lt;strong&gt;&lt;a href="https://dev.to/tjtanjin/react-chatbotify-v2-beta-release-whats-changed-whats-new-and-whats-next-a6n"&gt;initial v2 beta release&lt;/a&gt;&lt;/strong&gt;, the usage of themes has largely remained unchanged during the beta period - that is, developers will still browse the &lt;strong&gt;&lt;a href="https://react-chatbotify.com/themes" rel="noopener noreferrer"&gt;themes gallery&lt;/a&gt;&lt;/strong&gt; to pick out and apply suitable templates. However, the theme creation process has been gradually improved over time, with an upcoming &lt;strong&gt;command-line tool project&lt;/strong&gt; aimed at bringing the developer experience up another notch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hooks: External Control Made Simple
&lt;/h2&gt;

&lt;p&gt;Prior to the introduction of hooks, there was a very &lt;strong&gt;limited surface area&lt;/strong&gt; for developers to directly effect changes on the chatbot externally. For example, it wasn't possible to toggle the chatbot audio (without significant "hacks") via a button that resided outside of the chatbot. This created &lt;strong&gt;friction&lt;/strong&gt; for developers trying to build cohesive UI experiences that extended beyond the chatbot window.&lt;/p&gt;

&lt;p&gt;The addition of hooks in React ChatBotify v2 changes the game. With over a dozen &lt;strong&gt;specialized hooks&lt;/strong&gt; available, developers now have direct access to the internal state and behavior of the chatbot. For example, the &lt;code&gt;useAudio&lt;/code&gt; hook provides the &lt;code&gt;toggleAudio&lt;/code&gt; function which can be easily used in a custom button as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useAudio } from "react-chatbotify";

const CustomButton = () =&amp;gt; {
  const { toggleAudio } = useAudio();

  return (
    &amp;lt;button onClick={toggleAudio}&amp;gt;&amp;lt;/button&amp;gt;
  )
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This opens the door to fully customized, &lt;strong&gt;highly interactive applications&lt;/strong&gt; where the chatbot is just one of many orchestrated parts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Events: Listen and React in Real-Time
&lt;/h2&gt;

&lt;p&gt;In v1, there was &lt;strong&gt;virtually no visibility&lt;/strong&gt; into what the chatbot was doing. Developers had no built-in way to know when the chatbot was sending a message, transitioning paths, or toggling notifications. This made it extremely difficult - if not impossible, to build external logic or integrates with other parts of the website.&lt;/p&gt;

&lt;p&gt;With the extensive event system provided in v2, developers can listen to events ranging from message injections, to chatbot toggles, to user input submissions. It enables developers to &lt;strong&gt;build rich extensions&lt;/strong&gt; (such as in the form of &lt;strong&gt;&lt;a href="https://react-chatbotify.com/plugins" rel="noopener noreferrer"&gt;plugins&lt;/a&gt;&lt;/strong&gt;) around the chatbot's behavior - all without having to hack around the internals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upcoming Roadmap
&lt;/h2&gt;

&lt;p&gt;While v2 already packs numerous powerful features, the journey toward enhancing the chatbot development experience continues. Several exciting improvements are already underway, promising even &lt;strong&gt;greater ease and capability&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Command-line Tool
&lt;/h3&gt;

&lt;p&gt;Currently, React ChatBotify primarily caters to the &lt;strong&gt;React Ecosystem&lt;/strong&gt;. While it's possible for developers to produce standalone builds for integration outside of React, there is no official streamlined approach, making the process relatively cumbersome. To address this, an upcoming command-line tool is in the works, which aims to deliver the following features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scaffold Themes&lt;/strong&gt;: Easily create new themes using pre-configured templates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaffold Plugins&lt;/strong&gt;: Effortlessly set up plugin projects without manually cloning repositories.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate Standalone Widgets&lt;/strong&gt;: Quickly build chatbot widgets ready for embedding in environments outside of React, greatly expanding chatbot deployment scenarios.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By &lt;strong&gt;simplifying&lt;/strong&gt; these processes, the tool aims to reduce friction and improve development workflows, enabling developers to focus more on &lt;strong&gt;creativity and innovation&lt;/strong&gt;!&lt;/p&gt;

&lt;h3&gt;
  
  
  Chat Analytics Plugin
&lt;/h3&gt;

&lt;p&gt;Understanding chatbot interactions is crucial for improving both conversation design and the overall user experience. Currently, it requires considerable manual setup and integrations to gain insights into chatbot usage patterns. An upcoming chat analytics plugin is in the works, which aims to solve this problem by offering &lt;strong&gt;plug-and-play analytics&lt;/strong&gt;, right out of the box.&lt;/p&gt;

&lt;h3&gt;
  
  
  Live Chat Plugin
&lt;/h3&gt;

&lt;p&gt;One of the most frequently requested features has been the ability to seamlessly transition from &lt;strong&gt;automated conversations to human support&lt;/strong&gt;. While still in the early ideation phase, an upcoming Live Chat Plugin is being explored as a solution to bridge that gap.&lt;/p&gt;

&lt;p&gt;The goal is to allow &lt;strong&gt;real-time&lt;/strong&gt;, human-in-the-loop interactions to complement the chatbot experience. This would provide the means to escalate conversations when needed - without disrupting the overall flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;To round off, &lt;strong&gt;&lt;a href="https://react-chatbotify.com" rel="noopener noreferrer"&gt;React ChatBotify v2&lt;/a&gt;&lt;/strong&gt; represents a &lt;strong&gt;major leap forward&lt;/strong&gt; in simplifying and empowering chatbot development. With its robust plugin system, flexible theming, extensive hooks and event APIs, and a growing ecosystem of resources, developers now have &lt;strong&gt;unprecedented control&lt;/strong&gt; over how they build and customize their chatbot experiences.&lt;/p&gt;

&lt;p&gt;Yet, &lt;strong&gt;this is only the beginning&lt;/strong&gt;. With the items in the upcoming roadmap, React ChatBotify is evolving into a full-featured, developer-friendly platform that &lt;strong&gt;adapts to a wide range of use cases&lt;/strong&gt;, from simple customer support bots to complex, deeply integrated conversational interfaces.&lt;/p&gt;

&lt;p&gt;Whether you're just getting started or looking to take your chatbot to the next level, &lt;strong&gt;React ChatBotify v2&lt;/strong&gt; opens up a &lt;strong&gt;&lt;a href="https://react-chatbotify.com" rel="noopener noreferrer"&gt;world of possibilities&lt;/a&gt;&lt;/strong&gt;!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Introducing the LLM Connector Plugin: A Simpler Way to Build AI Chatbots with React ChatBotify</title>
      <dc:creator>tjtanjin</dc:creator>
      <pubDate>Thu, 15 May 2025 18:32:51 +0000</pubDate>
      <link>https://dev.to/tjtanjin/introducing-the-llm-connector-plugin-a-simpler-way-to-build-ai-chatbots-with-react-chatbotify-2bkg</link>
      <guid>https://dev.to/tjtanjin/introducing-the-llm-connector-plugin-a-simpler-way-to-build-ai-chatbots-with-react-chatbotify-2bkg</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbwgezkynk5yj92u0qkli.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbwgezkynk5yj92u0qkli.gif" alt="Google Gemini Model Demo" width="400" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've ever built an AI chatbot for a website, you know that integrating a large language model (LLM) often means &lt;strong&gt;wiring up API calls&lt;/strong&gt;, &lt;strong&gt;managing async flow&lt;/strong&gt;, and writing &lt;strong&gt;custom backend logic&lt;/strong&gt; - all before your bot can even say "Hello World".&lt;/p&gt;

&lt;p&gt;Over a year ago, I wrote about &lt;strong&gt;&lt;a href="https://medium.com/@tjtanjin/how-to-build-and-integrate-a-react-chatbot-with-llms-a-react-chatbotify-guide-part-4-b40cd59fd6e6" rel="noopener noreferrer"&gt;how to integrate LLMs with React ChatBotify&lt;/a&gt;&lt;/strong&gt;, which involves a manual method that worked, but required a fair bit of &lt;strong&gt;glue code and configuration&lt;/strong&gt;. While &lt;strong&gt;&lt;a href="https://react-chatbotify.com/" rel="noopener noreferrer"&gt;React ChatBotify&lt;/a&gt;&lt;/strong&gt; has made it easier to build a chatbot UI, LLM integration still demanded work that could quickly grow in complexity.&lt;/p&gt;

&lt;p&gt;That's exactly the pain point the &lt;strong&gt;&lt;a href="https://www.npmjs.com/package/@rcb-plugins/llm-connector" rel="noopener noreferrer"&gt;LLM Connector Plugin&lt;/a&gt;&lt;/strong&gt; is designed to solve - by providing &lt;strong&gt;out-of-the-box LLM integrations&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With the LLM Connector Plugin, you can &lt;strong&gt;eliminate boilerplate&lt;/strong&gt;, &lt;strong&gt;abstract away complexity&lt;/strong&gt;, and get your chatbot talking to an LLM in &lt;strong&gt;minutes&lt;/strong&gt;. In this post, I'll walk you through what the plugin does, how to install it, and how it makes building smart, conversational UIs with React ChatBotify not only simpler, but faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the LLM Connector Plugin?
&lt;/h2&gt;

&lt;p&gt;The LLM Connector Plugin is an abstraction layer to &lt;strong&gt;streamline LLM integrations&lt;/strong&gt; within &lt;strong&gt;&lt;a href="https://react-chatbotify.com/" rel="noopener noreferrer"&gt;React ChatBotify&lt;/a&gt;&lt;/strong&gt;. It enables developers to connect React ChatBotify to &lt;strong&gt;Large Language Model&lt;/strong&gt; Providers such as &lt;strong&gt;&lt;a href="https://platform.openai.com/" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://ai.google.dev/gemini-api/docs" rel="noopener noreferrer"&gt;Google Gemini&lt;/a&gt;&lt;/strong&gt; with ease. It even ships with integrations with &lt;strong&gt;&lt;a href="https://react-chatbotify.com/docs/examples/llm_conversation" rel="noopener noreferrer"&gt;Browser models&lt;/a&gt;&lt;/strong&gt;, achievable with an extremely simple setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ChatBot&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-chatbotify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;LlmConnector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WebLlmProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@rcb-plugins/llm-connector&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MyComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;llmConnector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;initialMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ask away!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebLlmProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Qwen2-0.5B-Instruct-q4f16_1-MLC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ChatBot&lt;/span&gt; &lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;LlmConnector&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As can be seen from the snippet above, by providing a simple declarative interface, the plugin lets you focus on your bot's behavior and flow. This is in contrast to the past where you'd have to manually handle API calls and message formatting. The result of the above snippet?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6dwi9skddr3c6mtqf7zm.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6dwi9skddr3c6mtqf7zm.gif" alt="Browser Model (WebLLM) Demo" width="400" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Despite how simple it looks, under the hood, the plugin does a lot of &lt;strong&gt;heavy lifting&lt;/strong&gt; - such as handling streaming of responses, syncing of audio, managing typing indicators and more! Ok so we saw a short snippet, but &lt;strong&gt;what did it contain&lt;/strong&gt; and &lt;strong&gt;how exactly do we use the plugin&lt;/strong&gt;? Let's find out!&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation and Setup
&lt;/h2&gt;

&lt;p&gt;The LLM Connector Plugin is available on NPM and can be installed via the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @rcb-plugins/llm-connector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take note that the plugin is only compatible with React ChatBotify versions later than &lt;strong&gt;v2.0.0-beta.34&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;After installing the plugin, you can &lt;strong&gt;import and initialize&lt;/strong&gt; it in your project as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ChatBot&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-chatbotify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;LlmConnector&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@rcb-plugins/llm-connector&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MyComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ChatBot&lt;/span&gt; &lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;LlmConnector&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll next create a block dedicated to handling LLM conversations and proceed to add the &lt;code&gt;llmConnector&lt;/code&gt; attribute to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ChatBot&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-chatbotify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;LlmConnector&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@rcb-plugins/llm-connector&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MyComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;llmConnector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ChatBot&lt;/span&gt; &lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;LlmConnector&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hmmm, nothing's happening. But don't fret, we're almost there! We're now just missing a &lt;strong&gt;LLM Provider&lt;/strong&gt; to have your chatbot start talking. We'll look at how that's done through a minimal example next!&lt;/p&gt;

&lt;h2&gt;
  
  
  A Minimal Example
&lt;/h2&gt;

&lt;p&gt;In this minimal example, we'll import and use the &lt;strong&gt;&lt;a href="https://github.com/React-ChatBotify-Plugins/llm-connector/blob/main/docs/providers/WebLlm.md" rel="noopener noreferrer"&gt;WebLlmProvider&lt;/a&gt;&lt;/strong&gt;, which is provided by default with the plugin. Note that the plugin ships with &lt;strong&gt;3 built-in providers&lt;/strong&gt; (&lt;strong&gt;OpenAI&lt;/strong&gt;, &lt;strong&gt;Gemini&lt;/strong&gt; and &lt;strong&gt;WebLlm&lt;/strong&gt;), which serve to cover the vast majority of common use cases. Let's go ahead and import the &lt;strong&gt;WebLlmProvider&lt;/strong&gt; as such and initialize it within the &lt;code&gt;provider&lt;/code&gt; property inside &lt;code&gt;llmConnector&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ChatBot&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-chatbotify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;LlmConnector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WebLlmProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@rcb-plugins/llm-connector&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MyComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;llmConnector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebLlmProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Qwen2-0.5B-Instruct-q4f16_1-MLC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ChatBot&lt;/span&gt; &lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;LlmConnector&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that when we initialized the &lt;strong&gt;&lt;a href="https://github.com/React-ChatBotify-Plugins/llm-connector/blob/main/docs/providers/WebLlm.md" rel="noopener noreferrer"&gt;WebLlmProvider&lt;/a&gt;&lt;/strong&gt;, we also passed in a minimal set of configurations which included the model. In this case, we tried it with Qwen2–0.5B-Instruct-q4f16_1-MLC but feel free to test it with &lt;strong&gt;&lt;a href="https://huggingface.co/mlc-ai" rel="noopener noreferrer"&gt;other models&lt;/a&gt;&lt;/strong&gt; as well (bear in mind the size of the model if you're running it in your browser)!&lt;/p&gt;

&lt;p&gt;It is important to note that configurations for providers can actually &lt;strong&gt;vary greatly&lt;/strong&gt;. For the configuration guides of the default providers, you may look &lt;strong&gt;&lt;a href="https://github.com/React-ChatBotify-Plugins/llm-connector/tree/main/docs/providers" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The React ChatBotify documentation website also comes with several &lt;strong&gt;live examples&lt;/strong&gt; demonstrating the default providers at work. You are &lt;strong&gt;strongly encouraged&lt;/strong&gt; to check them out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://react-chatbotify.com/docs/examples/llm_conversation" rel="noopener noreferrer"&gt;WebLlm Live Example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://react-chatbotify.com/docs/examples/openai_integration" rel="noopener noreferrer"&gt;OpenAI Provider Live Example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://react-chatbotify.com/docs/examples/gemini_integration" rel="noopener noreferrer"&gt;Gemini Provider Live Example&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creating Your Own Provider
&lt;/h2&gt;

&lt;p&gt;While the plugin offers &lt;strong&gt;default providers&lt;/strong&gt; to cater for the vast majority of common use cases, it's understandable that advanced users may wish to &lt;strong&gt;customize their LLM solutions&lt;/strong&gt;. With that in mind, the plugin is designed to allow users to easily provide their own &lt;strong&gt;custom providers&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;Developers looking to create custom providers can do so by simply importing and implementing the &lt;strong&gt;&lt;a href="https://github.com/React-ChatBotify-Plugins/llm-connector/blob/main/src/types/Provider.ts" rel="noopener noreferrer"&gt;Provider&lt;/a&gt;&lt;/strong&gt; interface. The only method enforced by the interface is &lt;code&gt;sendMessage&lt;/code&gt;, which returns an &lt;code&gt;AsyncGenerator&amp;lt;string&amp;gt;&lt;/code&gt; for the LlmConnector Plugin to consume. A minimal example of a custom provider is shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ChatBot&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-chatbotify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Provider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@rcb-plugins/llm-connector&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyCustomProvider&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;Provider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/**
   * Streams or batch-calls Openai and yields each chunk (or the full text).
   *
   * @param messages  messages to include in the request
   * @param stream    if true, yields each token as it arrives; if false, yields one full response
   */&lt;/span&gt;
   &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;sendMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="nx"&gt;AsyncGenerator&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="c1"&gt;// obviously we should do something with the messages (e.g. call a proxy) but this is just an example&lt;/span&gt;
     &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello World!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're looking to create your own provider, consider referencing the implementations for the &lt;strong&gt;&lt;a href="https://github.com/React-ChatBotify-Plugins/llm-connector/tree/main/src/providers" rel="noopener noreferrer"&gt;default providers&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope this article has demonstrated how much &lt;strong&gt;simpler and faster&lt;/strong&gt; it is now to integrate LLMs with &lt;strong&gt;&lt;a href="https://react-chatbotify.com/" rel="noopener noreferrer"&gt;React ChatBotify&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In the next few articles, we'll dive into &lt;strong&gt;more detailed integrations&lt;/strong&gt; with each of the default providers, including how we can &lt;strong&gt;end an LLM conversation&lt;/strong&gt;. If you're keen to dive deeper, do keep a lookout!&lt;/p&gt;

&lt;p&gt;Finally, if you have any &lt;strong&gt;feedback&lt;/strong&gt;, &lt;strong&gt;suggestions&lt;/strong&gt;, or &lt;strong&gt;thoughts&lt;/strong&gt; about what's shared, feel free to leave a comment or reach out on &lt;strong&gt;&lt;a href="https://discord.gg/6R4DK4G5Zh" rel="noopener noreferrer"&gt;discord&lt;/a&gt;&lt;/strong&gt;. Thank you for reading and see you around! 😊&lt;/p&gt;

</description>
      <category>react</category>
      <category>openai</category>
      <category>programming</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>A Hacktoberfest 2024 Journey: Insights from a First-Time Project Maintainer</title>
      <dc:creator>tjtanjin</dc:creator>
      <pubDate>Mon, 04 Nov 2024 00:29:48 +0000</pubDate>
      <link>https://dev.to/tjtanjin/a-hacktoberfest-2024-journey-insights-from-a-first-time-project-maintainer-96l</link>
      <guid>https://dev.to/tjtanjin/a-hacktoberfest-2024-journey-insights-from-a-first-time-project-maintainer-96l</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest"&gt;2024 Hacktoberfest Writing challenge&lt;/a&gt;: Maintainer Experience&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Maintaining an open-source project is &lt;strong&gt;incredibly fulfilling yet often exhausting&lt;/strong&gt;. Since late July this year, I've been knee-deep in preparations for the v2 stable release for my open-source library, &lt;a href="https://react-chatbotify.com" rel="noopener noreferrer"&gt;&lt;strong&gt;React ChatBotify&lt;/strong&gt;&lt;/a&gt;. It is a time-consuming process, and more than once, I found myself thinking how nice it’d be if there were &lt;strong&gt;extra hands around to help&lt;/strong&gt; with the efforts 😪.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hacktoberfest.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;Hacktoberfest&lt;/strong&gt;&lt;/a&gt;, a month-long open-source event held every October seemed like it could provide exactly that. Hosted by &lt;a href="https://cloud.digitalocean.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;DigitalOcean&lt;/strong&gt;&lt;/a&gt;, Hacktoberfest encourages developers from around the world to contribute to open-source projects in exchange for learning, community building, and digital rewards. It was a &lt;strong&gt;tempting opportunity to bring in fresh contributors, yet committing to it felt daunting&lt;/strong&gt; due to increased responsibilities. Add on to that the "maintainer's nightmare" stories I'd read about having to deal with spammy PRs arising from the event, and you might understand why I was rather apprehensive 😣.&lt;/p&gt;

&lt;p&gt;However, as the month of October kicked off, I noticed an increase in activity on Hacktoberfest's discord server and frankly, &lt;strong&gt;the excitement within was hard to ignore&lt;/strong&gt;. Although I did not plan to participate initially, I felt an urge to dive in and in the spur of a moment, I opened up my project for Hacktoberfest, eager, though anxious to see what the month would bring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparation &amp;amp; Setup
&lt;/h2&gt;

&lt;p&gt;Including a project for Hacktoberfest was &lt;strong&gt;surprisingly straightforward&lt;/strong&gt;. To participate, I only needed to add the &lt;code&gt;hacktoberfest&lt;/code&gt; topic to my repository. While the Hacktoberfest website suggested several other &lt;a href="https://hacktoberfest.com/participation/#maintainers" rel="noopener noreferrer"&gt;&lt;strong&gt;best practices&lt;/strong&gt;&lt;/a&gt; for maintainers, having the &lt;code&gt;hacktoberfest&lt;/code&gt; topic was technically sufficient to have my project included.&lt;/p&gt;

&lt;p&gt;That said, I also spent some time &lt;strong&gt;updating the project README, developer guide, as well as contribution guidelines&lt;/strong&gt; to ensure that they contained the most updated information. &lt;strong&gt;Clarity&lt;/strong&gt; was made a high priority, so that developers can better understand what they're getting into and how to get started.&lt;/p&gt;

&lt;p&gt;Troublesome as it may seem, I would eventually learn that &lt;strong&gt;this was actually the easiest part&lt;/strong&gt; 😂. From creating issues, to managing PRs, each of them presented a unique set of problems that ranged from interesting to frustrating. Let's &lt;strong&gt;look back on them together&lt;/strong&gt; in the rest of this article!&lt;/p&gt;

&lt;h2&gt;
  
  
  Creation of Issues
&lt;/h2&gt;

&lt;p&gt;Initially, I had thought that the creation of issues would be relatively quick and simple. After all, I just had to create issues and tag them with a &lt;code&gt;hacktoberfest&lt;/code&gt; label right? However, it proved to be more complex than expected. You see, when trying to create issues, &lt;strong&gt;a couple of important questions&lt;/strong&gt; came to mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who am I catering the issues for?&lt;/li&gt;
&lt;li&gt;Which areas does the project need help in?&lt;/li&gt;
&lt;li&gt;What kind of issues to create?&lt;/li&gt;
&lt;li&gt;How can I help people ease into the issues?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If I wanted to create issues for my project that others would be keen to work on, &lt;strong&gt;I had to answer these questions&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  An Initial Exploration
&lt;/h3&gt;

&lt;p&gt;Since I was already in the Hacktoberfest Discord, I looked within for answers. Based on a couple days of observations, I gathered that there were a &lt;strong&gt;significant number of first-timers&lt;/strong&gt; who are keen to dip into open source work. With that in mind, I decided to make most of my issues &lt;strong&gt;beginner-friendly&lt;/strong&gt;. It was after all my first time participating in Hacktoberfest as well 😝.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scoping the Issues
&lt;/h3&gt;

&lt;p&gt;Due to the ongoing v2 changes, significant code rewrites had occurred across the entire project. While there was a basic integration test in place to catch the occasional obvious bugs, it is &lt;strong&gt;far from enough&lt;/strong&gt; with the scale of features introduced in v2. Up till then, I had never properly put in the time and effort to setup unit testing (which would undoubtedly &lt;strong&gt;improve the reliability of my project&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;With that in mind, scoping my issues to writing tests sounded like an excellent idea. It's a win-win-win situation for aspiring first-time open-source contributors, the &lt;a href="https://github.com/tjtanjin/react-chatbotify" rel="noopener noreferrer"&gt;&lt;strong&gt;React ChatBotify&lt;/strong&gt;&lt;/a&gt; project, and definitely for myself 😝.&lt;/p&gt;

&lt;p&gt;Firstly, writing tests is a &lt;strong&gt;valuable skill for developers&lt;/strong&gt; to pick up. It may also comes across as &lt;strong&gt;less daunting for first-timers&lt;/strong&gt;, since tests do not directly interfere with the core logic of the library. Secondly, the project itself stands to benefit from having more &lt;strong&gt;robust test cases&lt;/strong&gt;. This is a particularly apt time as well given the massive rewrite that came along with v2. Lastly, I appreciate the help with writing tests, which gave me more &lt;strong&gt;time and confidence to work on the v2 features&lt;/strong&gt; 😊.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lowering the Barrier of Entry
&lt;/h3&gt;

&lt;p&gt;With that said, I still had concerns about the quality of the tests that would be written, and acknowledged that beginners may benefit more from &lt;strong&gt;a little hand-holding or reference&lt;/strong&gt;. It wasn't like I could just create the issues and then magically, people come along and the tests would be perfectly created (I wish 😅).&lt;/p&gt;

&lt;p&gt;So, what better way than to provide examples within the codebase itself? The project was already neatly structured into &lt;code&gt;components&lt;/code&gt;, &lt;code&gt;hooks&lt;/code&gt;, &lt;code&gt;services&lt;/code&gt; etc. For testing, it made sense to mirror this structure. Hence, before even creating the issues, I wrote several unit tests for a couple of internal hooks. These test cases were then &lt;strong&gt;referenced and used as examples in the issues&lt;/strong&gt; I created for writing test cases!&lt;/p&gt;

&lt;p&gt;Now imagine wanting to contribute to a project and being given the task details as below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjyopmlqdyryg3u4ijvx2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjyopmlqdyryg3u4ijvx2.png" alt="Example Task Details" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's &lt;strong&gt;short and sweet&lt;/strong&gt;, with appropriate links to the resources that you will need. Hopefully, you'll agree with me that it's &lt;strong&gt;pretty welcoming to beginners&lt;/strong&gt; 😋.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Continuous Process
&lt;/h3&gt;

&lt;p&gt;Much to my surprise, the issues received a lot of attention, so much so they were being assigned out &lt;strong&gt;much faster than I had anticipated&lt;/strong&gt;. Thus, throughout the month of October, I found myself continuously creating a bunch of fresh issues every few days to keep up with the growth in interest. I know what you may be thinking, but I wasn't creating issues unnecessarily 😐.&lt;/p&gt;

&lt;p&gt;In fact, the constant creation of issues was very much possible because the project itself had a lot of &lt;code&gt;components&lt;/code&gt;, &lt;code&gt;hooks&lt;/code&gt; and &lt;code&gt;services&lt;/code&gt; that could be &lt;strong&gt;tested individually&lt;/strong&gt;. As part of the v2 rewrite, majority of the core library logic had been broken down into smaller, more manageable parts which facilitated easier testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stale Issues
&lt;/h3&gt;

&lt;p&gt;Of course, not everything goes well. Some issues became stale overtime either due to a &lt;strong&gt;lack of interest&lt;/strong&gt;, or that their assignees had gone &lt;strong&gt;missing-in-action&lt;/strong&gt; (MIA) 😰. This was not unexpected, though on some occasions, dropping a friendly ping to the assignees brought them back into action 😝.&lt;/p&gt;

&lt;p&gt;All in all, by keeping issues &lt;strong&gt;beginner-friendly, scoped to testing and with proper references&lt;/strong&gt;, it worked out pretty well! That said, if given a chance again, I would probably have experimented with creating a handful of &lt;strong&gt;challenging issues&lt;/strong&gt; to cater for different skill levels. But hey, there's always next year!&lt;/p&gt;

&lt;h2&gt;
  
  
  Management of Pull Requests
&lt;/h2&gt;

&lt;p&gt;Before even the first pull request came in, I knew that the management of pull requests was going to consume a significant amount of time - and it did. Out of all work involved for Hacktoberfest, reviewing pull requests &lt;strong&gt;took up the most time&lt;/strong&gt; and because of that, I actually had to calibrate my approach towards managing pull requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Balanced Approach
&lt;/h3&gt;

&lt;p&gt;Issues only tell half the story. For every issue created, I had to be mentally prepared that there'd be one (or perhaps even more than one) pull request (PR) I have to review. However, unlike issues which were created with a structured template I curated, the solutions that came with pull requests took on various forms. This is understandable, as developers tend to have &lt;strong&gt;different approaches and preferences&lt;/strong&gt; when tackling problems.&lt;/p&gt;

&lt;p&gt;Now, there're 2 ways I could do this. One, I could be &lt;strong&gt;very picky, insisting that everyone write the same way and take the same approach&lt;/strong&gt;. Or two, I could focus on the &lt;strong&gt;quality of the test cases&lt;/strong&gt; themselves and close an eye to other minor differences. Some of you may be wondering why not combine both options - i.e. be picky, and focus on the quality of the test cases as well.&lt;/p&gt;

&lt;p&gt;That's not impossible, but in the interest of time and resources, I went with an approach to strike a balance. Given that Hacktoberfest was a timed-event with many first-timers, the value of the latter felt greater. After all, it doesn't really help my case if the test cases were poorly designed. As I already had test cases for reference, I was also not expecting approaches and code styles to stray too far off. Hence, unless there was something drastically different, I didn't nitpick too hard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resolving Conflicts
&lt;/h3&gt;

&lt;p&gt;Occasionally, &lt;strong&gt;multiple PRs are opened for the same issue&lt;/strong&gt;. This happens when developers directly open a PR &lt;strong&gt;without first expressing interest and being assigned an issue&lt;/strong&gt;. When faced with such a situation, I fell back to the contribution guidelines that I had clearly written:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq2ca64sf6c0lz9gt4jal.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq2ca64sf6c0lz9gt4jal.png" alt="Contribution Guidelines" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ultimately, it is only fair that the assigned developer who &lt;strong&gt;followed the guidelines&lt;/strong&gt; gets priority in having the PR accepted. That said, I also did not want the other party to feel unappreciated. Oftentimes, I would facilitate discussions between the 2 developers to decide how best to proceed. Sometimes, the assigned developers acknowledges that he/she has not started on the issue and was willing to pass it up. Other times, the developer who opened a PR without being assigned happily took up other issues.&lt;/p&gt;

&lt;p&gt;I was certainly lucky that all conflicts were resolved amicably, as the developers I worked with were &lt;strong&gt;very understanding&lt;/strong&gt;. I greatly appreciate their help in &lt;strong&gt;maintaining a safe environment&lt;/strong&gt; for the project 😊.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stale Pull Requests
&lt;/h3&gt;

&lt;p&gt;Like issues, there were also a handful of stale PRs. This happens when a PR is reviewed but there was no subsequent follow-up from the developer. For this Hacktoberfest, there were only 2 of such cases and thankfully, &lt;strong&gt;other developers came along to pick up the associated issues!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With that said, stale pull requests weren't much of a problem. In fact, at the end of the event, I was surprised to see the number of pull requests merged. Take a look below!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before Hacktoberfest:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa6mr7zwz1jt117zlg0zu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa6mr7zwz1jt117zlg0zu.png" alt="Before Hacktoberfest" width="800" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After Hacktoberfest:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv92qfo2iq5wt0fa6916n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv92qfo2iq5wt0fa6916n.png" alt="After Hacktoberfest" width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A whopping &lt;strong&gt;56 merged pull requests&lt;/strong&gt; arising from the event, amazing isn't it?&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways &amp;amp; Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Having just been through my first Hacktoberfest event, I must say it has &lt;strong&gt;far exceeded my expectations&lt;/strong&gt; in terms of the value that it brings. I would never have imagined the &lt;strong&gt;overwhelming interests&lt;/strong&gt;, as well as the &lt;strong&gt;quality of improvements&lt;/strong&gt; that would be made to my project. Numbers don't lie - the &lt;a href="https://github.com/tjtanjin/react-chatbotify/graphs/contributors" rel="noopener noreferrer"&gt;&lt;strong&gt;contributors&lt;/strong&gt;&lt;/a&gt; to my project has more than doubled in just a month:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fla1ofvw3yx9r5qwjtdvg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fla1ofvw3yx9r5qwjtdvg.png" alt="Contributors" width="602" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Reflecting on this entire experience, I believe there a few key areas that contributed to the &lt;strong&gt;overall very positive experience&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clear Contribution Guidelines
&lt;/h3&gt;

&lt;p&gt;Having a &lt;strong&gt;clear contribution guideline&lt;/strong&gt; is extremely important. Imagine trying to learn a game, but being given a poorly written tutorial - or worse, not even finding one. Would you still continue? Maybe, maybe not - but why take the chances?&lt;/p&gt;

&lt;p&gt;The same applies when developers search for projects to contribute to. A clear contribution guideline lets developers have &lt;strong&gt;clarity on where and how to get started&lt;/strong&gt;. For &lt;a href="https://github.com/tjtanjin/react-chatbotify" rel="noopener noreferrer"&gt;&lt;strong&gt;React ChatBotify&lt;/strong&gt;&lt;/a&gt;, I freshened up the contribution guidelines for Hacktoberfest alongside the project README and developer guide. They most definitely have a part to play in &lt;strong&gt;easing developers into the project!&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Quality of Issues
&lt;/h3&gt;

&lt;p&gt;As you may be able to tell from what I've shared, a great amount of effort went into ensuring that issues were &lt;strong&gt;well curated&lt;/strong&gt;. This meant that the issues created contained the necessary information and were of a reasonable quality.&lt;/p&gt;

&lt;p&gt;This was important, not just for forming a &lt;strong&gt;solid first-impression&lt;/strong&gt;. It actually gives developers the &lt;strong&gt;clarity and confidence required to tackle the issues&lt;/strong&gt;. As a bonus, it also helps &lt;strong&gt;lighten the load&lt;/strong&gt; on my end as developers are less likely to have to seek clarifications if the issues contain all the relevant information 😊.&lt;/p&gt;

&lt;h3&gt;
  
  
  Community Engagement
&lt;/h3&gt;

&lt;p&gt;In any collaborative setting, engagement is, if not the highest, certainly one of my &lt;strong&gt;top priorities&lt;/strong&gt;. For a project to thrive, the &lt;strong&gt;sense of community&lt;/strong&gt; is essential — and Hacktoberfest was no exception. I made a point to respond to issues/PRs as fast as I could and provided help to the best of my ability.&lt;/p&gt;

&lt;p&gt;This not only &lt;strong&gt;kept developers engaged&lt;/strong&gt;, but also &lt;strong&gt;offered them the assurance&lt;/strong&gt; that they were contributing to an actively maintained project. More importantly, they know that &lt;strong&gt;they are not working on issues alone&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  You Define Your Own Experience
&lt;/h3&gt;

&lt;p&gt;Despite best efforts, &lt;strong&gt;not all things will go as planned&lt;/strong&gt;. As project maintainers, it’s easy to get caught up in tracking project metrics — like how many issues were assigned or PRs merged.&lt;/p&gt;

&lt;p&gt;However, it's not often that we look within ourselves, and consider realistically what we think will come out of the experience. For me, I approached Hacktoberfest with minimal expectations, focusing instead on the process itself. In the end, I was &lt;strong&gt;pleasantly surprised&lt;/strong&gt;, gaining far more than I anticipated and it turned out to be a &lt;strong&gt;very fulfilling experience&lt;/strong&gt; 😊!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Looking back, Hacktoberfest went pass in a flash. It's &lt;strong&gt;heartening&lt;/strong&gt; to see that even now, after the event has ended, a few dedicated developers are still working on open issues and pull requests. Prior to the event, the "horror stories" of spam PRs had me really concerned but as it turns out, the event went &lt;strong&gt;much better than I expected&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Hopefully, with the &lt;strong&gt;insights and lessons learned&lt;/strong&gt; from this year, next year's experience will be &lt;strong&gt;even more rewarding&lt;/strong&gt;! As a personal goal, I hope to introduce &lt;strong&gt;more challenging and a larger variety of issues next Hacktoberfest&lt;/strong&gt;. For now, it's a wrap 😊! &lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hacktoberfest</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Behind the Scenes: Solutioning for React ChatBotify's Themes</title>
      <dc:creator>tjtanjin</dc:creator>
      <pubDate>Wed, 31 Jul 2024 14:58:48 +0000</pubDate>
      <link>https://dev.to/tjtanjin/behind-the-scenes-solutioning-for-react-chatbotifys-themes-1ogl</link>
      <guid>https://dev.to/tjtanjin/behind-the-scenes-solutioning-for-react-chatbotifys-themes-1ogl</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3jsumif28xm3iawwtsaq.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3jsumif28xm3iawwtsaq.gif" alt="Switching Themes" width="400" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've ever developed or interacted with chatbots on a website, you'll know that their &lt;strong&gt;aesthetics&lt;/strong&gt; play a &lt;strong&gt;crucial role&lt;/strong&gt; in enhancing the &lt;strong&gt;overall visual appeal of the site&lt;/strong&gt;. However, theming isn't just about making things pretty; it's also about &lt;strong&gt;crafting a seamless and engaging user experience&lt;/strong&gt;. In the past, customising chatbots with &lt;a href="https://www.npmjs.com/package/react-chatbotify" rel="noopener noreferrer"&gt;&lt;strong&gt;React ChatBotify&lt;/strong&gt;&lt;/a&gt; required a fair bit of effort. Users had to style everything from the base template, which could be very time-consuming, especially when pursuing a drastically different design.&lt;/p&gt;

&lt;p&gt;With this in mind, the idea for a theming feature emerged to streamline the customisation process. The goal - to provide users with a variety of ready-to-use themes that could be &lt;strong&gt;easily applied and refined&lt;/strong&gt; to match specific requirements. This article takes you behind the scenes on the solutioning journey, where we explore &lt;strong&gt;3 potential approaches&lt;/strong&gt; to implementing themes in React ChatBotify. We will walk through the factors for consideration, analyse the pros and cons of each approach, before committing to a final solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Prior to the &lt;a href="https://dev.to/tjtanjin/react-chatbotify-v2-beta-release-whats-changed-whats-new-and-whats-next-a6n"&gt;introduction of themes&lt;/a&gt; in React ChatBotify, customising chatbots could involve a highly manual and tedious process. Users started off with a &lt;strong&gt;base template&lt;/strong&gt;, which essentially consisted of the &lt;strong&gt;default settings and styles&lt;/strong&gt; that came with the chatbot.&lt;/p&gt;

&lt;p&gt;To achieve a unique look or to complement their websites, developers may have to comb through the &lt;a href="https://react-chatbotify.com/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; to meticulously &lt;strong&gt;override default styles&lt;/strong&gt;. For example, let's consider the stark contrast between the base appearance and a more sophisticated "Terminal" theme:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzt4q6sor8uiu723jy4hz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzt4q6sor8uiu723jy4hz.png" alt="Base Appearance vs Terminal Theme" width="800" height="556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To transition from the base appearance to the "Terminal" theme shown above, it &lt;strong&gt;involved numerous changes&lt;/strong&gt; across &lt;a href="https://github.com/tjtanjin/react-chatbotify-themes/tree/main/themes/terminal/0.1.0" rel="noopener noreferrer"&gt;3 files&lt;/a&gt; (&lt;code&gt;settings.json&lt;/code&gt;, &lt;code&gt;styles.json&lt;/code&gt; &amp;amp; &lt;code&gt;styles.css&lt;/code&gt;). Let us just take a quick look at the contents of the &lt;code&gt;styles.json&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "headerStyle": {
    "backgroundImage": "linear-gradient(to right, #2c2c2a, #2c2c2a)",
    "alignItems": "center",
    "fontSize": "14px",
    "border": 0
  },
  "botBubbleStyle": {
    "textAlign": "left",
    "border": 0,
    "backgroundColor": "#070707",
    "color": "rgba(255, 255, 255, 0.87)",
    "padding": "10px 15px",
    "maxWidth": "none",
    "margin": 0,
    "fontSize": "14px"
  },
  "userBubbleStyle": {
    "textAlign": "left",
    "border": 0,
    "backgroundColor": "#070707",
    "color": "rgba(255, 255, 255, 0.87)",
    "padding": "10px 15px",
    "maxWidth": "none",
    "margin": 0,
    "fontSize": "14px"
  },
  "botOptionStyle": {
    "color": "rgba(255, 255, 255, 0.87)",
    "backgroundColor": "#070707",
    "padding": 0,
    "border": 0,
    "fontSize": "14px"
  },
  "botOptionHoveredStyle": {
    "color": "rgba(255, 255, 255, 0.87)",
    "textDecoration": "underline",
    "backgroundColor": "#070707",
    "padding": 0,
    "border": 0,
    "transition": "none",
    "fontSize": "14px"
  },
  "chatInputContainerStyle": {
    "borderTop": 0,
    "backgroundColor": "transparent"
  },
  "chatInputAreaStyle": {
    "minHeight": 0,
    "border": 0,
    "padding": "8px 15px",
    "backgroundColor": "#070707",
    "color": "rgba(255, 255, 255, 0.87)",
    "fontSize": "14px"
  },
  "sendButtonStyle": {
    "display": "none"
  },
  "chatWindowStyle": {
    "height": "490px",
    "paddingBottom": "10px",
    "backgroundColor": "#070707",
    "border": "1px solid grey"
  },
  "bodyStyle": {
    "paddingBottom": 0,
    "backgroundColor": "#070707"
  },
  "tooltipStyle": {
    "padding": "8px 12px",
    "borderRadius": "15px",
    "color": "rgba(255, 255, 255, 0.87)"
  },
  "chatHistoryLineBreakStyle": {
    "color": "rgba(255, 255, 255, 0.87)"
  },
  "chatHistoryButtonStyle": {
    "color": "rgba(255, 255, 255, 0.87)",
    "backgroundColor": "#070707",
    "border": 0
  },
  "chatHistoryButtonHoveredStyle": {
    "color": "rgba(255, 255, 255, 0.87)",
    "backgroundColor": "#070707",
    "border": 0,
    "textDecoration": "underline"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that despite the great degree of &lt;strong&gt;flexibility for customisation&lt;/strong&gt;, the amount of work can get rather significant if you are seeking out a very different design.&lt;/p&gt;

&lt;p&gt;With that said, it was obvious that there was a huge room for improvement in the user experience. More specifically, if users could make selections from pre-configured settings and styles, it could potentially &lt;strong&gt;make their lives easier&lt;/strong&gt; by reducing the amount of refinement work required. Thus, the concept of &lt;strong&gt;themes&lt;/strong&gt; was born, which naturally led to the next question - &lt;strong&gt;how best to bring themes to users?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Naive Solution: Pre-package Themes with the Core Library
&lt;/h2&gt;

&lt;p&gt;A naive solution came to mind almost immediately. It is &lt;strong&gt;straightforward&lt;/strong&gt;, and &lt;strong&gt;trivial to implement&lt;/strong&gt;. Simply put, we could pre-make a couple of themes and include their settings and styles directly in the core library. When users specify the themes they desire, we simply fetch and load the relevant settings and styles. Pretty simple, isn't it?&lt;/p&gt;

&lt;p&gt;Not only is this solution guaranteed to work, all the changes required to support this feature can be done &lt;strong&gt;within the core library itself&lt;/strong&gt;. It's a tempting solution, but if we pause for a moment and ponder a bit deeper, we quickly realize that the ease of this approach comes with &lt;strong&gt;significant tradeoffs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Firstly, while delivering the initial feature is easy, its maintenance becomes a &lt;strong&gt;nightmarish experience&lt;/strong&gt; for users. This is because every update to the themes - whether it's adding new ones or fixing bugs in existing themes - would necessitate a version bump for the &lt;strong&gt;core library&lt;/strong&gt;. This would be confusing and extremely annoying for users who don't use themes, as they would have to check and update the library for changes &lt;strong&gt;irrelevant to their setup&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On top of that, the core library is bound to &lt;strong&gt;bloat up overtime&lt;/strong&gt; as more themes are added. This could potentially lead to degradation in the library's performance, which is most certainly &lt;strong&gt;undesirable&lt;/strong&gt; when trying to craft a seamless chatbot experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Simple to implement (all changes kept within the core library)&lt;/li&gt;
&lt;li&gt;Easy integration for users (themes can be simply applied via the themes prop)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Disadvantages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Poor user experience for maintaining the library in the long term (users may be confused and annoyed by irrelevant version bumps)&lt;/li&gt;
&lt;li&gt;Library bloat (unsustainable as more themes are added overtime)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Comparing the advantages and disadvantages, above, it becomes clear that adopting the naive solution trades away &lt;strong&gt;scalability and ease of maintenance&lt;/strong&gt; for a &lt;strong&gt;very short-term convenience&lt;/strong&gt;. With that said, the idea of combining themes into the core library quickly lost its appeal. So, is there a better approach?&lt;/p&gt;

&lt;h2&gt;
  
  
  A Refined Solution: Create a Themes Extension Package
&lt;/h2&gt;

&lt;p&gt;Drawing on the knowledge from exploring the naive solution, it is apparent that mixing themes with the core library is a terrible idea. So let's address that! In this second solution, we refine the initial idea by addressing its major disadvantages. To do so, we tackle the root of the problem - by &lt;strong&gt;keeping themes and the core library separate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Thus, the idea of a &lt;strong&gt;themes extension package&lt;/strong&gt; came to mind. With this improved approach, themes will be handled independently via a separate NPM package. This reduces the confusion and annoyance caused to users in the naive approach, and prevents the core library from bloating, which helps mitigate the issues faced previously. However, this refined solution is not without its drawbacks.&lt;/p&gt;

&lt;p&gt;Firstly, there is now &lt;strong&gt;increased complexity&lt;/strong&gt; in implementing this feature since changes are no longer localised to the core library. Rather, there are now 2 packages to work with, which also &lt;strong&gt;increases the effort required&lt;/strong&gt; to maintain the project. Furthermore, users who wish to use the themes feature will also need to make an &lt;strong&gt;additional installation&lt;/strong&gt; for the extension package and end up having more dependencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Improved user experience (no confusion or unnecessary updates for those not using themes)&lt;/li&gt;
&lt;li&gt;Mitigated library bloat (themes are separated from the core library)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Disadvantages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Additional implementation effort (changes across multiple packages to implement themes)&lt;/li&gt;
&lt;li&gt;Additional maintenance effort (2 packages instead of 1)&lt;/li&gt;
&lt;li&gt;Additional setup step required from users (installation of extension package required)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The second approach seems slightly better, as the advantages are now &lt;strong&gt;oriented to benefit the users&lt;/strong&gt; while the disadvantages are largely absorbed by… me 🥹. Hmmm, maybe this isn't a terrific idea after all. Can we try something even better?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Ideal Solution: Serve Themes with JsDeliver &amp;amp; GitHub
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8pr3jtax2zxehgf00vx8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8pr3jtax2zxehgf00vx8.png" alt="JSDelivr &amp;amp; GitHub" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the previous two approaches, we were weighing who should shoulder the seemingly inherent disadvantages that came with implementing themes - whether it was the burden on users or the additional maintenance effort for developers of the library. However, &lt;strong&gt;neither of the solutions felt satisfactory&lt;/strong&gt;. Could we find a way to eliminate or minimize drawbacks for both users and developers?&lt;br&gt;
As a platform engineer, I found inspiration in &lt;strong&gt;GitOps&lt;/strong&gt; -a methodology that uses a Git repository as the single source of truth, with version control providing a clear history of changes. Themes, in the context of React ChatBotify, could be seen as a &lt;strong&gt;set-meal&lt;/strong&gt; containing specific styles and settings (which were configurations stored in &lt;code&gt;json&lt;/code&gt; and &lt;code&gt;css&lt;/code&gt; files). This realisation led to the idea of hosting themes directly on GitHub, which not only &lt;strong&gt;decoupled themes from the core library&lt;/strong&gt;, but also &lt;strong&gt;eliminated the need for maintaining an additional package&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Hence, the third solution explored was to host all theme files, such as &lt;code&gt;settings.json&lt;/code&gt;, &lt;code&gt;styles.json&lt;/code&gt; and &lt;code&gt;styles.css&lt;/code&gt; on &lt;strong&gt;GitHub&lt;/strong&gt;. To avoid rate-limiting issues, the core library would be modified to fetch these themes dynamically using a &lt;strong&gt;content delivery network (CDN)&lt;/strong&gt; such as &lt;strong&gt;JSDelivr&lt;/strong&gt;. This approach offered several very compelling advantages.&lt;/p&gt;

&lt;p&gt;Firstly, it &lt;strong&gt;eliminated the need for maintaining additional NPM packages&lt;/strong&gt; and &lt;strong&gt;avoided version bumps&lt;/strong&gt; for the core library when updating themes. Secondly, it &lt;strong&gt;preserves the ease of use for users&lt;/strong&gt; as themes would work out of the box for them. Finally as an added bonus, this solution &lt;strong&gt;fosters a more collaborative community&lt;/strong&gt; environment, as the GitHub repository could be &lt;strong&gt;open sourced&lt;/strong&gt; to welcome contributions from other developers!&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Improved user experience (no confusion or unnecessary updates for those not using themes)&lt;/li&gt;
&lt;li&gt;Mitigated library bloat (themes are separated from the core library)&lt;/li&gt;
&lt;li&gt;Easy integration for users (themes can be simply applied via the themes prop)&lt;/li&gt;
&lt;li&gt;Encourages open source contributions from the community&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Disadvantages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Additional effort required to curate and maintain a GitHub themes repository&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reviewing the above pros and cons, it's clear that this third solution provides a seamless experience for users and allows both themes and the core project to be maintained independently - all while fostering community involvement through the open-source themes repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Winner
&lt;/h2&gt;

&lt;p&gt;After weighing the three options, the third solution emerged as the clear winner due to its &lt;strong&gt;strong appeal and comprehensive advantages&lt;/strong&gt;.&lt;br&gt;
To further enhance the user experience with themes, a separate &lt;a href="https://github.com/tjtanjin/react-chatbotify-gallery-website" rel="noopener noreferrer"&gt;&lt;strong&gt;React ChatBotify Gallery Project&lt;/strong&gt;&lt;/a&gt; was also launched. The &lt;a href="https://gallery.react-chatbotify.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;Gallery&lt;/strong&gt;&lt;/a&gt; essentially serves as a centralised platform for users to explore and select themes. More details about this was shared in a separate article detailing &lt;a href="https://dev.to/tjtanjin/react-chatbotify-v2-beta-release-whats-changed-whats-new-and-whats-next-a6n"&gt;&lt;strong&gt;the release of React ChatBotify v2&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The journey to implementing a theming feature in React ChatBotify was both &lt;strong&gt;challenging and rewarding&lt;/strong&gt;. By leveraging &lt;strong&gt;GitOps-inspired principles&lt;/strong&gt;, the final adopted solution not only simplifies the user experience, it also encourages contributions from the broader developer community. The next step is to &lt;strong&gt;encourage creative individuals to craft and contribute themes&lt;/strong&gt; to the community, but that's a challenge for another time.&lt;/p&gt;

&lt;p&gt;With this, we are &lt;strong&gt;one step closer&lt;/strong&gt; to making React ChatBotify a more versatile and customisable library for everyone. I hope you've found this solutioning journey interesting. As usual, if you have any feedback, suggestions, or thoughts about what's shared, feel free to leave a comment or reach out on &lt;a href="https://discord.com/invite/6R4DK4G5Zh" rel="noopener noreferrer"&gt;discord&lt;/a&gt;. Thank you for reading! 😊&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>git</category>
      <category>react</category>
    </item>
    <item>
      <title>React ChatBotify v2 Beta Release: What’s Changed, What’s New and What’s Next?</title>
      <dc:creator>tjtanjin</dc:creator>
      <pubDate>Fri, 26 Jul 2024 20:27:59 +0000</pubDate>
      <link>https://dev.to/tjtanjin/react-chatbotify-v2-beta-release-whats-changed-whats-new-and-whats-next-a6n</link>
      <guid>https://dev.to/tjtanjin/react-chatbotify-v2-beta-release-whats-changed-whats-new-and-whats-next-a6n</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdp3kv45j3yihxw7umgel.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdp3kv45j3yihxw7umgel.png" alt="Themes in v2" width="800" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The humble journey of &lt;a href="https://react-chatbotify.com/" rel="noopener noreferrer"&gt;React ChatBotify&lt;/a&gt; began in late July of last year. However, it wasn't until recent months that it saw a steady increase in adoptions alongside a series of updates. With the release of v2 beta, it's a great opportunity to look at &lt;strong&gt;what's changed&lt;/strong&gt;, &lt;strong&gt;what's new&lt;/strong&gt;, as well as &lt;strong&gt;what's next&lt;/strong&gt; in future plans down the road.&lt;/p&gt;

&lt;p&gt;This article serves to cover the major changes and the thought processes that goes behind them. It's &lt;strong&gt;not a migration guide&lt;/strong&gt;, so if you're looking for detailed steps to upgrade from v1 to v2, refer to the migration guide &lt;a href="https://react-chatbotify.com/docs/introduction/migration_v2/" rel="noopener noreferrer"&gt;&lt;strong&gt;here&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Changed?
&lt;/h2&gt;

&lt;p&gt;In v1, a lot of effort went into &lt;strong&gt;making things work&lt;/strong&gt;. Early adopters might recall issues with the chatbot on mobile devices, such as improper display on certain browsers or operating systems, or &lt;a href="https://dev.to/tjtanjin/mobile-web-audio-removing-media-controls-from-notifications-tray-1nl3"&gt;notifications causing media controls to appear&lt;/a&gt;. The chatbot's come a long way since then and has gotten a lot more stable, thanks to the community of bug reporters who surfaced these issues to be addressed.&lt;/p&gt;

&lt;p&gt;In v2, priority shifted to &lt;strong&gt;making things right&lt;/strong&gt;. Admittedly, some early design decisions were not optimal, and a v2 upgrade was timely for rectifying those mistakes. Below, we will look at &lt;strong&gt;3 significant changes&lt;/strong&gt; from this update.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reorganization of Options into Settings &amp;amp; Styles
&lt;/h3&gt;

&lt;p&gt;One of the most significant breaking changes in v2 is the reorganization of the &lt;code&gt;options&lt;/code&gt; prop (also referred to as &lt;code&gt;BotOptions&lt;/code&gt;) into &lt;code&gt;settings&lt;/code&gt; and &lt;code&gt;styles&lt;/code&gt;. Previously in v1, all configurations for the chatbot were centrally managed within the &lt;code&gt;options&lt;/code&gt; prop. This included both configurations for determining the &lt;strong&gt;functionalities&lt;/strong&gt; of the chatbot, as well as &lt;strong&gt;styling&lt;/strong&gt; for the appearance of the chatbot.&lt;/p&gt;

&lt;p&gt;However, as the library expanded its capabilities, the &lt;code&gt;options&lt;/code&gt; prop grew significantly larger and it became apparent that mixing both functionalities and styles into a single prop was not a great idea. For better clarity and to make configurations more manageable, it was necessary to &lt;strong&gt;separate styling from functionalities&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Hence in v2, the &lt;code&gt;settings&lt;/code&gt; prop was introduced to manage the functional configurations, such as &lt;a href="https://react-chatbotify.com/docs/api/settings/#notification" rel="noopener noreferrer"&gt;&lt;code&gt;notification&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://react-chatbotify.com/docs/api/settings/#audio" rel="noopener noreferrer"&gt;&lt;code&gt;audio&lt;/code&gt;&lt;/a&gt;, while the &lt;code&gt;styles&lt;/code&gt; prop handles the visual aspects such as &lt;a href="https://react-chatbotify.com/docs/api/styles/" rel="noopener noreferrer"&gt;&lt;code&gt;headerStyle&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://react-chatbotify.com/docs/api/styles/" rel="noopener noreferrer"&gt;&lt;code&gt;botBubbleStyle&lt;/code&gt;&lt;/a&gt;. This separation not only clarifies the API, it also facilitates future expansions and maintenance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dropping of Dynamic Attributes Concept
&lt;/h3&gt;

&lt;p&gt;In v1, dynamic attributes was introduced to refer to a specific set of &lt;a href="https://react-chatbotify.com/docs/api/attributes/" rel="noopener noreferrer"&gt;&lt;code&gt;attributes&lt;/code&gt;&lt;/a&gt; that allowed you to use &lt;a href="https://react-chatbotify.com/docs/api/params/" rel="noopener noreferrer"&gt;&lt;code&gt;params&lt;/code&gt;&lt;/a&gt;. On hindsight, the concept of dynamic attributes was restrictive, allowing only specific attributes to use params. In fact, removing it would simplify the mental model for developers.&lt;/p&gt;

&lt;p&gt;And that's what was done! In v2, all attributes can now access params and users no longer need to make any distinction between them. This change improves consistency across the API and reduces the need for special handling or conditional checks when working with various attributes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Updates to Internal Message Component
&lt;/h3&gt;

&lt;p&gt;This change is subtle, but for users who were using their own custom components with the chatbot, it is a &lt;strong&gt;potential breaking change&lt;/strong&gt; as the message container itself was updated.&lt;/p&gt;

&lt;p&gt;In v1, there was a specific logic written to control the showing of avatars alongside bot or user messages. Internally, default chat messages from both the user and the bot were considered to be &lt;strong&gt;chat bubbles&lt;/strong&gt; and &lt;code&gt;showAvatar&lt;/code&gt; was written to specifically only apply to them. Thus, &lt;code&gt;options&lt;/code&gt; and &lt;code&gt;checkboxes&lt;/code&gt; (which are not chat bubbles) would be specifically filtered out to avoid sending any avatars at all.&lt;/p&gt;

&lt;p&gt;This was never an issue, until the need to support &lt;strong&gt;media display&lt;/strong&gt; arose as part of the new &lt;code&gt;showMediaDisplay&lt;/code&gt; enhancement for file attachments. With this new feature, there was a need to support sending of avatars for non-chat bubble messages as well because if a user was sending an attachment, it would be weird to not show the avatar if it was enabled. This meant the current design would not work, and prompted a &lt;strong&gt;re-look at the need for such special handling&lt;/strong&gt; in the first place.&lt;/p&gt;

&lt;p&gt;To address this and avoid similar issues in future, the decision was made to standardise how avatars will be shown. In short, if enabled, an avatar appears at the beginning of a message group from the same sender, &lt;strong&gt;similar to conventional messaging apps&lt;/strong&gt; (I know, why even do something different in the first place? 😔).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqxngww1yfoduxy9cetc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqxngww1yfoduxy9cetc.png" alt="Changes to Avatar Display Logic: Before (left), After (right)" width="800" height="555"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this change, all message components now adopt a standardised style, which required changes to how messages were displayed. Thus, custom components &lt;strong&gt;may experience breaking changes&lt;/strong&gt; where minor styling updates need to be applied. This was a genuine oversight but it's a necessary update to avoid further issues down the road.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's New?
&lt;/h2&gt;

&lt;p&gt;Beyond the above changes, v2 itself also brings several exciting new features. A bulk of the new features in v2 stem from &lt;strong&gt;user requirements&lt;/strong&gt;, which often present themselves in the form of feature requests or clarifications for use cases. Below, we'll look at a handful of new features that you might find useful!&lt;/p&gt;

&lt;h3&gt;
  
  
  Greater Customisation of Header, Chat Input &amp;amp; Footer Buttons
&lt;/h3&gt;

&lt;p&gt;A question that has popped up numerous times - Is it possible to add my own buttons in the header?&lt;/p&gt;

&lt;p&gt;In v1, the chatbot came with a set of buttons available in the &lt;code&gt;header&lt;/code&gt;, &lt;code&gt;chatInput&lt;/code&gt; and &lt;code&gt;footer&lt;/code&gt; which could be enabled or disabled. However, re-ordering them was &lt;strong&gt;not possible&lt;/strong&gt;, much less adding custom buttons.&lt;/p&gt;

&lt;p&gt;This has changed in v2, with a new &lt;code&gt;buttons&lt;/code&gt; prop added to the &lt;code&gt;header&lt;/code&gt;, &lt;code&gt;chatInput&lt;/code&gt; and &lt;code&gt;footer&lt;/code&gt; sections. The &lt;code&gt;buttons&lt;/code&gt; prop is essentially an array of buttons, which allows not only custom buttons to be added but also the ability to &lt;strong&gt;re-order them however you wish&lt;/strong&gt;!&lt;/p&gt;

&lt;h3&gt;
  
  
  Media Displays In Messages
&lt;/h3&gt;

&lt;p&gt;While it was possible to send file attachments in v1, it was not possible to display their content even if they were media files. This meant that viewing images and playing videos or audio in the chatbot was not possible.&lt;/p&gt;

&lt;p&gt;Furthermore, without support for media display, it also meant that it's not possible to send voice messages as a playable audio. With v2, support has been added for media display in messages which enables the following features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Display image, video or audio in file attachments&lt;/li&gt;
&lt;li&gt;Send voice messages as an audio file to be played&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are &lt;strong&gt;disabled by default&lt;/strong&gt; but for those who are actively using the file attachment features or would like to send voice messages in the form of audio, enabling them could &lt;strong&gt;enhance the overall experience&lt;/strong&gt; that your chatbot provides for your users!&lt;/p&gt;

&lt;h3&gt;
  
  
  New Themes Prop
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fww2ocvr8kic9bjx82jnn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fww2ocvr8kic9bjx82jnn.png" alt="Cyborg &amp;amp; Terminal Theme" width="300" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In v1, customising the chatbot appearance could involve &lt;strong&gt;heavy styling work&lt;/strong&gt;, especially if you were looking for a design that was &lt;strong&gt;drastically different&lt;/strong&gt; from the base template.&lt;/p&gt;

&lt;p&gt;To improve the customisation experience, a new &lt;code&gt;themes&lt;/code&gt; prop was added in v2 which allows users to specify a theme (or list of themes) to &lt;strong&gt;apply pre-configured settings and styles quickly&lt;/strong&gt;. This meant that it is now possible to &lt;a href="https://gallery.react-chatbotify.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;browse for themes&lt;/strong&gt;&lt;/a&gt;, pick out a design that you like, apply it to your chatbot, then add further refinements on top of it. It helps to think of &lt;code&gt;themes&lt;/code&gt; as a set-meal containing settings and styles that save you the work of having to do all the customisations from scratch.&lt;/p&gt;

&lt;p&gt;On a small note, implementing the &lt;code&gt;themes&lt;/code&gt; feature was an interesting experience itself and I’ve dedicated a &lt;a href="https://dev.to/tjtanjin/react-chatbotify-v2-beta-release-whats-changed-whats-new-and-whats-next-a6n"&gt;&lt;strong&gt;separate short article&lt;/strong&gt;&lt;/a&gt; to share about my experience working on it. If you're keen, &lt;strong&gt;do take a look&lt;/strong&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;So far, we've explored features that have been improved or made newly available. Looking ahead, there are a plethora of exciting features still in the works. Below, we visit a few ideas that hold &lt;strong&gt;great potential&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To manage expectations, it is important to note that while these features hold great potential, they are all still being explored or worked on. The result might be different or the approach may change along the way while grappling with challenges and navigating new ideas. Keep an open mind!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Theme Builder
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjszof7yuma3imekrln7r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjszof7yuma3imekrln7r.png" alt="React ChatBotify Gallery Landing Page" width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've checked out the &lt;a href="https://gallery.react-chatbotify.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;React ChatBotify Gallery&lt;/strong&gt;&lt;/a&gt; website mentioned earlier, you might have noticed a few other pages apart from themes. If you've also tried logging on, you'll notice that &lt;strong&gt;GitHub OAuth integration&lt;/strong&gt; has been added. These are all leading towards an eventual plan to have a theme builder on the website, allowing users to craft custom themes directly before optionally uploading them to &lt;strong&gt;share with others&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you're wondering about the GitHub integration, it is an implementation detail of themes, which will be covered in a separate article that will discuss about this more in depth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Plugins
&lt;/h3&gt;

&lt;p&gt;Plugins arose as an idea from several requests for &lt;strong&gt;live chat support&lt;/strong&gt;. While exploring how best to support this feature, the appeal of a plugin stood out. This is because there simply isn't a clean way to provide in-built live chat support without restricting the kind of backend setup that could be supported. On the contrary, enabling third-party plugin integrations presents a much more &lt;strong&gt;adaptable solution&lt;/strong&gt;, opening the doors for &lt;strong&gt;more possibilities&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With that in mind, the live chat feature evolved into something even more ambitious - rewriting parts of the library to support third-party plugin integrations. More than just live chats, we're looking at the possibility of features such as &lt;strong&gt;chatbot analytics&lt;/strong&gt;, how cool is that!&lt;/p&gt;

&lt;h3&gt;
  
  
  Emitting ChatBot Events
&lt;/h3&gt;

&lt;p&gt;Last but not least and as an &lt;strong&gt;extension&lt;/strong&gt; to &lt;strong&gt;complement&lt;/strong&gt; the plugin feature, exploratory work is being done for emitting &lt;strong&gt;chatbot events&lt;/strong&gt;. The intention is to allow users to listen for these chatbot events and &lt;strong&gt;trigger custom logic&lt;/strong&gt; based on them.&lt;/p&gt;

&lt;p&gt;If you've ever thought about performing certain actions &lt;strong&gt;when a message is sent&lt;/strong&gt;, &lt;strong&gt;when a chatbot is opened&lt;/strong&gt;, or &lt;strong&gt;when certain features are enabled&lt;/strong&gt;, this would be a feature to look forward to!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The newly released v2 beta of React ChatBotify marks the beginning of &lt;strong&gt;many more exciting updates&lt;/strong&gt;. There are various interesting pieces of ongoing work, several of which are gearing us towards &lt;strong&gt;enabling and fostering a much more collaborative community&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;While I'd like to think that the library is already very feature-rich with a great degree of flexibility for customisation, the truth is &lt;strong&gt;there's always room for improvements&lt;/strong&gt; if you look hard enough and definitely &lt;strong&gt;much more that can be achieved&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I hope that this sharing has been interesting, and if you happen to be looking to contribute to open source projects, then do consider picking up some of the &lt;a href="https://github.com/tjtanjin/react-chatbotify/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22" rel="noopener noreferrer"&gt;&lt;strong&gt;good-first-issues&lt;/strong&gt;&lt;/a&gt;! It's always great to have people come together to build something cool! Finally and as usual, feel free to reach out and connect on &lt;a href="https://discord.com/invite/6R4DK4G5Zh" rel="noopener noreferrer"&gt;discord&lt;/a&gt; or share your thoughts, suggestions and feedback in the comments. Hope to catch you in the next article!😊&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Automating Telegram Bot Deployment with GitHub Actions and Docker</title>
      <dc:creator>tjtanjin</dc:creator>
      <pubDate>Wed, 12 Jun 2024 16:16:24 +0000</pubDate>
      <link>https://dev.to/tjtanjin/automating-telegram-bot-deployment-with-github-actions-and-docker-1af1</link>
      <guid>https://dev.to/tjtanjin/automating-telegram-bot-deployment-with-github-actions-and-docker-1af1</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In previous articles, we explored the &lt;a href="https://dev.to/tjtanjin/from-screen-to-docker-a-look-at-two-hosting-options-42eh"&gt;benefits of Docker&lt;/a&gt;, as well as walked through examples of &lt;a href="https://dev.to/tjtanjin/how-to-dockerize-a-telegram-bot-a-step-by-step-guide-37ol"&gt;how we may dockerize our project&lt;/a&gt; and &lt;a href="https://dev.to/tjtanjin/level-up-your-projects-with-github-actions-cicd-2lnh"&gt;integrate CI/CD into our development workflows&lt;/a&gt;. However, we haven't yet examined how to piece them together in practical applications.&lt;/p&gt;

&lt;p&gt;In this article, we will embark on a journey to automate the deployment of a Telegram bot with &lt;strong&gt;Docker&lt;/strong&gt; and &lt;strong&gt;GitHub Actions&lt;/strong&gt;. By the end of this walkthrough, you will garner deeper insights into how Docker and GitHub Actions may be used in tandem to streamline the deployment of your projects. So without further ado, let's dive into the content!&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we explore the automating of a Telegram bot deployment, do note that the guide assumes knowledge of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Familiarity with Linux command line&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/tjtanjin/how-to-dockerize-a-telegram-bot-a-step-by-step-guide-37ol"&gt;Dockerizing a Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/tjtanjin/level-up-your-projects-with-github-actions-cicd-2lnh"&gt;Basic Understanding of GitHub Actions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will not be covering the above as they are not the focus of this guide - links to relevant guides for them have been provided! All that said, if you need help with any of the above, you are more than welcome to &lt;a href="https://discord.com/invite/X8VSdZvBQY" rel="noopener noreferrer"&gt;reach out&lt;/a&gt; for assistance.&lt;/p&gt;

&lt;p&gt;Note that the contents of this tutorial can be &lt;strong&gt;generalized&lt;/strong&gt; and applied to projects other than Telegram bots. However, for the purpose of this tutorial, you are encouraged to follow it through with &lt;a href="https://github.com/tjtanjin/tele-qr" rel="noopener noreferrer"&gt;this simple Telegram bot project&lt;/a&gt;. &lt;strong&gt;Even better&lt;/strong&gt; if you have read my &lt;a href="https://dev.to/tjtanjin/how-to-build-a-telegram-bot-a-beginners-step-by-step-guide-1kd1"&gt;previous articles&lt;/a&gt;, because then you would be comfortable working with Telegram bots by now!&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Setup
&lt;/h2&gt;

&lt;p&gt;As mentioned, we will be using (&lt;a href="https://github.com/tjtanjin/tele-qr" rel="noopener noreferrer"&gt;TeleQR&lt;/a&gt;) as an example so go ahead and clone the project to your local computer with the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/tjtanjin/tele-qr.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project already comes with GitHub Actions setup by default. For the purpose of this tutorial, let us &lt;strong&gt;remove&lt;/strong&gt; that by deleting all the contents within the &lt;code&gt;.github/workflows&lt;/code&gt; folder. Once that's done, we are ready to create our own workflows!&lt;/p&gt;

&lt;p&gt;Specifically for TeleQR, we are keen to create &lt;strong&gt;3 workflows&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flake8 Lint (for checking of code styles)&lt;/li&gt;
&lt;li&gt;Docker Hub (for building and uploading of our docker image)&lt;/li&gt;
&lt;li&gt;Deployment (for deploying our Telegram bot)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will create the workflow files in the order above and watch how all of them come together at the end of this! But first, what makes a typical GitHub Actions workflow?&lt;/p&gt;

&lt;h2&gt;
  
  
  A Typical GitHub Actions Workflow
&lt;/h2&gt;

&lt;p&gt;A GitHub Actions workflow is a YAML file that automates tasks in your development lifecycle, such as for &lt;strong&gt;linting your code&lt;/strong&gt; or &lt;strong&gt;deploying your project&lt;/strong&gt;. Typically, it's located in the &lt;code&gt;.github/workflows&lt;/code&gt; directory of your repository and consist of the following parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name:&lt;/strong&gt; Provides a convenient identifier for the workflow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trigger Events:&lt;/strong&gt; Defines what events trigger the workflow, such as &lt;code&gt;push&lt;/code&gt; or &lt;code&gt;pull_request&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jobs and Steps:&lt;/strong&gt; Specifies jobs that are made up of steps that perform tasks like checking out code, setting up environments, installing dependencies and running tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment Variables:&lt;/strong&gt; Defines environment variables that can be used throughout the workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are more properties not covered above but this is enough for understanding the walkthrough. If they look foreign to you, &lt;strong&gt;don't fret!&lt;/strong&gt; We'll now curate a simple workflow file for linting our code that will provide you with more clarity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create Flake8 Lint Workflow
&lt;/h2&gt;

&lt;p&gt;To create the Lint workflow, let us first create a &lt;code&gt;flake8-lint.yml&lt;/code&gt; file within the &lt;code&gt;.github/workflows&lt;/code&gt; folder. For linting, we are using flake8 so let us &lt;strong&gt;name&lt;/strong&gt; this workflow &lt;code&gt;Flake8 Lint&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Flake8 Lint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are also keen to run this workflow only when pushes are made to the master branch, so let us include that as a &lt;strong&gt;trigger event&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;on:
  push:
    branches: [ "master" ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we will specify a single lint &lt;strong&gt;job&lt;/strong&gt; to run using the latest Ubuntu image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs:
  lint:
    name: Lint Codebase
    runs-on: ubuntu-latest
    steps:
    # our steps here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see from the comment in the snippet above, we actually need to define the &lt;strong&gt;steps&lt;/strong&gt; in the job. For this lint job, we will carry out the following &lt;strong&gt;4 steps&lt;/strong&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Checkout Code
&lt;/h3&gt;

&lt;p&gt;Before we can lint our codebase, we need to understand that workflow runs are done on a &lt;strong&gt;runner&lt;/strong&gt;. In this case, you can think of it as a separate server or virtual machine that will run the linting job. The Checkout Code step uses &lt;a href="https://github.com/actions/checkout" rel="noopener noreferrer"&gt;actions/checkout&lt;/a&gt;, which is crucial for bringing your repository code into the runner environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Checkout code
  uses: actions/checkout@v4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this step, your codebase will not even be available for linting!&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Setup Python
&lt;/h3&gt;

&lt;p&gt;For this Python project, we are using Python 3.10 so let us setup and install this Python version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Set up Python
  run: |
    sudo add-apt-repository ppa:deadsnakes/ppa
    sudo apt-get update
    sudo apt-get install python3.10 -y
    sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 1
    sudo update-alternatives --set python3 /usr/bin/python3.10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Install Dependencies
&lt;/h3&gt;

&lt;p&gt;Next, in order to run &lt;a href="https://flake8.pycqa.org/en/latest/" rel="noopener noreferrer"&gt;flake8&lt;/a&gt;, we need it to be installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Install dependencies
  run: |
    python -m pip install --upgrade pip
    pip install flake8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Run flake8
&lt;/h3&gt;

&lt;p&gt;Finally, once the setup is complete, we can do the actual linting by running flake8:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Run Flake8
  run: |
    python -m flake8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Putting all our configurations together, our final file for linting will look similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Flake8 Lint
run-name: Flake8 Lint

on:
  push:
    branches: [ "master" ]

jobs:
  lint:
    name: Lint Codebase
    runs-on: ubuntu-latest
    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Python
      run: |
        sudo add-apt-repository ppa:deadsnakes/ppa
        sudo apt-get update
        sudo apt-get install python3.10 -y
        sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 1
        sudo update-alternatives --set python3 /usr/bin/python3.10

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install flake8

    - name: Run Flake8
      run: |
        python -m flake8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we've gone through the various parts of the workflow, it's getting pretty straightforward - isn't it? Let us move on and look at how we can create the Docker Hub workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create Docker Hub Workflow
&lt;/h2&gt;

&lt;p&gt;Similar to the Lint workflow, we will add a &lt;code&gt;docker-hub.yml&lt;/code&gt; file within the &lt;code&gt;.github/workflows folder&lt;/code&gt;. Since we will be publishing a docker image onto &lt;a href="https://hub.docker.com/" rel="noopener noreferrer"&gt;Docker Hub&lt;/a&gt; in this workflow, let us &lt;strong&gt;name&lt;/strong&gt; it &lt;code&gt;Docker Hub&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Docker Hub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, &lt;strong&gt;unlike the Lint workflow&lt;/strong&gt;, we only want the Docker Hub workflow to run &lt;strong&gt;after the Lint workflow is completed&lt;/strong&gt;. Thus, we add the following &lt;strong&gt;trigger event&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;on:
  workflow_run:
    workflows: ["Flake8 Lint"]
    types:
      - completed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that said, just waiting for the Lint workflow to be completed before running the Docker Hub workflow is &lt;strong&gt;not enough&lt;/strong&gt;, we also want to ensure that the &lt;strong&gt;Lint workflow completed successfully&lt;/strong&gt;. Thus, we will add a check to our job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    steps:
    # our steps here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice an extra line that checks if the &lt;code&gt;workflow_run&lt;/code&gt; concluded successfully. We can then move on to adding the &lt;strong&gt;job and steps&lt;/strong&gt; for our Docker Hub workflow:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Checkout Code
&lt;/h3&gt;

&lt;p&gt;As with before, we use &lt;a href="https://github.com/actions/checkout" rel="noopener noreferrer"&gt;actions/checkout&lt;/a&gt; to checkout our code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Checkout code
  uses: actions/checkout@v4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Login to Docker Hub
&lt;/h3&gt;

&lt;p&gt;In order to publish our Docker Image to Docker Hub, we need to first login to our account. We can make use of &lt;a href="https://github.com/docker/login-action" rel="noopener noreferrer"&gt;docker/login-action&lt;/a&gt; for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Log in to Docker Hub
  uses: docker/login-action@
  with:
    username: ${{ secrets.DOCKER_USERNAME }}
    password: ${{ secrets.DOCKER_PASSWORD }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, some of you may be wondering - how are we going to provide the username and password required for logging in? It's certainly not wise to include our credentials directly in the workflow file, but we can use &lt;strong&gt;GitHub secrets&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;Head over to your &lt;strong&gt;repository settings&lt;/strong&gt; and look to the tab on the left. Under the &lt;strong&gt;Security&lt;/strong&gt; section, click on &lt;strong&gt;Secrets and variables&lt;/strong&gt; and in the dropdown, click on &lt;strong&gt;Actions&lt;/strong&gt;. This is what you should see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4vl0u910d325v09tqscy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4vl0u910d325v09tqscy.png" alt="GitHub Actions Secrets" width="720" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since we'll need a &lt;strong&gt;username and password&lt;/strong&gt; to login to docker hub, we'll create &lt;strong&gt;2 secrets&lt;/strong&gt; with the following names:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DOCKER_USERNAME&lt;/li&gt;
&lt;li&gt;DOCKER_PASSWORD&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The values for these secrets will have to come from your &lt;a href="https://hub.docker.com/" rel="noopener noreferrer"&gt;Docker Hub&lt;/a&gt; account. Once these are created, they will be available for our Docker Hub workflow to use!&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Extract Metadata for Docker
&lt;/h3&gt;

&lt;p&gt;We will next then extract the metadata that will be included with our docker image when we push it to Docker Hub using &lt;a href="https://github.com/docker/metadata-action" rel="noopener noreferrer"&gt;docker/metadata-action&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Extract metadata (tags, labels) for Docker
  id: meta
  uses: docker/metadata-action@v5
  with:
    images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're observant, you may notice that we have 2 environment variables (&lt;strong&gt;REGISTRY&lt;/strong&gt; and &lt;strong&gt;IMAGE_NAME&lt;/strong&gt;) declared here. Go ahead and declare an &lt;code&gt;env&lt;/code&gt; property at the top of the file for these variables in the following manner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;env:
  REGISTRY: docker.io
  IMAGE_NAME: ${{ github.repository }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are basically specifying that the registry we'll be uploading our docker image to is &lt;code&gt;docker.io&lt;/code&gt; and that the repository name will be used as the image name.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Build and Push Docker Image
&lt;/h3&gt;

&lt;p&gt;Finally, we build and push our image to Docker Hub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Build and push Docker image
  uses: docker/build-push-action@v5
  with:
    context: .
    file: ./Dockerfile
    push: true
    tags: ${{ steps.meta.outputs.tags }}
    labels: ${{ steps.meta.outputs.labels }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the tags and labels are obtained from the previous step! Your final Docker Hub workflow file should look similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Docker Hub
run-name: Docker Hub

on:
  workflow_run:
    workflows: ["Flake8 Lint"]
    types:
      - completed

env:
  REGISTRY: docker.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  push_to_registry:
    name: Push Docker image to Docker Hub
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    steps:
      - name: Check out the repo
        uses: actions/checkout@v3

      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ./Dockerfile
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create Deployment WorkFlow
&lt;/h2&gt;

&lt;p&gt;Upon successfully pushing our docker image to Docker Hub, the final workflow we want to run would be deployment. Create a &lt;code&gt;deployment.yml&lt;/code&gt; file within the &lt;code&gt;.github/workflows&lt;/code&gt; folder. We will simply &lt;strong&gt;name&lt;/strong&gt; this &lt;code&gt;Deployment&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Deployment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For deployment, we only want it to run after our image has been pushed to Docker Hub. Thus, we add the following &lt;strong&gt;trigger event&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;on:
  workflow_run:
    workflows: ["Docker Hub"]
    types:
      - completed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, we only want to deploy if the Docker Hub workflow succeeded so we add a quick check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    steps:
    # our steps here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this workflow, the &lt;strong&gt;job&lt;/strong&gt; only has a single &lt;strong&gt;step&lt;/strong&gt; which calls the &lt;a href="https://github.com/tjtanjin/tele-qr/blob/master/scripts/deploy.sh" rel="noopener noreferrer"&gt;deployment script&lt;/a&gt; - the script resides on a &lt;strong&gt;Virtual Private Server&lt;/strong&gt; (VPS) that I have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: executing deployment script
  uses: appleboy/ssh-action@v0.1.10
  with:
    host: ${{ secrets.DEPLOYMENT_HOST }}
    username: ${{ secrets.DEPLOYMENT_USERNAME }}
    password: ${{ secrets.DEPLOYMENT_PASSWORD }}
    script: cd tele-qr/ &amp;amp;&amp;amp; ./scripts/deploy.sh tele-qr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we have provided &lt;strong&gt;3 more secrets&lt;/strong&gt; to authenticate to my VPS using &lt;a href="https://github.com/appleboy/ssh-action" rel="noopener noreferrer"&gt;appleboy/ssh-action&lt;/a&gt;. For the deployment workflow, it will likely vary depending on how you wish to deploy/host the project. For my project, I &lt;a href="https://dev.to/tjtanjin/how-to-host-a-telegram-bot-on-ubuntu-a-step-by-step-guide-4jk3"&gt;hosted it on a simple VPS server&lt;/a&gt; and this is the final workflow file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Deployment
run-name: Deployment

on:
  workflow_run:
    workflows: ["Docker Hub"]
    types:
      - completed

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    steps:
    - name: executing deployment script
      uses: appleboy/ssh-action@v0.1.10
      with:
        host: ${{ secrets.DEPLOYMENT_HOST }}
        username: ${{ secrets.DEPLOYMENT_USERNAME }}
        password: ${{ secrets.DEPLOYMENT_PASSWORD }}
        script: cd tele-qr/ &amp;amp;&amp;amp; ./scripts/deploy.sh tele-qr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing &amp;amp; Verification
&lt;/h2&gt;

&lt;p&gt;Having put everything together, we can easily do a quick test to verify that our workflows are setup as intended. Trigger the workflows to run by &lt;strong&gt;making any code changes&lt;/strong&gt; to the project (e.g. adding a comment) and you should see the Lint workflow beginning to run under the &lt;code&gt;Actions&lt;/code&gt; tab of your repository. If everything runs successfully, you'll be greeted with results similar to the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftz20fpyibob7uo9hplul.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftz20fpyibob7uo9hplul.png" alt="GitHub Actions Results" width="720" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Anddddd it's a wrap! In this tutorial, we've gone through detailed steps of setting up our GitHub Actions workflows to automate the deployment of a Telegram bot. If you are keen, you can go further and explore the adding of a &lt;strong&gt;test workflow&lt;/strong&gt; as well as using &lt;strong&gt;matrix strategy&lt;/strong&gt; to run against multiple versions. For reference, the workflows for a chatbot project of mine can be found &lt;a href="https://github.com/tjtanjin/react-chatbotify" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, I hope that you've found this sharing useful, and that you'll see value in applying what was covered here into your projects. As usual, feel free to share your thoughts, ideas, or feedback in the comments or &lt;a href="https://discord.com/invite/X8VSdZvBQY" rel="noopener noreferrer"&gt;reach out&lt;/a&gt; to me directly. Thank you for your time and see you in the next article!&lt;/p&gt;

</description>
      <category>docker</category>
      <category>githubactions</category>
      <category>telegrambot</category>
      <category>telegram</category>
    </item>
    <item>
      <title>Level Up Your Projects with GitHub Actions &amp; CI/CD</title>
      <dc:creator>tjtanjin</dc:creator>
      <pubDate>Sat, 27 Apr 2024 09:49:13 +0000</pubDate>
      <link>https://dev.to/tjtanjin/level-up-your-projects-with-github-actions-cicd-2lnh</link>
      <guid>https://dev.to/tjtanjin/level-up-your-projects-with-github-actions-cicd-2lnh</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In today's rapidly-evolving landscape of software development, &lt;strong&gt;streamlining workflows, fostering collaboration&lt;/strong&gt; and &lt;strong&gt;producing reliable software&lt;/strong&gt; have become indispensable goals for developers. Consequently, there has been a notable surge in the adoption of Continuous Integration/Continuous Deployment (CI/CD) practices across the industry.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, as one of the leading web-based Git repository hosting service, provides a powerful suite of CI/CD tools in the form of &lt;a href="https://github.com/features/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt;. These are directly integrated into the platform which empowers developers to increase the speed, efficiency and reliability of delivering products. In this brief article, we will take a look at &lt;strong&gt;what CI/CD is&lt;/strong&gt;, &lt;strong&gt;why we should use it&lt;/strong&gt;, as well as some of &lt;strong&gt;its applications&lt;/strong&gt; in my projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is CI/CD?
&lt;/h2&gt;

&lt;p&gt;But first, what is CI/CD? CI/CD, as you may have glimpsed earlier, is an acronym for Continuous Integration/Continuous Deployment, which encompasses a set of practices and methodologies aimed at &lt;strong&gt;automating various stages of the software development lifecycle&lt;/strong&gt;. Specifically in GitHub, CI/CD is also often referred to as &lt;strong&gt;GitHub Actions&lt;/strong&gt;. GitHub Actions can be used to automate tasks ranging from basic code linting and testing for correctness, to even simplifying and speeding up the deployment process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why use CI/CD?
&lt;/h2&gt;

&lt;p&gt;Some of you might be thinking: "But I can lint, test and deploy all without CI/CD!" Now, while it's certainly true that all these tasks can be performed manually, leveraging on CI/CD brings about several advantages that can significantly &lt;strong&gt;enhance a developer's experience:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automation:&lt;/strong&gt; A key benefit of CI/CD is automation. By automating repetitive tasks such as linting and testing, developers can free up their mind to focus on more critical aspects of their development work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliability:&lt;/strong&gt; Running checks in your CI/CD allow you to catch bugs early, and acts as a safeguard in scenarios where individual developers may have neglected to test their changes. The catching of potential problems before they escalate leads to more reliable software and fewer surprises in production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Efficiency:&lt;/strong&gt; With CI/CD, developers are able to quickly validate and receive feedback on their changes, leading to faster iterations and shorter development cycles. Having a streamlined development workflow also boasts better collaboration, enabling teams to deliver updates to production more rapidly and with greater confidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency:&lt;/strong&gt; If you have developed in a team, then you may be familiar with the phrase: "But it works on my computer!" If you've been in those shoes, then the benefits of having CI/CD is obvious. With workflows managed on the Repository, all tests are run in a standardized environment which makes issues more easily reproducible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thus, despite it being possible to perform the above tasks manually, the benefits of CI/CD are manifold and adopting it can significantly &lt;strong&gt;improve the development process&lt;/strong&gt; and the &lt;strong&gt;quality of the software&lt;/strong&gt; produced.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Applications
&lt;/h2&gt;

&lt;p&gt;Now that we've got a better idea of what CI/CD entails and the advantages that it offers, let's delve into a couple of real-world scenarios! Below, I share three examples drawn from my projects to illustrate how CI/CD, specifically GitHub Actions, helps enhance my development experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  QuickTax
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjs4z6lpiy67nfdqx6axe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjs4z6lpiy67nfdqx6axe.png" alt="QuickTax Logo" width="300" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.spigotmc.org/resources/quicktax.96495/" rel="noopener noreferrer"&gt;QuickTax&lt;/a&gt; originated as a Minecraft plugin designed to keep checks on economies in Minecraft servers. Initially developed for personal use, I eventually released it as an &lt;a href="https://github.com/tjtanjin/QuickTax" rel="noopener noreferrer"&gt;open-source project&lt;/a&gt; and the plugin found itself actively used by dozens of servers. With an active user base, maintaining the &lt;strong&gt;integrity&lt;/strong&gt; of the plugin was important. At the bare minimum, changes shouldn't cause the build process to fail.&lt;/p&gt;

&lt;p&gt;To provide myself with that &lt;strong&gt;reassurance&lt;/strong&gt;, I added a simple workflow to GitHub Actions which I share below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Build

on:
  push:
    branches: [ "master" ]
  pull_request:
    branches: [ "master" ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'temurin'
        cache: maven
    - name: Build with Maven
      run: mvn -B package --file pom.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the snippet above, we can see that the job is set to run whenever there are pushes or pull requests being made to the master branch. Within the job itself, it is simply checking that the plugin can be successfully built with Maven and Java 17. By automating this process, I am able to have the &lt;strong&gt;confidence&lt;/strong&gt; that passing GitHub Actions imply my project can be built reliably.&lt;/p&gt;

&lt;h3&gt;
  
  
  React ChatBotify
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fipi629nbpy9fmikxg09w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fipi629nbpy9fmikxg09w.png" alt="React ChatBotify Logo" width="300" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://react-chatbotify.tjtanjin.com/" rel="noopener noreferrer"&gt;React ChatBotify&lt;/a&gt; is a React library that allows users to effortlessly integrate a highly customizable chatbot to their websites. Available on &lt;a href="https://www.npmjs.com/package/react-chatbotify" rel="noopener noreferrer"&gt;npm&lt;/a&gt;, it is slowly gaining adoption and there is an increasing need to ensure its &lt;strong&gt;reliability and consistency&lt;/strong&gt; for users. While the previous project had a single build workflow, this project contains 3 separate workflows for &lt;strong&gt;lint, test and build&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We won't delve into the details of the workflow files, as they are quite lengthy, but you can explore them on the &lt;a href="https://github.com/tjtanjin/react-chatbotify" rel="noopener noreferrer"&gt;project repository&lt;/a&gt;. In summary, the lint stage checks for &lt;strong&gt;code quality&lt;/strong&gt;, while the build and test stage ensures &lt;strong&gt;correctness&lt;/strong&gt;. Thus, when GitHub Actions succeed, I have the assurance that the quality of the codebase is upheld and that the library functions as intended.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simple Media Converter
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzqql7d74dltcyqvdz5ab.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzqql7d74dltcyqvdz5ab.png" alt="Simple Media Converter Logo" width="300" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://t.me/simplemediaconverterbot" rel="noopener noreferrer"&gt;Simple Media Converter&lt;/a&gt; is a Telegram bot created for easy media conversions in my university days when lessons and submissions were mostly online due to covid. Initially, the bot was &lt;a href="https://dev.to/tjtanjin/how-to-host-a-telegram-bot-on-ubuntu-a-step-by-step-guide-4jk3"&gt;deployed via Screen&lt;/a&gt; and I had to do manual deployments after every code change. When I moved to &lt;a href="https://dev.to/tjtanjin/ensuring-uptime-a-pythonic-approach-to-liveness-monitoring-lp8"&gt;Docker&lt;/a&gt;, I &lt;strong&gt;automated the entire deployment flow&lt;/strong&gt; with GitHub Actions to simplify this process. You can find the workflow files on the &lt;a href="https://github.com/tjtanjin/simple-media-converter" rel="noopener noreferrer"&gt;project repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Recently, &lt;a href="https://pypi.org/project/health-ping/" rel="noopener noreferrer"&gt;HealthPing&lt;/a&gt; was also added to notify me to &lt;a href="https://dev.to/tjtanjin/ensuring-uptime-a-pythonic-approach-to-liveness-monitoring-lp8"&gt;ensure the application liveness&lt;/a&gt;. The end result? Pushing code changes to my repository &lt;strong&gt;automatically re-deploys&lt;/strong&gt; my Telegram bot. Should there be errors with the bot, I am notified by HealthPing and I can simply run the deployment workflow again to re-deploy the bot. In fact, I could potentially take things further by having a failed HealthPing automatically trigger a redeployment - but that's a consideration for another time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;To wrap things up, we have seen what CI/CD is along with the benefits it can bring to your software development experience. I hope the examples provided above have convinced you of its &lt;strong&gt;usefulness in real-world applications&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you are keen to look into how to automate an entire deployment process with GitHub Actions, look out for the upcoming article that will be covering how to &lt;strong&gt;automate Telegram Bot Deployment with GitHub Actions and Docker&lt;/strong&gt;. Over there, we will dive deeper into the workflow files that we have skipped over in this article. As usual, if you have suggestions or feedback, feel free to leave them in the comments or reach out &lt;a href="https://discord.com/invite/X8VSdZvBQY" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Thank you for reading, and I'll see you in the next article!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>github</category>
      <category>githubactions</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to Dockerize a Telegram Bot: A Step-by-Step Guide</title>
      <dc:creator>tjtanjin</dc:creator>
      <pubDate>Mon, 01 Apr 2024 18:52:56 +0000</pubDate>
      <link>https://dev.to/tjtanjin/how-to-dockerize-a-telegram-bot-a-step-by-step-guide-37ol</link>
      <guid>https://dev.to/tjtanjin/how-to-dockerize-a-telegram-bot-a-step-by-step-guide-37ol</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In a &lt;a href="https://dev.to/tjtanjin/how-to-host-a-telegram-bot-on-ubuntu-a-step-by-step-guide-4jk3"&gt;previous article&lt;/a&gt;, I covered how to host a Telegram bot with &lt;strong&gt;Screen&lt;/strong&gt;. More recently I also did a sharing on my gradual shift &lt;a href="https://dev.to/tjtanjin/from-screen-to-docker-a-look-at-two-hosting-options-42eh"&gt;from &lt;strong&gt;Screen&lt;/strong&gt; to &lt;strong&gt;Docker&lt;/strong&gt;&lt;/a&gt; for hosting applications. Building upon this progression, this tutorial aims to guide you through the process of &lt;strong&gt;dockerizing&lt;/strong&gt; a Telegram bot!&lt;/p&gt;

&lt;p&gt;This preparation sets the stage for our next article, where we will delve into fully automating deployment using &lt;strong&gt;Docker and GitHub CI/CD&lt;/strong&gt;. By the conclusion of this tutorial, not only will you have acquired practical knowledge on dockerizing your bot, you'll also gain a better appreciation for leveraging Docker as a deployment tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we dive into the world of Docker, do note that this guide assumes knowledge of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Familiarity with linux command line&lt;/li&gt;
&lt;li&gt;Basic Understanding of Python&lt;/li&gt;
&lt;li&gt;Basic Understanding of Docker (if you don't, check out &lt;a href="https://tjtanjin.medium.com/from-screen-to-docker-a-look-at-two-hosting-options-6682a08d51bf" rel="noopener noreferrer"&gt;this article&lt;/a&gt;!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will not be covering the above as they are not the focus of this guide - there are plenty of guides out there for them as well! All that said, if you need help with any of the above, you are more than welcome to &lt;a href="https://discord.gg/X8VSdZvBQY" rel="noopener noreferrer"&gt;reach out&lt;/a&gt; for assistance.&lt;/p&gt;

&lt;p&gt;Note that the contents of this tutorial can be generalized and applied to programs other than Telegram bots. However, for the purpose of this tutorial, you are encouraged to follow it through with &lt;a href="https://github.com/tjtanjin/tele-qr" rel="noopener noreferrer"&gt;a simple Telegram bot that I have&lt;/a&gt;. Even better if you have read my previous articles, because then you would be comfortable working with Telegram bots by now. So without further ado, let us begin!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Installing Docker
&lt;/h2&gt;

&lt;p&gt;Unsurprisingly, the first step to dockerizing our Telegram bot is to have Docker installed. Depending on your operating system, the installation steps &lt;strong&gt;may differ&lt;/strong&gt; as provided in &lt;a href="https://docs.docker.com/engine/install/ubuntu/" rel="noopener noreferrer"&gt;Docker's installation guide&lt;/a&gt;. The steps below are for &lt;strong&gt;Ubuntu&lt;/strong&gt; but you should reference Docker's installation guide if you're using a different operating system:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Set up Docker's apt repository.
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release &amp;amp;&amp;amp; echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list &amp;gt; /dev/null
sudo apt-get update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Install the Docker packages.
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Verify that the Docker Engine installation is successful by running the hello-world image.
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo docker run hello-world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2 Clone the Project
&lt;/h2&gt;

&lt;p&gt;Once we've got Docker setup, proceed to &lt;strong&gt;clone&lt;/strong&gt; the &lt;a href="https://github.com/tjtanjin/tele-qr" rel="noopener noreferrer"&gt;sample Telegram bot&lt;/a&gt; project with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/tjtanjin/tele-qr.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Setup the Project
&lt;/h2&gt;

&lt;p&gt;Within the project directory, you'll find a file called &lt;em&gt;main.py&lt;/em&gt; which has the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import os

from dotenv import load_dotenv
from health_ping import HealthPing
from telegram.ext import Application, CommandHandler, MessageHandler, filters

from submodules.user_input import show_help, get_input

dotenv_path = os.path.join(os.path.dirname(__file__), '.env')
load_dotenv(dotenv_path)

if os.getenv("HEALTHCHECKS_ENDPOINT"):
    HealthPing(url=os.getenv("HEALTHCHECKS_ENDPOINT"),
               schedule="1 * * * *",
               retries=[60, 300, 720]).start()

def main():
    """
    Handles the initial launch of the program (entry point).
    """
    token = os.getenv("BOT_TOKEN")
    application = Application.builder().token(token).concurrent_updates(True).read_timeout(30).write_timeout(30).build() # noqa
    application.add_handler(CommandHandler('start', show_help))
    application.add_handler(CommandHandler('help', show_help))
    application.add_handler(MessageHandler(filters.TEXT, get_input))
    print("TeleQR instance started!")
    application.run_polling()

if __name__ == '__main__':
    main()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that a &lt;strong&gt;BOT_TOKEN&lt;/strong&gt; is required but it has not been set. If you'd like the complete experience, then you may wish to obtain a bot token which was covered in the guide on &lt;a href="https://dev.to/tjtanjin/how-to-build-a-telegram-bot-a-beginners-step-by-step-guide-1kd1"&gt;how to build a telegram bot&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Alternatively, for ease of following through this tutorial, you may also use a modified code snippet which contains a &lt;code&gt;while True&lt;/code&gt; loop to simulate a permanently running program. Here's how the modified script would look like if you choose the latter option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import time

def main():
    """
    Handles the initial launch of the program (entry point).
    """
    print("TeleQR instance started!")
    while True:
       time.sleep(5)
       print("I am running!")

if __name__ == '__main__':
    main()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let us verify that the project is working by installing the required packages and then running the program:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install -r requirements.txt
python main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the program is running successfully, you will see "TeleQR instance started!" printed into the console.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Create a Dockerfile
&lt;/h2&gt;

&lt;p&gt;Now that we've got the project running, it's time to look at the &lt;em&gt;Dockerfile&lt;/em&gt;. For those of you who are using the &lt;a href="https://github.com/tjtanjin/tele-qr" rel="noopener noreferrer"&gt;sample Telegram bot project&lt;/a&gt;, you will notice that a &lt;em&gt;Dockerfile&lt;/em&gt; already exists. That's because the sample project is already dockerized. Fret not, go ahead and delete the file and we will recreate it in no time!&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Create an empty Dockerfile:
&lt;/h4&gt;

&lt;p&gt;In your project directory, create an empty file named &lt;em&gt;Dockerfile&lt;/em&gt;. This file will contain instructions for Docker on &lt;strong&gt;how to build your container&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Adding the base image:
&lt;/h4&gt;

&lt;p&gt;The first line in the &lt;em&gt;Dockerfile&lt;/em&gt; specifies the base image that our container will use. In this case, we're using &lt;strong&gt;Python version 3.10.12&lt;/strong&gt; as our base image so we'll declare this at the top of the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM python:3.10.12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Setting the working directory:
&lt;/h4&gt;

&lt;p&gt;Next, we set the working directory inside the docker container &lt;strong&gt;where our application code will reside&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WORKDIR /usr/src/app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4. Copying files into the container:
&lt;/h4&gt;

&lt;p&gt;Following which, we need to &lt;strong&gt;copy all the files&lt;/strong&gt; from our local directory into the container which includes our Python scripts, requirements file, and any other project files. In the &lt;code&gt;COPY&lt;/code&gt; command below, the first &lt;code&gt;.&lt;/code&gt; represents our &lt;strong&gt;current project directory&lt;/strong&gt; while the second &lt;code&gt;.&lt;/code&gt; represents the docker &lt;strong&gt;container's working directory&lt;/strong&gt; (which we have just set with the previous command):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;COPY . .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  5. Creating a directory:
&lt;/h4&gt;

&lt;p&gt;Then, we use the &lt;code&gt;RUN&lt;/code&gt; command to execute &lt;code&gt;mkdir ./images&lt;/code&gt; within our docker container's working directory. This step is &lt;strong&gt;specific to the sample Telegram bot project&lt;/strong&gt; we are using but you can generalize this portion of the &lt;em&gt;Dockerfile&lt;/em&gt; for any other setup in your projects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RUN mkdir ./images
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  6. Installing dependencies:
&lt;/h4&gt;

&lt;p&gt;With the project files setup, we now install the dependencies required by our Python application. We'll first install the packages listed in &lt;em&gt;requirements.txt&lt;/em&gt; using &lt;code&gt;pip&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RUN pip install - no-cache-dir -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additionally, we'll install the &lt;strong&gt;python-telegram-bot&lt;/strong&gt; package with the &lt;strong&gt;[job-queue]&lt;/strong&gt; extras (again specific to the project):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RUN pip install python-telegram-bot[job-queue]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  7. Specifying the command to run the application:
&lt;/h4&gt;

&lt;p&gt;Finally, we specify the command that should be executed when the container starts. In this case, we're running our Python script named &lt;em&gt;main.py&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  CMD ["python", "-u", "./main.py"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Phew! That was quite a lot to step through, wasn't it? But if you've followed till this far, then congratulations! You've successfully created a &lt;em&gt;Dockerfile&lt;/em&gt; for your Telegram bot. There's just one last step left - to build a Docker image based on this &lt;em&gt;Dockerfile&lt;/em&gt; and run your bot inside a Docker container!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Build &amp;amp; Run
&lt;/h2&gt;

&lt;p&gt;Let us first build our docker image with &lt;code&gt;docker build&lt;/code&gt;. Within your project directory, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -t my_telegram_bot .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This process may take a while the first time so give it a few moments. The above command essentially reads the instructions from your &lt;em&gt;Dockerfile&lt;/em&gt; and builds a Docker image with it. Feel free to replace &lt;code&gt;my_telegram_bot&lt;/code&gt; with whatever you wish to name your Docker image.&lt;/p&gt;

&lt;p&gt;Once the Docker image is built, we can then finally run our Docker container with &lt;code&gt;docker run&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run my_telegram_bot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you've followed all the steps so far, you'll see your application launching successfully! Before you happily call it a day, here are some &lt;strong&gt;useful basic Docker commands&lt;/strong&gt; to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;docker run -d &amp;lt;image_id_or_name&amp;gt;&lt;/code&gt; - to run the Docker container in detached mode&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker ps&lt;/code&gt; - to list all running containers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker stop &amp;lt;container_id_or_name&amp;gt;&lt;/code&gt; - to stop a running container&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker rm &amp;lt;container_id_or_name&amp;gt;&lt;/code&gt; - to remove a container&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker images&lt;/code&gt; - to list all images&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker rmi &amp;lt;image_id_or_name&amp;gt;&lt;/code&gt; - to remove an image&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker logs &amp;lt;container_id_or_name&amp;gt;&lt;/code&gt; - to view the logs of a container&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are some basic commands you may find useful but there are plenty of other commands that you will be able to find from &lt;a href="https://docs.docker.com/reference/cli/docker/" rel="noopener noreferrer"&gt;Docker's documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With that, we've successfully dockerized and run our Telegram bot inside a Docker container! In this tutorial, we stepped through the process of &lt;strong&gt;creating a Dockerfile&lt;/strong&gt;, &lt;strong&gt;building a Docker image&lt;/strong&gt; from it, and finally &lt;strong&gt;running a Docker container&lt;/strong&gt; based on that image. Additionally, we've explored some &lt;strong&gt;useful Docker commands&lt;/strong&gt; for managing containers and images.&lt;/p&gt;

&lt;p&gt;As you continue your journey with Docker, remember that there are plenty of other features to explore. Docker's documentation is a &lt;strong&gt;great place&lt;/strong&gt; to checkout if you wish to learn more about advanced Docker concepts and best practices. As always, if you've got ideas, suggestions or feedback to share, feel free to drop them in the comments or &lt;a href="https://discord.com/invite/X8VSdZvBQY" rel="noopener noreferrer"&gt;reach out&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Happy coding, and if you're keen to automate your deployment process with me, then &lt;strong&gt;stay tuned for the upcoming article!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>cloud</category>
      <category>hosting</category>
      <category>telegrambot</category>
    </item>
    <item>
      <title>Mobile Web Audio: Removing Media Controls from Notifications Tray</title>
      <dc:creator>tjtanjin</dc:creator>
      <pubDate>Fri, 22 Mar 2024 13:45:59 +0000</pubDate>
      <link>https://dev.to/tjtanjin/mobile-web-audio-removing-media-controls-from-notifications-tray-1nl3</link>
      <guid>https://dev.to/tjtanjin/mobile-web-audio-removing-media-controls-from-notifications-tray-1nl3</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In the world of web development, even the tiniest details can significantly impact user experience. Recently, while working on improving &lt;a href="https://www.npmjs.com/package/react-chatbotify" rel="noopener noreferrer"&gt;React ChatBotify&lt;/a&gt;, I encountered a persistent issue concerning its &lt;strong&gt;notifications feature&lt;/strong&gt;. This problem had lingered since the initial release, but was relegated to the sidelines as other features took precedence.&lt;/p&gt;

&lt;p&gt;So what's the issue? While notification sounds played fine for Desktop users, a subtle inconvenience emerged for mobile users: notification sounds triggered the emergence of &lt;strong&gt;media controls&lt;/strong&gt; within the device's &lt;strong&gt;notifications tray&lt;/strong&gt;. Take a look below to see what I mean:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvxgmgw80vrvf0imlxgt1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvxgmgw80vrvf0imlxgt1.png" alt="Phone Notifications Tray Example" width="300" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feature-breaking? No. &lt;strong&gt;Annoying? Yes!&lt;/strong&gt; In the latest version of this library however, I finally resolved this annoyance once and for all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Original Implementation
&lt;/h2&gt;

&lt;p&gt;Before delving into my attempts to rectify the issue, I thought it'd help to share some context on the notifications feature. Basically, the chatbot permits toggling of notification sounds for incoming messages. The library achieves this by initially &lt;strong&gt;setting up the notification audio&lt;/strong&gt; and subsequently &lt;strong&gt;triggering it when necessary&lt;/strong&gt;. For those curious about the inner workings, here's the function responsible for setting up notifications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const setUpNotifications = () =&amp;gt; {
  setNotificationToggledOn(botOptions.notification?.defaultToggledOn as boolean);

  let notificationSound = botOptions.notification?.sound;

  if (notificationSound?.startsWith("data:audio")) {
    const binaryString = atob(notificationSound.split(",")[1]);
    const arrayBuffer = new ArrayBuffer(binaryString.length);
    const uint8Array = new Uint8Array(arrayBuffer);
    for (let i = 0; i &amp;lt; binaryString.length; i++) {
      uint8Array[i] = binaryString.charCodeAt(i);
    }
    const blob = new Blob([uint8Array], { type: "audio/wav" });
    notificationSound = URL.createObjectURL(blob);
  }

  notificationAudio.current = new Audio(notificationSound);
  notificationAudio.current.volume = botOptions.notification?.volume as number;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additionally, here's the function responsible for playing the notifications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const handleNotifications = () =&amp;gt; {
  // if embedded, or no message found, no need for notifications
  if (botOptions.theme?.embedded || messages.length == 0) {
    return;
  }
  const message = messages[messages.length - 1]
  // yes i know these conditions are ugly but i will clean this up someday ;-;
  if (message != null &amp;amp;&amp;amp; message?.sender !== "user" &amp;amp;&amp;amp; !isBotTyping
    &amp;amp;&amp;amp; (!botOptions.isOpen || document.visibilityState !== "visible")) {
    setUnreadCount(prev =&amp;gt; prev + 1);
    if (!botOptions.notification?.disabled &amp;amp;&amp;amp; notificationToggledOn &amp;amp;&amp;amp; hasInteracted) {
      notificationAudio.current?.play();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In essence, I loaded the audio and adjusted its volume upon initialization, triggering its playback when &lt;code&gt;handleNotifications&lt;/code&gt; was invoked. Though functional, the appearance of &lt;strong&gt;media controls&lt;/strong&gt; in mobile devices' notification trays was a thorn in the flesh!&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt #1
&lt;/h2&gt;

&lt;p&gt;Hoping for a quick remedy, I did a swift Google search that led me to the &lt;code&gt;controls&lt;/code&gt; attribute, which appeared to be what I needed. Surely, setting &lt;code&gt;controls&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; would remove the media controls from the notifications tray right? With a hint of optimism, I threw in a single line at the end of &lt;code&gt;setUpNotifications&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;notificationAudio.current.controls = false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Much to my dismay, it didn't work. The media controls were still appearing in the notifications tray.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt #2
&lt;/h2&gt;

&lt;p&gt;Since I was using the HTML 5 Audio, I decided to look into its &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; provided. That was when I stumbled upon &lt;code&gt;controlsList&lt;/code&gt;, which also seemed like a promising attribute. Seeking to replace &lt;code&gt;controls&lt;/code&gt; with &lt;code&gt;controlsList&lt;/code&gt;, I was stumped when I noticed that there was no built-in &lt;code&gt;controlsList&lt;/code&gt; attribute for my &lt;code&gt;notificationAudio&lt;/code&gt;. With a "let's just try" attitude, I added &lt;code&gt;controlsList&lt;/code&gt; directly as an attribute instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;notificationAudio.current.setAttribute("controlsList", "nodownload");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hmmmmm, not too unexpectedly, it's not working!&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt #3
&lt;/h2&gt;

&lt;p&gt;As I combed the internet for more inspirations, I came upon a suggestion to call &lt;code&gt;.load()&lt;/code&gt; again on the audio after playing it. I then had the idea to reset the audio after playing it to hopefully make the media controls go away. Feeling a little confident of this idea, I went into &lt;code&gt;handleNotifications&lt;/code&gt; and added a line right after I called &lt;code&gt;.play()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;notificationAudio.current?.load()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anddddd nope, still not working!!&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt #4
&lt;/h2&gt;

&lt;p&gt;Feeling a little desperate and restless, I decided to try adding an &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; tag directly and then using &lt;code&gt;controlsList&lt;/code&gt; within it. Hopping over to the return block responsible for the render, I plonked in the following line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;audio ref={notificationAudio} controlsList="nodownload" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ughhhhh! As you may have guessed, it's still not working.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt #5
&lt;/h2&gt;

&lt;p&gt;Exhausted and having tried out various approaches without much success, I decided to give &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API" rel="noopener noreferrer"&gt;Web Audio API&lt;/a&gt; a try. While it appeared slightly harder to work with, I was frankly running out of options. All that said, moving from HTML5 Audio to Web Audio API involved quite a fair bit of changes and experimentation.&lt;/p&gt;

&lt;p&gt;Without boring you with the details of my struggles, my final changes involved me ditching &lt;code&gt;notificationAudio&lt;/code&gt; and replacing it with &lt;code&gt;audioContextRef&lt;/code&gt;, &lt;code&gt;audioBufferRef&lt;/code&gt; and &lt;code&gt;gainNodeRef&lt;/code&gt; as you can see below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// audio to play for notifications
 const audioContextRef = useRef&amp;lt;AudioContext | null&amp;gt;(null);
 const audioBufferRef = useRef&amp;lt;AudioBuffer&amp;gt;();
 const gainNodeRef = useRef&amp;lt;AudioNode | null&amp;gt;(null);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Needless to say, I also made significant updates to both functions for setting up and playing notification sounds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/**
 * Sets up the notifications feature (initial toggle status and sound).
 */
const setUpNotifications = async () =&amp;gt; {
  setNotificationToggledOn(botOptions.notification?.defaultToggledOn as boolean);

  let notificationSound = botOptions.notification?.sound;
  audioContextRef.current = new AudioContext();
  const gainNode = audioContextRef.current.createGain();
  gainNode.gain.value = botOptions.notification?.volume || 0.2;
  gainNodeRef.current = gainNode;

  let audioSource;
  if (notificationSound?.startsWith("data:audio")) {
    const binaryString = atob(notificationSound.split(",")[1]);
    const arrayBuffer = new ArrayBuffer(binaryString.length);
    const uint8Array = new Uint8Array(arrayBuffer);
    for (let i = 0; i &amp;lt; binaryString.length; i++) {
      uint8Array[i] = binaryString.charCodeAt(i);
    }
    audioSource = arrayBuffer;
  } else {
    const response = await fetch(notificationSound as string);
    audioSource = await response.arrayBuffer();
  }

  audioBufferRef.current = await audioContextRef.current.decodeAudioData(audioSource);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/**
 * Handles notification count update and notification sound.
 */
const handleNotifications = () =&amp;gt; {
  // if no audio context or no messages, return
  if (!audioContextRef.current || messages.length === 0) {
    return;
  }

  const message = messages[messages.length - 1]
  // if message is null or sent by user or is bot typing, return
  if (message == null || message.sender === "user" || isBotTyping) {
    return;
  }

  // if chat is open but user is not scrolling, return
  if (botOptions.isOpen &amp;amp;&amp;amp; !isScrolling) {
    return;
  }

  setUnreadCount(prev =&amp;gt; prev + 1);
  if (!botOptions.notification?.disabled &amp;amp;&amp;amp; notificationToggledOn &amp;amp;&amp;amp; hasInteracted &amp;amp;&amp;amp; audioBufferRef.current) {
    const source = audioContextRef.current.createBufferSource();
    source.buffer = audioBufferRef.current;
    source.connect(gainNodeRef.current as AudioNode).connect(audioContextRef.current.destination);
    source.start();
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Given the substantial amount of changes, I had to tinker around and try out various attempts first. At some point, the notifications even stopped sounding off entirely, which felt like an even larger setback. However, with a bit more testing and determination, the notification sound eventually chimed again!&lt;/p&gt;

&lt;p&gt;Needless to say, pulling down the notifications tray, and &lt;strong&gt;not seeing the media controls&lt;/strong&gt; anymore was extremely satisfying! I am very satisfied with the outcome and while I cannot be certain that this is the best approach, this at least worked out for me. I was also a little surprised that I could not find a solution easily online. Perhaps, I'm just not looking in the right place!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope this article along with the &lt;a href="https://github.com/tjtanjin/react-chatbotify/blob/main/src/components/ChatBotContainer.tsx" rel="noopener noreferrer"&gt;code snippets&lt;/a&gt; I shared above will be helpful to anyone out there grappling with similar issues. While I've shared my journey in tackling the pesky problem of media controls showing up on mobile devices' notification trays, I'm still open to ideas, suggestions and feedback - &lt;strong&gt;especially if you know a better solution!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Feel free to leave your thoughts in the comments below or &lt;a href="https://discord.com/invite/X8VSdZvBQY" rel="noopener noreferrer"&gt;reach out&lt;/a&gt; anytime. If you've read so far, thank you for tuning in to my struggles. Catch you later, and happy coding!&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>From Screen To Docker: A Look at Two Hosting Options</title>
      <dc:creator>tjtanjin</dc:creator>
      <pubDate>Mon, 18 Mar 2024 17:19:21 +0000</pubDate>
      <link>https://dev.to/tjtanjin/from-screen-to-docker-a-look-at-two-hosting-options-42eh</link>
      <guid>https://dev.to/tjtanjin/from-screen-to-docker-a-look-at-two-hosting-options-42eh</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;As you progress in your journey as a software engineer, you'll inevitably ponder how best to showcase your projects - perhaps you've &lt;a href="https://dev.to/tjtanjin/how-to-build-a-telegram-bot-a-beginners-step-by-step-guide-1kd1"&gt;developed a Telegram bot&lt;/a&gt; or a Discord bot. At this stage, you might not find much satisfaction if your projects are solely confined to running on your computer. Wouldn't it be great if your work could be &lt;strong&gt;accessible&lt;/strong&gt; from &lt;strong&gt;anywhere&lt;/strong&gt;, &lt;strong&gt;at any time&lt;/strong&gt;, for others to experience?&lt;/p&gt;

&lt;h2&gt;
  
  
  A Brief Reflection
&lt;/h2&gt;

&lt;p&gt;In my early days, I wasn't aware of &lt;em&gt;Docker&lt;/em&gt;, so &lt;em&gt;Screen&lt;/em&gt; was always my go-to option. I first stumbled upon &lt;em&gt;Screen&lt;/em&gt; while attempting to train a simple model on my machine, and its simplicity appealed to me. As a novice, its ease of use allowed me to easily develop and run programs that I could share with others. However, as my number of applications grew, and when I made the decision to change my hosting provider, I found myself facing the arduous task of reconfiguring and setting up each application &lt;strong&gt;from scratch&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It was a time-consuming and painful process, so when I came upon &lt;em&gt;Docker&lt;/em&gt;, I knew I had been missing out. Don't get me wrong, it's not that &lt;em&gt;Screen&lt;/em&gt; is bad - matter of fact, I still host some of my applications via &lt;em&gt;Screen&lt;/em&gt;. Yet, the advantages of &lt;em&gt;Docker&lt;/em&gt; were apparent, and driven by the desire for enhanced deployment efficiency and streamlined management of projects, I began a journey towards &lt;em&gt;Docker&lt;/em&gt; adoption.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Tale of Two Scenarios
&lt;/h2&gt;

&lt;p&gt;Let's envision two scenarios that vividly contrast the experiences of utilizing &lt;em&gt;Screen&lt;/em&gt; and &lt;em&gt;Docker&lt;/em&gt;. In the first scenario, likened to a lavish buffet, &lt;em&gt;Screen&lt;/em&gt; presents an array of options with its &lt;strong&gt;straightforward setup and immediate accessibility&lt;/strong&gt;. However, unforeseen disruptions akin to a toppling chocolate tower can swiftly derail plans, leaving users stranded amidst chaos.&lt;/p&gt;

&lt;p&gt;Conversely, &lt;em&gt;Docker&lt;/em&gt; embodies a neatly packed lunchbox, containing a curated selection of applications within containers. Even if a chocolate tower were to topple, items in your lunchbox remain safe. &lt;em&gt;Docker's&lt;/em&gt; &lt;strong&gt;encapsulation&lt;/strong&gt; ensures seamless continuity, enabling users to resume tasks with ease. This juxtaposition vividly portrays the evolution from the simplicity of &lt;em&gt;Screen&lt;/em&gt; to the robustness and adaptability of &lt;em&gt;Docker&lt;/em&gt; in managing hosting needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Docker and Screen
&lt;/h2&gt;

&lt;p&gt;Continuing from our exploration of contrasting scenarios, let's delve into &lt;em&gt;Docker&lt;/em&gt; and &lt;em&gt;Screen&lt;/em&gt; in more detail. &lt;em&gt;Docker&lt;/em&gt; serves as a platform for developing, packaging, and deploying applications in lightweight, isolated containers. These containers encapsulate not only the application code, but also its dependencies, ensuring consistency across different environments and simplifying configuration management. Consequently, &lt;em&gt;Docker&lt;/em&gt; not only offers &lt;strong&gt;portability&lt;/strong&gt; but also helps &lt;strong&gt;streamline deployment&lt;/strong&gt; processes.&lt;/p&gt;

&lt;p&gt;On the other hand, &lt;em&gt;Screen&lt;/em&gt; offers a &lt;strong&gt;convenient&lt;/strong&gt; way to manage terminal sessions, especially for scenarios involving long-running processes or management of multiple sessions. With &lt;em&gt;Screen&lt;/em&gt;, users can detach from a session, allowing processes to continue running in the background even after the terminal connection is closed. This functionality proves invaluable for server processes or remote tasks that necessitate &lt;strong&gt;continuous operation&lt;/strong&gt; without an active terminal connection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the Right Tool
&lt;/h2&gt;

&lt;p&gt;While both &lt;em&gt;Docker&lt;/em&gt; and &lt;em&gt;Screen&lt;/em&gt; are capable of running applications, the decision between the two boils down to &lt;strong&gt;project requirements&lt;/strong&gt; and &lt;strong&gt;personal preferences&lt;/strong&gt;. &lt;em&gt;Docker&lt;/em&gt; boasts several strengths that make it ideal for various scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building and deploying applications in isolated environments.&lt;/li&gt;
&lt;li&gt;Ensuring consistency and reproducibility across different environments.&lt;/li&gt;
&lt;li&gt;Scaling applications and managing resources efficiently.&lt;/li&gt;
&lt;li&gt;Simplifying dependency management and deployment workflows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Meanwhile, &lt;em&gt;Screen&lt;/em&gt; shines in specific scenarios, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prototyping quickly for testing and sharing.&lt;/li&gt;
&lt;li&gt;Running on resource-constrained systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition to the above considerations, it is also worth noting that &lt;strong&gt;investing time&lt;/strong&gt; in learning &lt;em&gt;Docker&lt;/em&gt; may be necessary for those unfamiliar with it. While &lt;em&gt;Screen's&lt;/em&gt; &lt;strong&gt;simplicity&lt;/strong&gt; may appeal to hobbyists and beginners, ambitious projects aiming for &lt;strong&gt;scalability and consistency&lt;/strong&gt; may find &lt;em&gt;Docker&lt;/em&gt; more suitable.&lt;/p&gt;

&lt;p&gt;As I shared earlier in my journey, I initially favored &lt;em&gt;Screen&lt;/em&gt; for its ease of use, particularly as a novice hosting projects. However, as I embarked on more projects, I began to see more value in having streamlined deployment processes. Currently, I host projects like &lt;a href="https://github.com/tjtanjin/simple-media-converter" rel="noopener noreferrer"&gt;Simple Media Converter&lt;/a&gt;, &lt;a href="https://github.com/tjtanjin/simple-transcriptions" rel="noopener noreferrer"&gt;Simple Transcriptions&lt;/a&gt; and &lt;a href="https://github.com/tjtanjin/tele-qr" rel="noopener noreferrer"&gt;TeleQR&lt;/a&gt; in &lt;em&gt;Docker&lt;/em&gt;, while a couple of prototypes, such as my Discord bot, remain on &lt;em&gt;Screen&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In conclusion, whether you're savoring a lavish buffet or enjoying a well-packed lunch, remember to &lt;strong&gt;evaluate your circumstances&lt;/strong&gt; and select the hosting solution that aligns best with your &lt;strong&gt;needs&lt;/strong&gt;. Ultimately, your decision between &lt;em&gt;Docker&lt;/em&gt; and &lt;em&gt;Screen&lt;/em&gt; should be guided by the &lt;strong&gt;nature of your projects&lt;/strong&gt;, your &lt;strong&gt;technical capabilities&lt;/strong&gt;, and your &lt;strong&gt;long-term hosting objectives&lt;/strong&gt;. In an upcoming article, I will do a sharing on how to deploy a Telegram bot with &lt;em&gt;Docker&lt;/em&gt;, where I'll delve into the practical steps of this process. If you're keen, do &lt;strong&gt;keep a lookout&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;Finally, if you've found this comparison helpful in navigating your decision-making process, feel free to share your thoughts, ideas, or feedback in the comments or &lt;a href="https://discord.com/invite/X8VSdZvBQY" rel="noopener noreferrer"&gt;reach out&lt;/a&gt; to me directly. I am more than happy to engage in a conversation. Thank you for taking the time to read my short sharing, and I wish you success in your hosting journey!&lt;/p&gt;

</description>
      <category>docker</category>
      <category>cloud</category>
      <category>hosting</category>
      <category>cloudskills</category>
    </item>
    <item>
      <title>How to Build and Integrate a React Chatbot with LLMs: A React ChatBotify Guide (Part 4)</title>
      <dc:creator>tjtanjin</dc:creator>
      <pubDate>Tue, 12 Mar 2024 13:14:39 +0000</pubDate>
      <link>https://dev.to/tjtanjin/how-to-build-and-integrate-a-react-chatbot-with-llms-a-react-chatbotify-guide-part-4-3gbk</link>
      <guid>https://dev.to/tjtanjin/how-to-build-and-integrate-a-react-chatbot-with-llms-a-react-chatbotify-guide-part-4-3gbk</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F70b2nt8gga9bs45m3o0q.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F70b2nt8gga9bs45m3o0q.gif" alt="Demo GIF"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Welcome back to the fourth installment of the &lt;a href="https://react-chatbotify.tjtanjin.com/" rel="noopener noreferrer"&gt;React ChatBotify&lt;/a&gt; series! In the rapidly evolving landscape of technology, Large Language Models (LLMs) have become a popular topic today and it should come as no surprise that we are witnessing increased adoption of such models in everyday services. In this tutorial, we will explore the integration with Gemini and dive into how we can build a chatbot empowered by LLMs!&lt;/p&gt;

&lt;h2&gt;
  
  
  A Quick Thought
&lt;/h2&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/tjtanjin/building-a-faq-bot-a-react-chatbotify-guide-part-3-2548"&gt;previous tutorial&lt;/a&gt;, we briefly took a look at how we can build an effective FAQ bot to address commonly asked questions. However, we also encountered issues such as hard-coded responses and the inability to respond to unforeseen queries.&lt;/p&gt;

&lt;p&gt;As we embark on the fourth part of our tutorial series, we will shift our focus to how we can integrate our chatbot with LLMs to provide more dynamic responses. In the upcoming fifth and final installation, we will further delve into the application of Retrieval Augmented Generation (RAG). This step will fully transition our FAQ bot into an LLM empowered solution, enriched with contextual information and capable of answering questions with a personality!&lt;/p&gt;

&lt;p&gt;It is important to note that this segment assumes you already have a &lt;a href="https://react-chatbotify.tjtanjin.com/" rel="noopener noreferrer"&gt;React ChatBotify&lt;/a&gt; chatbot setup. If you have not, then do visit &lt;a href="https://dev.to/tjtanjin/how-to-setup-a-chatbot-with-react-chatbotify-a-step-by-step-tutorial-56d6"&gt;this guide&lt;/a&gt; first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gemini AI
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8lpnnt8xd7d60hg423rq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8lpnnt8xd7d60hg423rq.png" alt="Gemini Logo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Amongst the many emerging LLM models, &lt;a href="https://openai.com/chatgpt" rel="noopener noreferrer"&gt;ChatGPT&lt;/a&gt; and &lt;a href="https://ai.google.dev/" rel="noopener noreferrer"&gt;Gemini&lt;/a&gt; are highly spoken about. For this tutorial, I will be using Gemini as an example since it generously provides free API usage. To the credit of OpenAI, ChatGPT also offers generously low-cost APIs (which I use myself!) so don't hesitate to give them a try in your free time.&lt;/p&gt;

&lt;p&gt;All that said, before proceeding with the rest of this tutorial, please go ahead and obtain your Gemini API key by following the instructions on the &lt;a href="https://ai.google.dev/" rel="noopener noreferrer"&gt;Gemini&lt;/a&gt; website. If you're stuck and require assistance, feel free to &lt;a href="https://discord.com/invite/6R4DK4G5Zh" rel="noopener noreferrer"&gt;reach out&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Basic Bot
&lt;/h2&gt;

&lt;p&gt;Armed with your API key, let us quickly get a basic bot up and running. In fact, if you already have the chatbot setup from &lt;a href="https://dev.to/tjtanjin/tailoring-a-chat-bot-a-react-chatbotify-guide-part-2-2f3j"&gt;part 2 of this series&lt;/a&gt;, then you can easily build off it! However, for the purpose of making this tutorial complete, let's assume we have a clean setup with a bot that greets the user! This can be achieved with the following code snippet:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

// MyChatBot.js
import React from "react";
import ChatBot from "react-chatbotify";

const MyChatBot = () =&amp;gt; {
  const flow = {
    start: {
      message: "Hello, I will become sentient soon!"
    }
  }
return (
    &amp;lt;ChatBot/&amp;gt;
  );
};
export default MyChatBot;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;At this point, we have a very excited chatbot that unfortunately does nothing more than wanting to become sentient. Let's make its wish come true!&lt;/p&gt;

&lt;h2&gt;
  
  
  Initialize Model
&lt;/h2&gt;

&lt;p&gt;The following is a code snippet from &lt;a href="https://ai.google.dev/tutorials/web_quickstart" rel="noopener noreferrer"&gt;Gemini's quickstart guide&lt;/a&gt; as of writing this article:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

import { GoogleGenerativeAI } from "@google/generative-ai";

// Access your API key (see "Set up your API key" above)
const genAI = new GoogleGenerativeAI(API_KEY);

async function run() {
  // For text-only input, use the gemini-pro model
  const model = genAI.getGenerativeModel({ model: "gemini-pro"});

  const prompt = "Write a story about a magic backpack."

  const result = await model.generateContent(prompt);
  const response = await result.response;
  const text = response.text();
  console.log(text);
}

run();


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We will go ahead to copy and paste this snippet within our own chatbot above and replace the &lt;code&gt;API_KEY&lt;/code&gt; with what you have obtained earlier. The result would look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

// MyChatBot.js
import React from "react";
import ChatBot from "react-chatbotify";

const MyChatBot = () =&amp;gt; {
  const genAI = new GoogleGenerativeAI("YOUR_API_KEY");
  async function run() {
    // For text-only input, use the gemini-pro model
    const model = genAI.getGenerativeModel({ model: "gemini-pro"});

    const prompt = "Write a story about a magic backpack."

    const result = await model.generateContent(prompt);
    const response = await result.response;
    const text = response.text();
    console.log(text);
  }

  const flow = {
    start: {
      message: "Hello, I will become sentient soon!"
    }
  }
return (
    &amp;lt;ChatBot/&amp;gt;
  );
};
export default MyChatBot;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We're getting somewhere, but the model hasn't been integrated with our chatbot! In order to achieve that, we are going to make the following changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add another looping &lt;code&gt;block&lt;/code&gt; to our conversation &lt;code&gt;flow&lt;/code&gt; that will call the &lt;code&gt;run&lt;/code&gt; function to handle interactions with the model&lt;/li&gt;
&lt;li&gt;Modify the &lt;code&gt;run&lt;/code&gt; function to &lt;strong&gt;take in a prompt parameter&lt;/strong&gt; that is provided by the user, &lt;strong&gt;remove the hard-coded prompt&lt;/strong&gt; from the run function and &lt;strong&gt;return the text generated&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

// MyChatBot.js
import React from "react";
import ChatBot from "react-chatbotify";

const MyChatBot = () =&amp;gt; {
  const genAI = new GoogleGenerativeAI("YOUR_API_KEY");
  async function run(prompt) {
    // For text-only input, use the gemini-pro model
    const model = genAI.getGenerativeModel({ model: "gemini-pro"});

    const result = await model.generateContent(prompt);
    const response = await result.response;
    const text = response.text();
    return text;
  }

  const flow = {
    start: {
      message: "Hello, I am sentient now, talk to me!",
      path: "model_loop",
    },
    model_loop: {
      message: async (params) =&amp;gt; {
        return await run(params.userInput);
      },
      path: "model_loop"
    },
  }
return (
    &amp;lt;ChatBot/&amp;gt;
  );
};
export default MyChatBot;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;At this point, you have an initial working integration! If you would like to visualize the above code in action, you may copy and paste the above snippet into the &lt;a href="https://react-chatbotify.tjtanjin.com/playground/" rel="noopener noreferrer"&gt;playground&lt;/a&gt; (remember to provide your API key).&lt;/p&gt;

&lt;p&gt;The current integration with Gemini is cool and all, but you may have noticed that the chatbot is showing you the return result in entire blocks within messages. This may be fine for short messages, but can get a little problematic for long paragraphs. After all, it's not great to have your users wait forever! Wouldn't it be nice if we could show users parts of the response first?&lt;/p&gt;

&lt;h2&gt;
  
  
  Stream Responses
&lt;/h2&gt;

&lt;p&gt;The streaming of messages is a feature newly introduced in version 1.3.0 of &lt;a href="https://react-chatbotify.tjtanjin.com" rel="noopener noreferrer"&gt;React ChatBotify&lt;/a&gt;. This is particularly useful for integrations with LLMs where responses may be streamed in parts. Let's take a look at how we may modify our current approach to support streaming responses.&lt;/p&gt;

&lt;p&gt;Referring back once again to the &lt;a href="https://ai.google.dev/tutorials/web_quickstart" rel="noopener noreferrer"&gt;Gemini's quickstart guide&lt;/a&gt;, the following snippet is presented for streaming to support faster interactions:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

const result = await model.generateContentStream([prompt, ...imageParts]);

let text = '';
for await (const chunk of result.stream) {
  const chunkText = chunk.text();
  console.log(chunkText);
  text += chunkText;
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We will thus make the following modifications to our code to utilize this new approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a second &lt;code&gt;streamMessage&lt;/code&gt; parameter (provided via params) to the &lt;code&gt;run&lt;/code&gt; function&lt;/li&gt;
&lt;li&gt;Replace &lt;code&gt;model.generateContent&lt;/code&gt; with &lt;code&gt;model.generateContentStream&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add a &lt;code&gt;for-loop&lt;/code&gt; to iterate through the stream response chunks and call &lt;code&gt;params.streamMessage&lt;/code&gt; with the currently completed text to stream the message into the chatbot&lt;/li&gt;
&lt;li&gt;Modify the &lt;code&gt;model-loop&lt;/code&gt; block to include &lt;code&gt;params.streamMessage&lt;/code&gt; as a second parameter to the &lt;code&gt;run&lt;/code&gt; function&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

// MyChatBot.js
import React from "react";
import ChatBot from "react-chatbotify";

const MyChatBot = () =&amp;gt; {
  const genAI = new GoogleGenerativeAI("YOUR_API_KEY");
  async function run(prompt, streamMessage) {
    // For text-only input, use the gemini-pro model
    const model = genAI.getGenerativeModel({ model: "gemini-pro"});

    const result = await model.generateContentStream(prompt);
    let text = '';
    for await (const chunk of result.stream) {
      const chunkText = chunk.text();
      text += chunkText;
      streamMessage(text);
    }
    return text;
  }

  const flow = {
    start: {
      message: "Hello, I am sentient now, talk to me!",
      path: "model_loop",
    },
    model_loop: {
      message: async (params) =&amp;gt; {
        return await run(params.userInput, params.streamMessage);
      },
      path: "model_loop"
    },
  }
return (
    &amp;lt;ChatBot/&amp;gt;
  );
};
export default MyChatBot;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Go ahead and give the modified code snippet above a run in the &lt;a href="https://react-chatbotify.tjtanjin.com/playground/" rel="noopener noreferrer"&gt;playground&lt;/a&gt;. We've done everything right but it still appears a bit strange, isn't it? The messages are no longer sent as a single block, but it's still coming out in chunked parts instead of appearing character by character.&lt;/p&gt;

&lt;p&gt;We are seeing the above behavior because the stream response is providing us text in chunks. If we want to have the text show up character by character, we can manually handle the stream logic. This is slightly more involved and we won't be covering it in this guide but you may refer to the &lt;a href="https://react-chatbotify.tjtanjin.com/docs/examples/real_time_stream" rel="noopener noreferrer"&gt;&lt;strong&gt;live example&lt;/strong&gt;&lt;/a&gt; that I have provided here.&lt;/p&gt;

&lt;p&gt;Note that for demonstration in this tutorial, the API keys are directly included in the chatbot. While convenient, it is generally &lt;strong&gt;bad practice&lt;/strong&gt; to expose your API keys on client side. Instead, a &lt;strong&gt;best practice&lt;/strong&gt; would be to employ a &lt;strong&gt;server-side&lt;/strong&gt; component to manage API calls and have your client communicate with your server-side component instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simulated Stream Responses
&lt;/h2&gt;

&lt;p&gt;Recognizing the aesthetics and popularity of streaming responses, &lt;a href="https://react-chatbotify.tjtanjin.com" rel="noopener noreferrer"&gt;React ChatBotify&lt;/a&gt; also provides &lt;code&gt;simStream&lt;/code&gt; and &lt;code&gt;streamSpeed&lt;/code&gt; options for developers who would like to simulate streaming of text response to users. If you are interested in this option, you may refer to the example found &lt;a href="https://react-chatbotify.tjtanjin.com/docs/examples/simulated_stream/" rel="noopener noreferrer"&gt;&lt;strong&gt;here&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this tutorial, we've explored the exciting realm of integrating LLMs with &lt;a href="https://react-chatbotify.tjtanjin.com/" rel="noopener noreferrer"&gt;React ChatBotify&lt;/a&gt;. Using Gemini as an example, we have seen how easily we can empower our chatbots with the ability to provide dynamic responses.&lt;/p&gt;

&lt;p&gt;As we conclude the fourth installment, the path ahead promises even more innovation. In the upcoming final segment, we'll explore Retrieval Augmented Generation (RAG) to infuse our chatbot with personality and contextual awareness, elevating it to a truly engaging conversational partner. Thank you for reading, and look out for more content!&lt;/p&gt;

</description>
      <category>react</category>
      <category>ai</category>
      <category>javascript</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>How to Build a Telegram Bot: A Beginner’s Step-by-Step Guide</title>
      <dc:creator>tjtanjin</dc:creator>
      <pubDate>Sat, 09 Mar 2024 18:09:25 +0000</pubDate>
      <link>https://dev.to/tjtanjin/how-to-build-a-telegram-bot-a-beginners-step-by-step-guide-1kd1</link>
      <guid>https://dev.to/tjtanjin/how-to-build-a-telegram-bot-a-beginners-step-by-step-guide-1kd1</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Are you a newcomer to Python, uncertain about what to do for your first project? Or perhaps you are a seasoned developer seeking a new endeavor? If so, then welcome to the world of Telegram bot development!&lt;/p&gt;

&lt;p&gt;In this beginner-friendly guide, we'll embark on a journey to create our very own Telegram bot using the Python programming language. Whether you're a coding enthusiast taking initial steps or an experienced developer exploring new avenues, this article will provide you with a hands-on experience to help you bring up your very own Telegram bot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we dive into the exciting contents, do note that this guide assumes knowledge of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Familiarity with Python&lt;/li&gt;
&lt;li&gt;Familiarity with pip&lt;/li&gt;
&lt;li&gt;Familiarity with Telegram&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basic Python knowledge will suffice, and we will not be covering the above prerequisites as they are not the focus of this guide - there are plenty of Python &amp;amp; pip guides out there while Telegram is a messaging platform that needs little elaboration. All that said, if you need help with any of the above, you are more than welcome to &lt;a href="https://discord.com/invite/X8VSdZvBQY" rel="noopener noreferrer"&gt;reach out&lt;/a&gt; for assistance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Since we are working with a simple Telegram bot, the setup is relatively straightforward. Create and head into the folder where we will house our project (e.g. &lt;em&gt;my_telegram_bot&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;We will also be using &lt;a href="https://python-telegram-bot.org/" rel="noopener noreferrer"&gt;python-telegram-bot&lt;/a&gt;, which is a wrapper around the &lt;a href="https://core.telegram.org/" rel="noopener noreferrer"&gt;Telegram API&lt;/a&gt;. Install the library with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install python-telegram-bot --upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create a &lt;em&gt;main.py&lt;/em&gt; file and paste the following short snippet within it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from telegram.ext import Application

def main():
    """
    Handles the initial launch of the program (entry point).
    """
    token = ""
    application = Application.builder().token(token).concurrent_updates(True).read_timeout(30).write_timeout(30).build()
    print("Telegram Bot started!", flush=True)
    application.run_polling()

if __name__ == '__main__':
    main()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's breakdown the above code. We first define a &lt;code&gt;main&lt;/code&gt; function that serves as an entry point for the program. Within the function, a variable named &lt;code&gt;token&lt;/code&gt; is initialized with an empty string (not to worry, we'll revisit this soon). We then proceed to initialize the &lt;code&gt;Application&lt;/code&gt; class, providing configurations via a builder pattern. Finally, we print a message to notify us that the instance has been started before calling the &lt;code&gt;run_polling&lt;/code&gt; method to listen for updates.&lt;/p&gt;

&lt;p&gt;If you save this file and try running it with &lt;code&gt;python main.py&lt;/code&gt; now, you will be faced with an invalid token error. This is not much surprise, since our token hasn't even been set, which brings us on to the next section on obtaining bot tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bot Token
&lt;/h2&gt;

&lt;p&gt;For our program to startup successfully, the missing ingredient is the bot token that uniquely identifies our Telegram bot! Thankfully, Telegram provides a bot for us to easily obtain bot tokens:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdre9e8vrlutv5asfm0q3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdre9e8vrlutv5asfm0q3.png" alt="BotFather" width="378" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Head over to &lt;a href="https://t.me/botfather" rel="noopener noreferrer"&gt;BotFather&lt;/a&gt; and type &lt;code&gt;/newbot&lt;/code&gt;. You will be prompted with a series of questions such as the bot name and handle. Follow through with the creation process and you'll be greeted with a bot token at the end of the steps. This token can be used to control your Telegram bot so keep it safe and do not share it with others!&lt;/p&gt;

&lt;p&gt;With the bot token obtained above, return to &lt;em&gt;main.py&lt;/em&gt; and replace it where your token is currently an empty string. Try running the file again and this time, you'll see your console printing "Telegram Bot started!".&lt;/p&gt;

&lt;p&gt;Great! We've now got a bot running. Go ahead and send a message to your Telegram bot. Is it responding? Something feels amiss, we have not defined what the bot should respond to!&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Message
&lt;/h2&gt;

&lt;p&gt;For the bot to respond to user actions, we need to provide it with handlers. Since we would like our bot to respond to our text messages, let us start with a simple &lt;code&gt;MessageHandler&lt;/code&gt;. Within &lt;em&gt;main.py&lt;/em&gt; define a short function as outlined below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def reply(update, context):
    update.message.reply_text("Hello there!")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we add our &lt;code&gt;MessageHandler&lt;/code&gt; and have it filter for text messages by applying &lt;code&gt;filters.TEXT&lt;/code&gt;. Your new &lt;em&gt;main.py&lt;/em&gt; should now look like this (remember to update the imports at the top as well):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from telegram.ext import Application, MessageHandler, filters

async def reply(update, context):
    await update.message.reply_text("Hello there!")

def main():
    """
    Handles the initial launch of the program (entry point).
    """
    token = YOUR_BOT_TOKEN_HERE
    application = Application.builder().token(token).concurrent_updates(True).read_timeout(30).write_timeout(30).build()
    application.add_handler(MessageHandler(filters.TEXT, reply)) # new text handler here
    print("Telegram Bot started!", flush=True)
    application.run_polling()

if __name__ == '__main__':
    main()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Re-run your bot and try sending in a hello message again. Your bot should reply with "Hello there!". Pretty nice, but what if we want it to run commands?&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Command
&lt;/h2&gt;

&lt;p&gt;You got your bot to utter its first word, now let's try having it greet us when we say hello via a command instead! Similar to how we added messages, we will add another line but instead of a &lt;code&gt;MessageHandler&lt;/code&gt;, we will be using a &lt;code&gt;CommandHandler&lt;/code&gt; (again, remember to update your imports):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from telegram.ext import Application, CommandHandler, MessageHandler, filters

async def reply(update, context):
    await update.message.reply_text("Hello there!")

def main():
    """
    Handles the initial launch of the program (entry point).
    """
    token = YOUR_BOT_TOKEN_HERE
    application = Application.builder().token(token).concurrent_updates(True).read_timeout(30).write_timeout(30).build()
    application.add_handler(MessageHandler(filters.TEXT, reply))
    application.add_handler(CommandHandler("hello", reply)) # new command handler here
    print("Telegram Bot started!", flush=True)
    application.run_polling()

if __name__ == '__main__':
    main()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, go ahead and type &lt;code&gt;/hello&lt;/code&gt; to your chatbot. If you've been following this guide diligently, the bot would have responded with "Hello there!" again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Upload
&lt;/h2&gt;

&lt;p&gt;Getting the hang of things? Let's try with a slightly more advanced option, sending an image to your bot! This time round, we will be using &lt;code&gt;MessageHandler&lt;/code&gt; again but unlike the first example, the &lt;code&gt;PHOTO&lt;/code&gt; filter will be applied instead of the &lt;code&gt;TEXT&lt;/code&gt; filter. Go ahead and add the new line as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from telegram.ext import Application, CommandHandler, MessageHandler, filters

async def reply(update, context):
    await update.message.reply_text("Hello there!")

def main():
    """
    Handles the initial launch of the program (entry point).
    """
    token = YOUR_BOT_TOKEN_HERE
    application = Application.builder().token(token).concurrent_updates(True).read_timeout(30).write_timeout(30).build()
    application.add_handler(MessageHandler(filters.TEXT, reply))
    application.add_handler(CommandHandler("hello", reply))
    application.add_handler(MessageHandler(filters.PHOTO, reply)) # new photo handler here
    print("Telegram Bot started!", flush=True)
    application.run_polling()

if __name__ == '__main__':
    main()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if you send your bot a photo, it will once again greet you enthusiastically with a "Hello there!" - not the most intuitive for a photo upload but you get the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unleash Your Creativity
&lt;/h2&gt;

&lt;p&gt;We've just gone through some basic examples but I hope you're convinced that creating your own Telegram bot is no tough work! From here on out, you may be interested to check out the documentation for &lt;a href="https://python-telegram-bot.org/" rel="noopener noreferrer"&gt;python-telegram-bot&lt;/a&gt; to find out more of what you can do with the bot.&lt;/p&gt;

&lt;p&gt;For those who are interested to take things further, you may also wish to extend the functionalities of the bot into a project. In case you need some inspirations, I've open-sourced a handful of Telegram bots such as &lt;a href="https://github.com/tjtanjin/tele-qr" rel="noopener noreferrer"&gt;TeleQR&lt;/a&gt;, &lt;a href="https://github.com/tjtanjin/simple-media-converter" rel="noopener noreferrer"&gt;Simple Media Converter&lt;/a&gt; and &lt;a href="https://github.com/tjtanjin/simple-transcriptions" rel="noopener noreferrer"&gt;Simple Transcriptions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As a stretch goal, consider &lt;a href="https://dev.to/tjtanjin/how-to-host-a-telegram-bot-on-ubuntu-a-step-by-step-guide-4jk3"&gt;hosting your Telegram bot&lt;/a&gt; and including &lt;a href="https://dev.to/tjtanjin/ensuring-uptime-a-pythonic-approach-to-liveness-monitoring-lp8"&gt;liveness checks&lt;/a&gt; once you've completed your project!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this short walkthrough, we've explored how to create a basic Telegram bot. I hope you found this journey useful for your learning and that you're motivated to explore beyond just this guide. If you've got ideas or suggestions, feel free to leave them in the comments below or bounce ideas with me &lt;a href="https://discord.com/invite/X8VSdZvBQY" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Thank you for reading!&lt;/p&gt;

</description>
      <category>telegram</category>
      <category>telegrambot</category>
      <category>chatbot</category>
      <category>python</category>
    </item>
  </channel>
</rss>
