<?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>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>Sun, 22 Mar 2026 00:00:00 +0000</lastBuildDate>
      <item>
          <title>Voice AI in 2026</title>
          <pubDate>Sun, 22 Mar 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://kingstonkuan.com/blog/voice-ai-in-2026/</link>
          <guid>https://kingstonkuan.com/blog/voice-ai-in-2026/</guid>
          <description xml:base="https://kingstonkuan.com/blog/voice-ai-in-2026/">&lt;p&gt;Since I’ve been exploring the extended universe of voice AI and voice agents,
I’ve met a lot of people working with them in the real world and it’s not just
the average customer support bot anymore. I’ve met teams building debt
collection agents, emergency services routing, and even language-specific
services for regions outside the US.&lt;&#x2F;p&gt;
&lt;p&gt;In the spirit of &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.swyx.io&#x2F;learn-in-public&quot;&gt;Learning in Public&lt;&#x2F;a&gt;, I
wanted to write down some of what I know in case someone might learn from it.&lt;&#x2F;p&gt;
&lt;p&gt;I mostly cover conversational voice agents using the full pipeline in this post
(STT -&amp;gt; LLM -&amp;gt; TTS) but they are far from the only use case of voice AI. Voice
interfaces like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;wisprflow.ai&#x2F;&quot;&gt;Wispr Flow&lt;&#x2F;a&gt; or
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;willowvoice.com&#x2F;&quot;&gt;Willow&lt;&#x2F;a&gt; use STT and LLM without TTS for AI-powered
voice dictation in any app. I also maintain an open source voice interface
called &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;. Bulk
transcription and summarization tools like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.granola.ai&#x2F;&quot;&gt;Granola&lt;&#x2F;a&gt;
have less real-time requirements like latency and can optimize for intelligence.
You could even go the other direction and use only TTS for generating media
content. The voice stack is flexible so you can pick the pieces that you need.&lt;&#x2F;p&gt;
&lt;p&gt;I focus on conversational voice agents just because they touch the entire stack,
making them a good lens for understanding the technologies involved.&lt;&#x2F;p&gt;
&lt;p&gt;So, if you’re wondering if it’s possible to run voice agents in production in
2026, for me the answer is yes. Even though it might not be as easy as building
web apps just yet, many players in the industry are vying to make it simpler for
anyone to build in voice. If you’re interested in building a voice agent today,
we can start off with choosing what layer of abstraction to build upon.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;This post names a few models and providers as examples. There are many more out
there so do your own research.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;layers-of-abstraction&quot;&gt;Layers of abstraction&lt;&#x2F;h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;High-Level&lt;&#x2F;strong&gt;: Services like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;vapi.ai&#x2F;&quot;&gt;Vapi&lt;&#x2F;a&gt; &amp;amp;
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.retellai.com&#x2F;&quot;&gt;Retell&lt;&#x2F;a&gt; offer orchestration-as-a-service. They
handle the handoffs between STT, LLM, and TTS, telephony bridging, and
complex state management of a live conversation. This path is often chosen
for faster speed to market.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Low-Level&lt;&#x2F;strong&gt;: &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;livekit&#x2F;agents&quot;&gt;LiveKit&lt;&#x2F;a&gt; &amp;amp;
&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; are open source frameworks
that give you full control over the agent pipeline. Instead of calling a
black-box API, you’re building the “brain” of the agent yourself. This path
is typically used if you require granular control or need to optimize costs.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Using higher level orchestration services is more straightforward so let’s focus
on the low level path to learn more about the underlying technology. When you
own the pipeline you need to start thinking about the two fundamental technical
challenges of voice AI: &lt;strong&gt;The Network&lt;&#x2F;strong&gt; and &lt;strong&gt;The Model Pipeline&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;For a deeper technical dive into these components, the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;voiceaiandvoiceagents.com&#x2F;&quot;&gt;Voice AI and Voice
Agents Primer&lt;&#x2F;a&gt; is an excellent resource.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;the-network&quot;&gt;The Network&lt;&#x2F;h2&gt;
&lt;p&gt;The average developer rarely thinks about networking problems because most web
apps are built around request-response patterns. That works fine because we’ve
spent years optimizing the internet for that. When it comes to building voice AI
on top of streaming protocols, optimizing the network becomes crucial for
performance and quality. You can have the fastest and smartest LLM in the world,
but if your audio packets are getting lost hopping across the globe, the
conversation will feel broken.&lt;&#x2F;p&gt;
&lt;p&gt;When dealing with audio streaming for voice, you will hear a lot of different
infra terms that do not come up as often in the classic HTTP and REST API stack:
Websockets, WebRTC, SFU, SIP, PSTN, and so on.&lt;&#x2F;p&gt;
&lt;p&gt;As much of this still requires physical infrastructure, you will likely lean on
existing global edge networks. Even &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;livekit.io&#x2F;&quot;&gt;LiveKit&lt;&#x2F;a&gt; and
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.daily.co&#x2F;&quot;&gt;Daily&lt;&#x2F;a&gt; (the company behind Pipecat) provide cloud
solutions to abstract this away. Daily and LiveKit grew significantly during the
teleconferencing era and both have since applied their expertise to building for
voice agents. Their global networks are hard for new entrants to replicate
quickly, which makes them a good foundation for voice AI infrastructure.&lt;&#x2F;p&gt;
&lt;p&gt;That being said, I’ve been surprised by how many teams are starting to roll
their own regional network stack. If you’re building for a specific market (say,
Vietnam), you don’t necessarily need a global mesh. You can stand up your own
WebRTC infra, using &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;livekit&#x2F;livekit&quot;&gt;LiveKit’s open-source
stack&lt;&#x2F;a&gt; for example. For regional users, this
approach can be more efficient than relying on generalized cloud solutions.&lt;&#x2F;p&gt;
&lt;p&gt;Telephony is the final piece of this puzzle. You still need CPaaS
(Communications Platform As A Service) providers like
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.twilio.com&#x2F;&quot;&gt;Twilio&lt;&#x2F;a&gt; or &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;telnyx.com&#x2F;&quot;&gt;Telnyx&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Twilio is an established market leader in CPaaS with a mature ecosystem that
many enterprises have historically relied on (I used it back in 2019) while
Telnyx has focused on vertical integration specific to voice AI, owning their
own networking and colocating GPUs for fast model inference.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-model-pipeline&quot;&gt;The Model Pipeline&lt;&#x2F;h2&gt;
&lt;p&gt;With the underlying network out of the way, you still have to worry about the
actual intelligence part of the agent. Today, the industry standard is still the
3-model pipeline: STT (Speech-to-Text), also called ASR (Automatic Speech
Recognition) -&amp;gt; LLM (Large Language Model) -&amp;gt; TTS (Text-to-Speech).&lt;&#x2F;p&gt;
&lt;p&gt;You might wonder why we aren’t all using direct Speech-to-Speech (S2S) models
like the Gemini Live API. While such models might be more capable of capturing
nuanced emotional cues, production agents still face two major hurdles:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Intelligence vs. Latency:&lt;&#x2F;strong&gt; The models currently capable of handling
complex tool calling with the highest reliability are still text-based.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Cost:&lt;&#x2F;strong&gt; Running a conversation through S2S is still significantly more
expensive than multi-model pipeline.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;By owning the pipeline, you can optimize for latency, cost, and advanced
features. Developers typically aim to hit an &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;voiceaiandvoiceagents.com&#x2F;&quot;&gt;800ms voice-to-voice round
trip&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;stt-asr&quot;&gt;STT &#x2F; ASR&lt;&#x2F;h3&gt;
&lt;p&gt;Providers like Deepgram offer &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;deepgram.com&#x2F;learn&#x2F;designing-voice-ai-workflows-using-stt-nlp-tts&quot;&gt;sub-300ms
transcription&lt;&#x2F;a&gt;,
while Speechmatics provides features like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.speechmatics.com&#x2F;speech-to-text&#x2F;features&#x2F;diarization&quot;&gt;diarization for multi-user
conversations&lt;&#x2F;a&gt;.
You can also often self-host STT models as they are smaller and don’t require
GPU clusters, like Nemotron ASR which I use in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;kingstonkuan.com&#x2F;blog&#x2F;nemotron-voice-dictation&#x2F;&quot;&gt;my personal
setup&lt;&#x2F;a&gt; or optimized
versions of OpenAI’s Whisper. This isn’t an option with a monolithic S2S model.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;pipecat-ai&#x2F;stt-benchmark&quot;&gt;Pipecat’s recent STT benchmarks&lt;&#x2F;a&gt;
are a great reference for anyone building in this space.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;llm&quot;&gt;LLM&lt;&#x2F;h3&gt;
&lt;p&gt;LLM selection for voice agents follows similar principles as other agentic use
cases. You need reliable tool calling, instruction following, and reasoning, but
with the added focus on latency. Heavier models give better reliability but add
round-trip time, so many production agents use lighter models like Gemini Flash.
Providers like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.cerebras.ai&#x2F;&quot;&gt;Cerebras&lt;&#x2F;a&gt; specialize in delivering
fast model inference for open weights models like gpt-oss-120b.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;tts&quot;&gt;TTS&lt;&#x2F;h3&gt;
&lt;p&gt;For the final leg, TTS converts the LLM’s text response into speech.
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;cartesia.ai&#x2F;sonic&quot;&gt;Cartesia&lt;&#x2F;a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;elevenlabs.io&#x2F;&quot;&gt;ElevenLabs&lt;&#x2F;a&gt;
are some common providers you might have heard of that offer realistic,
low-latency speech generation across many languages and tones. The TTS space
hasn’t quite settled on a dominant approach yet, with different model
architectures still competing. For example, Cartesia’s Sonic is built on State
Space Models (SSMs) optimized for streaming, while ElevenLabs uses
transformer-based architectures. It’s an area worth keeping an eye on as the
research matures.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;collapsing-stt-and-llm&quot;&gt;Collapsing STT and LLM&lt;&#x2F;h3&gt;
&lt;p&gt;An interesting approach that blurs the line between the 3-model pipeline and
full S2S is &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;fixie-ai&#x2F;ultravox&quot;&gt;Ultravox&lt;&#x2F;a&gt;. Instead of
transcribing audio to text and then feeding it to an LLM, Ultravox encodes audio
directly into the LLM’s token embedding space. This effectively merges the STT
and LLM steps into one, reducing latency while still outputting text that can be
fed to a standard TTS model. A tradeoff here is inspectability. With the
intermediate transcript from STT you can log, debug, and audit, but with this
this approach that visibility is lost.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;taking-turns&quot;&gt;Taking Turns&lt;&#x2F;h2&gt;
&lt;p&gt;Even with a perfect network and fast models, a voice agent can still feel
robotic if it doesn’t know when to jump into a conversation or stop talking.
Turn management is where some of the hardest engineering challenges are being
tackeled right now, solving for VAD and Interruptibility for example.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;VAD (Voice Activity Detection):&lt;&#x2F;strong&gt; How do you know when someone is finished
talking? Naive volume-based VAD fails because natural speech has pauses
between clauses, and silence alone doesn’t mean the speaker is done. We’re
moving toward semantic VAD, using a tiny model to understand the intent of
the pause. By far the most popular solution right now is &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;snakers4&#x2F;silero-vad&quot;&gt;Silero
VAD&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Interruptibility:&lt;&#x2F;strong&gt; If the agent is speaking and the user starts answering
a question, how does the agent handle it? It can’t keep talking over the
user and has to stop appropriately to take in the new information quickly
and modify its response. Some providers like Pipecat provide their own
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;pipecat-ai&#x2F;smart-turn&quot;&gt;Smart Turn&lt;&#x2F;a&gt; model to try and
solve for this.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;observability-and-evals&quot;&gt;Observability and Evals&lt;&#x2F;h2&gt;
&lt;p&gt;Another piece of the puzzle in the voice space is observability and evals. This
is common in other agentic applications too but the voice stack comes with
additional challenges.&lt;&#x2F;p&gt;
&lt;p&gt;Voice agents are harder to evaluate than text agents because they have more
dimensions of failure. It’s not just “did it give the right answer?” but “did it
interrupt the user inappropriately?” or “was the latency spike caused by the
model provider or an intermittent network issue?”&lt;&#x2F;p&gt;
&lt;p&gt;A common approach today is to focus on binary ratings for specific behaviors
(“Did the agent complete the booking?” or “Did the agent correctly verify the
caller’s identity?”) and keep a human in the loop to review traces, rather than
looking for a single accuracy score based on arbitrary metrics. You can curate
eval sets from real conversations, often called “Golden Datasets”, to test how
tweaking the model pipeline would affect past conversations. Startups like
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;hamming.ai&#x2F;&quot;&gt;Hamming AI&lt;&#x2F;a&gt; or &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.coval.dev&#x2F;&quot;&gt;Coval&lt;&#x2F;a&gt; are
attempting to automate parts of this.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;it-s-time-to-build&quot;&gt;It’s time to build&lt;&#x2F;h2&gt;
&lt;p&gt;Building in voice right now feels like the early days of LLMs. Whether you rely
on out-of-the-box solutions or deploy your own infra, there hasn’t been a better
time to be building in voice. There is plenty more to say on any of these topics
and I’m barely scratching the surface. While I’m still learning and growing in
the space myself, I hope you learned something new from this post, or at least
have more resources to dive deeper.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;acknowledgements&quot;&gt;Acknowledgements&lt;&#x2F;h2&gt;
&lt;p&gt;I would like to thank all the great folks I’ve met in the community that have
advised and supported me with my learning. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;x.com&#x2F;kwindla&quot;&gt;Kwindla&lt;&#x2F;a&gt;
from Daily, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;x.com&#x2F;BinaryCoder&quot;&gt;Edgars&lt;&#x2F;a&gt; and the rest of the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.speechmatics.com&#x2F;&quot;&gt;Speechmatics&lt;&#x2F;a&gt; team for the support in 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;.
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;x.com&#x2F;transitive_bs&quot;&gt;Travis&lt;&#x2F;a&gt; for encouragement on writing this. And
everyone else I have met building in the voice community!&lt;&#x2F;p&gt;
</description>
      </item>
      <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>
