<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Webex on 0x2142 | Networking Nonsense</title>
    <link>https://0x2142.com/tags/webex/</link>
    <description>Recent content in Webex on 0x2142 | Networking Nonsense</description>
    <image>
      <title>0x2142 | Networking Nonsense</title>
      <url>https://0x2142.com/logo.jpg</url>
      <link>https://0x2142.com/logo.jpg</link>
    </image>
    <generator>Hugo -- 0.143.1</generator>
    <language>en-us</language>
    <lastBuildDate>Sat, 12 Mar 2022 15:21:35 +0000</lastBuildDate>
    <atom:link href="https://0x2142.com/tags/webex/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>[How to] Webex Chatbot (Part 2): Digging in to Adaptive Cards</title>
      <link>https://0x2142.com/webex-chatbot-with-adaptivecards/</link>
      <pubDate>Sat, 12 Mar 2022 15:21:35 +0000</pubDate>
      <guid>https://0x2142.com/webex-chatbot-with-adaptivecards/</guid>
      <description>In this post, we&amp;rsquo;ll expand upon the previous Webex weather bot &amp;amp; add in AdaptiveCards to improve user interaction.</description>
      <content:encoded><![CDATA[<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/q4LaBvMePTw?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>A few months ago, I had written a blog post on building a simple chatbot with Webex. You can find that <a href="/how-to-building-a-basic-webex-chatbot/">here</a>.</p>
<p>At the time I was already thinking about some follow-up content, where I could walk through how to use AdaptiveCards to make the bot a little fancier - but I decided to wait a bit &amp; see how the first post performed first.</p>
<p>Well, now it&rsquo;s a few months later &amp; I suppose it&rsquo;s about time I write this 😄.</p>
<p>So in this post - We&rsquo;ll build on the <a href="https://github.com/0x2142/example-scripts/tree/master/simple-webex-chatbot">code</a> I used in the last post. We&rsquo;ll  show how to add cards to both display information, as well as take user input.</p>
<hr>
<h2 id="what-are-adaptivecards">What are AdaptiveCards?</h2>
<p><a href="https://adaptivecards.io/">AdaptiveCards</a> are actually not a Webex/Cisco-specific thing! They were actually created by Microsoft with the intention of being a platform-agnostic way of displaying information. So in theory, a card schema written for a Webex bot could be taken &amp; re-used on another messaging platform that implements AdaptiveCards - without any/much rework.</p>
<p>Hopefully this should mean a more consistent user experience for a bot that is supported across multiple platforms. The cards themselves can also enhance the experience by making your bot more dynamic &amp; interactive. Maybe a developer or network engineer is okay with issuing specific syntax-based commands to a bit - but what about someone who is less computer savvy? Using cards makes it easy to provide guided input with textboxes, buttons, and toggles.</p>
<p>AdaptiveCards are built on a standard JSON structure with a pre-defined <a href="https://adaptivecards.io/explorer/">schema</a> of card properties. Let&rsquo;s take a quick look at a simple example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;AdaptiveCard&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;$schema&#34;</span><span class="p">:</span> <span class="s2">&#34;http://adaptivecards.io/schemas/adaptive-card.json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;1.2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;body&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;TextBlock&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;text&#34;</span><span class="p">:</span> <span class="s2">&#34;Hello there! What&#39;s your name?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;wrap&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;Input.Text&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;placeholder&#34;</span><span class="p">:</span> <span class="s2">&#34;Your name here&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;user_name&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;ActionSet&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;actions&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;Action.Submit&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;title&#34;</span><span class="p">:</span> <span class="s2">&#34;Submit&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>So in the JSON payload above, we&rsquo;re creating a card that asks for user input. We have a block of text that asks the user &ldquo;What&rsquo;s your name?&rdquo;, then we provide a text input field, and finally a submit button. Looks pretty straightforward, yeah?</p>
<p>We&rsquo;ll get into the specifics around how to create that card JSON in a bit, but you can always use Microsoft&rsquo;s <a href="https://adaptivecards.io/designer">AdaptiveCard Designer</a> to play around with card elements/structure.</p>
<p>So what would that card look like when rendered in Webex? Let&rsquo;s take a look:</p>
<p><img alt="sample-card" loading="lazy" src="/content/images/2022/02/sample-card.png#center"></p>
<p>That card seems a lot more user-friendly than trying to explain to your users that they need to remember some sytax like <code>/botcommand username set &lt;firstname&gt; &lt;lastname&gt;</code>. Instead, they can just enter the info on the card &amp; click submit.</p>
<h2 id="sending-a-static-default-card-from-a-json-file">Sending a Static, Default Card from a JSON File</h2>
<p>So first we&rsquo;ll start off with the easier scenario. We&rsquo;ll have a pre-defined JSON card built, and use the bot to load &amp; send this card response.</p>
<p>One note before we get started - I&rsquo;ll be building on my simple weather chatbot code that I mentioned earlier. If you&rsquo;re following along, you can pull down that code from <a href="https://github.com/0x2142/example-scripts/tree/master/simple-webex-chatbot">here</a>.</p>
<p>Okay, to start off - we&rsquo;ll be creating a card that allows a user to enter their desired zip code. Instead of having to remember the specific command syntax, a user will instead just issue the <code>weather</code> command &amp; our bot will prompt for information via a card.</p>
<p>I&rsquo;ve used the Microsoft Adaptive Card Designer to build out the basic card structure, which I&rsquo;ve kept simple for now. Here&rsquo;s what that looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;AdaptiveCard&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;$schema&#34;</span><span class="p">:</span> <span class="s2">&#34;http://adaptivecards.io/schemas/adaptive-card.json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;1.2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;body&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;TextBlock&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;text&#34;</span><span class="p">:</span> <span class="s2">&#34;Get Current Weather&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;wrap&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="s2">&#34;Medium&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;weight&#34;</span><span class="p">:</span> <span class="s2">&#34;Bolder&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;TextBlock&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;text&#34;</span><span class="p">:</span> <span class="s2">&#34;Enter a Zip code:&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;wrap&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;Input.Number&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;placeholder&#34;</span><span class="p">:</span> <span class="s2">&#34;00000&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;zip_code&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;min&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;max&#34;</span><span class="p">:</span> <span class="mi">99950</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;ActionSet&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;actions&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;Action.Submit&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;title&#34;</span><span class="p">:</span> <span class="s2">&#34;Get Weather&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;data&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;callback_keyword&#34;</span><span class="p">:</span> <span class="s2">&#34;weather&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This card looks pretty similar to the example I used earlier. We have an initial text block that displays the title &ldquo;Get Current Weather&rdquo;, and we&rsquo;ve added attributes for bold text &amp; a medium size.</p>
<p>Next, we have a normal text block prompting the user to enter their zip code. This is followed by a number input box for that data entry.</p>
<p>The input box has a &ldquo;min&rdquo; &amp; &ldquo;max&rdquo; value for the zip code from 1 to 99950 - which is the full range of US zip codes. We also need to assign any input a unique &ldquo;id&rdquo; value - which we&rsquo;ll see later is used to pull out the user input from the response.</p>
<p>Last, we have our actions - which similar to earlier. This will present as a simple submit button with the text &ldquo;Get Weather&rdquo;. The important note here is adding a <code>data</code> dictionary with a <code>callback_keyword</code> sub-element. We set the <code>callback_keyword</code> to the command we want this submission to be sent to. In our case, we want the input to be sent back to the weather command.</p>
<p>Now to implement. Back in our bot, we&rsquo;ll edit the <code>weather.py</code> file to load the card &amp; assign as the default command response. Let&rsquo;s take a look:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1">### Script snippet</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;./input-card.json&#34;</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">card</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">INPUT_CARD</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">card</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">WeatherByZIP</span><span class="p">(</span><span class="n">Command</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">command_keyword</span><span class="o">=</span><span class="s2">&#34;weather&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">help_message</span><span class="o">=</span><span class="s2">&#34;Get current weather conditions by ZIP code.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">card</span><span class="o">=</span><span class="n">INPUT_CARD</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl"><span class="c1">### Script snippet</span>
</span></span></code></pre></div><p>In the above snippet, I&rsquo;ve extracted just the pieces we&rsquo;ll be editing. For full examples, please see the final example repo <a href="https://github.com/0x2142/example-scripts/tree/master/simple-webex-chatbot-with-cards">here</a>.</p>
<p>I&rsquo;ve saved that JSON payload from earlier as <code>input-card.json</code>. Then we&rsquo;ll open that card file &amp; assign that JSON payload to a variable called <code>INPUT_CARD</code>.</p>
<p>Since we&rsquo;ll expect this card to be the primary method of collecting a zip code - we&rsquo;ll make this card the default response from our bot when the command is triggered. In my original code, we initialized the WeatherByZIP class with <code>card=None</code> - which instead passed our input directly to the <code>execute()</code> function. In this case, we&rsquo;ll set that to <code>card=INPUT_CARD</code> to send our form.</p>
<p>With all that done, let&rsquo;s test it out:</p>
<p><img alt="bot-input" loading="lazy" src="/content/images/2022/03/bot-input.png#center"></p>
<h2 id="collecting--processing-card-submissions">Collecting &amp; Processing Card Submissions</h2>
<p>Now that we have our input card, let&rsquo;s move on to processing the response.</p>
<p>In the original example, we expected the user to enter a command like <code>weather 12345</code> - and it was easy enough to just strip the 5-digit zip code from the response.</p>
<p>Since a card could have many input boxes, we instead receive that data back in a structured JSON format.</p>
<p>If we check out the logging in the webex_bot console, we can see the payload that gets returned to us when the card is submitted:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="mi">2022-03-03</span> <span class="mi">14</span><span class="err">:</span><span class="mi">59</span><span class="err">:</span><span class="mi">26</span>  <span class="p">[</span><span class="err">INFO</span><span class="p">]</span>  <span class="p">[</span><span class="err">webex_websocket_client.root._process_incoming_websocket_message</span><span class="p">]</span><span class="err">:</span><span class="mi">62</span> <span class="err">attachment_actions</span> <span class="err">from</span> <span class="err">message_base_</span><span class="mi">64</span><span class="err">_id:</span> <span class="err">Webex</span> <span class="err">Teams</span> <span class="err">AttachmentAction:</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;--removed--&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;submit&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;messageId&#34;</span><span class="p">:</span> <span class="s2">&#34;--removed--&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;inputs&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;callback_keyword&#34;</span><span class="p">:</span> <span class="s2">&#34;weather&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;zip_code&#34;</span><span class="p">:</span> <span class="s2">&#34;12345&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;personId&#34;</span><span class="p">:</span> <span class="s2">&#34;--removed--&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;roomId&#34;</span><span class="p">:</span> <span class="s2">&#34;--removed--&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;created&#34;</span><span class="p">:</span> <span class="s2">&#34;2022-03-03T19:59:24.820Z&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Using this output, we can see exactly what data is passed back to our bot. Under imports, we do see the correct <code>callback_keyword</code> that we defined in our card JSON. But we also see the user input - which is <code>12345</code>. This is listed under the key <code>zip_code</code> which is the <code>id</code> we assigned to our input field earlier.</p>
<p>Now we can easily dig through this response to pull out the data we need. This JSON payload is delivered to the <code>execute</code> function under the <code>attachment_actions</code> parameter.</p>
<p>So we&rsquo;ll make some minor modifications to our original code to pull that value out.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">,</span> <span class="n">attachment_actions</span><span class="p">,</span> <span class="n">activity</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">zip_code</span> <span class="o">=</span> <span class="n">attachment_actions</span><span class="o">.</span><span class="n">inputs</span><span class="p">[</span><span class="s1">&#39;zip_code&#39;</span><span class="p">]</span>
</span></span></code></pre></div><p>As a reminder, <code>zip_code</code> is the variable we&rsquo;re using inside the <code>execute()</code> function to store the requested zip code - which is then passed to the OpenWeather API.</p>
<p>Previously, we had just assigned the value of <code>message</code> to <code>zip_code</code>, which was the incoming text message from the user.</p>
<p>Instead, this time we&rsquo;ll fetch the zip code from the incoming card response, using <code>attachment_actions.inputs</code> &amp; pulling out the <code>zip_code</code> key.</p>
<p>With that simple change completed, we should be able to ask the bot for the weather, input our zip code, then receive the same text-based output as before.</p>
<p>Here&rsquo;s what that looks like now:</p>
<p><img alt="bot-input-standard-response-1" loading="lazy" src="/content/images/2022/03/bot-input-standard-response-1.png#center"></p>
<p>As we would expect, upon hitting <code>Get Weather</code> - the bot returns the text-based weather report.</p>
<p>Next, we&rsquo;ll take a look at dynamically building a card to display weather info.</p>
<h2 id="dynamically-building-cards">Dynamically Building Cards</h2>
<p>Now we get to the real fun part. We&rsquo;ll need to dynamically build an AdaptiveCard response, depending on what values we want to present to the user.</p>
<p>I will be using a Python module called <a href="https://github.com/ku222/AdaptiveCardBuilder">adaptivecardbuilder</a>. There are a handful of modules available that can help dynamically build cards, but this was the one I found to be most intuitive for me. While I will walk through an example here, I would also recommend reviewing their docs for additional context/examples.</p>
<p>You could also generate a JSON payload manually, or build one using the card designer &amp; try to edit the JSON dynamically to change card values - but I won&rsquo;t be doing that here.</p>
<p>So we&rsquo;ll start off by installing the adaptive card module:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install adaptivecardbuilder
</span></span></code></pre></div><p>Before we build out the full card response, let&rsquo;s take a quick look at how we can use this module.</p>
<p>In our <code>execute()</code> function, we&rsquo;ll create a simple card that shows what zip code was entered.</p>
<p>First, we&rsquo;ll need to add two new imports:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">webex_bot.models.response</span> <span class="kn">import</span> <span class="n">Response</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">adaptivecardbuilder</span> <span class="kn">import</span> <span class="o">*</span>
</span></span></code></pre></div><p>We&rsquo;ll import the <code>Response</code> module from <code>webex_bot</code>, which will be required to send any attachments / custom response to the client. We&rsquo;ll also import our <code>adaptivecardbuilder</code> module.</p>
<p>Next, we&rsquo;ll modify the <code>execute()</code> function to look like the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">,</span> <span class="n">attachment_actions</span><span class="p">,</span> <span class="n">activity</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">zip_code</span> <span class="o">=</span> <span class="n">attachment_actions</span><span class="o">.</span><span class="n">inputs</span><span class="p">[</span><span class="s1">&#39;zip_code&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="n">card</span> <span class="o">=</span> <span class="n">AdaptiveCard</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">TextBlock</span><span class="p">(</span><span class="n">text</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Weather for </span><span class="si">{</span><span class="n">zip_code</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="s2">&#34;Medium&#34;</span><span class="p">,</span> <span class="n">weight</span><span class="o">=</span><span class="s2">&#34;Bolder&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="n">card_data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">card</span><span class="o">.</span><span class="n">to_json</span><span class="p">()))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">card_payload</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;contentType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.microsoft.card.adaptive&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;content&#34;</span><span class="p">:</span> <span class="n">card_data</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="n">Response</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="s2">&#34;Test Card&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span><span class="o">.</span><span class="n">attachments</span> <span class="o">=</span> <span class="n">card_payload</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">response</span>
</span></span></code></pre></div><p>First we create a new <code>AdaptiveCard()</code> instance. With this new object we can add different card elements.</p>
<p>In this case, we add a single card element - a <code>TextBlock</code>. This has multiple attributes, including the actual text content, and we&rsquo;re specifying that we want a medium font size &amp; bold text.</p>
<p>Next we serialize the card payload to JSON using <code>asyncio.run</code>.</p>
<p>In order to prepare the card object to be attached to our response, we will need to set the appropriate headers. In this case we need to wrap the JSON structure with the <code>contentType</code> header. We could use the <code>response.attachments</code> object to send any number of different file types to the user, but in this case we&rsquo;ll set that value to <code>application/vnd.microsoft.card.adaptive</code>.</p>
<p>Then we can assemble the response to the client. We&rsquo;ll create a new instance of the <code>Response()</code> object, where we&rsquo;ll define some necessary attributes.</p>
<p>We&rsquo;ll set our <code>response.text</code> first. This will be the text message sent to the client. If the client supports adaptive cards, then this message won&rsquo;t be shown to the user - however, it may still appear in pop notifications. If the client does not support adaptive cards, only this message will be shown to the user.</p>
<p>Next we attach our complete card payload using <code>response.attachments</code>.</p>
<p>Last but not least, we can <code>return</code> that <code>response</code> object - which is then sent back to the user.</p>
<p><img alt="bot-response-simple-1" loading="lazy" src="/content/images/2022/03/bot-response-simple-1.png#center"></p>
<p>In this case, we can see the bot responds back to our request with a simple card that displays the zip code.</p>
<p>Let&rsquo;s move onto building out the remainder of the card. For the purposes of this example, I&rsquo;ll also be pulling out a few additional items from the OpenWeather API - including sunrise, sunset, pressure, and high/low temperatures.</p>
<p>I&rsquo;ll go ahead and show the full card build here:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">card</span> <span class="o">=</span> <span class="n">AdaptiveCard</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">TextBlock</span><span class="p">(</span><span class="n">text</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Weather for </span><span class="si">{</span><span class="n">city</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="s2">&#34;Medium&#34;</span><span class="p">,</span> <span class="n">weight</span><span class="o">=</span><span class="s2">&#34;Bolder&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">ColumnSet</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">Column</span><span class="p">(</span><span class="n">width</span><span class="o">=</span><span class="s2">&#34;stretch&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">FactSet</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Temp&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">temperature</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Conditions&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">conditions</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Wind&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">wind</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">up_one_level</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">up_one_level</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">Column</span><span class="p">(</span><span class="n">width</span><span class="o">=</span><span class="s2">&#34;stretch&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">FactSet</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;High&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">high_temp</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Low&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">low_temp</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Humidity&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">humidity</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">card_data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">card</span><span class="o">.</span><span class="n">to_json</span><span class="p">()))</span>
</span></span></code></pre></div><p>If this looks a little messy, it is. There is a cleaner way to do this, which I&rsquo;ll show in a moment.</p>
<p>The first thing to know is that the card JSON structure is hierarchical. So individual card items need to be added under the correct hierarchy - for example, you create a <code>ColumnSet</code> first, then add a <code>Column</code> underneath, then the items within that Column last.</p>
<p>See the below visualization of this card:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Top Level
</span></span><span class="line"><span class="cl"> ↳ Text Block
</span></span><span class="line"><span class="cl"> ↳ ColumnSet
</span></span><span class="line"><span class="cl">   ↳ Column
</span></span><span class="line"><span class="cl">     ↳ Fact Set
</span></span><span class="line"><span class="cl">       ↳ Fact
</span></span><span class="line"><span class="cl">       ↳ Fact
</span></span><span class="line"><span class="cl">       ↳ Fact
</span></span><span class="line"><span class="cl">    ↳ Column
</span></span><span class="line"><span class="cl">     ↳ Fact Set
</span></span><span class="line"><span class="cl">       ↳ Fact
</span></span><span class="line"><span class="cl">       ↳ Fact
</span></span><span class="line"><span class="cl">       ↳ Fact
</span></span></code></pre></div><p>So on our card, we first placed the title <code>TextBlock</code>. Underneath that we created a <code>ColumnSet</code>, which will contain two <code>Columns</code>.</p>
<p>Each one of those <code>Columns</code> contains a <code>FactSet</code>, which is a key/value pair listing of information. And of course, we add each <code>Fact</code> pair underneath the <code>FactSet</code>.</p>
<p>You&rsquo;ll notice after building one <code>Column</code> and <code>FactSet</code>, we use <code>card.up_one_level()</code> twice - to return back to the <code>ColumnSet</code> level, where we then add a second <code>Column</code>.</p>
<p>This can certainly get a little confusing at first, but I&rsquo;ll show a more intuitive method in a moment.</p>
<p>Let&rsquo;s take a peak at what this looks like now:</p>
<p><img alt="bot-response-full-1" loading="lazy" src="/content/images/2022/03/bot-response-full-1.png#center"></p>
<h2 id="sub-cards--better-card-building">Sub-cards &amp; Better Card Building</h2>
<p>In this section we&rsquo;ll do two quick things. First we&rsquo;ll take a look at a simpler, more visual way of building the cards. Then we&rsquo;ll also expand our example with sub-cards.</p>
<p>The <code>adaptivecardsbuilder</code> module supports a second way of building cards, which I personally find to be much easier to use.</p>
<p>In this method, we just create a single <code>card.add()</code> object - and feed it a list of all the items on the card.</p>
<p>For me, this makes it easier because you can add indentation to each list item - which helps keep track of where you are at in the hierarchical structure.</p>
<p>To navigate within the structure, we use <code>&quot;&lt;&quot;</code> to go up one level, or <code>&quot;^&quot;</code> to return to the top level.</p>
<p>I&rsquo;ve re-written the earlier example with this new structure below:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">card</span> <span class="o">=</span> <span class="n">AdaptiveCard</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="n">TextBlock</span><span class="p">(</span><span class="n">text</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Weather for </span><span class="si">{</span><span class="n">city</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="s2">&#34;Medium&#34;</span><span class="p">,</span> <span class="n">weight</span><span class="o">=</span><span class="s2">&#34;Bolder&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ColumnSet</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">            <span class="n">Column</span><span class="p">(</span><span class="n">width</span><span class="o">=</span><span class="s2">&#34;stretch&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">FactSet</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Temp&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">temperature</span><span class="si">}</span><span class="s2">F&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Conditions&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">conditions</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Wind&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">wind</span><span class="si">}</span><span class="s2"> mph&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;&lt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;&lt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">Column</span><span class="p">(</span><span class="n">width</span><span class="o">=</span><span class="s2">&#34;stretch&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">FactSet</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                <span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;High&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">high_temp</span><span class="si">}</span><span class="s2">F&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Low&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">low_temp</span><span class="si">}</span><span class="s2">F&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;^&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">ActionSet</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ActionShowCard</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;More Details&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ColumnSet</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">Column</span><span class="p">(</span><span class="n">width</span><span class="o">=</span><span class="s2">&#34;stretch&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">FactSet</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                            <span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Humidity&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">humidity</span><span class="si">}</span><span class="s2">%&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                            <span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Pressure&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">pressure</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                            <span class="s2">&#34;&lt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;&lt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">Column</span><span class="p">(</span><span class="n">width</span><span class="o">=</span><span class="s2">&#34;stretch&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">FactSet</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                            <span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Sunrise&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">sunrise</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                            <span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Sunset&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">sunset</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">card_data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">card</span><span class="o">.</span><span class="n">to_json</span><span class="p">()))</span>
</span></span></code></pre></div><p>Again, you&rsquo;re welcome to use whichever method suits you - but I find this method to be a bit cleaner &amp; easier to read/troubleshoot.</p>
<p>You can also see that I&rsquo;ve added a new <code>ActionSet</code>, with a single action: <code>ActionShowCard</code>. This adds a nice little button to our card where we can optionally display additional information.</p>
<p>Here&rsquo;s what that looks like now:</p>
<p><img alt="bot-response-full-pt2" loading="lazy" src="/content/images/2022/03/bot-response-full-pt2.png#center"></p>
<p>And if we expand that sub-card:</p>
<p><img alt="bot-response-full-pt2-expanded-2" loading="lazy" src="/content/images/2022/03/bot-response-full-pt2-expanded-2.png#center"></p>
<p>Sub-cards can be an easy way to hide excess information to keep the response clean &amp; focus on the important information.</p>
<hr>
<p>Well that&rsquo;s it for this blog post. There&rsquo;s a lot more you can do with cards, including adding images, video, tables, dropdown menus, etc. But I just wanted to show a few examples of building cards. They can be a simple way to make chatbots a little less boring!</p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>[How To] Building a Simple Webex Chatbot with Python Websockets &amp; OpenWeather APIs</title>
      <link>https://0x2142.com/how-to-building-a-basic-webex-chatbot/</link>
      <pubDate>Thu, 18 Nov 2021 14:59:53 +0000</pubDate>
      <guid>https://0x2142.com/how-to-building-a-basic-webex-chatbot/</guid>
      <description>Let&amp;rsquo;s build a simple chatbot using Cisco Webex that can query the weather!</description>
      <content:encoded><![CDATA[<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/yZQjoe5XUYE?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>I&rsquo;ve been spending some of my time lately working on a new project that involves an interactive chatbot - where you can send commands to the bot &amp; ask for it to take certain actions or return information.</p>
<p>I didn&rsquo;t previously have a ton of experience building something like this. Mostly what I&rsquo;ve done before is just using Webex or Discord APIs to send alerts or messages to a room.</p>
<p>As with anything new - I&rsquo;ve stumbled my way through a couple of things, but have been enjoying the challenge of trying something different. Building out some of the more interactive parts of the bot hasn&rsquo;t been quite as difficult as I originally imagined, and some of the newer modules &amp; SDKs have made the process much simpler.</p>
<p>With all that being said - I wanted to throw together a quick guide on how to get started building a basic chatbot, in case anyone else is interested but worried that it may be too complicated.</p>
<blockquote>
<p>Repo <a href="https://github.com/0x2142/example-scripts/tree/master/simple-webex-chatbot">here</a> for all the sample code used below</p></blockquote>
<hr>
<h1 id="step-1-getting-api-keys">Step 1: Getting API Keys</h1>
<p>So the first thing we&rsquo;ll worry about is getting signed up for a Webex developer account &amp; generating API keys.</p>
<p>So head on over to the <a href="https://developer.webex.com/">Webex Developer</a> site &amp; log in (or sign up for a new account).</p>
<p>Once logged in, we&rsquo;ll go to the upper-right corner of the screen and click on our user icon - and select &ldquo;My Webex Apps&rdquo; from the drop down:</p>
<p><img alt="01&mdash;login" loading="lazy" src="/content/images/2021/09/01---login.png#center"></p>
<p>Then we&rsquo;ll be asked what type of app we&rsquo;re creating. In this case, we&rsquo;ll select <strong>bot</strong>:</p>
<p><img alt="02&mdash;appselection" loading="lazy" src="/content/images/2021/09/02---appselection.png#center"></p>
<p>Next we&rsquo;ll need to provide some details about our bot. This includes a display name, username, image, and some details. Once the bot is completed, we have the ability to publish it via the Webex App Hub where anyone could interact with it. If you&rsquo;re looking to do this, make sure you give a good description of your bot!</p>
<p><img alt="03&mdash;botdetails" loading="lazy" src="/content/images/2021/09/03---botdetails.png#center"></p>
<p>After we finish all that, we&rsquo;ll finally get our API key!</p>
<p><img alt="04&mdash;apikey" loading="lazy" src="/content/images/2021/09/04---apikey.png#center"></p>
<h1 id="webhook-vs-websocket-which-to-use">Webhook vs Websocket: Which to use?</h1>
<p>Next, I wanted to cover the two primary methods of sending &amp; receiving messages from the Webex APIs: Webhooks and websockets.</p>
<h3 id="webhooks">Webhooks</h3>
<p>Likely webhooks are the method most people are familiar with. Using this method, we need to run out bot code on a publicly reachable URL. When we run our bot, one of our first actions will be sending a request to the Webex APIs with our bot URL. This way, every time Webex receives a message from a user that wants to interact with our bot, the Webex cloud knows where to send that chat message.</p>
<p>Now, the potential downside of using webhooks is that we need to 1) manage a web server and 2) expose the bot publicly to the internet. Both of these could be overcome with easy workarounds, if needed. For example, we could use something like <a href="https://fastapi.tiangolo.com/">FastAPI</a> to quickly build our web listener &amp; handle incoming POST requests. We could also use a tunneling method (like <a href="https://ngrok.com/">ngrok</a>) to avoid having to directly open ports to our webserver.</p>
<h3 id="websockets">Websockets</h3>
<p>On the other hand, we could use websockets to accomplish the same function. With a websocket, we&rsquo;re instead opening a direct, persistent TCP connection to the Webex APIs. Through this persistent connection, we can send &amp; receive messages very quickly and easily.</p>
<p>This method offers a few advantages over webhooks. Primarily not having to expose our app publicly to the internet. Websockets act almost similar to something like ngrok, where we create an outbound tunnel - except in this case, only Webex knows about it and has access to it (where ngrok is still providing you a public URL that anyone could hit, if they knew about it). Additionally, this method removes the need for maintaining a web server &amp; having to handle all of the incoming HTTP requests in your bot&rsquo;s code. There may also be a handful of API calls which are only supported via websockets today as well - like having your bot show that they&rsquo;ve read a message, for example.</p>
<p>I&rsquo;ve also found that the websocket method seems to be much more responsive, likely because there is a persistent TCP connection open - and you no longer have to rely on a full HTTP session establishment &amp; teardown for every message to the bot.</p>
<p>Historically, websockets were only officially supported via the Webex JavaScript SDK. However, it seems recently that someone has built a Python equivalent earlier this year - which we&rsquo;ll be using throughout this blog post. You can find that package <a href="https://github.com/fbradyirl/webex_bot">here</a></p>
<h1 id="getting-started-registering-the-bot-with-webex">Getting started: Registering the Bot with Webex</h1>
<p>So we&rsquo;ll start with some basic functionality. Let&rsquo;s grab the modules we need, apply our API key, and see if we can get a response from our bot.</p>
<p>Luckily, the module we&rsquo;re using makes this super easy. So let&rsquo;s install via pip:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install webex_bot
</span></span></code></pre></div><p>Once that&rsquo;s installed, we&rsquo;ll create a new Python file - I&rsquo;ll call mine <code>bot.py</code></p>
<p>In that file, we&rsquo;ll start our script like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">webex_bot.webex_bot</span> <span class="kn">import</span> <span class="n">WebexBot</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">api_token</span> <span class="o">=</span> <span class="s2">&#34;&lt;TOKEN_HERE&gt;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">bot</span> <span class="o">=</span> <span class="n">WebexBot</span><span class="p">(</span><span class="n">api_token</span><span class="p">,</span> <span class="n">approved_domains</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;0x2142.com&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">bot</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
</span></span></code></pre></div><p>Looks easy enough, yeah? We&rsquo;ll import our webex_bot module first, as always. Then we&rsquo;ll define our API key. Obviously this can be stored as an environmental variable or another more secure location, but we&rsquo;ll define it here for demonstration.</p>
<p>Then we create a new instance of <code>WebexBot</code>, by providing our API key and an optional <code>approved_domains</code> value. In my case, I&rsquo;ve provided the 0x2142.com domain - which means the bot will reject messages from anyone who isn&rsquo;t from that organization.</p>
<p>Last, we start our bot with <code>bot.run()</code>.</p>
<p>Let&rsquo;s go ahead and run our bot with <code>python bot.py</code> and see what happens!</p>
<p>You should see something similar to this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">matt@0x2142:~/demo-webex-bot$ python bot.py
</span></span><span class="line"><span class="cl">2021-10-25 15:42:52  <span class="o">[</span>INFO<span class="o">]</span>  <span class="o">[</span>webex_bot.webex_bot.webex_bot.__init__<span class="o">]</span>:39 Registering bot with Webex cloud
</span></span><span class="line"><span class="cl">2021-10-25 15:42:52  <span class="o">[</span>INFO<span class="o">]</span>  <span class="o">[</span>restsession.webexteamssdk.restsession.user_agent<span class="o">]</span>:167 User-Agent: webexteamssdk/0+unknown <span class="o">{</span><span class="s2">&#34;implementation&#34;</span>: <span class="o">{</span><span class="s2">&#34;name&#34;</span>: <span class="s2">&#34;CPython&#34;</span>, <span class="s2">&#34;version&#34;</span>: <span class="s2">&#34;3.8.10&#34;</span><span class="o">}</span>, <span class="s2">&#34;system&#34;</span>: <span class="o">{</span><span class="s2">&#34;name&#34;</span>: <span class="s2">&#34;Linux&#34;</span>, <span class="s2">&#34;release&#34;</span>: <span class="s2">&#34;5.10.16.3-microsoft-standard-WSL2&#34;</span><span class="o">}</span>, <span class="s2">&#34;cpu&#34;</span>: <span class="s2">&#34;x86_64&#34;</span><span class="o">}</span>
</span></span><span class="line"><span class="cl">2021-10-25 15:42:52  <span class="o">[</span>WARNING<span class="o">]</span>  <span class="o">[</span>command.webex_bot.models.command.__init__<span class="o">]</span>:33 no card actions data so no entry <span class="k">for</span> <span class="s1">&#39;callback_keyword&#39;</span> <span class="k">for</span> <span class="nb">echo</span>
</span></span><span class="line"><span class="cl">2021-10-25 15:42:52  <span class="o">[</span>INFO<span class="o">]</span>  <span class="o">[</span>command.webex_bot.models.command.set_default_card_callback_keyword<span class="o">]</span>:47 Added default action <span class="k">for</span> <span class="s1">&#39;echo&#39;</span> <span class="nv">callback_keyword</span><span class="o">=</span>callback___echo
</span></span><span class="line"><span class="cl">2021-10-25 15:42:53  <span class="o">[</span>INFO<span class="o">]</span>  <span class="o">[</span>webex_bot.webex_bot.webex_bot.get_me_info<span class="o">]</span>:74 Running as bot <span class="s1">&#39;0x2142 Bot&#39;</span> with email <span class="o">[</span><span class="s1">&#39;0x2142@webex.bot&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">2021-10-25 15:42:54  <span class="o">[</span>INFO<span class="o">]</span>  <span class="o">[</span>webex_websocket_client.root._connect_and_listen<span class="o">]</span>:151 Opening websocket connection to wss://mercury-connection-partition2-a.wbx2.com/v1/apps/wx2/registrations/06b5c858-174a-4977-a268-04530d777563/messages
</span></span><span class="line"><span class="cl">2021-10-25 15:42:54  <span class="o">[</span>INFO<span class="o">]</span>  <span class="o">[</span>webex_websocket_client.root._connect_and_listen<span class="o">]</span>:154 WebSocket Opened.
</span></span></code></pre></div><p>So long as we continue to run this script in the foreground - we&rsquo;ll be able to see the live log of anything that happens, including messages to our bot. So for now, we&rsquo;ll leave this up &amp; running.</p>
<p>Now we should be able to try sending our bot a message in Webex.</p>
<p>In the Webex app, if we do a quick search for our bot (by email or name), we should see it pop up pretty quickly:</p>
<p><img alt="bot" loading="lazy" src="/content/images/2021/10/bot.png#center"></p>
<p>With our new space, we can interact with our bot. I&rsquo;ll send a &ldquo;Hello&rdquo; message:</p>
<p><img alt="bot2" loading="lazy" src="/content/images/2021/10/bot2.png#center"></p>
<p>And the webex_bot module will automatically respond back with a built-in help card.</p>
<p>Currently the bot only supports one command: echo. We&rsquo;ll add our own commands shortly, but for now let&rsquo;s give that a try:</p>
<p><img alt="bot4" loading="lazy" src="/content/images/2021/10/bot4.png#center"></p>
<p>So in the example above, I used the <code>echo</code> command. The bot returns a card with an input field. Once you fill out the textbox &amp; hit submit, the bot will echo back whatever text was provided!</p>
<p>So now we have a running bot. But what about implementing our own commands? Let&rsquo;s take a look!</p>
<h1 id="creating-custom-bot-commands">Creating custom bot commands</h1>
<p><em>Now</em> we get to the fun part!! To demonstrate, let&rsquo;s add a command to ask our bot for the current weather conditions. To accomplish this, we&rsquo;ll use the <a href="https://openweathermap.org/api">OpenWeather</a> APIs.</p>
<p>So to begin - We&rsquo;ll probably want a separate Python file for each command we want to add. First we&rsquo;ll add <code>weather.py</code> to our current directory.</p>
<p>Next, we&rsquo;ll throw in a bit of boilerplate code that will be used for each command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">webex_bot.models.command</span> <span class="kn">import</span> <span class="n">Command</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">logging</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">log</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">WeatherByZIP</span><span class="p">(</span><span class="n">Command</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">command_keyword</span><span class="o">=</span><span class="s2">&#34;weather&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">help_message</span><span class="o">=</span><span class="s2">&#34;Get current weather conditions by ZIP code.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">card</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">,</span> <span class="n">attachment_actions</span><span class="p">,</span> <span class="n">activity</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;Got the message: </span><span class="si">{</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span></code></pre></div><p>So first we&rsquo;ll import <code>Command</code> from our webex_bot module, which will be used to create our custom command class.</p>
<p>Next we create a new class, in this case <code>WeatherByZIP</code>, and inherit the <code>Command</code> class from webex_bot. Within this new class, we&rsquo;ll create our <code>__init__</code> function &amp; define some information about our command.</p>
<p>So using <code>super().__init__()</code>, we&rsquo;ll provide what keyword should trigger this command, any help message to be provided to the user, and an optional card. We saw how the card could work earlier with the <code>echo</code> command - but to keep this module simple, we&rsquo;ll go without a card for now.</p>
<p>After that - we just need to create an <code>execute</code> function. This is what gets called by webex_bot when our command is triggered. By default, webex_bot will pass us the user&rsquo;s message - and, in the case of an action (like a card submission), it will provide those details.</p>
<p>Two things to keep in mind with our <code>execute</code> function:</p>
<ol>
<li>the <code>message</code> variable will contain the user&rsquo;s message with the command keyword removed. So in our case, if the user&rsquo;s message was &ldquo;weather 12345&rdquo; - then the message variable would just contain &ldquo;12345&rdquo;.</li>
<li>Any text we return from our function will be sent via the bot as a chat response. If we wanted to provide extras, like cards or attachments, we would need to use the webex_bot <code>Response</code> object. I might cover this in a separate blog post later</li>
</ol>
<p>Okay! So right now our new command is configured to just echo back any test that is sent to the bot - similar to the <code>echo</code> command earlier, except without the card.</p>
<p>We&rsquo;ll add our weather functionality shortly - but let&rsquo;s first add our command to the bot &amp; give it a quick test.</p>
<p>In our <code>bot.py</code> file, we&rsquo;ll just need to import our command module and call <code>bot.add_command()</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">webex_bot.webex_bot</span> <span class="kn">import</span> <span class="n">WebexBot</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">weather</span> <span class="kn">import</span> <span class="n">WeatherByZIP</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">api_token</span> <span class="o">=</span> <span class="s2">&#34;&lt;TOKEN_HERE&gt;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">bot</span> <span class="o">=</span> <span class="n">WebexBot</span><span class="p">(</span><span class="n">api_token</span><span class="p">,</span> <span class="n">approved_domains</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;0x2142.com&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">bot</span><span class="o">.</span><span class="n">add_command</span><span class="p">(</span><span class="n">WeatherByZIP</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">bot</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
</span></span></code></pre></div><p>That&rsquo;s it! Now we should be able to try it out. We&rsquo;ll restart our bot, and send the command <code>weather 12345</code>:</p>
<p><img alt="bot5" loading="lazy" src="/content/images/2021/10/bot5.png#center"></p>
<p>Sure enough, our bot gets the message, passes it to our custom command, and returns the stripped message - all as expected.</p>
<p>So let&rsquo;s add our weather query &amp; actually make this thing work.</p>
<p>Using the OpenWeather APIs, I&rsquo;ll create a new API key - then create a variable in the command module called <code>OPENWEATHER_KEY</code>. Then, I&rsquo;ve updated my <code>execute</code> function to look like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">,</span> <span class="n">attachment_actions</span><span class="p">,</span> <span class="n">activity</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Message may include spaces. Strip whitespace:</span>
</span></span><span class="line"><span class="cl">    <span class="n">zip_code</span> <span class="o">=</span> <span class="n">message</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Define our URL, with requested ZIP code &amp; API Key</span>
</span></span><span class="line"><span class="cl">    <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;https://api.openweathermap.org/data/2.5/weather?&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">url</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34;zip=</span><span class="si">{</span><span class="n">zip_code</span><span class="si">}</span><span class="s2">&amp;units=imperial&amp;appid=</span><span class="si">{</span><span class="n">OPENWEATHER_KEY</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Query weather</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">weather</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Pull out desired info</span>
</span></span><span class="line"><span class="cl">    <span class="n">city</span> <span class="o">=</span> <span class="n">weather</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">conditions</span> <span class="o">=</span> <span class="n">weather</span><span class="p">[</span><span class="s1">&#39;weather&#39;</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="s1">&#39;description&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">temperature</span> <span class="o">=</span> <span class="n">weather</span><span class="p">[</span><span class="s1">&#39;main&#39;</span><span class="p">][</span><span class="s1">&#39;temp&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">humidity</span> <span class="o">=</span> <span class="n">weather</span><span class="p">[</span><span class="s1">&#39;main&#39;</span><span class="p">][</span><span class="s1">&#39;humidity&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">wind</span> <span class="o">=</span> <span class="n">weather</span><span class="p">[</span><span class="s1">&#39;wind&#39;</span><span class="p">][</span><span class="s1">&#39;speed&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">response_message</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;In </span><span class="si">{</span><span class="n">city</span><span class="si">}</span><span class="s2">, it&#39;s currently </span><span class="si">{</span><span class="n">temperature</span><span class="si">}</span><span class="s2">F with </span><span class="si">{</span><span class="n">conditions</span><span class="si">}</span><span class="s2">. &#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">response_message</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34;Wind speed is </span><span class="si">{</span><span class="n">wind</span><span class="si">}</span><span class="s2">mph. Humidity is </span><span class="si">{</span><span class="n">humidity</span><span class="si">}</span><span class="s2">%&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">response_message</span>
</span></span></code></pre></div><p>So because of the way that <code>message</code> is passed to us, we&rsquo;ll first need to strip whitespace. This is because when the user enters a command like &ldquo;weather 12345&rdquo;, webex_bot will only remove the &ldquo;weather&rdquo; portion - leaving us with &quot; 12345&quot;. So by stripping that value, we&rsquo;re left with just the numeric ZIP Code: &ldquo;12345&rdquo;.</p>
<p>Next we assemble our URL, supplying our parameters: zip, units, and appid. Zip is the numeric ZIP code provided by our user, units will be either imperial or metric, and appid is our API key.</p>
<p>After that, it&rsquo;s as simple as sending the GET request &amp; parsing the data. In the code above, I assemble the final text response into <code>response_message</code> which is then returned to the user.</p>
<p>All that being done - Let&rsquo;s try it out!!</p>
<p><img alt="bot6" loading="lazy" src="/content/images/2021/10/bot6.png#center"></p>
<p>In this example, I used the ZIP code for Richfield, OH - and our bot quickly returned that information to us.</p>
<hr>
<p>And that&rsquo;s it! In just a few steps we created a new Webex bot &amp; added custom commands. Once we have that process down, it&rsquo;s easy enough to continue extending our bot by adding additional commands.</p>
<p>There are obviously more advanced use cases - and a whole lot of <em>fun</em> that comes with using Adaptive Cards&hellip; But I hope this post was helpful in getting you started!</p>
<p><strong>Update 2022/03/12 -</strong> If you&rsquo;re interested in seeing additional content around Cards, please check out the new blog post &amp; video <a href="/webex-chatbot-with-adaptivecards/">here</a></p>
]]></content:encoded>
    </item>
  </channel>
</rss>
