<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>Kingston Kuan</title>
      <link>https://kingstonkuan.com/blog</link>
      <description>My adventures in tech</description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://kingstonkuan.com/blog/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Tue, 03 Feb 2026 00:00:00 +0000</lastBuildDate>
      <item>
          <title>Dictating anywhere with NVIDIA open models - Nemotron ASR + Tambourine</title>
          <pubDate>Tue, 03 Feb 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://kingstonkuan.com/blog/nemotron-voice-dictation/</link>
          <guid>https://kingstonkuan.com/blog/nemotron-voice-dictation/</guid>
          <description xml:base="https://kingstonkuan.com/blog/nemotron-voice-dictation/">&lt;p&gt;I saw &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;blogs.nvidia.com&#x2F;blog&#x2F;open-models-data-tools-accelerate-ai&#x2F;&quot;&gt;NVIDIA’s announcement&lt;&#x2F;a&gt; on X about their new series of open Nemotron models and ended up reading &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.daily.co&#x2F;blog&#x2F;building-voice-agents-with-nvidia-open-models&#x2F;&quot;&gt;a blog post from Daily.co&lt;&#x2F;a&gt; about building voice agents with Nemotron and Pipecat.&lt;&#x2F;p&gt;
&lt;p&gt;I’ve been using voice dictation daily since building &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;kstonekuan&#x2F;tambourine-voice&quot;&gt;Tambourine&lt;&#x2F;a&gt;, so I decided to test Nemotron with it.
It’s fast for real-time use, and the accuracy is solid, unfortunately you do need a CUDA GPU.
Here’s how I set it up.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;my-setup&quot;&gt;My Setup&lt;&#x2F;h2&gt;
&lt;p&gt;Tambourine is the desktop app that ties everything together.
It captures audio, sends it through the pipeline, and types the result wherever my cursor is.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[Microphone] -&amp;gt; [Nemotron ASR] -&amp;gt; [LLM] -&amp;gt; [Insert text at cursor]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;There are great options for AI voice dictation like Wispr Flow or Willow, but Tambourine is open source and allows you to have full control over the pipeline: swap STT&#x2F;ASR and LLM, customizable formatting prompts, and import dictionaries for different domains.&lt;&#x2F;p&gt;
&lt;p&gt;As Tambourine is built on top of Pipecat, adding Nemotron as an ASR provider was straightforward.&lt;&#x2F;p&gt;
&lt;p&gt;I’m running this on a desktop with an RTX 4080 Super. Nemotron ASR requires a GPU with CUDA support, which means Windows or Linux only. macOS users can still use Tambourine with cloud STT&#x2F;ASR providers.&lt;&#x2F;p&gt;
&lt;p&gt;You can use any LLM provider compatible with Pipecat, I tend to use &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.cerebras.ai&quot;&gt;Cerebras&lt;&#x2F;a&gt;  with &lt;code&gt;gpt-oss-120b&lt;&#x2F;code&gt; for fast cloud inference.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;setting-up-nemotron-asr&quot;&gt;Setting Up Nemotron ASR&lt;&#x2F;h2&gt;
&lt;p&gt;To run only the ASR model via Docker, I forked &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;pipecat-ai&#x2F;nemotron-january-2026&quot;&gt;PipeCat’s example&lt;&#x2F;a&gt; to strip it down to an &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;kstonekuan&#x2F;nemotron-january-2026?tab=readme-ov-file#asr-only-deployment-lightweight&quot;&gt;ASR-only image&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;git&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; clone https:&#x2F;&#x2F;github.com&#x2F;kstonekuan&#x2F;nemotron-january-2026&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;cd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; nemotron-january-2026&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;docker&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; build -f Dockerfile.asr -t nemotron-asr .&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;docker&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; run --gpus all -p 9765:9765&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  -v ~&#x2F;.cache&#x2F;huggingface:&#x2F;root&#x2F;.cache&#x2F;huggingface&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  nemotron-asr&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Once running, you can check the logs in docker.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;setting-up-tambourine&quot;&gt;Setting Up Tambourine&lt;&#x2F;h2&gt;
&lt;p&gt;See the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;kstonekuan&#x2F;tambourine-voice&quot;&gt;Tambourine repo&lt;&#x2F;a&gt; for full setup instructions.&lt;&#x2F;p&gt;
&lt;p&gt;To use Nemotron, set &lt;code&gt;NEMOTRON_ASR_URL&lt;&#x2F;code&gt; in &lt;code&gt;.env&lt;&#x2F;code&gt; and select Nemotron as the STT provider in the app settings.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;NEMOTRON_ASR_URL=ws:&#x2F;&#x2F;localhost:9765&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If running the Tambourine server in docker as well you might have to use&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;NEMOTRON_ASR_URL=ws:&#x2F;&#x2F;host.docker.internal:9765&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;wrap-up&quot;&gt;Wrap Up&lt;&#x2F;h2&gt;
&lt;p&gt;I’ve really enjoyed exploring this stack, and look forward to trying out new models as they come out.
AI-powered voice dictation has changed how I interact with all my apps.
Especially when it comes to using AI tools like Claude Code or ChatGPT, I find myself reaching for the keyboard less and just thinking out loud, trusting Tambourine to capture my intent on top of the words.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Quick Links:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;kstonekuan&#x2F;tambourine-voice&quot;&gt;Tambourine&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;kstonekuan&#x2F;nemotron-january-2026&quot;&gt;Pipecat Nemotron Example Fork&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;blogs.nvidia.com&#x2F;blog&#x2F;open-models-data-tools-accelerate-ai&#x2F;&quot;&gt;NVIDIA Announcement&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.daily.co&#x2F;blog&#x2F;building-voice-agents-with-nvidia-open-models&#x2F;&quot;&gt;Daily.co Blog Post&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;pipecat-ai&#x2F;pipecat&quot;&gt;PipeCat&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.cerebras.ai&quot;&gt;Cerebras&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Why is it so hard to get rid of deepfakes?</title>
          <pubDate>Sat, 07 May 2022 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://kingstonkuan.com/blog/why-is-it-so-hard-to-get-rid-of-deepfakes/</link>
          <guid>https://kingstonkuan.com/blog/why-is-it-so-hard-to-get-rid-of-deepfakes/</guid>
          <description xml:base="https://kingstonkuan.com/blog/why-is-it-so-hard-to-get-rid-of-deepfakes/">&lt;p&gt;&lt;em&gt;Originally published on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@kstonekuan&#x2F;why-is-it-so-hard-to-get-rid-of-deepfakes-e7130b608069&quot;&gt;Medium&lt;&#x2F;a&gt;.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;deepfakes-hero.jpg&quot; alt=&quot;Photo by Sander Sammy on Unsplash&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;On April 17, 2018, the world was greeted by Former President Barack Obama saying outrageous things in a video titled “You Won’t Believe What Obama Says In This Video!”. About 30 seconds in, it is revealed that is a deepfake allowing the famous comedian Jordan Peele to use the face and voice of “Obama”. As technology advances further, higher forms of media manipulation such as video and sound have been made possible and must be addressed quickly.&lt;&#x2F;p&gt;
&lt;div class=&quot;youtube-embed&quot;&gt;
    &lt;iframe
        src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;cQ54GDm1eL0&quot;
        width=&quot;100%&quot;
        height=&quot;400&quot;
        frameborder=&quot;0&quot;
        allowfullscreen
        loading=&quot;lazy&quot;
        allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot;&gt;
    &lt;&#x2F;iframe&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;doi.org&#x2F;10.22215&#x2F;timreview&#x2F;1282&quot;&gt;Deepfake technology&lt;&#x2F;a&gt; can produce fake content indistinguishable from real content by the human eye. This technology has risen in popularity due to abuse in sensational media by the spread of misinformation and infringement of privacy. However, not only is it impossible to eradicate these misuses completely but the core technology also has other benefits and thus does not warrant an outright ban of its applications. Furthermore, regardless of the benefits or detriments, the advent of deepfake technology means media regulations must keep up before widespread use is commonplace. While technology is being developed to definitively &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;doi.org&#x2F;10.1177&#x2F;1365712718807226&quot;&gt;identify Deepfakes&lt;&#x2F;a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;doi.org&#x2F;10.1093&#x2F;jiplp&#x2F;jpz167&quot;&gt;new regulations&lt;&#x2F;a&gt; should be put in place to cater to their uses. Specialized categories should be set up on platforms to ensure users comply with the rules and technical agencies must be set up for appropriate policing.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;deepfakes-misinformation.jpg&quot; alt=&quot;Photo by Jorge Franganillo on Unsplash&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Manual manipulation of images and videos is neither new nor rare and the war on misinformation has been fought on many fronts. Ranging from digital editing in marketing to false evidence in court, a big hurdle is being faced now with deepfakes being created by artificial intelligence. Yet, it is believed that the very technology capable of &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;doi.org&#x2F;10.1177&#x2F;1365712718807226&quot;&gt;detecting deepfakes&lt;&#x2F;a&gt; can also be used to improve them and thus even harder to detect. In a battle of computing power and datasets, it is imperative to not only support the detection but be able to keep this away from nefarious users. Yet the development of such powerful technology has traditionally been open source and only possible with the collaboration of many individuals made possible by the interconnectivity of the web. It is not reasonable to rely on such teams to be able to securely store their algorithms. It is better to instead consider a hybrid approach to development where the project can be managed by trusted parties such as a government body or large private institution while still allowing for open contributions.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;deepfakes-social-media.jpg&quot; alt=&quot;Photo by Adem AY on Unsplash&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Technology evolves rapidly and where public regulations often fail to keep up, fast-moving private bodies are well-suited for this role. Given that the open-source nature of artificial intelligence development is unlikely to change significantly, regulations prove even more crucial than combative technology. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;doi.org&#x2F;10.1093&#x2F;jiplp&#x2F;jpz167&quot;&gt;Media platforms, such as Youtube or Facebook, are often in the most appropriate position to regulate deepfake technology&lt;&#x2F;a&gt;. Not only do they have access to large datasets, but they have already been dealing with similar hostile uses of their platforms and have amassed many skilled employees. The power given to these privately owned platforms by the internet has already been immense, being able to affect economics, politics, and societies on a large scale. Indeed, with this should come the responsibility to police their own platforms and be punished for not investing heavily in countermeasures.&lt;&#x2F;p&gt;
&lt;p&gt;Current measures to combat deepfakes have been to put a lid on them rather than attempts at integration. Yet, completely erasing deepfakes is a fruitless endeavor. It would more productive to &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.iadclaw.org&#x2F;assets&#x2F;1&#x2F;6&#x2F;18_2_MY2020_WestLaw_Reject_the_Evidence_of_your_Eyes_and_Ears_DeepFakes_and_the_Law_of_Virtual_Replicants_REVIEWED.pdf&quot;&gt;embrace deepfakes with their own category&lt;&#x2F;a&gt; on these platforms for creative uses. In this manner, the technology may still develop while being healthily regulated. By complying with ethical and legal standards, personal privacy and commercial rights can be protected more smoothly. Desensitisation will also play an essential role in aiding the public with understanding deepfake technology which helps them to identify and report illegal activities. With the stigma behind deepfakes shifted away from sensational topics such as politics and obscene material, the research behind combative technologies also gains an edge in terms of financial and technical support.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;deepfakes-law.jpg&quot; alt=&quot;Photo by Tingey Injury Law Firm on Unsplash&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;doi.org&#x2F;10.2139&#x2F;ssrn.3213954&quot;&gt;Existing agencies still play important roles&lt;&#x2F;a&gt; in attempting to regulate deepfakes. However, their current capabilities are highly restricted. A new agency would be more suited to dealing with the impending threat of deepfakes. This agency must be technically equipped to deal with deepfakes and must also be able to administer appropriate penalties to rule breakers. As the internet has no borders, an international agency, like what the World Health Organisation is to healthcare, needs to create general guidelines that can thereafter be tailored to individual countries’ needs. This body should punish individual agencies for not being able to properly police within their jurisdiction.&lt;&#x2F;p&gt;
&lt;p&gt;On the other hand, it is worth analyzing the deepfake situation from a social perspective. We need to understand the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;doi.org&#x2F;10.1515&#x2F;opis-2019-0003&quot;&gt;reasons behind the creation of deepfakes and their uses&lt;&#x2F;a&gt;. As advanced as technology gets, even though the intelligence being used is artificial, there is always a human aspect to how and when it is used. By focusing on the community and their technological literacy, malicious use is reduced, and misconceptions corrected. As a remedy, these measures are hard to quantify but similarly, it is known that &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;doi.org&#x2F;10.1016&#x2F;B978-0-12-815391-8.00009-4&quot;&gt;better education contributes to a lower crime rate&lt;&#x2F;a&gt;. Targeting the root behind the misuse of deepfakes also allows greater freedom in its research which has wider benefits than just entertainment.&lt;&#x2F;p&gt;
&lt;p&gt;In conclusion, deepfakes are very much a part of modern technology and therefore difficult to eradicate. Governments and platforms have important roles to play in contributing to the development of technology capable of identifying deepfakes. As fighting solely on the technical front is insufficient, laws and regulations must attempt to pull ahead by catering to expected growth in deepfakes with deliberate exposure, the appointment of responsibility, and education. If left unchecked, deepfakes may begin to rule all media and the distortion of reality may become the rule rather than the exception.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Building a chatbot for Angel &amp; Mortal</title>
          <pubDate>Sun, 13 Jun 2021 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://kingstonkuan.com/blog/building-a-chatbot-for-angel-mortal/</link>
          <guid>https://kingstonkuan.com/blog/building-a-chatbot-for-angel-mortal/</guid>
          <description xml:base="https://kingstonkuan.com/blog/building-a-chatbot-for-angel-mortal/">&lt;p&gt;&lt;em&gt;Originally published on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@kstonekuan&#x2F;building-a-chatbot-for-angel-mortal-5d389ab7acde&quot;&gt;Medium&lt;&#x2F;a&gt;.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;angel-mortal-hero.jpg&quot; alt=&quot;Photo by Jonas Leupe on Unsplash&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I was starting an hour long commute home when my phone came abuzz. A friend was organizing the annual Angel &amp;amp; Mortal game for our residential college community and was interested in leveraging some online platform for anonymous communication between players. The rules of Angel &amp;amp; Mortal are simple:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Each player is assigned a Mortal to whom they act as an Angel.&lt;&#x2F;li&gt;
&lt;li&gt;The Angel must give gifts to or play pranks on their Mortal without being discovered.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Typically, players would send messages to the organizers to be manually forwarded to their Mortal or Angel, asking for their likes&#x2F;dislikes or informing them of new gifts. I immediately found this project to be simple yet impactful and was compelled to help. Unfortunately the game was starting soon, in a week, so I had to work fast.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h2&gt;
&lt;p&gt;This bot was developed in Python using the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;python-telegram-bot&#x2F;python-telegram-bot&quot;&gt;python-telegram-bot&lt;&#x2F;a&gt; package. This allows you to run a simple server that will poll for messages sent to your bot on telegram. Before starting, you will need to set up a bot using the &lt;em&gt;BotFather&lt;&#x2F;em&gt; and generate a token (instructions &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;core.telegram.org&#x2F;bots#6-botfather&quot;&gt;here&lt;&#x2F;a&gt;). The initial player data are loaded from CSV.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Original code used when I was running the bot (sorry if it’s messy!) can be found here: &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;kstonekuan&#x2F;angel-mortal-bot&quot;&gt;https:&#x2F;&#x2F;github.com&#x2F;kstonekuan&#x2F;angel-mortal-bot&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;development&quot;&gt;Development&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;player-class&quot;&gt;Player class&lt;&#x2F;h3&gt;
&lt;p&gt;Before diving into the chatbot logic, it’s important to understand the theory of the game itself. Each player has an Angel and a Mortal. Furthermore, your Angel is another player in the game who has you as their Mortal and vice versa for your Mortal. From an &lt;em&gt;OOP&lt;&#x2F;em&gt; perspective, &lt;strong&gt;each player can be represented as an object that references other player objects&lt;&#x2F;strong&gt;. Along with some telegram attributes, this can be represented by a &lt;em&gt;Player&lt;&#x2F;em&gt; class.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F9E2AF;font-style: italic;&quot;&gt; Player&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89DCEB;font-style: italic;&quot;&gt; __init__&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; username&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; room&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; angel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; mortal&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;        self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;username&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; username&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;        self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;room&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; room&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;        self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;angel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; angel&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;        self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;mortal&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; mortal&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F38BA8;font-style: italic;&quot;&gt;        self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;chat_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; None&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;angel-mortal-graph.png&quot; alt=&quot;Directed graph representing players in the game&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;While not crucial, you might recognise this as a graph node from graph theory. In particular, the game is designed as a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Directed_graph&quot;&gt;directed graph&lt;&#x2F;a&gt;. Each node has two outgoing edges (to the Angel and Mortal) and two incoming edges (from the Angel and Mortal).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;loading-players&quot;&gt;Loading players&lt;&#x2F;h3&gt;
&lt;p&gt;While it might be possible to use our system to generate Angel-Mortal mappings, organizers often have a more in-depth understanding of social dynamics within the group. &lt;strong&gt;I chose to load provided mappings from a CSV file&lt;&#x2F;strong&gt;, as opposed to using something more complex (but efficient at scale), as it was simpler for organizers to modify the data. After loading, players can be stored in a dictionary or hash map in memory, similar to an adjacency list.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; load_players&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;filename&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    players&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    with&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;font-style: italic;&quot;&gt; open&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;filename&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;#39;r&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        reader&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; csv&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;DictReader&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;        for&lt;&#x2F;span&gt;&lt;span&gt; row&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span&gt; reader&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;            players&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;row&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;font-style: italic;&quot;&gt;username&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt; Player&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;                row&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;font-style: italic;&quot;&gt;username&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;                row&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;font-style: italic;&quot;&gt;room&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;                row&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;font-style: italic;&quot;&gt;angel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;                row&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;font-style: italic;&quot;&gt;mortal&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;            )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;    # Link player objects&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; player&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span&gt; players&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;values&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;():&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        player&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;angel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; players&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;player&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;angel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        player&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;mortal&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; players&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;player&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;mortal&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span&gt; players&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;forwarding-messages&quot;&gt;Forwarding messages&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;em&gt;Note: I reference parts of the python-telegram-bot API without explaining them, please refer to their &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;python-telegram-bot&#x2F;python-telegram-bot&#x2F;wiki&#x2F;Introduction-to-the-API&quot;&gt;tutorials&lt;&#x2F;a&gt; for this.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;You might have noticed that our &lt;em&gt;Player&lt;&#x2F;em&gt; class includes a &lt;em&gt;username&lt;&#x2F;em&gt; and &lt;em&gt;chat_id&lt;&#x2F;em&gt;. The &lt;em&gt;username&lt;&#x2F;em&gt; is used to identify players and is provided by the organizers while &lt;em&gt;chat_id&lt;&#x2F;em&gt; is what is used to send messages. We cannot send messages directly to a &lt;em&gt;username&lt;&#x2F;em&gt; as it would be unclear how we want the bot to communicate such as in a private message or group chat. &lt;strong&gt;Each user needs to start a chat with the bot first in order to get the &lt;em&gt;chat_id&lt;&#x2F;em&gt; that is generated by Telegram&lt;&#x2F;strong&gt;. I also went one step further to reject users that do not appear as a key in the dictionary as they are not registered players in the game.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; start&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;update&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; context&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    username&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; update&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;effective_user&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;username&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; username&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; not in&lt;&#x2F;span&gt;&lt;span&gt; players&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        update&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;message&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;reply_text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;You are not registered for this game.&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;    players&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;username&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;].&lt;&#x2F;span&gt;&lt;span&gt;chat_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; update&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;effective_chat&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;id&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    update&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;message&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;reply_text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;font-style: italic;&quot;&gt;f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;Welcome &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;username&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;! You can now send messages.&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;python-telegram-bot&#x2F;python-telegram-bot&#x2F;blob&#x2F;master&#x2F;examples&#x2F;conversationbot.py&quot;&gt;Conversation handlers&lt;&#x2F;a&gt; can be used to create simple menus to guide users through your bot flow. For this bot the user must first choose either their Angel or Mortal before sending their messages across. They may cancel the conversation at any time to switch between the two. Thanks to the class created in previous steps, it is not difficult to &lt;strong&gt;find the reference to their Mortal or Angel and get their respective &lt;em&gt;chat_id&lt;&#x2F;em&gt; to forward messages&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; send_to_mortal&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;update&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; context&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    username&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; update&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;effective_user&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;username&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    player&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; players&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;username&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    mortal&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; player&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;mortal&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; mortal&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;chat_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; is&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; None&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        update&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;message&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;reply_text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;Your mortal hasn&amp;#39;t started the bot yet.&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;        return&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;    # Forward the message&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    context&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;bot&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;send_message&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;        chat_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;mortal&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;chat_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt;        text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;font-style: italic;&quot;&gt;f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;Message from your Angel:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;\n\n{&lt;&#x2F;span&gt;&lt;span&gt;update&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;message&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    update&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;message&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;&quot;&gt;reply_text&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;&amp;quot;Message sent to your mortal!&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The result should look something like this (with custom names for Angel and Mortal):&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;images&#x2F;angel-mortal-chat.jpeg&quot; alt=&quot;Example of a conversation with the bot&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;deployment&quot;&gt;Deployment&lt;&#x2F;h3&gt;
&lt;p&gt;So far you might have been testing your bot locally but it is probably not a good idea to keep to run this on your own machine for the entire duration of such events. I personally made use of &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;azure.microsoft.com&#x2F;en-us&#x2F;free&#x2F;students&#x2F;&quot;&gt;Microsoft Azure free student credits&lt;&#x2F;a&gt; to host but there are many &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;python-telegram-bot&#x2F;python-telegram-bot&#x2F;wiki&#x2F;Where-to-host-Telegram-Bots&quot;&gt;options&lt;&#x2F;a&gt; available.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;improvements&quot;&gt;Improvements&lt;&#x2F;h3&gt;
&lt;p&gt;As this was built in a few days with little time for testing there are many improvements you may want to make.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Common feedback from players was that they &lt;strong&gt;could not send pictures or emojis&lt;&#x2F;strong&gt; through the bot. I had forgotten to account for this which might have to do with the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;python-telegram-bot.readthedocs.io&#x2F;en&#x2F;stable&#x2F;telegram.ext.filters.html&quot;&gt;Filters&lt;&#x2F;a&gt; module.&lt;&#x2F;li&gt;
&lt;li&gt;For long term games that require modifications or continuity you may also consider building a proper &lt;strong&gt;backend database&lt;&#x2F;strong&gt; such as using &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.mongodb.com&#x2F;nosql-explained&#x2F;nosql-vs-sql&quot;&gt;SQL or NoSQL&lt;&#x2F;a&gt; instead of a simple CSV.&lt;&#x2F;li&gt;
&lt;li&gt;Using the &lt;em&gt;Player&lt;&#x2F;em&gt; class and directed graph design, you can &lt;strong&gt;develop advanced algorithms&lt;&#x2F;strong&gt; to generate, validate or even modify the player graph for larger games.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;I really enjoyed building this bot and helping out my community. This is my first article about programming so I hope you enjoyed reading and am always open to feedback about my writing!&lt;&#x2F;p&gt;
</description>
      </item>
    </channel>
</rss>
