<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hugo Gresse</title>
    <description>The latest articles on DEV Community by Hugo Gresse (@hugogresse).</description>
    <link>https://dev.to/hugogresse</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4006%2F9a3eb769-1203-4fce-956e-f28860f4d0ae.jpg</url>
      <title>DEV Community: Hugo Gresse</title>
      <link>https://dev.to/hugogresse</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hugogresse"/>
    <language>en</language>
    <item>
      <title>OpenFeedback.io: open source talk &amp; conference feedbacks SaaS</title>
      <dc:creator>Hugo Gresse</dc:creator>
      <pubDate>Mon, 23 Nov 2020 14:28:29 +0000</pubDate>
      <link>https://dev.to/hugogresse/openfeedback-io-open-source-talk-conference-feedbacks-saas-2n16</link>
      <guid>https://dev.to/hugogresse/openfeedback-io-open-source-talk-conference-feedbacks-saas-2n16</guid>
      <description>&lt;p&gt;Meetings and conferences often provide session feedback systems that have proven their benefits for both speakers and organizers. Among all the possible feedback tools, the “sticker board” had become a popular choice, because it is a easy and efficient method where attendees just stick their vote in a predefined box (Figure 1).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4jymbo5ow9n3w5bcnb46.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4jymbo5ow9n3w5bcnb46.jpg" alt="Old speaker feedback sheet"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;(Figure 1) Old speaker board where colored stickers were applied by attendees&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The world is facing a long-term pandemic and most meetings and conferences take place online where such feedback system is not possible. Hence an online vote &amp;amp; feedback service has become a relevant solution to fill the gap: &lt;a href="https://github.com/HugoGresse/open-feedback" rel="noopener noreferrer"&gt;OpenFeedback.io&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Basically, the idea was simple: keeping the previous feedback system, by transposing it into a user-friendly online service. The concept can be explained in 3 steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;QR Codes are provided to attendees (either by speakers or organizers) at the end of the talk or during the Q &amp;amp; A.&lt;/li&gt;
&lt;li&gt;Attendees scan the QR code, and land in the online sticker board.&lt;/li&gt;
&lt;li&gt;Attendees click the pre-defined choices they agree with (represented by a box) and a colored sticker is added to their vote. Optionally, they can share additional insights in a comment section.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Note: you can also use the feedback url in place of QR Codes.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  DEMO
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjfqzx8f8svvwn3nbecih.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjfqzx8f8svvwn3nbecih.gif" alt="Open Feedback demo"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;(Figure 2) Feedback demonstration&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://openfeedback.io/eaJnyMXD3oNfhrrnBYDT/2019-06-27/30" rel="noopener noreferrer"&gt;Online demo here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The central idea was to create a fast and painless voting system, which means no login and no account required, attendees just click &amp;amp; vote. They can also navigate between talks and add feedbacks hours or days after talks are finished.&lt;/p&gt;

&lt;p&gt;To date, the service has been used by &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.bdxio.fr/" rel="noopener noreferrer"&gt;BDX I/O&lt;/a&gt;, &lt;a href="https://2019.sunny-tech.io/" rel="noopener noreferrer"&gt;Sunny Tech&lt;/a&gt;, &lt;a href="https://cloudnord.fr/" rel="noopener noreferrer"&gt;CloudNord&lt;/a&gt; remote or physical conferences, each having 200+ voters and 1000-1500+ votes,
&lt;/li&gt;
&lt;li&gt;Several french meetups such as &lt;a href="https://www.twitch.tv/frenchandroidusergroup" rel="noopener noreferrer"&gt;French Android User Group&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Medium-sized companies using it for their internal talks, one of them also put OpenFeedback in its company rules. /o/&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And it's &lt;a href="https://github.com/HugoGresse/open-feedback" rel="noopener noreferrer"&gt;Open Source on GitHub.com&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Admin: a real SAAS
&lt;/h1&gt;

&lt;p&gt;Open Feedback is built as a &lt;a href="https://en.wikipedia.org/wiki/Software_as_a_service" rel="noopener noreferrer"&gt;SaaS&lt;/a&gt;. It is composed of 3 applications: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Voting app (described above) &lt;/li&gt;
&lt;li&gt;&lt;a href="https://openfeedback.io" rel="noopener noreferrer"&gt;Landing / marketing website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://openfeedback.io/admin" rel="noopener noreferrer"&gt;Admin/console&lt;/a&gt; website to manage events. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://openfeedback.io/admin" rel="noopener noreferrer"&gt;admin&lt;/a&gt; provides a user friendly interface for organizers whishing to create and manage their own events on the OpenFeedback service. It includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating events and customizing the logos/colors.&lt;/li&gt;
&lt;li&gt;Managing speakers and sessions.&lt;/li&gt;
&lt;li&gt;Invitation of user(s) to co-manage an event.&lt;/li&gt;
&lt;li&gt;Editing the feedback form, translating it in any languages for cross languages events.&lt;/li&gt;
&lt;li&gt;Moderating comment's section.&lt;/li&gt;
&lt;li&gt;Speakers and sessions can also be loaded from an external data source.&lt;/li&gt;
&lt;li&gt;Votings statistics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyu0sz20qytshpzts906v.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyu0sz20qytshpzts906v.gif" alt="Open Feedback admin demo"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;(Figure 3) demonstration of the administration user interface&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How is it made?
&lt;/h2&gt;

&lt;p&gt;Open Feedback is a single page application developed in React for the frontend, and using Firebase for the backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend details
&lt;/h3&gt;

&lt;p&gt;The frontend is a ~ &lt;a href="https://en.wikipedia.org/wiki/Monorepo" rel="noopener noreferrer"&gt;monorepo&lt;/a&gt; ready app composed of: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Landing/marketing website: &lt;a href="https://openfeedback.io" rel="noopener noreferrer"&gt;openfeedback.io&lt;/a&gt;, lazy loaded&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openfeedback.io/eaJnyMXD3oNfhrrnBYDT/2019-06-27/30" rel="noopener noreferrer"&gt;Feedback app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://openfeedback.io/admin" rel="noopener noreferrer"&gt;Console/admin&lt;/a&gt;, lazy loaded&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 3 apps are using React with the classical stack for React:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://reactrouter.com/web/" rel="noopener noreferrer"&gt;React Router&lt;/a&gt; which manages routing based on URL adresses bar, &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redux.js.org/" rel="noopener noreferrer"&gt;Redux&lt;/a&gt;+&lt;a href="https://github.com/reduxjs/redux-thunk" rel="noopener noreferrer"&gt;thunk&lt;/a&gt;: state and action management, &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://reactjs.org/docs/hooks-reference.html" rel="noopener noreferrer"&gt;React Hooks&lt;/a&gt; (almost everywhere), &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://jaredpalmer.com/formik/" rel="noopener noreferrer"&gt;Formik&lt;/a&gt;+&lt;a href="https://github.com/jquense/yup/" rel="noopener noreferrer"&gt;yup&lt;/a&gt;: form and form validation, &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://storybook.js.org/" rel="noopener noreferrer"&gt;Storybook&lt;/a&gt;: documentation and components demos, &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/i18next/react-i18next/" rel="noopener noreferrer"&gt;i18next&lt;/a&gt;: intertionalisation/translation/i18n, split for each app&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://moment.github.io/luxon/" rel="noopener noreferrer"&gt;Luxon&lt;/a&gt;: dates lib, Moment.js was removed to benefit for the small package size &amp;amp; browsers built-in intertionalization APIs,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nivo.rocks/" rel="noopener noreferrer"&gt;Nivo&lt;/a&gt; for an awesome graph experience,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.cypress.io/" rel="noopener noreferrer"&gt;Cypress&lt;/a&gt; for End to end test, running in a browser on GitHub actions,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://jestjs.io/" rel="noopener noreferrer"&gt;Jest&lt;/a&gt; for few unit tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Backend details / servers
&lt;/h3&gt;

&lt;p&gt;OpenFeedback mainly uses &lt;a href="https://firebase.google.com/" rel="noopener noreferrer"&gt;Firebase&lt;/a&gt; which includes many services provided by Google. Firebase is running on Google Cloud Platform. Some of the following services are wrappers over GCP own services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://firebase.google.com/docs/functions" rel="noopener noreferrer"&gt;Serverless cloud functions&lt;/a&gt;: vote aggregation, user invitation and emails in Typescript,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://firebase.google.com/docs/firestore/quickstart" rel="noopener noreferrer"&gt;Firestore&lt;/a&gt;: NoSQL database with security rules,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://firebase.google.com/docs/storage" rel="noopener noreferrer"&gt;Storage&lt;/a&gt;: store events images,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://firebase.google.com/docs/analytics" rel="noopener noreferrer"&gt;Analytics&lt;/a&gt;: track not the users but the overall Saas usages,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://firebase.google.com/docs/hosting" rel="noopener noreferrer"&gt;Hosting&lt;/a&gt;: static hosting for the React app,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://firebase.google.com/docs/auth" rel="noopener noreferrer"&gt;Authentification&lt;/a&gt;: anonymous login for voters, GitHub/Google/Email/Phone for admins. It is a lock-in features for Firebase but it remains &lt;strong&gt;very&lt;/strong&gt; convenient,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://firebase.google.com/docs/perf-mon" rel="noopener noreferrer"&gt;Performance&lt;/a&gt;: website speed analyzer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition, OpenFeedback uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.mailgun.com/" rel="noopener noreferrer"&gt;MailGun&lt;/a&gt; for emails&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.atlassian.com/software/opsgenie" rel="noopener noreferrer"&gt;OpsGenie&lt;/a&gt; for system wide alerting and when a JS or fatal error are throwns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These two apps have free tiers or free quotas, which is very conveniant. &lt;/p&gt;

&lt;h1&gt;
  
  
  Future plans
&lt;/h1&gt;

&lt;p&gt;Next major steps of OpenFeedback are: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Organization&lt;/strong&gt;: users can join an organization allowing them to see the same events and share a common pool of settings, &lt;a href="https://github.com/HugoGresse/open-feedback/issues/616" rel="noopener noreferrer"&gt;issue here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration&lt;/strong&gt; of &lt;a href="https://github.com/bpetetot/conference-hall/" rel="noopener noreferrer"&gt;Conference-Hall.io&lt;/a&gt;, an awesome CFP service. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More tests&lt;/strong&gt;: it takes a lot of time to keep dependencies up to date and prevent regression on new features, so I plan to improve the tests and cover everything so Pull Request can be merged when tests pass without more manual checks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Please let me know in the comment's section if you think this service can be useful, and if you have any ideas or suggestions. Thank you very much in advance!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Also, &lt;a href="https://openfeedback.io/of-devto/0/OftU0q6IEJmrpEVnK2bY" rel="noopener noreferrer"&gt;you can let additional feedbacks on this article on OpenFeedback&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Take care and don't work too much 🖖. &lt;/p&gt;

&lt;h3&gt;
  
  
  Acknowledgements
&lt;/h3&gt;

&lt;p&gt;I thank &lt;a href="https://www.linkedin.com/in/lauriane-anthony-a3696665/" rel="noopener noreferrer"&gt;Lauriane Anthony&lt;/a&gt; for her help with the design and integration work in the earlier stage of this project. I also thank &lt;a href="https://www.researchgate.net/profile/Marceau_Gresse" rel="noopener noreferrer"&gt;Marceau Gresse&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/baptistehaudegand/" rel="noopener noreferrer"&gt;Baptiste Haudegand&lt;/a&gt; for their proofreadings. &lt;/p&gt;

</description>
      <category>meetups</category>
      <category>conferences</category>
      <category>talks</category>
      <category>feedbacks</category>
    </item>
    <item>
      <title>Making a Slack bot from scratch with embedded player (part 1) </title>
      <dc:creator>Hugo Gresse</dc:creator>
      <pubDate>Sat, 15 Aug 2020 17:07:12 +0000</pubDate>
      <link>https://dev.to/hugogresse/making-a-slack-bot-from-scratch-with-embedded-player-part-1-1dg0</link>
      <guid>https://dev.to/hugogresse/making-a-slack-bot-from-scratch-with-embedded-player-part-1-1dg0</guid>
      <description>&lt;p&gt;Some time ago, I've found an &lt;a href="https://kaamelott-soundboard.2ec0b4.fr/"&gt;awesome soundboard&lt;/a&gt; for french TV Show Kaamelott. I wanted to make it available on Slack like the Giphy app, and distribute it. Here is the story. &lt;br&gt;
In this part 1, I will explain how to make a Slack bot, and in part 2, I will explain the pitfall &amp;amp; specific details for an embedded player within Slack. &lt;/p&gt;
&lt;h1&gt;
  
  
  Demo
&lt;/h1&gt;

&lt;p&gt;Here is the actual result&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0U6kaVg2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5ui374sb7oyqzakqwci9.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0U6kaVg2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5ui374sb7oyqzakqwci9.gif" alt="Bot preview"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Vocabulary
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Interactivity: when a user click or interact with a message a bot sent. For example, when Giphy propose you a gif, selecting it to be sent in the channel will send an Interactivity request. &lt;/li&gt;
&lt;li&gt;Slash command: when a user submit a new &lt;code&gt;/mycommand myinput&lt;/code&gt; within Slack, this will trigger an HTTP Request. &lt;/li&gt;
&lt;li&gt;OAuth 2: authentification protocol used by Slack to authentificate a bot and link it to a Slack workplace&lt;/li&gt;
&lt;li&gt;Unfurling: the process that Slack does to add a preview (text/image/rich) below a link&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; this is not an indepth tutorial but an overview on the steps that you'll need to do in order to make your first Slack command bot.&lt;/p&gt;
&lt;h1&gt;
  
  
  Preparing the server
&lt;/h1&gt;

&lt;p&gt;Let's pick &lt;a href="https://firebase.google.com/"&gt;Firebase&lt;/a&gt; stack to make this project. Firebase offer all the services we need to setup this bot easily: an hosting, a serverless platform and a database + all the SDKs you need to communicate with them. &lt;br&gt;
Within Firebase, we'll need at least 3 serverless functions: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;slackCommand&lt;/li&gt;
&lt;li&gt;slackInteractivity&lt;/li&gt;
&lt;li&gt;slackOAuth &lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Create a new project on &lt;a href="//firebase.google.com"&gt;firebase.google.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Install Firebase CLI: &lt;code&gt;npm install -g firebase-tools&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Login: &lt;code&gt;firebase login&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Init the local repo: &lt;code&gt;firebase init&lt;/code&gt; and select Firestore, Functions, Hosting. I've picked Typescript for the functions language.
&lt;/li&gt;
&lt;li&gt;Within &lt;code&gt;functions/&lt;/code&gt; dir, create the &lt;a href="https://firebase.google.com/docs/functions/http-events"&gt;3 HTTP functions&lt;/a&gt; listed above and implement them with a dumb answer:  &lt;code&gt;response.send()&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;Export them in the &lt;code&gt;index.ts&lt;/code&gt;: &lt;code&gt;export {slackOAuth} from './https/slackOAuth'&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Deploy them using &lt;code&gt;firebase deploy&lt;/code&gt; &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An exemple of a dumb serverless functions on Firebase in TS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import * as functions from 'firebase-functions'

export const slackCommand = functions.https.onRequest(async (request, response) =&amp;gt; {
    return response.send()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this time, you'll have deployed three functions. For each function you have a unique URL that you can call from everywhere. Save those URLs for the following part. &lt;/p&gt;

&lt;h1&gt;
  
  
  Creating the Slack App
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5eOAYcm_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/10te1tehfano6cq48bt7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5eOAYcm_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/10te1tehfano6cq48bt7.png" alt="useless screenshot of a Slack app dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We now need to create a new Slack app &lt;a href="https://api.slack.com/apps"&gt;here&lt;/a&gt;. The app will be registered to a workplace so you can test it easily. You'll need to fill some information &amp;amp; images, and then configure it as bellow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable Interactivity: this will allow you to receive an HTTP Request when a user click on a button your Slack bot sent. The URL needs to be the one you've setup in the last parth for the &lt;code&gt;slackInteractivity&lt;/code&gt; function. &lt;/li&gt;
&lt;li&gt;Add a Slash command, use the URL for the &lt;code&gt;slackCommand&lt;/code&gt; function.&lt;/li&gt;
&lt;li&gt;Configure OAuth: 

&lt;ul&gt;
&lt;li&gt;add a Bot Token Scope: &lt;code&gt;commands&lt;/code&gt; that will allow the bot the receive the command&lt;/li&gt;
&lt;li&gt;add a Redirect URLs: the URL for the &lt;code&gt;slackOAuth&lt;/code&gt; function&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is almost all of what you'll need related to Slack.  At this time, a bot user will already be available within your Slack workplace, and the command too, though it will not do much. &lt;/p&gt;

&lt;p&gt;You can also find the "Add to Slack" button in "Manage distribution" of the app, the URL will be useful to test the OAuth proccess.  &lt;/p&gt;

&lt;h1&gt;
  
  
  Add the Slack app credentials as environment variable for the functions
&lt;/h1&gt;

&lt;p&gt;We'll need the following environement var in the serverless functions: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slack &lt;code&gt;client_id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Slack &lt;code&gt;client_secret&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Slack &lt;code&gt;signing_secret&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can add them to the function using &lt;a href="https://github.com/firebase/firebase-tools/"&gt;Firebase CLI&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;firebase functions:config:set slack.client_id=XXXX
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After deploying the functions, your code can access them with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import * as functions from 'firebase-functions'

functions.config().slack.client_id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run the functions locally (not very useful for a Slack bot), you'll need to populate a &lt;code&gt;.runtimeconfig&lt;/code&gt; within the &lt;code&gt;functions&lt;/code&gt; dir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;firebase functions:config:get &amp;gt; .runtimeconfig.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Implementing the OAuth
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KQBsuXLZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ph7ynwkihdna9fjm9k72.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KQBsuXLZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ph7ynwkihdna9fjm9k72.png" alt="slak install process"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The goal of this part is to handle any workplace when the bot is added using the "Add to Slack" button. &lt;br&gt;
After allowing the bot to a workplace with the "Add to Slack" button, Slack will redirect the user to the &lt;code&gt;slackOAuth&lt;/code&gt; function. This request will allow the Slack app to add its configuration to the new workplace (slash command, command scope, bot profil) as well has having a valid token on this workplace. &lt;br&gt;
The function is composed of the following parts: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Validating that the environment variables exist&lt;/li&gt;
&lt;li&gt;Answering for the SSL Check Slack does regularly&lt;/li&gt;
&lt;li&gt;Validating the request (code, method)&lt;/li&gt;
&lt;li&gt;Exchange the temporary Slack OAuth verifier code for an access token (using &lt;code&gt;oauth.v2.access&lt;/code&gt; Slack API)&lt;/li&gt;
&lt;li&gt;Saving the token to Firestore database. &lt;/li&gt;
&lt;li&gt;Redirecting the user to a success or failure page. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can find the full code of this function &lt;a href="https://github.com/HugoGresse/kaamelott-bot/blob/master/functions/src/https/oauthRedirect.ts#L6"&gt;here&lt;/a&gt;, but here is some interesting part: &lt;/p&gt;

&lt;p&gt;Get environment variable in functions using &lt;code&gt;functions.config().my_object.my_key&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Call the Slack &lt;code&gt;oauth.v2.access&lt;/code&gt; API&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    import fetch from 'node-fetch'
    import {URLSearchParams} from 'url'

    const params = new URLSearchParams()
    params.append('code', `${request.query.code}`)
    params.append('client_id', `${slack.client_id}`)
    params.append('client_secret', `${slack.client_secret}`)
    params.append('redirect_uri', `https://us-central1-${process.env.GCLOUD_PROJECT}.cloudfunctions.net/slackOAuth`)

    const oauthV2AccessResult = await fetch("https://slack.com/api/oauth.v2.access", {
        method: "POST",
        body: params
    })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the token to Firestore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   import * as admin from "firebase-admin"
   admin.initializeApp()

   const slackResultData = await oauthV2AccessResult.json()

   admin.firestore()
        .collection("installations")
        .doc(slackResultData.team.id).set({
            token: slackResultData.access_token,
            teamId: slackResultData.team.id,
            teamName: slackResultData.team.name,
            createdAt: serverTimestamp()
        })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The not funny part is that Slack cannot call a local URL, so you'll need to deploy this each time to test it. Here is a shortcut to only deploy a function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;firebase deploy --only functions:slackOAuth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this time, if everything work, upon calling the app installation URL (within Slack Manage distribution app page), you should be asked to allow the app and then redirected to success page (in my case, it's &lt;a href="https://kaamelott-bot.firebaseapp.com/slack/success"&gt;this one&lt;/a&gt;). &lt;br&gt;
The token will have been saved in Firestore: &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2nYzP-5e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ioan06j9tzna5x6wgycw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2nYzP-5e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ioan06j9tzna5x6wgycw.png" alt="Firestore database installations"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://api.slack.com/authentication/oauth-v2"&gt;Slack OAuth documentation&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Responding to a command request
&lt;/h1&gt;

&lt;p&gt;When a new command is executed in Slack, the &lt;code&gt;slackCommand&lt;/code&gt; function will be called. This is post of your bot logic will take place. I believe this was the hardest part for me. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--m2aAMPNt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/h13dsom6kv7cvgjaitn7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--m2aAMPNt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/h13dsom6kv7cvgjaitn7.png" alt="slak command"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Implementation guidelines: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verify the request integrity:

&lt;ul&gt;
&lt;li&gt;only POST request
&lt;/li&gt;
&lt;li&gt;checking if &lt;code&gt;x-slack-signature&lt;/code&gt; &amp;amp; &lt;code&gt;x-slack-request-timestamp&lt;/code&gt; headers are present
&lt;/li&gt;
&lt;li&gt;preventing request attack
&lt;/li&gt;
&lt;li&gt;making a hash of the request body using hmac 256
&lt;/li&gt;
&lt;li&gt;comparing the hash with the &lt;code&gt;x-slack-signature&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Sending a ACK to Slack: &lt;code&gt;response.send()&lt;/code&gt; to prevent a timeout after 3s on Slack side. &lt;/li&gt;
&lt;li&gt;Getting the app token, if any&lt;/li&gt;
&lt;li&gt;[Your bot logic here]&lt;/li&gt;
&lt;li&gt;Answering to the &lt;code&gt;response_url&lt;/code&gt; with the message. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's go a little deeper in the integrity check. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First of all, you'll need to stringify the request body using &lt;code&gt;RFC1738&lt;/code&gt; which is possible using the &lt;code&gt;qs&lt;/code&gt; package in Node. 
&lt;code&gt;const requestBody = qs.stringify(request.body, {format: 'RFC1738'})&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Using the Node crypto package, make the digest:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import * as crypto from "crypto"
const digest = 'v0=' + crypto.createHmac('sha256', signingSecret)
    .update(`v0:${requestSlackTimestamp}:${requestBody}`)
    .digest('hex')
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Checking the Slack signature and your new digest:&lt;br&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;crypto.timingSafeEqual(
        Buffer.from(digest, 'utf8'),
        Buffer.from(requestSlackSignature, 'utf8'))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are using this safe equal method to prevent a timing attack on the string compare (more info &lt;a href="https://stackoverflow.com/questions/31095905/whats-the-difference-between-a-secure-compare-and-a-simple"&gt;here&lt;/a&gt;) &lt;/p&gt;

&lt;p&gt;&lt;a href="https://api.slack.com/authentication/verifying-requests-from-slack"&gt;Slack verifying documentation&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Sending some Block back to Slack
&lt;/h1&gt;

&lt;p&gt;Assuming you now have the token (bot token usually start with &lt;code&gt;xoxb-&lt;/code&gt;), you can send message in Slack using the &lt;code&gt;response_url&lt;/code&gt; provided in the request body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    const responseUrl = request.body.response_url

    return fetch(responseUrl, {
        method: "POST",
        body: JSON.stringify({
            blocks: [
                {
                    type: "section",
                    text: {
                        type: "mrkdwn",
                        text: `An \n awesome _text_`
                    },
                    accessory: {
                        type: "button",
                        text: {
                            type: "plain_text",
                            text: "Send",
                            emoji: true
                        },
                        value: someValue
                    }
                }
            ]
        })
    })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is two ways to &lt;a href="https://api.slack.com/messaging/composing"&gt;compose message&lt;/a&gt; in Slack: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;a href="https://api.slack.com/block-kit/building"&gt;block format&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;the legacy format: a &lt;code&gt;text&lt;/code&gt; and some optional &lt;code&gt;attachments&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Usually, you should go with the block format, as the &lt;a href="https://api.slack.com/tools/block-kit-builder"&gt;BlockKit Builder&lt;/a&gt; is really great and the layout possibilities are all you need. &lt;/p&gt;

&lt;p&gt;The API is &lt;a href="https://api.slack.com/reference/messaging/payload"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some messages are only visible to the user issuing the command (ephemeral) and other are public within a conversation (in_channel). This will be explained in part 2. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--d_ktyzqq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gmi2v20bsuku061oor5h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--d_ktyzqq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gmi2v20bsuku061oor5h.png" alt="Ephemeral message from the bot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Interactivity implementation
&lt;/h1&gt;

&lt;p&gt;When an user interact with a bot message (clicking on button from the message), &lt;code&gt;slackInteractivity&lt;/code&gt; function is called. &lt;/p&gt;

&lt;p&gt;Here is some implementation steps: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send an empty response to prevent the 3s Slack timeout. (some functions may be longer to start if they are cold (not called for 10min) &lt;/li&gt;
&lt;li&gt;Verify the signature of the message&lt;/li&gt;
&lt;li&gt;Get the payload type &amp;amp; check request data
a. type === &lt;code&gt;block_actions&lt;/code&gt;?
b. action value present? (this will be the &lt;code&gt;accessory.value&lt;/code&gt; of the block in example above)&lt;/li&gt;
&lt;li&gt;[your bot logic]&lt;/li&gt;
&lt;li&gt;Call the &lt;code&gt;response_url&lt;/code&gt; with a new payload of your choosing. &lt;/li&gt;
&lt;li&gt;To convert a &lt;code&gt;ephemeral&lt;/code&gt; message to a &lt;code&gt;in_channel&lt;/code&gt; you'll need to wait for the part 2 soon. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And  🎉&lt;/p&gt;

&lt;p&gt;You'll then need to deploy everything, add your bot logic and fix the bugs. &lt;/p&gt;

&lt;p&gt;In part 2, I'll explain my own bot logic where I'm using Twitter as an embeded player for Slack as well as some other implementation details.  Stay tuned. &lt;/p&gt;

&lt;p&gt;Sources code of the bot is &lt;a href="https://github.com/HugoGresse/kaamelott-bot"&gt;here on github.com&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Thanks &lt;a href="https://www.linkedin.com/in/rdardie/?originalSubdomain=fr"&gt;Roch Dardié&lt;/a&gt; for proofreading me. &lt;/p&gt;

</description>
      <category>slack</category>
      <category>bot</category>
      <category>firebase</category>
      <category>twitter</category>
    </item>
  </channel>
</rss>
