<?xml version="1.0" encoding="utf-8"?><rss version="2.0"
    xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:atom="http://www.w3.org/2005/Atom"
    xmlns:media="http://search.yahoo.com/mrss/"
    xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
    xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
>
<channel>
  <title>Srijan Choudhary, all posts tagged: slack</title>
  <link>https://srijan.ch/feed/all/tag:slack</link>
  <lastBuildDate>Fri, 27 Dec 2024 11:50:00 +0000</lastBuildDate>
  <image>
    <url>https://srijan.ch/assets/favicon/favicon-32x32.png</url>
    <title>Srijan Choudhary, all posts tagged: slack</title>
    <link>https://srijan.ch/feed/all/tag:slack</link>
  </image>
  <sy:updatePeriod>daily</sy:updatePeriod>
  <sy:updateFrequency>1</sy:updateFrequency>
  <generator>Kirby</generator>
  <atom:link href="https://srijan.ch/feed/all.xml/tag:slack" rel="self" type="application/rss+xml" />
  <description>Srijan Choudhary&#039;s Articles and Notes Feed for tag: slack</description>
  <item>
    <title>Capturing slack messages directly into Emacs orgmode inbox</title>
    <description><![CDATA[Learn how to seamlessly capture Slack messages into your Emacs Orgmode (GTD) inbox using a custom browser userscript.]]></description>
    <link>https://srijan.ch/capturing-slack-messages-directly-into-emacs-orgmode-inbox</link>
    <guid isPermaLink="false">tag:srijan.ch:/capturing-slack-messages-directly-into-emacs-orgmode-inbox</guid>
    <category><![CDATA[emacs]]></category>
    <category><![CDATA[orgmode]]></category>
    <category><![CDATA[slack]]></category>
    <category><![CDATA[gtd]]></category>
    <dc:creator>Srijan Choudhary</dc:creator>
    <pubDate>Fri, 27 Dec 2024 11:50:00 +0000</pubDate>
    <media:content url="https://srijan.ch/media/pages/blog/capturing-slack-messages-directly-into-emacs-orgmode-inbox/d0e1ffec00-1735275760/screenshot-2024-12-27-at-12.01.31am.png" medium="image" />
    <content:encoded><![CDATA[<figure data-ratio="auto">
    <img src="https://srijan.ch/media/pages/blog/capturing-slack-messages-directly-into-emacs-orgmode-inbox/d0e1ffec00-1735275760/screenshot-2024-12-27-at-12.01.31am.png" alt="Screenshot of a custom org capture button in Slack message popup">
  
    <figcaption class="text-center">
    Org capture button in Slack message popup  </figcaption>
  </figure>
<p>Ever since switching to orgmode as my GTD system, I've been trying to (slowly) optimize my capture system for things coming to me from different places. One of the things that I was not satisfied with was capturing and processing inputs from Slack.</p>
<p>Slack messages can easily get lost in the fast-paced stream of conversations. Manually copying and pasting messages introduces too much friction, and also removes some context (backlinks), unless a link to the original message is also manually copied. There are ways to save messages in Slack itself, but it just makes it another Inbox to check, and I'm trying to reduce that.</p>
<p>I started by using the saved messages feature to save messages in a single place, and later either shifting them to my org inbox manually, or directly working on them and completing them. Then, I shifted to using the <a href="https://todoist.com/integrations/apps/slack">Slack Todoist integration</a> to save slack messages to Todoist, and <a href="https://srijan.ch/todoist-cloud-inbox-for-gtd-in-emacs-orgmode">pulling them from Todoist into my org Inbox</a>.</p>
<p>I've now found a better mechanism that allows me to seamlessly capture Slack message with it's context into orgmode. Here's a demo:</p><figure>
  <video controls muted preload="auto"><source src="https://srijan.ch/media/pages/blog/capturing-slack-messages-directly-into-emacs-orgmode-inbox/4ee876ac11-1728494126/slack-to-org-demo.webm" type="video/webm"></video>    <figcaption>Demo video showing a custom slack button to trigger org capture with the selected message's contents and a link back to the message</figcaption>
  </figure>
<h2>Demo breakdown</h2>
<p>This method uses userscripts to add a custom button to the hover menu that comes up for any message in slack, and triggers org protocol capture with the message details when the button is clicked. The message details include the sender, message text, and a direct link back to the message. I've set up this protocol handler to ask me to enter the heading of the capured item, but it can be as easily set up to directly capture the message without user input.</p>
<h2>Setup and Implementation</h2>
<h3>Prerequisites</h3>
<ol>
<li>Slack running in a browser (instead of a desktop app)</li>
<li>Browser extention for userscripts (Tampermonkey, Violentmonkey, Greasemonkey, etc)</li>
<li>Emacs with orgmode installed</li>
<li>Org protocol setup</li>
</ol>
<p>I didn't find a good way to inject userscripts into the Slack destop app, so for now, this method requires using Slack in a browser. It also works when Slack is installed using the "Install Page as App" feature of the browser.</p>
<p>Update: I got a comment that using this in the Slack app is also possible, though I've not evaluated this yet. You can <a href="#komment_6503f91cf9943cf408e47743f863bc12">check this comment for details</a>.</p>
<h3>Setting up Org Protocol Capture</h3>
<p>Setting up org protocol capture involves two steps: configuring your OS to use Emacs to open <code>org-protocol://</code> links, and configuring Emacs to save the captured data as you want.</p>
<p>For OS setup, please check the guides for your OS here: <a href="https://orgmode.org/worg/org-contrib/org-protocol.html#orge00964c">https://orgmode.org/worg/org-contrib/org-protocol.html#orge00964c</a></p>
<p>On Emacs side, this is the minimal config required:</p>
<pre><code class="language-emacs-lisp">(server-start)
(setq-default org-agenda-files '("~/org"))
(setq-default my-org-inbox
    (expand-file-name "inbox.org" "~/org"))
(setq-default org-capture-templates
      '(    
    ("i" "Inbox" entry (file my-org-inbox)
         "* %?\n%i\n%U"
         :kill-buffer t)
    ("l" "Inbox with link" entry (file my-org-inbox)
         "* %?\n%i\n%a\n%U"
         :kill-buffer t)))
(setq-default org-protocol-default-template-key "l")
(require 'org-protocol)</code></pre>
<p>An optional enhancement that can be seen in the demo is: open a new emacs frame to capture this message, then close it automatically after the capture has been done. For this, I use the config snippet from prot's excellent post: <a href="https://protesilaos.com/codelog/2024-09-19-emacs-command-popup-frame-emacsclient/">https://protesilaos.com/codelog/2024-09-19-emacs-command-popup-frame-emacsclient/</a></p>
<p>To run emacsclient with the popup frame parameter, I use:</p>
<pre><code class="language-shell-session">emacsclient --create-frame -F \
    '((prot-window-popup-frame . t) (name . "org-protocol-capture") (width . 80))' \
    -- %u</code></pre>
<p>Emacsclient's args can be set in the desktop entry file (linux) or org-protocol.app file (OSX) when setting up org-protocol.</p>
<h3>The userscript</h3>
<p>For the userscript, I searched for an existing published userscript for Slack that <a href="https://greasyfork.org/en/scripts/500127-slack-quick-edit-button">adds a button</a>, then tweaked it a bit to configure the button according to my needs.</p>
<p>I've published the userscript here: <a href="https://greasyfork.org/en/scripts/521908-slack-org-protocol-capture">https://greasyfork.org/en/scripts/521908-slack-org-protocol-capture</a></p>
<p>From this, I learned about an interesting API called <a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver">MutationObserver</a> that seems very useful for userscripts.</p>
<h2>Notes</h2>
<h3>Using emacs-slack</h3>
<p>Another simple approach for this can be to use the <a href="https://github.com/emacs-slack/emacs-slack">emacs-slack</a> package to directly use Slack from Emacs. I tried this, and it does not work very well for me because:</p>
<ol>
<li>My org limits Slack session authentication to 1 week, and authenticating in emacs-slack is a little cumbersome.</li>
<li>We also use Slack huddles, and it does not work well with emacs-slack.</li>
</ol>
<h3>Possible Improvements</h3>
<ol>
<li>Make org capture template configurable</li>
<li>Add tooltip to the button</li>
<li>Pass even more metadata like message timestamp, channel name, emojis, etc.</li>
<li>Maybe some keyboard shortcuts to trigger the capture?</li>
</ol>
<h3>Limitations / Drawbacks</h3>
<p>Since this is dependent on the HTML structure of the slack web app, it can be fragile and can break easily if there are changes in the app.</p>
<p>This userscript uses the MutationObserver API to observe DOM changes in the whole slack workspace. So, it has some impact on performance. However, I've been using this daily for the last several months and I have not noticed any issues.</p>]]></content:encoded>
    <comments>https://srijan.ch/capturing-slack-messages-directly-into-emacs-orgmode-inbox#comments</comments>
    <slash:comments>20</slash:comments>
  </item><item>
    <title>Slackbot using google cloud serverless functions</title>
    <description><![CDATA[Slack bot using Google Cloud Functions to post a roundup of recently created channels]]></description>
    <link>https://srijan.ch/slackbot-google-cloud-part-1</link>
    <guid isPermaLink="false">634164f0219ca50001581813</guid>
    <category><![CDATA[development]]></category>
    <category><![CDATA[cloud]]></category>
    <category><![CDATA[slack]]></category>
    <dc:creator>Srijan Choudhary</dc:creator>
    <pubDate>Fri, 04 Nov 2022 19:15:00 +0000</pubDate>
    <media:content url="https://srijan.ch/media/pages/blog/slackbot-google-cloud-part-1/a7a4e31f92-1699621096/screenshot_20221009_161507.png" medium="image" />
    <content:encoded><![CDATA[<p>At my org, we wanted a simple Slack bot that posts a roundup 
of new channels created recently in the workspace to a channel. While 
writing this is easy enough, I wanted to do it using <a href="https://cloud.google.com/functions" rel="noreferrer">Google Cloud Functions</a> with Python, trying to follow best practices as much as possible.</p> <p>Here's how the overall flow will look like:</p><figure data-ratio="auto">
    <img src="https://srijan.ch/media/pages/blog/slackbot-google-cloud-part-1/1fc9e55238-1699621096/slackbot01.5.excalidraw.png" alt="">
  
    <figcaption class="text-center">
    Google Cloud Functions Slack Bot  </figcaption>
  </figure>
<p>We want this roundup post triggered on some schedule (maybe daily), so the <a href="https://cloud.google.com/scheduler" rel="noreferrer">Cloud Scheduler</a> is required to send an event to a <a href="https://cloud.google.com/pubsub" rel="noreferrer">Google Pub/Sub</a>
 topic that triggers our cloud function, which queries the slack API to 
get channels details, filter recently created, and post it back to a 
slack channel. <a href="https://cloud.google.com/secret-manager" rel="noreferrer">Secret Manager</a> is used to securely store slack's bot token and signing secret.</p> <p>Note that the credentials shown in any screenshots below are not valid.</p><h2>Create the slack app</h2>
<p>The
 first step will be to create the slack app. Go to https://api.slack.com
 and click on "Create an app". Choose "From scratch" in the first 
dialog; enter an app name and choose a workspace for your app in the 
second dialog. In the next screen, copy the "<strong>Signing Secret</strong>" from the "App Credentials" section and save it for later use.</p><figure data-ratio="auto">
  <ul>
        <li>
      <img alt="" src="https://srijan.ch/media/pages/blog/slackbot-google-cloud-part-1/a7a4e31f92-1699621096/screenshot_20221009_161507.png">    </li>
        <li>
      <img alt="" src="https://srijan.ch/media/pages/blog/slackbot-google-cloud-part-1/d075d74c25-1699621096/screenshot_20221009_161622-1.png">    </li>
        <li>
      <img alt="" src="https://srijan.ch/media/pages/blog/slackbot-google-cloud-part-1/e0500cfbdf-1699621096/screenshot_20221009_162905.png">    </li>
      </ul>
  </figure>
<p>Next,
 go to the "OAuth and Permissions" tab from the left sidebar, and scroll
 down to "Scopes" -&gt; "Bot Token Scopes". Here, add the scopes:</p><ul><li><a href="https://api.slack.com/scopes/channels:read" rel="noopener noreferrer"><code>channels:read</code></a>: required to query public channels and find their creation times</li><li><a href="https://api.slack.com/scopes/chat:write" rel="noopener noreferrer"><code>chat:write</code></a>: required to write to a channel (where the bot is invited)</li></ul><figure data-ratio="auto">
    <img src="https://srijan.ch/media/pages/blog/slackbot-google-cloud-part-1/0cf73d1181-1699621096/screenshot_20221009_162243.png" alt="">
  
  </figure>
<p>Next,
 scroll up on the same screen and click "Install to Workspace" to 
install to your workspace. Click "Allow" in the next screen to allow the
 installation. Next, copy the "<strong>Bot User OAuth Token</strong>" from the "OAuth Tokens for Your Workspace" section on the same page and save it for later use.</p><figure data-ratio="auto">
  <ul>
        <li>
      <img alt="" src="https://srijan.ch/media/pages/blog/slackbot-google-cloud-part-1/a79785c4f6-1699621096/screenshot_20221009_162420.png">    </li>
        <li>
      <img alt="" src="https://srijan.ch/media/pages/blog/slackbot-google-cloud-part-1/d075d74c25-1699621096/screenshot_20221009_161622-1.png">    </li>
        <li>
      <img alt="" src="https://srijan.ch/media/pages/blog/slackbot-google-cloud-part-1/f0e359ac2e-1699621096/screenshot_20221009_163221.png">    </li>
      </ul>
  </figure>
<p>💡Keep track of the <strong>Bot User OAuth Token</strong> and <strong>Signing Secret</strong> you copied above.</p><h2>Post to a Slack channel from a Google Cloud Function</h2>
<p>Next, we will try to use the credentials copied above to enable a Google Cloud Function to send a message to a Slack channel.</p><h3>Google Cloud Basic Setup</h3>
<p>We will use gcloud cli for the following sections, so <a href="https://cloud.google.com/sdk/docs/install" rel="noreferrer">install</a> and <a href="https://cloud.google.com/sdk/docs/initializing" rel="noreferrer">initialize</a> the Google Cloud CLI if not done yet. If you already have gcloud cli, run <code>gcloud components update</code> to update it to the latest version.</p> <p>Create
 a new project for this if required, or choose an existing project, set 
it as default, and export the project id as a shell environment for 
using later. Also export the region you want to use.</p><figure>
  <pre><code class="language-shell">export PROJECT_ID=slackbot-project
export REGION=us-central1

gcloud config set project ${PROJECT_ID}</code></pre>
  </figure>
<p>You will have to enable billing for this project to be able to use some of the functionality we require.</p> <p>You
 may also have to enable the Secret Manager, Cloud Functions, Cloud 
Build, Artifact Registry, and Logging APIs if this is the first time 
you're using Functions in this project. Note that some services like 
Secret Manager need billing to be setup before they can be enabled.</p><figure>
  <pre><code class="language-shell">gcloud services enable --project slackbot-project \
        secretmanager.googleapis.com \
        cloudfunctions.googleapis.com \
        cloudbuild.googleapis.com \
        artifactregistry.googleapis.com \
        logging.googleapis.com</code></pre>
  </figure>
<h3>Create a service account</h3>
<p>By default, Cloud Functions uses a <a href="https://cloud.google.com/functions/docs/securing/function-identity#runtime_service_account" rel="noreferrer">default service account</a> as its identity for function execution. These default service accounts have the <strong>Editor</strong>
 role, which allows them broad access to many Google Cloud services. Of 
course, this is not recommended for production, so we will create a new 
service account for this and <a href="https://cloud.google.com/iam/docs/understanding-service-accounts#granting_minimum" rel="noreferrer">grant it the minimum permissions</a> that it requires.</p><figure>
  <pre><code class="language-shell">SA_NAME=channelbot-sa
SA_EMAIL=${SA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com

gcloud iam service-accounts create ${SA_NAME} \
    --description=&quot;Service Account for ChannelBot slackbot&quot; \
    --display-name=&quot;ChannelBot SlackBot SA&quot;</code></pre>
  </figure>
<h3>Store secrets and give permissions to service account</h3>
<p>First, we need to store the secrets in Secret Manager.</p><figure>
  <pre><code class="language-shell">printf $SLACK_BOT_TOKEN | gcloud secrets create \
    channelbot-slack-bot-token --data-file=- \
    --project=${PROJECT_ID} \
    --replication-policy=user-managed \
    --locations=${REGION}

printf $SLACK_SIGNING_SECRET | gcloud secrets create \
    channelbot-slack-signing-secret --data-file=- \
    --project=${PROJECT_ID} \
    --replication-policy=user-managed \
    --locations=${REGION}</code></pre>
  </figure>
<p>And give our service account the <code><a href="https://cloud.google.com/secret-manager/docs/access-control#secretmanager.secretAccessor" rel="noreferrer">roles/secretmanager.secretAccessor</a></code> role on these secrets.</p><figure>
  <pre><code class="language-shell">gcloud secrets add-iam-policy-binding \
    projects/${PROJECT_ID}/secrets/channelbot-slack-bot-token \
    --member serviceAccount:${SA_EMAIL} \
    --role roles/secretmanager.secretAccessor

gcloud secrets add-iam-policy-binding \
    projects/${PROJECT_ID}/secrets/channelbot-slack-signing-secret \
    --member serviceAccount:${SA_EMAIL} \
    --role roles/secretmanager.secretAccessor</code></pre>
  </figure>
<h3>Create and deploy the function</h3>
<p>Here's a simple HTTP function that sends a message to slack on any HTTP call:</p><figure>
  <pre><code class="language-python">import functions_framework
from slack_bolt import App

# process_before_response must be True when running on FaaS
app = App(process_before_response=True)

print(&#039;Function has started&#039;)

@functions_framework.http
def send_to_slack(request):
    print(&#039;send_to_slack triggered&#039;)
    channel = &#039;#general&#039;
    text = &#039;Hello from Google Cloud Functions!&#039;
    app.client.chat_postMessage(channel=channel, text=text)
    return &#039;Sent to slack!&#039;</code></pre>
    <figcaption class="text-center">src-v1/main.py</figcaption>
  </figure>
<figure>
  <pre><code class="language-text">functions-framework
slack_bolt</code></pre>
    <figcaption class="text-center">src-v1/requirements.txt</figcaption>
  </figure>
<p>Assuming <code>main.py</code> and <code>requirements.txt</code> are present in <code>src-v1</code> folder, deploy using:</p><figure>
  <pre><code class="language-shell">gcloud beta functions deploy channelbot-send-to-slack \
    --gen2 \
    --runtime python310 \
    --project=${PROJECT_ID} \
    --service-account=${SA_EMAIL} \
    --source ./src-v1 \
    --entry-point send_to_slack \
    --trigger-http \
    --allow-unauthenticated \
    --region ${REGION} \
    --memory=128MiB \
    --min-instances=0 \
    --max-instances=1 \
    --set-secrets &#039;SLACK_BOT_TOKEN=channelbot-slack-bot-token:latest,SLACK_SIGNING_SECRET=channelbot-slack-signing-secret:latest&#039; \
    --timeout 60s</code></pre>
  </figure>
<p>💡We're using <code>--allow-unauthenticated</code> here just to test it out. It will be removed in later sections.</p><h3>Test it out</h3>
<p>Once the deployment is complete, we can view the function logs using:</p><figure>
  <pre><code class="language-shell">gcloud beta functions logs read channelbot-send-to-slack \
	--project ${PROJECT_ID} --gen2</code></pre>
  </figure>
<p>If everything was successful above, once of the recent log statements should say: <code>Function has started</code>.</p> <p>Next, add the bot to the <code>#general</code> channel using <code>/invite @ChannelBot</code> in the general channel on your slack workspace.</p> <p>Next, find the service endpoint using:</p><figure>
  <pre><code class="language-shell">gcloud functions describe channelbot-send-to-slack \
    --project ${PROJECT_ID} \
    --gen2 \
    --region ${REGION} \
    --format &quot;value(serviceConfig.uri)&quot;</code></pre>
  </figure>
<p>This will give a URL like <code>https://channelbot-send-to-slack-ga6Ofi9to0-uc.a.run.app</code>.</p> <p>To trigger the channel post, just do <code>curl ${SERVICE_URL}</code>. This should result in a test message from ChannelBot to the <code>#general</code> channel.</p><figure data-ratio="auto">
    <img src="https://srijan.ch/media/pages/blog/slackbot-google-cloud-part-1/27e2d0931e-1699621096/screenshot_20221018_235104.png" alt="">
  
    <figcaption class="text-center">
    ChannelBot message from Google Cloud Functions  </figcaption>
  </figure>
<h2>Trigger via Google Pub/Sub</h2>
<p>Now,
 instead of an unauthenticated HTTP trigger, we would like to trigger 
this via Google Pub/Sub. We would also like to pass the channel name and
 the message to post in the event.</p><h3>Google Pub/Sub basics</h3>
<p>Pub/Sub enables you to create systems of event producers and consumers, called <strong><strong>publishers</strong></strong> and <strong><strong>subscribers</strong></strong>. Publishers communicate with subscribers asynchronously by broadcasting events. Some core concepts:</p><ul><li><strong><strong>Topic.</strong></strong> A named resource to which messages are sent by publishers.</li><li><strong><strong>Subscription.</strong></strong>
 A named resource representing the stream of messages from a single, 
specific topic, to be delivered to the subscribing application.</li><li><strong><strong>Message.</strong></strong> The combination of data and (optional) attributes that a publisher sends to a topic and is eventually delivered to subscribers.</li><li><strong><strong>Publisher.</strong></strong> An application that creates and sends messages to a single or multiple topics.</li></ul><p>In
 this section, we will create a topic, create a subscription for our 
cloud function to listen to messages to that topic, and produce messages
 manually to that topic using <code>gcloud</code> cli. The message will 
contain the channel name and message to post, and the cloud function 
will post that message to the specified slack channel.</p><h3>Create pub/sub topic</h3>
<p>First, we need to create a topic.</p><figure>
  <pre><code class="language-shell">export PUBSUB_TOPIC=channelbot-pubsub
gcloud pubsub topics create ${PUBSUB_TOPIC} \
    --project ${PROJECT_ID}</code></pre>
  </figure>
<h3>Grant permissions to the service account</h3>
<p>Next, we need to give the <code>roles/pubsub.editor</code> role to the service account we're using for the function execution so that it can create a subscription to this pub/sub topic.</p><figure>
  <pre><code class="language-shell">gcloud pubsub topics add-iam-policy-binding ${PUBSUB_TOPIC} \
    --project ${PROJECT_ID} \
    --member serviceAccount:${SA_EMAIL} \
    --role roles/pubsub.editor</code></pre>
  </figure>
<h3>Update the function code</h3>
<p>Here's the <code>main.py</code> we'll need to listen to pub/sub events, extract <code>channel</code> and <code>text</code>, and sent it to slack:</p><figure>
  <pre><code class="language-python">import base64
import json
import functions_framework
from slack_bolt import App

# process_before_response must be True when running on FaaS
app = App(process_before_response=True)

print(&#039;Function has started&#039;)

# Triggered from a message on a Cloud Pub/Sub topic.
@functions_framework.cloud_event
def pubsub_handler(cloud_event):
    try:
        data = base64.b64decode(
            cloud_event.data[&quot;message&quot;][&quot;data&quot;]).decode()
        print(&quot;Received from pub/sub: %s&quot; % data)
        event_data = json.loads(data)
        channel = event_data[&quot;channel&quot;]
        text = event_data[&quot;text&quot;]
        app.client.chat_postMessage(channel=channel, text=text)
    except Exception as E:
        print(&quot;Error decoding message: %s&quot; % E)</code></pre>
    <figcaption class="text-center">src-v2/main.py</figcaption>
  </figure>
<p>Before deploying, we also need to enable the Eventarc API in this project.</p><figure>
  <pre><code class="language-shell">gcloud services enable --project ${PROJECT_ID} \
    eventarc.googleapis.com</code></pre>
  </figure>
<h3>Deploy and Test</h3>
<p>Now, there's a slightly modified version of the deploy command to deploy this:</p><figure>
  <pre><code class="language-shell">gcloud beta functions deploy channelbot-send-to-slack \
    --gen2 \
    --runtime python310 \
    --project ${PROJECT_ID} \
    --service-account ${SA_EMAIL} \
    --source ./src-v2 \
    --entry-point pubsub_handler \
    --trigger-topic ${PUBSUB_TOPIC} \
    --region ${REGION} \
    --memory 128MiB \
    --min-instances 0 \
    --max-instances 1 \
    --set-secrets &#039;SLACK_BOT_TOKEN=channelbot-slack-bot-token:latest,SLACK_SIGNING_SECRET=channelbot-slack-signing-secret:latest&#039; \
    --timeout 60s</code></pre>
  </figure>
<p>The main changes are:</p><ul><li>Changed entry-point to the new function <code>pubsub_handler</code></li><li>Replaced <code>--trigger-http</code> with <code>--trigger-topic</code></li><li>Removed <code>--allow-unauthenticated</code></li></ul><p>Before sending a pub/sub message, we will also need to give the <code>roles/run.invoker</code> role to our service account to be able to trigger our newly deployed function.</p><figure>
  <pre><code class="language-shell">gcloud run services add-iam-policy-binding channelbot-send-to-slack \
    --project ${PROJECT_ID} \
    --region ${REGION} \
    --member=serviceAccount:${SA_EMAIL} \
    --role=roles/run.invoker</code></pre>
  </figure>
<p>To test this out, we can send a pub/sub message using gcloud cli:</p><figure>
  <pre><code class="language-shell">gcloud pubsub topics publish ${PUBSUB_TOPIC} \
    --project ${PROJECT_ID} \
    --message &#039;{&quot;channel&quot;: &quot;#general&quot;, &quot;text&quot;: &quot;Hello from Cloud Pub/Sub!&quot;}&#039;</code></pre>
  </figure>
<figure data-ratio="auto">
    <img src="https://srijan.ch/media/pages/blog/slackbot-google-cloud-part-1/f184b7795b-1699621096/screenshot_20221104_232055.png" alt="">
  
    <figcaption class="text-center">
    ChannelBot message via pub/sub  </figcaption>
  </figure>
<h2>Post new channels roundup using cloud scheduler</h2>
<h3>Manually post recently created channels</h3>
<p>Now
 that we have gained the capability to trigger a message from pub/sub to
 slack, we can add some logic to fetch the recently created channels 
from slack and post it as a message on this trigger.</p> <p>Here's the modified <code>main.py</code> to do this:</p><figure>
  <pre><code class="language-python">import base64
import json
import time
import functions_framework
from slack_bolt import App

# process_before_response must be True when running on FaaS
app = App(process_before_response=True)

print(&#039;Function has started&#039;)

# Triggered from a message on a Cloud Pub/Sub topic.
@functions_framework.cloud_event
def pubsub_handler(cloud_event):
    try:
        data = base64.b64decode(
            cloud_event.data[&quot;message&quot;][&quot;data&quot;]).decode()
        print(&quot;Received from pub/sub: %s&quot; % data)
        event_data = json.loads(data)
        max_days = event_data[&quot;max_days&quot;] # Max age of channels
        channel = event_data[&quot;channel&quot;]
        recent_channels = get_recent_channels(app, max_days)
        if len(recent_channels) &gt; 0:
            blocks, text = format_channels(recent_channels, max_days)
            app.client.chat_postMessage(channel=channel, text=text,
                                        blocks=blocks)
        else:
            print(&quot;No recent channels&quot;)
    except Exception as E:
        print(&quot;Error decoding message: %s&quot; % E)


def get_recent_channels(app, max_days):
    max_age_s = max_days * 24 * 60 * 60
    result = app.client.conversations_list()
    all = result[&quot;channels&quot;]
    now = time.time()
    return [ c for c in all if (now - c[&quot;created&quot;] &lt;= max_age_s) ]

def format_channels(channels, max_days):
    text = (&quot;%s channels created in the last %s day(s):&quot; %
            (len(channels), max_days))
    blocks = [{
        &quot;type&quot;: &quot;header&quot;,
        &quot;text&quot;: {
            &quot;type&quot;: &quot;plain_text&quot;,
            &quot;text&quot;: text
        }
    }]
    summary = &quot;&quot;
    for c in channels:
        summary += &quot;\n*&lt;#%s&gt;*: %s&quot; % (c[&quot;id&quot;], c[&quot;purpose&quot;][&quot;value&quot;])
    blocks.append({
        &quot;type&quot;: &quot;section&quot;,
        &quot;text&quot;: {
            &quot;type&quot;: &quot;mrkdwn&quot;,
            &quot;text&quot;: summary
        }
    })
    return blocks, text</code></pre>
    <figcaption class="text-center">src-v3/main.py</figcaption>
  </figure>
<p>After deploying this with the same command above (just change <code>--source ./src-v2</code> to <code>--source ./src-v3</code>), we can send a pub/sub event to trigger it:</p><figure>
  <pre><code class="language-shell">gcloud pubsub topics publish ${PUBSUB_TOPIC} \
    --project ${PROJECT_ID} \
    --message &#039;{&quot;channel&quot;: &quot;#general&quot;, &quot;max_days&quot;: 7}&#039;</code></pre>
  </figure>
<p>And it posts a message like this:</p><figure data-ratio="auto">
    <img src="https://srijan.ch/media/pages/blog/slackbot-google-cloud-part-1/7e9ecc0ef4-1699621096/screenshot_20221104_235500.png" alt="">
  
    <figcaption class="text-center">
    Recently created channels posted by ChannelBot  </figcaption>
  </figure>
<h3>Create schedule</h3>
<p>Next,
 we want to periodically schedule this message. For this, we will 
configure a cron job in Google Cloud Scheduler to send a Pub/Sub event 
with the required parameters periodically.</p> <p>Before we create a schedule, we will have to enable the Cloud Scheduler API</p><figure>
  <pre><code class="language-shell">gcloud services enable --project ${PROJECT_ID} \
    cloudscheduler.googleapis.com</code></pre>
  </figure>
<p>To schedule the Pub/Sub trigger at 1600 hours UTC time every day:</p><figure>
  <pre><code class="language-shell">gcloud scheduler jobs create pubsub channelbot-job \
    --project ${PROJECT_ID} \
    --location ${REGION} \
    --schedule &quot;0 16 * * *&quot; \
    --time-zone &quot;UTC&quot; \
    --topic ${PUBSUB_TOPIC} \
    --message-body &#039;{&quot;channel&quot;: &quot;#general&quot;, &quot;max_days&quot;: 1}&#039;</code></pre>
  </figure>
<p>After this, a Pub/Sub event should be fired to the <code>channelbot-pubsub</code> topic every day, which should result in a slack message to <code>#general</code> with a list of channels created in the last day.</p><h2>Closing Thoughts</h2>
<p>Full code samples for this can be found in <a href="https://github.com/srijan/gcloud_slackbot" rel="noreferrer">this github repo</a>. I've also included a <code>Makefile</code> with targets split into sections associated with the different steps in this post.</p> <p>I
 also plan to follow this up with a part 2 where we will use slack's 
slash commands to allow the end-user of this bot to setup the channel 
and frequency of posting of the recent channels list, and even configure
 multiple schedules. Please comment below if this is something you will 
be interested in.</p>]]></content:encoded>
    <comments>https://srijan.ch/slackbot-google-cloud-part-1#comments</comments>
    <slash:comments>0</slash:comments>
  </item></channel>
</rss>
