diff --git a/docs/_site/assets/basic-information-page.png b/docs/_site/assets/basic-information-page.png deleted file mode 100644 index 0467c01f5..000000000 Binary files a/docs/_site/assets/basic-information-page.png and /dev/null differ diff --git a/docs/_site/assets/bolt-favicon.png b/docs/_site/assets/bolt-favicon.png deleted file mode 100644 index bfe5456c1..000000000 Binary files a/docs/_site/assets/bolt-favicon.png and /dev/null differ diff --git a/docs/_site/assets/bolt-js-logo.svg b/docs/_site/assets/bolt-js-logo.svg deleted file mode 100644 index c14f317d5..000000000 --- a/docs/_site/assets/bolt-js-logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/_site/assets/bolt-logo.svg b/docs/_site/assets/bolt-logo.svg deleted file mode 100644 index 5077600d5..000000000 --- a/docs/_site/assets/bolt-logo.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/_site/assets/bot-token.png b/docs/_site/assets/bot-token.png deleted file mode 100644 index 8fa723b15..000000000 Binary files a/docs/_site/assets/bot-token.png and /dev/null differ diff --git a/docs/_site/assets/css/style.css b/docs/_site/assets/css/style.css deleted file mode 100644 index ba6a2a128..000000000 --- a/docs/_site/assets/css/style.css +++ /dev/null @@ -1,6694 +0,0 @@ -/*! normalize.css v4.1.1 | MIT License | github.com/necolas/normalize.css */ -/** 1. Change the default font family in all browsers (opinionated). 2. Prevent adjustments of font size after orientation changes in IE and iOS. */ -html { - font-family: sans-serif; /* 1 */ - -ms-text-size-adjust: 100%; /* 2 */ - -webkit-text-size-adjust: 100%; /* 2 */ -} - -/** Remove the margin in all browsers (opinionated). */ -body { - margin: 0; -} - -/* HTML5 display definitions ========================================================================== */ -/** Add the correct display in IE 9-. 1. Add the correct display in Edge, IE, and Firefox. 2. Add the correct display in IE. */ -article, -aside, -details, -figcaption, -figure, -footer, -header, -main, -menu, -nav, -section { - /* 1 */ display: block; -} - -summary { - display: list-item; -} - -/** Add the correct display in IE 9-. */ -audio, -canvas, -progress, -video { - display: inline-block; -} - -/** Add the correct display in iOS 4-7. */ -audio:not([controls]) { - display: none; - height: 0; -} - -/** Add the correct vertical alignment in Chrome, Firefox, and Opera. */ -progress { - vertical-align: baseline; -} - -/** Add the correct display in IE 10-. 1. Add the correct display in IE. */ -template, -[hidden] { - display: none !important; -} - -/* Links ========================================================================== */ -/** Remove the gray background on active links in IE 10. */ -a { - background-color: transparent; /* 1 */ -} - -/** Remove the outline on focused links when they are also active or hovered in all browsers (opinionated). */ -a:active, -a:hover { - outline-width: 0; -} - -/* Text-level semantics ========================================================================== */ -/** 1. Remove the bottom border in Firefox 39-. 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. */ -abbr[title] { - border-bottom: none; /* 1 */ - text-decoration: underline; /* 2 */ - text-decoration: underline dotted; /* 2 */ -} - -/** Prevent the duplicate application of `bolder` by the next rule in Safari 6. */ -b, -strong { - font-weight: inherit; -} - -/** Add the correct font weight in Chrome, Edge, and Safari. */ -b, -strong { - font-weight: bolder; -} - -/** Add the correct font style in Android 4.3-. */ -dfn { - font-style: italic; -} - -/** Correct the font size and margin on `h1` elements within `section` and `article` contexts in Chrome, Firefox, and Safari. */ -h1 { - font-size: 2em; - margin: 0.67em 0; -} - -/** Add the correct background and color in IE 9-. */ -mark { - background-color: #ff0; - color: #000; -} - -/** Add the correct font size in all browsers. */ -small { - font-size: 80%; -} - -/** Prevent `sub` and `sup` elements from affecting the line height in all browsers. */ -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} - -sub { - bottom: -0.25em; -} - -sup { - top: -0.5em; -} - -/* Embedded content ========================================================================== */ -/** Remove the border on images inside links in IE 10-. */ -img { - border-style: none; -} - -/** Hide the overflow in IE. */ -svg:not(:root) { - overflow: hidden; -} - -/* Grouping content ========================================================================== */ -/** 1. Correct the inheritance and scaling of font size in all browsers. 2. Correct the odd `em` font sizing in all browsers. */ -code, -kbd, -pre, -samp { - font-family: monospace, monospace; /* 1 */ - font-size: 1em; /* 2 */ -} - -/** Add the correct margin in IE 8. */ -figure { - margin: 1em 40px; -} - -/** 1. Add the correct box sizing in Firefox. 2. Show the overflow in Edge and IE. */ -hr { - box-sizing: content-box; /* 1 */ - height: 0; /* 1 */ - overflow: visible; /* 2 */ -} - -/* Forms ========================================================================== */ -/** 1. Change font properties to `inherit` in all browsers (opinionated). 2. Remove the margin in Firefox and Safari. */ -button, -input, -select, -textarea { - font: inherit; /* 1 */ - margin: 0; /* 2 */ -} - -/** Restore the font weight unset by the previous rule. */ -optgroup { - font-weight: bold; -} - -/** Show the overflow in IE. 1. Show the overflow in Edge. */ -button, -input { - /* 1 */ overflow: visible; -} - -/** Remove the inheritance of text transform in Edge, Firefox, and IE. 1. Remove the inheritance of text transform in Firefox. */ -button, -select { - /* 1 */ text-transform: none; -} - -/** 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` controls in Android 4. 2. Correct the inability to style clickable types in iOS and Safari. */ -button, -html [type="button"], -[type="reset"], -[type="submit"] { - -webkit-appearance: button; /* 2 */ -} - -/** Remove the inner border and padding in Firefox. */ -button::-moz-focus-inner, -[type="button"]::-moz-focus-inner, -[type="reset"]::-moz-focus-inner, -[type="submit"]::-moz-focus-inner { - border-style: none; - padding: 0; -} - -/** Restore the focus styles unset by the previous rule. */ -button:-moz-focusring, -[type="button"]:-moz-focusring, -[type="reset"]:-moz-focusring, -[type="submit"]:-moz-focusring { - outline: 1px dotted ButtonText; -} - -/** Change the border, margin, and padding in all browsers (opinionated). */ -fieldset { - border: 1px solid #c0c0c0; - margin: 0 2px; - padding: 0.35em 0.625em 0.75em; -} - -/** 1. Correct the text wrapping in Edge and IE. 2. Correct the color inheritance from `fieldset` elements in IE. 3. Remove the padding so developers are not caught out when they zero out `fieldset` elements in all browsers. */ -legend { - box-sizing: border-box; /* 1 */ - color: inherit; /* 2 */ - display: table; /* 1 */ - max-width: 100%; /* 1 */ - padding: 0; /* 3 */ - white-space: normal; /* 1 */ -} - -/** Remove the default vertical scrollbar in IE. */ -textarea { - overflow: auto; -} - -/** 1. Add the correct box sizing in IE 10-. 2. Remove the padding in IE 10-. */ -[type="checkbox"], -[type="radio"] { - box-sizing: border-box; /* 1 */ - padding: 0; /* 2 */ -} - -/** Correct the cursor style of increment and decrement buttons in Chrome. */ -[type="number"]::-webkit-inner-spin-button, -[type="number"]::-webkit-outer-spin-button { - height: auto; -} - -/** 1. Correct the odd appearance in Chrome and Safari. 2. Correct the outline style in Safari. */ -[type="search"] { - -webkit-appearance: textfield; /* 1 */ - outline-offset: -2px; /* 2 */ -} - -/** Remove the inner padding and cancel buttons in Chrome and Safari on OS X. */ -[type="search"]::-webkit-search-cancel-button, -[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} - -/** Correct the text style of placeholders in Chrome, Edge, and Safari. */ -::-webkit-input-placeholder { - color: inherit; - opacity: 0.54; -} - -/** 1. Correct the inability to style clickable types in iOS and Safari. 2. Change font properties to `inherit` in Safari. */ -::-webkit-file-upload-button { - -webkit-appearance: button; /* 1 */ - font: inherit; /* 2 */ -} - -* { - box-sizing: border-box; -} - -input, -select, -textarea, -button { - font-family: inherit; - font-size: inherit; - line-height: inherit; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", - "Segoe UI Emoji", "Segoe UI Symbol"; - font-size: 14px; - line-height: 1.5; - color: #24292e; - background-color: #fff; -} - -a { - color: #0366d6; - text-decoration: none; -} -a:hover { - text-decoration: underline; -} - -b, -strong { - font-weight: 600; -} - -hr, -.rule { - height: 0; - margin: 15px 0; - overflow: hidden; - background: transparent; - border: 0; - border-bottom: 1px solid #dfe2e5; -} -hr::before, -.rule::before { - display: table; - content: ""; -} -hr::after, -.rule::after { - display: table; - clear: both; - content: ""; -} - -table { - border-spacing: 0; - border-collapse: collapse; -} - -td, -th { - padding: 0; -} - -button { - cursor: pointer; - border-radius: 0; -} - -[hidden][hidden] { - display: none !important; -} - -details summary { - cursor: pointer; -} -details:not([open]) > *:not(summary) { - display: none !important; -} - -h1, -h2, -h3, -h4, -h5, -h6 { - margin-top: 0; - margin-bottom: 0; -} - -h1 { - font-size: 32px; - font-weight: 600; -} - -h2 { - font-size: 24px; - font-weight: 600; -} - -h3 { - font-size: 20px; - font-weight: 600; -} - -h4 { - font-size: 16px; - font-weight: 600; -} - -h5 { - font-size: 14px; - font-weight: 600; -} - -h6 { - font-size: 12px; - font-weight: 600; -} - -p { - margin-top: 0; - margin-bottom: 10px; -} - -small { - font-size: 90%; -} - -blockquote { - margin: 0; -} - -ul, -ol { - padding-left: 0; - margin-top: 0; - margin-bottom: 0; -} - -ol ol, -ul ol { - list-style-type: lower-roman; -} - -ul ul ol, -ul ol ol, -ol ul ol, -ol ol ol { - list-style-type: lower-alpha; -} - -dd { - margin-left: 0; -} - -tt, -code { - font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; - font-size: 12px; -} - -pre { - margin-top: 0; - margin-bottom: 0; - font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; - font-size: 12px; -} - -.octicon { - vertical-align: text-bottom; -} - -/* Fade in an element */ -.anim-fade-in { - animation-name: fade-in; - animation-duration: 1s; - animation-timing-function: ease-in-out; -} -.anim-fade-in.fast { - animation-duration: 300ms; -} - -@keyframes fade-in { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} -/* Fade out an element */ -.anim-fade-out { - animation-name: fade-out; - animation-duration: 1s; - animation-timing-function: ease-out; -} -.anim-fade-out.fast { - animation-duration: 0.3s; -} - -@keyframes fade-out { - 0% { - opacity: 1; - } - 100% { - opacity: 0; - } -} -/* Fade in and slide up an element */ -.anim-fade-up { - opacity: 0; - animation-name: fade-up; - animation-duration: 0.3s; - animation-fill-mode: forwards; - animation-timing-function: ease-out; - animation-delay: 1s; -} - -@keyframes fade-up { - 0% { - opacity: 0.8; - transform: translateY(100%); - } - 100% { - opacity: 1; - transform: translateY(0); - } -} -/* Fade an element out and slide down */ -.anim-fade-down { - animation-name: fade-down; - animation-duration: 0.3s; - animation-fill-mode: forwards; - animation-timing-function: ease-in; -} - -@keyframes fade-down { - 0% { - opacity: 1; - transform: translateY(0); - } - 100% { - opacity: 0.5; - transform: translateY(100%); - } -} -/* Grow an element width from 0 to 100% */ -.anim-grow-x { - width: 0%; - animation-name: grow-x; - animation-duration: 0.3s; - animation-fill-mode: forwards; - animation-timing-function: ease; - animation-delay: 0.5s; -} - -@keyframes grow-x { - to { - width: 100%; - } -} -/* Shrink an element from 100% to 0% */ -.anim-shrink-x { - animation-name: shrink-x; - animation-duration: 0.3s; - animation-fill-mode: forwards; - animation-timing-function: ease-in-out; - animation-delay: 0.5s; -} - -@keyframes shrink-x { - to { - width: 0%; - } -} -/* Fade in an element and scale it fast */ -.anim-scale-in { - animation-name: scale-in; - animation-duration: 0.15s; - animation-timing-function: cubic-bezier(0.2, 0, 0.13, 1.5); -} - -@keyframes scale-in { - 0% { - opacity: 0; - transform: scale(0.5); - } - 100% { - opacity: 1; - transform: scale(1); - } -} -/* Pulse an element's opacity */ -.anim-pulse { - animation-name: pulse; - animation-duration: 2s; - animation-timing-function: linear; - animation-iteration-count: infinite; -} - -@keyframes pulse { - 0% { - opacity: 0.3; - } - 10% { - opacity: 1; - } - 100% { - opacity: 0.3; - } -} -/* Pulse in an element */ -.anim-pulse-in { - animation-name: pulse-in; - animation-duration: 0.5s; -} - -@keyframes pulse-in { - 0% { - transform: scale3d(1, 1, 1); - } - 50% { - transform: scale3d(1.1, 1.1, 1.1); - } - 100% { - transform: scale3d(1, 1, 1); - } -} -/* Increase scale of an element on hover */ -.hover-grow { - transition: transform 0.3s; - backface-visibility: hidden; -} -.hover-grow:hover { - transform: scale(1.025); -} - -/* Add a gray border on all sides */ -.border { - border: 1px #e1e4e8 solid !important; -} - -/* Add a gray border to the left and right */ -.border-y { - border-top: 1px #e1e4e8 solid !important; - border-bottom: 1px #e1e4e8 solid !important; -} - -/* Remove borders from all sides */ -.border-0 { - border: 0 !important; -} - -.border-dashed { - border-style: dashed !important; -} - -/* Use with .border to turn the border blue */ -.border-blue { - border-color: #0366d6 !important; -} - -/* Use with .border to turn the border blue-light */ -.border-blue-light { - border-color: #c8e1ff !important; -} - -/* Use with .border to turn the border green */ -.border-green { - border-color: #34d058 !important; -} - -/* Use with .border to turn the border green light */ -.border-green-light { - border-color: #a2cbac !important; -} - -/* Use with .border to turn the border red */ -.border-red { - border-color: #d73a49 !important; -} - -/* Use with .border to turn the border red-light */ -.border-red-light { - border-color: #cea0a5 !important; -} - -/* Use with .border to turn the border purple */ -.border-purple { - border-color: #6f42c1 !important; -} - -/* Use with .border to turn the border yellow */ -.border-yellow { - border-color: #d9d0a5 !important; -} - -/* Use with .border to turn the border gray-light */ -.border-gray-light { - border-color: #eaecef !important; -} - -/* Use with .border to turn the border gray-dark */ -.border-gray-dark { - border-color: #d1d5da !important; -} - -/* Use with .border to turn the border rgba black 0.15 */ -.border-black-fade { - border-color: rgba(27, 31, 35, 0.15) !important; -} - -/* Add a gray border */ -/* Add a gray border to the top */ -.border-top { - border-top: 1px #e1e4e8 solid !important; -} - -/* Add a gray border to the right */ -.border-right { - border-right: 1px #e1e4e8 solid !important; -} - -/* Add a gray border to the bottom */ -.border-bottom { - border-bottom: 1px #e1e4e8 solid !important; -} - -/* Add a gray border to the left */ -.border-left { - border-left: 1px #e1e4e8 solid !important; -} - -/* Remove the top border */ -.border-top-0 { - border-top: 0 !important; -} - -/* Remove the right border */ -.border-right-0 { - border-right: 0 !important; -} - -/* Remove the bottom border */ -.border-bottom-0 { - border-bottom: 0 !important; -} - -/* Remove the left border */ -.border-left-0 { - border-left: 0 !important; -} - -/* Remove the border-radius */ -.rounded-0 { - border-radius: 0 !important; -} - -/* Add a border-radius to all corners */ -.rounded-1 { - border-radius: 3px !important; -} - -/* Add a 2x border-radius to all corners */ -.rounded-2 { - border-radius: 6px !important; -} - -.rounded-top-0 { - border-top-left-radius: 0 !important; - border-top-right-radius: 0 !important; -} - -.rounded-top-1 { - border-top-left-radius: 3px !important; - border-top-right-radius: 3px !important; -} - -.rounded-top-2 { - border-top-left-radius: 6px !important; - border-top-right-radius: 6px !important; -} - -.rounded-right-0 { - border-top-right-radius: 0 !important; - border-bottom-right-radius: 0 !important; -} - -.rounded-right-1 { - border-top-right-radius: 3px !important; - border-bottom-right-radius: 3px !important; -} - -.rounded-right-2 { - border-top-right-radius: 6px !important; - border-bottom-right-radius: 6px !important; -} - -.rounded-bottom-0 { - border-bottom-right-radius: 0 !important; - border-bottom-left-radius: 0 !important; -} - -.rounded-bottom-1 { - border-bottom-right-radius: 3px !important; - border-bottom-left-radius: 3px !important; -} - -.rounded-bottom-2 { - border-bottom-right-radius: 6px !important; - border-bottom-left-radius: 6px !important; -} - -.rounded-left-0 { - border-bottom-left-radius: 0 !important; - border-top-left-radius: 0 !important; -} - -.rounded-left-1 { - border-bottom-left-radius: 3px !important; - border-top-left-radius: 3px !important; -} - -.rounded-left-2 { - border-bottom-left-radius: 6px !important; - border-top-left-radius: 6px !important; -} - -@media (min-width: 544px) { - /* Add a gray border */ - /* Add a gray border to the top */ - .border-sm-top { - border-top: 1px #e1e4e8 solid !important; - } - /* Add a gray border to the right */ - .border-sm-right { - border-right: 1px #e1e4e8 solid !important; - } - /* Add a gray border to the bottom */ - .border-sm-bottom { - border-bottom: 1px #e1e4e8 solid !important; - } - /* Add a gray border to the left */ - .border-sm-left { - border-left: 1px #e1e4e8 solid !important; - } - /* Remove the top border */ - .border-sm-top-0 { - border-top: 0 !important; - } - /* Remove the right border */ - .border-sm-right-0 { - border-right: 0 !important; - } - /* Remove the bottom border */ - .border-sm-bottom-0 { - border-bottom: 0 !important; - } - /* Remove the left border */ - .border-sm-left-0 { - border-left: 0 !important; - } - /* Remove the border-radius */ - .rounded-sm-0 { - border-radius: 0 !important; - } - /* Add a border-radius to all corners */ - .rounded-sm-1 { - border-radius: 3px !important; - } - /* Add a 2x border-radius to all corners */ - .rounded-sm-2 { - border-radius: 6px !important; - } - .rounded-sm-top-0 { - border-top-left-radius: 0 !important; - border-top-right-radius: 0 !important; - } - .rounded-sm-top-1 { - border-top-left-radius: 3px !important; - border-top-right-radius: 3px !important; - } - .rounded-sm-top-2 { - border-top-left-radius: 6px !important; - border-top-right-radius: 6px !important; - } - .rounded-sm-right-0 { - border-top-right-radius: 0 !important; - border-bottom-right-radius: 0 !important; - } - .rounded-sm-right-1 { - border-top-right-radius: 3px !important; - border-bottom-right-radius: 3px !important; - } - .rounded-sm-right-2 { - border-top-right-radius: 6px !important; - border-bottom-right-radius: 6px !important; - } - .rounded-sm-bottom-0 { - border-bottom-right-radius: 0 !important; - border-bottom-left-radius: 0 !important; - } - .rounded-sm-bottom-1 { - border-bottom-right-radius: 3px !important; - border-bottom-left-radius: 3px !important; - } - .rounded-sm-bottom-2 { - border-bottom-right-radius: 6px !important; - border-bottom-left-radius: 6px !important; - } - .rounded-sm-left-0 { - border-bottom-left-radius: 0 !important; - border-top-left-radius: 0 !important; - } - .rounded-sm-left-1 { - border-bottom-left-radius: 3px !important; - border-top-left-radius: 3px !important; - } - .rounded-sm-left-2 { - border-bottom-left-radius: 6px !important; - border-top-left-radius: 6px !important; - } -} -@media (min-width: 768px) { - /* Add a gray border */ - /* Add a gray border to the top */ - .border-md-top { - border-top: 1px #e1e4e8 solid !important; - } - /* Add a gray border to the right */ - .border-md-right { - border-right: 1px #e1e4e8 solid !important; - } - /* Add a gray border to the bottom */ - .border-md-bottom { - border-bottom: 1px #e1e4e8 solid !important; - } - /* Add a gray border to the left */ - .border-md-left { - border-left: 1px #e1e4e8 solid !important; - } - /* Remove the top border */ - .border-md-top-0 { - border-top: 0 !important; - } - /* Remove the right border */ - .border-md-right-0 { - border-right: 0 !important; - } - /* Remove the bottom border */ - .border-md-bottom-0 { - border-bottom: 0 !important; - } - /* Remove the left border */ - .border-md-left-0 { - border-left: 0 !important; - } - /* Remove the border-radius */ - .rounded-md-0 { - border-radius: 0 !important; - } - /* Add a border-radius to all corners */ - .rounded-md-1 { - border-radius: 3px !important; - } - /* Add a 2x border-radius to all corners */ - .rounded-md-2 { - border-radius: 6px !important; - } - .rounded-md-top-0 { - border-top-left-radius: 0 !important; - border-top-right-radius: 0 !important; - } - .rounded-md-top-1 { - border-top-left-radius: 3px !important; - border-top-right-radius: 3px !important; - } - .rounded-md-top-2 { - border-top-left-radius: 6px !important; - border-top-right-radius: 6px !important; - } - .rounded-md-right-0 { - border-top-right-radius: 0 !important; - border-bottom-right-radius: 0 !important; - } - .rounded-md-right-1 { - border-top-right-radius: 3px !important; - border-bottom-right-radius: 3px !important; - } - .rounded-md-right-2 { - border-top-right-radius: 6px !important; - border-bottom-right-radius: 6px !important; - } - .rounded-md-bottom-0 { - border-bottom-right-radius: 0 !important; - border-bottom-left-radius: 0 !important; - } - .rounded-md-bottom-1 { - border-bottom-right-radius: 3px !important; - border-bottom-left-radius: 3px !important; - } - .rounded-md-bottom-2 { - border-bottom-right-radius: 6px !important; - border-bottom-left-radius: 6px !important; - } - .rounded-md-left-0 { - border-bottom-left-radius: 0 !important; - border-top-left-radius: 0 !important; - } - .rounded-md-left-1 { - border-bottom-left-radius: 3px !important; - border-top-left-radius: 3px !important; - } - .rounded-md-left-2 { - border-bottom-left-radius: 6px !important; - border-top-left-radius: 6px !important; - } -} -@media (min-width: 1012px) { - /* Add a gray border */ - /* Add a gray border to the top */ - .border-lg-top { - border-top: 1px #e1e4e8 solid !important; - } - /* Add a gray border to the right */ - .border-lg-right { - border-right: 1px #e1e4e8 solid !important; - } - /* Add a gray border to the bottom */ - .border-lg-bottom { - border-bottom: 1px #e1e4e8 solid !important; - } - /* Add a gray border to the left */ - .border-lg-left { - border-left: 1px #e1e4e8 solid !important; - } - /* Remove the top border */ - .border-lg-top-0 { - border-top: 0 !important; - } - /* Remove the right border */ - .border-lg-right-0 { - border-right: 0 !important; - } - /* Remove the bottom border */ - .border-lg-bottom-0 { - border-bottom: 0 !important; - } - /* Remove the left border */ - .border-lg-left-0 { - border-left: 0 !important; - } - /* Remove the border-radius */ - .rounded-lg-0 { - border-radius: 0 !important; - } - /* Add a border-radius to all corners */ - .rounded-lg-1 { - border-radius: 3px !important; - } - /* Add a 2x border-radius to all corners */ - .rounded-lg-2 { - border-radius: 6px !important; - } - .rounded-lg-top-0 { - border-top-left-radius: 0 !important; - border-top-right-radius: 0 !important; - } - .rounded-lg-top-1 { - border-top-left-radius: 3px !important; - border-top-right-radius: 3px !important; - } - .rounded-lg-top-2 { - border-top-left-radius: 6px !important; - border-top-right-radius: 6px !important; - } - .rounded-lg-right-0 { - border-top-right-radius: 0 !important; - border-bottom-right-radius: 0 !important; - } - .rounded-lg-right-1 { - border-top-right-radius: 3px !important; - border-bottom-right-radius: 3px !important; - } - .rounded-lg-right-2 { - border-top-right-radius: 6px !important; - border-bottom-right-radius: 6px !important; - } - .rounded-lg-bottom-0 { - border-bottom-right-radius: 0 !important; - border-bottom-left-radius: 0 !important; - } - .rounded-lg-bottom-1 { - border-bottom-right-radius: 3px !important; - border-bottom-left-radius: 3px !important; - } - .rounded-lg-bottom-2 { - border-bottom-right-radius: 6px !important; - border-bottom-left-radius: 6px !important; - } - .rounded-lg-left-0 { - border-bottom-left-radius: 0 !important; - border-top-left-radius: 0 !important; - } - .rounded-lg-left-1 { - border-bottom-left-radius: 3px !important; - border-top-left-radius: 3px !important; - } - .rounded-lg-left-2 { - border-bottom-left-radius: 6px !important; - border-top-left-radius: 6px !important; - } -} -@media (min-width: 1280px) { - /* Add a gray border */ - /* Add a gray border to the top */ - .border-xl-top { - border-top: 1px #e1e4e8 solid !important; - } - /* Add a gray border to the right */ - .border-xl-right { - border-right: 1px #e1e4e8 solid !important; - } - /* Add a gray border to the bottom */ - .border-xl-bottom { - border-bottom: 1px #e1e4e8 solid !important; - } - /* Add a gray border to the left */ - .border-xl-left { - border-left: 1px #e1e4e8 solid !important; - } - /* Remove the top border */ - .border-xl-top-0 { - border-top: 0 !important; - } - /* Remove the right border */ - .border-xl-right-0 { - border-right: 0 !important; - } - /* Remove the bottom border */ - .border-xl-bottom-0 { - border-bottom: 0 !important; - } - /* Remove the left border */ - .border-xl-left-0 { - border-left: 0 !important; - } - /* Remove the border-radius */ - .rounded-xl-0 { - border-radius: 0 !important; - } - /* Add a border-radius to all corners */ - .rounded-xl-1 { - border-radius: 3px !important; - } - /* Add a 2x border-radius to all corners */ - .rounded-xl-2 { - border-radius: 6px !important; - } - .rounded-xl-top-0 { - border-top-left-radius: 0 !important; - border-top-right-radius: 0 !important; - } - .rounded-xl-top-1 { - border-top-left-radius: 3px !important; - border-top-right-radius: 3px !important; - } - .rounded-xl-top-2 { - border-top-left-radius: 6px !important; - border-top-right-radius: 6px !important; - } - .rounded-xl-right-0 { - border-top-right-radius: 0 !important; - border-bottom-right-radius: 0 !important; - } - .rounded-xl-right-1 { - border-top-right-radius: 3px !important; - border-bottom-right-radius: 3px !important; - } - .rounded-xl-right-2 { - border-top-right-radius: 6px !important; - border-bottom-right-radius: 6px !important; - } - .rounded-xl-bottom-0 { - border-bottom-right-radius: 0 !important; - border-bottom-left-radius: 0 !important; - } - .rounded-xl-bottom-1 { - border-bottom-right-radius: 3px !important; - border-bottom-left-radius: 3px !important; - } - .rounded-xl-bottom-2 { - border-bottom-right-radius: 6px !important; - border-bottom-left-radius: 6px !important; - } - .rounded-xl-left-0 { - border-bottom-left-radius: 0 !important; - border-top-left-radius: 0 !important; - } - .rounded-xl-left-1 { - border-bottom-left-radius: 3px !important; - border-top-left-radius: 3px !important; - } - .rounded-xl-left-2 { - border-bottom-left-radius: 6px !important; - border-top-left-radius: 6px !important; - } -} -/* Add a 50% border-radius to make something into a circle */ -.circle { - border-radius: 50% !important; -} - -.box-shadow { - box-shadow: 0 1px 1px rgba(27, 31, 35, 0.1) !important; -} - -.box-shadow-medium { - box-shadow: 0 1px 5px rgba(27, 31, 35, 0.15) !important; -} - -.box-shadow-large { - box-shadow: 0 1px 15px rgba(27, 31, 35, 0.15) !important; -} - -.box-shadow-extra-large { - box-shadow: 0 10px 50px rgba(27, 31, 35, 0.07) !important; -} - -.box-shadow-none { - box-shadow: none !important; -} - -/* Set the background to $bg-white */ -.bg-white { - background-color: #fff !important; -} - -/* Set the background to $bg-blue */ -.bg-blue { - background-color: #0366d6 !important; -} - -/* Set the background to $bg-blue-light */ -.bg-blue-light { - background-color: #f1f8ff !important; -} - -/* Set the background to $bg-gray-dark */ -.bg-gray-dark { - background-color: #24292e !important; -} - -/* Set the background to $bg-gray */ -.bg-gray { - background-color: #f6f8fa !important; -} - -/* Set the background to $bg-gray-light */ -.bg-gray-light { - background-color: #fafbfc !important; -} - -/* Set the background to $bg-green */ -.bg-green { - background-color: #28a745 !important; -} - -/* Set the background to $bg-green-light */ -.bg-green-light { - background-color: #dcffe4 !important; -} - -/* Set the background to $bg-red */ -.bg-red { - background-color: #d73a49 !important; -} - -/* Set the background to $bg-red-light */ -.bg-red-light { - background-color: #ffdce0 !important; -} - -/* Set the background to $bg-yellow */ -.bg-yellow { - background-color: #ffd33d !important; -} - -/* Set the background to $bg-yellow-light */ -.bg-yellow-light { - background-color: #fff5b1 !important; -} - -/* Set the background to $bg-purple */ -.bg-purple { - background-color: #6f42c1 !important; -} - -/* Set the background to $bg-purple-light */ -.bg-purple-light { - background-color: #f5f0ff !important; -} - -.bg-shade-gradient { - background-image: linear-gradient(180deg, rgba(27, 31, 35, 0.065), rgba(27, 31, 35, 0)) !important; - background-repeat: no-repeat !important; - background-size: 100% 200px !important; -} - -/* Set the text color to $text-blue */ -.text-blue { - color: #0366d6 !important; -} - -/* Set the text color to $text-red */ -.text-red { - color: #cb2431 !important; -} - -/* Set the text color to $text-gray-light */ -.text-gray-light { - color: #6a737d !important; -} - -/* Set the text color to $text-gray */ -.text-gray { - color: #586069 !important; -} - -/* Set the text color to $text-gray-dark */ -.text-gray-dark { - color: #24292e !important; -} - -/* Set the text color to $text-green */ -.text-green { - color: #28a745 !important; -} - -/* Set the text color to $text-orange */ -.text-orange { - color: #a04100 !important; -} - -/* Set the text color to $text-orange-light */ -.text-orange-light { - color: #e36209 !important; -} - -/* Set the text color to $text-purple */ -.text-purple { - color: #6f42c1 !important; -} - -/* Set the text color to $text-white */ -.text-white { - color: #fff !important; -} - -/* Set the text color to inherit */ -.text-inherit { - color: inherit !important; -} - -.text-pending { - color: #b08800 !important; -} - -.bg-pending { - color: #dbab09 !important; -} - -.link-gray { - color: #586069 !important; -} -.link-gray:hover { - color: #0366d6 !important; -} - -.link-gray-dark { - color: #24292e !important; -} -.link-gray-dark:hover { - color: #0366d6 !important; -} - -/* Set the link color to $text-blue on hover Useful when you want only part of a link to turn blue on hover */ -.link-hover-blue:hover { - color: #0366d6 !important; -} - -/* Make a link $text-gray, then $text-blue on hover and removes the underline */ -.muted-link { - color: #586069 !important; -} -.muted-link:hover { - color: #0366d6 !important; - text-decoration: none; -} - -.details-overlay[open] > summary::before { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 80; - display: block; - cursor: default; - content: " "; - background: transparent; -} - -.details-overlay-dark[open] > summary::before { - z-index: 99; - background: rgba(27, 31, 35, 0.5); -} - -.flex-row { - flex-direction: row !important; -} - -.flex-row-reverse { - flex-direction: row-reverse !important; -} - -.flex-column { - flex-direction: column !important; -} - -.flex-wrap { - flex-wrap: wrap !important; -} - -.flex-nowrap { - flex-wrap: nowrap !important; -} - -.flex-justify-start { - justify-content: flex-start !important; -} - -.flex-justify-end { - justify-content: flex-end !important; -} - -.flex-justify-center { - justify-content: center !important; -} - -.flex-justify-between { - justify-content: space-between !important; -} - -.flex-justify-around { - justify-content: space-around !important; -} - -.flex-items-start { - align-items: flex-start !important; -} - -.flex-items-end { - align-items: flex-end !important; -} - -.flex-items-center { - align-items: center !important; -} - -.flex-items-baseline { - align-items: baseline !important; -} - -.flex-items-stretch { - align-items: stretch !important; -} - -.flex-content-start { - align-content: flex-start !important; -} - -.flex-content-end { - align-content: flex-end !important; -} - -.flex-content-center { - align-content: center !important; -} - -.flex-content-between { - align-content: space-between !important; -} - -.flex-content-around { - align-content: space-around !important; -} - -.flex-content-stretch { - align-content: stretch !important; -} - -.flex-auto { - flex: 1 1 auto !important; -} - -.flex-shrink-0 { - flex-shrink: 0 !important; -} - -.flex-self-auto { - align-self: auto !important; -} - -.flex-self-start { - align-self: flex-start !important; -} - -.flex-self-end { - align-self: flex-end !important; -} - -.flex-self-center { - align-self: center !important; -} - -.flex-self-baseline { - align-self: baseline !important; -} - -.flex-self-stretch { - align-self: stretch !important; -} - -.flex-item-equal { - flex-grow: 1; - flex-basis: 0; -} - -@media (min-width: 544px) { - .flex-sm-row { - flex-direction: row !important; - } - .flex-sm-row-reverse { - flex-direction: row-reverse !important; - } - .flex-sm-column { - flex-direction: column !important; - } - .flex-sm-wrap { - flex-wrap: wrap !important; - } - .flex-sm-nowrap { - flex-wrap: nowrap !important; - } - .flex-sm-justify-start { - justify-content: flex-start !important; - } - .flex-sm-justify-end { - justify-content: flex-end !important; - } - .flex-sm-justify-center { - justify-content: center !important; - } - .flex-sm-justify-between { - justify-content: space-between !important; - } - .flex-sm-justify-around { - justify-content: space-around !important; - } - .flex-sm-items-start { - align-items: flex-start !important; - } - .flex-sm-items-end { - align-items: flex-end !important; - } - .flex-sm-items-center { - align-items: center !important; - } - .flex-sm-items-baseline { - align-items: baseline !important; - } - .flex-sm-items-stretch { - align-items: stretch !important; - } - .flex-sm-content-start { - align-content: flex-start !important; - } - .flex-sm-content-end { - align-content: flex-end !important; - } - .flex-sm-content-center { - align-content: center !important; - } - .flex-sm-content-between { - align-content: space-between !important; - } - .flex-sm-content-around { - align-content: space-around !important; - } - .flex-sm-content-stretch { - align-content: stretch !important; - } - .flex-sm-auto { - flex: 1 1 auto !important; - } - .flex-sm-shrink-0 { - flex-shrink: 0 !important; - } - .flex-sm-self-auto { - align-self: auto !important; - } - .flex-sm-self-start { - align-self: flex-start !important; - } - .flex-sm-self-end { - align-self: flex-end !important; - } - .flex-sm-self-center { - align-self: center !important; - } - .flex-sm-self-baseline { - align-self: baseline !important; - } - .flex-sm-self-stretch { - align-self: stretch !important; - } - .flex-sm-item-equal { - flex-grow: 1; - flex-basis: 0; - } -} -@media (min-width: 768px) { - .flex-md-row { - flex-direction: row !important; - } - .flex-md-row-reverse { - flex-direction: row-reverse !important; - } - .flex-md-column { - flex-direction: column !important; - } - .flex-md-wrap { - flex-wrap: wrap !important; - } - .flex-md-nowrap { - flex-wrap: nowrap !important; - } - .flex-md-justify-start { - justify-content: flex-start !important; - } - .flex-md-justify-end { - justify-content: flex-end !important; - } - .flex-md-justify-center { - justify-content: center !important; - } - .flex-md-justify-between { - justify-content: space-between !important; - } - .flex-md-justify-around { - justify-content: space-around !important; - } - .flex-md-items-start { - align-items: flex-start !important; - } - .flex-md-items-end { - align-items: flex-end !important; - } - .flex-md-items-center { - align-items: center !important; - } - .flex-md-items-baseline { - align-items: baseline !important; - } - .flex-md-items-stretch { - align-items: stretch !important; - } - .flex-md-content-start { - align-content: flex-start !important; - } - .flex-md-content-end { - align-content: flex-end !important; - } - .flex-md-content-center { - align-content: center !important; - } - .flex-md-content-between { - align-content: space-between !important; - } - .flex-md-content-around { - align-content: space-around !important; - } - .flex-md-content-stretch { - align-content: stretch !important; - } - .flex-md-auto { - flex: 1 1 auto !important; - } - .flex-md-shrink-0 { - flex-shrink: 0 !important; - } - .flex-md-self-auto { - align-self: auto !important; - } - .flex-md-self-start { - align-self: flex-start !important; - } - .flex-md-self-end { - align-self: flex-end !important; - } - .flex-md-self-center { - align-self: center !important; - } - .flex-md-self-baseline { - align-self: baseline !important; - } - .flex-md-self-stretch { - align-self: stretch !important; - } - .flex-md-item-equal { - flex-grow: 1; - flex-basis: 0; - } -} -@media (min-width: 1012px) { - .flex-lg-row { - flex-direction: row !important; - } - .flex-lg-row-reverse { - flex-direction: row-reverse !important; - } - .flex-lg-column { - flex-direction: column !important; - } - .flex-lg-wrap { - flex-wrap: wrap !important; - } - .flex-lg-nowrap { - flex-wrap: nowrap !important; - } - .flex-lg-justify-start { - justify-content: flex-start !important; - } - .flex-lg-justify-end { - justify-content: flex-end !important; - } - .flex-lg-justify-center { - justify-content: center !important; - } - .flex-lg-justify-between { - justify-content: space-between !important; - } - .flex-lg-justify-around { - justify-content: space-around !important; - } - .flex-lg-items-start { - align-items: flex-start !important; - } - .flex-lg-items-end { - align-items: flex-end !important; - } - .flex-lg-items-center { - align-items: center !important; - } - .flex-lg-items-baseline { - align-items: baseline !important; - } - .flex-lg-items-stretch { - align-items: stretch !important; - } - .flex-lg-content-start { - align-content: flex-start !important; - } - .flex-lg-content-end { - align-content: flex-end !important; - } - .flex-lg-content-center { - align-content: center !important; - } - .flex-lg-content-between { - align-content: space-between !important; - } - .flex-lg-content-around { - align-content: space-around !important; - } - .flex-lg-content-stretch { - align-content: stretch !important; - } - .flex-lg-auto { - flex: 1 1 auto !important; - } - .flex-lg-shrink-0 { - flex-shrink: 0 !important; - } - .flex-lg-self-auto { - align-self: auto !important; - } - .flex-lg-self-start { - align-self: flex-start !important; - } - .flex-lg-self-end { - align-self: flex-end !important; - } - .flex-lg-self-center { - align-self: center !important; - } - .flex-lg-self-baseline { - align-self: baseline !important; - } - .flex-lg-self-stretch { - align-self: stretch !important; - } - .flex-lg-item-equal { - flex-grow: 1; - flex-basis: 0; - } -} -@media (min-width: 1280px) { - .flex-xl-row { - flex-direction: row !important; - } - .flex-xl-row-reverse { - flex-direction: row-reverse !important; - } - .flex-xl-column { - flex-direction: column !important; - } - .flex-xl-wrap { - flex-wrap: wrap !important; - } - .flex-xl-nowrap { - flex-wrap: nowrap !important; - } - .flex-xl-justify-start { - justify-content: flex-start !important; - } - .flex-xl-justify-end { - justify-content: flex-end !important; - } - .flex-xl-justify-center { - justify-content: center !important; - } - .flex-xl-justify-between { - justify-content: space-between !important; - } - .flex-xl-justify-around { - justify-content: space-around !important; - } - .flex-xl-items-start { - align-items: flex-start !important; - } - .flex-xl-items-end { - align-items: flex-end !important; - } - .flex-xl-items-center { - align-items: center !important; - } - .flex-xl-items-baseline { - align-items: baseline !important; - } - .flex-xl-items-stretch { - align-items: stretch !important; - } - .flex-xl-content-start { - align-content: flex-start !important; - } - .flex-xl-content-end { - align-content: flex-end !important; - } - .flex-xl-content-center { - align-content: center !important; - } - .flex-xl-content-between { - align-content: space-between !important; - } - .flex-xl-content-around { - align-content: space-around !important; - } - .flex-xl-content-stretch { - align-content: stretch !important; - } - .flex-xl-auto { - flex: 1 1 auto !important; - } - .flex-xl-shrink-0 { - flex-shrink: 0 !important; - } - .flex-xl-self-auto { - align-self: auto !important; - } - .flex-xl-self-start { - align-self: flex-start !important; - } - .flex-xl-self-end { - align-self: flex-end !important; - } - .flex-xl-self-center { - align-self: center !important; - } - .flex-xl-self-baseline { - align-self: baseline !important; - } - .flex-xl-self-stretch { - align-self: stretch !important; - } - .flex-xl-item-equal { - flex-grow: 1; - flex-basis: 0; - } -} -/* Set position to static */ -.position-static { - position: static !important; -} - -/* Set position to relative */ -.position-relative { - position: relative !important; -} - -/* Set position to absolute */ -.position-absolute { - position: absolute !important; -} - -/* Set position to fixed */ -.position-fixed { - position: fixed !important; -} - -/* Set top 0 */ -.top-0 { - top: 0 !important; -} - -/* Set right 0 */ -.right-0 { - right: 0 !important; -} - -/* Set bottom 0 */ -.bottom-0 { - bottom: 0 !important; -} - -/* Set left 0 */ -.left-0 { - left: 0 !important; -} - -/* Vertical align middle */ -.v-align-middle { - vertical-align: middle !important; -} - -/* Vertical align top */ -.v-align-top { - vertical-align: top !important; -} - -/* Vertical align bottom */ -.v-align-bottom { - vertical-align: bottom !important; -} - -/* Vertical align to the top of the text */ -.v-align-text-top { - vertical-align: text-top !important; -} - -/* Vertical align to the bottom of the text */ -.v-align-text-bottom { - vertical-align: text-bottom !important; -} - -/* Vertical align to the parent's baseline */ -.v-align-baseline { - vertical-align: baseline !important; -} - -/* Set the overflow hidden */ -.overflow-hidden { - overflow: hidden !important; -} - -/* Set the overflow scroll */ -.overflow-scroll { - overflow: scroll !important; -} - -/* Set the overflow auto */ -.overflow-auto { - overflow: auto !important; -} - -/* Clear floats around the element */ -.clearfix::before { - display: table; - content: ""; -} -.clearfix::after { - display: table; - clear: both; - content: ""; -} - -/* Float to the left */ -.float-left { - float: left !important; -} - -/* Float to the right */ -.float-right { - float: right !important; -} - -/* No float */ -.float-none { - float: none !important; -} - -@media (min-width: 544px) { - /* Float to the left */ - .float-sm-left { - float: left !important; - } - /* Float to the right */ - .float-sm-right { - float: right !important; - } - /* No float */ - .float-sm-none { - float: none !important; - } -} -@media (min-width: 768px) { - /* Float to the left */ - .float-md-left { - float: left !important; - } - /* Float to the right */ - .float-md-right { - float: right !important; - } - /* No float */ - .float-md-none { - float: none !important; - } -} -@media (min-width: 1012px) { - /* Float to the left */ - .float-lg-left { - float: left !important; - } - /* Float to the right */ - .float-lg-right { - float: right !important; - } - /* No float */ - .float-lg-none { - float: none !important; - } -} -@media (min-width: 1280px) { - /* Float to the left */ - .float-xl-left { - float: left !important; - } - /* Float to the right */ - .float-xl-right { - float: right !important; - } - /* No float */ - .float-xl-none { - float: none !important; - } -} -/* Max width 100% */ -.width-fit { - max-width: 100% !important; -} - -/* Set the width to 100% */ -.width-full { - width: 100% !important; -} - -/* Max height 100% */ -.height-fit { - max-height: 100% !important; -} - -/* Set the height to 100% */ -.height-full { - height: 100% !important; -} - -/* Remove min-width from element */ -.min-width-0 { - min-width: 0 !important; -} - -/* Set the direction to rtl */ -.direction-rtl { - direction: rtl !important; -} - -/* Set the direction to ltr */ -.direction-ltr { - direction: ltr !important; -} - -@media (min-width: 544px) { - /* Set the direction to rtl */ - .direction-sm-rtl { - direction: rtl !important; - } - /* Set the direction to ltr */ - .direction-sm-ltr { - direction: ltr !important; - } -} -@media (min-width: 768px) { - /* Set the direction to rtl */ - .direction-md-rtl { - direction: rtl !important; - } - /* Set the direction to ltr */ - .direction-md-ltr { - direction: ltr !important; - } -} -@media (min-width: 1012px) { - /* Set the direction to rtl */ - .direction-lg-rtl { - direction: rtl !important; - } - /* Set the direction to ltr */ - .direction-lg-ltr { - direction: ltr !important; - } -} -@media (min-width: 1280px) { - /* Set the direction to rtl */ - .direction-xl-rtl { - direction: rtl !important; - } - /* Set the direction to ltr */ - .direction-xl-ltr { - direction: ltr !important; - } -} -/* Set a $size margin to all sides at $breakpoint */ -.m-0 { - margin: 0 !important; -} - -/* Set a $size margin on the top at $breakpoint */ -.mt-0 { - margin-top: 0 !important; -} - -/* Set a $size margin on the right at $breakpoint */ -.mr-0 { - margin-right: 0 !important; -} - -/* Set a $size margin on the bottom at $breakpoint */ -.mb-0 { - margin-bottom: 0 !important; -} - -/* Set a $size margin on the left at $breakpoint */ -.ml-0 { - margin-left: 0 !important; -} - -/* Set a $size margin on the left & right at $breakpoint */ -.mx-0 { - margin-right: 0 !important; - margin-left: 0 !important; -} - -/* Set a $size margin on the top & bottom at $breakpoint */ -.my-0 { - margin-top: 0 !important; - margin-bottom: 0 !important; -} - -/* Set a $size margin to all sides at $breakpoint */ -.m-1 { - margin: 4px !important; -} - -/* Set a $size margin on the top at $breakpoint */ -.mt-1 { - margin-top: 4px !important; -} - -/* Set a $size margin on the right at $breakpoint */ -.mr-1 { - margin-right: 4px !important; -} - -/* Set a $size margin on the bottom at $breakpoint */ -.mb-1 { - margin-bottom: 4px !important; -} - -/* Set a $size margin on the left at $breakpoint */ -.ml-1 { - margin-left: 4px !important; -} - -/* Set a negative $size margin on top at $breakpoint */ -.mt-n1 { - margin-top: -4px !important; -} - -/* Set a negative $size margin on the right at $breakpoint */ -.mr-n1 { - margin-right: -4px !important; -} - -/* Set a negative $size margin on the bottom at $breakpoint */ -.mb-n1 { - margin-bottom: -4px !important; -} - -/* Set a negative $size margin on the left at $breakpoint */ -.ml-n1 { - margin-left: -4px !important; -} - -/* Set a $size margin on the left & right at $breakpoint */ -.mx-1 { - margin-right: 4px !important; - margin-left: 4px !important; -} - -/* Set a $size margin on the top & bottom at $breakpoint */ -.my-1 { - margin-top: 4px !important; - margin-bottom: 4px !important; -} - -/* Set a $size margin to all sides at $breakpoint */ -.m-2 { - margin: 8px !important; -} - -/* Set a $size margin on the top at $breakpoint */ -.mt-2 { - margin-top: 8px !important; -} - -/* Set a $size margin on the right at $breakpoint */ -.mr-2 { - margin-right: 8px !important; -} - -/* Set a $size margin on the bottom at $breakpoint */ -.mb-2 { - margin-bottom: 8px !important; -} - -/* Set a $size margin on the left at $breakpoint */ -.ml-2 { - margin-left: 8px !important; -} - -/* Set a negative $size margin on top at $breakpoint */ -.mt-n2 { - margin-top: -8px !important; -} - -/* Set a negative $size margin on the right at $breakpoint */ -.mr-n2 { - margin-right: -8px !important; -} - -/* Set a negative $size margin on the bottom at $breakpoint */ -.mb-n2 { - margin-bottom: -8px !important; -} - -/* Set a negative $size margin on the left at $breakpoint */ -.ml-n2 { - margin-left: -8px !important; -} - -/* Set a $size margin on the left & right at $breakpoint */ -.mx-2 { - margin-right: 8px !important; - margin-left: 8px !important; -} - -/* Set a $size margin on the top & bottom at $breakpoint */ -.my-2 { - margin-top: 8px !important; - margin-bottom: 8px !important; -} - -/* Set a $size margin to all sides at $breakpoint */ -.m-3 { - margin: 16px !important; -} - -/* Set a $size margin on the top at $breakpoint */ -.mt-3 { - margin-top: 16px !important; -} - -/* Set a $size margin on the right at $breakpoint */ -.mr-3 { - margin-right: 16px !important; -} - -/* Set a $size margin on the bottom at $breakpoint */ -.mb-3 { - margin-bottom: 16px !important; -} - -/* Set a $size margin on the left at $breakpoint */ -.ml-3 { - margin-left: 16px !important; -} - -/* Set a negative $size margin on top at $breakpoint */ -.mt-n3 { - margin-top: -16px !important; -} - -/* Set a negative $size margin on the right at $breakpoint */ -.mr-n3 { - margin-right: -16px !important; -} - -/* Set a negative $size margin on the bottom at $breakpoint */ -.mb-n3 { - margin-bottom: -16px !important; -} - -/* Set a negative $size margin on the left at $breakpoint */ -.ml-n3 { - margin-left: -16px !important; -} - -/* Set a $size margin on the left & right at $breakpoint */ -.mx-3 { - margin-right: 16px !important; - margin-left: 16px !important; -} - -/* Set a $size margin on the top & bottom at $breakpoint */ -.my-3 { - margin-top: 16px !important; - margin-bottom: 16px !important; -} - -/* Set a $size margin to all sides at $breakpoint */ -.m-4 { - margin: 24px !important; -} - -/* Set a $size margin on the top at $breakpoint */ -.mt-4 { - margin-top: 24px !important; -} - -/* Set a $size margin on the right at $breakpoint */ -.mr-4 { - margin-right: 24px !important; -} - -/* Set a $size margin on the bottom at $breakpoint */ -.mb-4 { - margin-bottom: 24px !important; -} - -/* Set a $size margin on the left at $breakpoint */ -.ml-4 { - margin-left: 24px !important; -} - -/* Set a negative $size margin on top at $breakpoint */ -.mt-n4 { - margin-top: -24px !important; -} - -/* Set a negative $size margin on the right at $breakpoint */ -.mr-n4 { - margin-right: -24px !important; -} - -/* Set a negative $size margin on the bottom at $breakpoint */ -.mb-n4 { - margin-bottom: -24px !important; -} - -/* Set a negative $size margin on the left at $breakpoint */ -.ml-n4 { - margin-left: -24px !important; -} - -/* Set a $size margin on the left & right at $breakpoint */ -.mx-4 { - margin-right: 24px !important; - margin-left: 24px !important; -} - -/* Set a $size margin on the top & bottom at $breakpoint */ -.my-4 { - margin-top: 24px !important; - margin-bottom: 24px !important; -} - -/* Set a $size margin to all sides at $breakpoint */ -.m-5 { - margin: 32px !important; -} - -/* Set a $size margin on the top at $breakpoint */ -.mt-5 { - margin-top: 32px !important; -} - -/* Set a $size margin on the right at $breakpoint */ -.mr-5 { - margin-right: 32px !important; -} - -/* Set a $size margin on the bottom at $breakpoint */ -.mb-5 { - margin-bottom: 32px !important; -} - -/* Set a $size margin on the left at $breakpoint */ -.ml-5 { - margin-left: 32px !important; -} - -/* Set a negative $size margin on top at $breakpoint */ -.mt-n5 { - margin-top: -32px !important; -} - -/* Set a negative $size margin on the right at $breakpoint */ -.mr-n5 { - margin-right: -32px !important; -} - -/* Set a negative $size margin on the bottom at $breakpoint */ -.mb-n5 { - margin-bottom: -32px !important; -} - -/* Set a negative $size margin on the left at $breakpoint */ -.ml-n5 { - margin-left: -32px !important; -} - -/* Set a $size margin on the left & right at $breakpoint */ -.mx-5 { - margin-right: 32px !important; - margin-left: 32px !important; -} - -/* Set a $size margin on the top & bottom at $breakpoint */ -.my-5 { - margin-top: 32px !important; - margin-bottom: 32px !important; -} - -/* Set a $size margin to all sides at $breakpoint */ -.m-6 { - margin: 40px !important; -} - -/* Set a $size margin on the top at $breakpoint */ -.mt-6 { - margin-top: 40px !important; -} - -/* Set a $size margin on the right at $breakpoint */ -.mr-6 { - margin-right: 40px !important; -} - -/* Set a $size margin on the bottom at $breakpoint */ -.mb-6 { - margin-bottom: 40px !important; -} - -/* Set a $size margin on the left at $breakpoint */ -.ml-6 { - margin-left: 40px !important; -} - -/* Set a negative $size margin on top at $breakpoint */ -.mt-n6 { - margin-top: -40px !important; -} - -/* Set a negative $size margin on the right at $breakpoint */ -.mr-n6 { - margin-right: -40px !important; -} - -/* Set a negative $size margin on the bottom at $breakpoint */ -.mb-n6 { - margin-bottom: -40px !important; -} - -/* Set a negative $size margin on the left at $breakpoint */ -.ml-n6 { - margin-left: -40px !important; -} - -/* Set a $size margin on the left & right at $breakpoint */ -.mx-6 { - margin-right: 40px !important; - margin-left: 40px !important; -} - -/* Set a $size margin on the top & bottom at $breakpoint */ -.my-6 { - margin-top: 40px !important; - margin-bottom: 40px !important; -} - -/* responsive horizontal auto margins */ -.mx-auto { - margin-right: auto !important; - margin-left: auto !important; -} - -@media (min-width: 544px) { - /* Set a $size margin to all sides at $breakpoint */ - .m-sm-0 { - margin: 0 !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-sm-0 { - margin-top: 0 !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-sm-0 { - margin-right: 0 !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-sm-0 { - margin-bottom: 0 !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-sm-0 { - margin-left: 0 !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-sm-0 { - margin-right: 0 !important; - margin-left: 0 !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-sm-0 { - margin-top: 0 !important; - margin-bottom: 0 !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-sm-1 { - margin: 4px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-sm-1 { - margin-top: 4px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-sm-1 { - margin-right: 4px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-sm-1 { - margin-bottom: 4px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-sm-1 { - margin-left: 4px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-sm-n1 { - margin-top: -4px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-sm-n1 { - margin-right: -4px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-sm-n1 { - margin-bottom: -4px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-sm-n1 { - margin-left: -4px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-sm-1 { - margin-right: 4px !important; - margin-left: 4px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-sm-1 { - margin-top: 4px !important; - margin-bottom: 4px !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-sm-2 { - margin: 8px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-sm-2 { - margin-top: 8px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-sm-2 { - margin-right: 8px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-sm-2 { - margin-bottom: 8px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-sm-2 { - margin-left: 8px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-sm-n2 { - margin-top: -8px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-sm-n2 { - margin-right: -8px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-sm-n2 { - margin-bottom: -8px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-sm-n2 { - margin-left: -8px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-sm-2 { - margin-right: 8px !important; - margin-left: 8px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-sm-2 { - margin-top: 8px !important; - margin-bottom: 8px !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-sm-3 { - margin: 16px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-sm-3 { - margin-top: 16px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-sm-3 { - margin-right: 16px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-sm-3 { - margin-bottom: 16px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-sm-3 { - margin-left: 16px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-sm-n3 { - margin-top: -16px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-sm-n3 { - margin-right: -16px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-sm-n3 { - margin-bottom: -16px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-sm-n3 { - margin-left: -16px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-sm-3 { - margin-right: 16px !important; - margin-left: 16px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-sm-3 { - margin-top: 16px !important; - margin-bottom: 16px !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-sm-4 { - margin: 24px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-sm-4 { - margin-top: 24px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-sm-4 { - margin-right: 24px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-sm-4 { - margin-bottom: 24px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-sm-4 { - margin-left: 24px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-sm-n4 { - margin-top: -24px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-sm-n4 { - margin-right: -24px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-sm-n4 { - margin-bottom: -24px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-sm-n4 { - margin-left: -24px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-sm-4 { - margin-right: 24px !important; - margin-left: 24px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-sm-4 { - margin-top: 24px !important; - margin-bottom: 24px !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-sm-5 { - margin: 32px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-sm-5 { - margin-top: 32px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-sm-5 { - margin-right: 32px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-sm-5 { - margin-bottom: 32px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-sm-5 { - margin-left: 32px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-sm-n5 { - margin-top: -32px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-sm-n5 { - margin-right: -32px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-sm-n5 { - margin-bottom: -32px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-sm-n5 { - margin-left: -32px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-sm-5 { - margin-right: 32px !important; - margin-left: 32px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-sm-5 { - margin-top: 32px !important; - margin-bottom: 32px !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-sm-6 { - margin: 40px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-sm-6 { - margin-top: 40px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-sm-6 { - margin-right: 40px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-sm-6 { - margin-bottom: 40px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-sm-6 { - margin-left: 40px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-sm-n6 { - margin-top: -40px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-sm-n6 { - margin-right: -40px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-sm-n6 { - margin-bottom: -40px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-sm-n6 { - margin-left: -40px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-sm-6 { - margin-right: 40px !important; - margin-left: 40px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-sm-6 { - margin-top: 40px !important; - margin-bottom: 40px !important; - } - /* responsive horizontal auto margins */ - .mx-sm-auto { - margin-right: auto !important; - margin-left: auto !important; - } -} -@media (min-width: 768px) { - /* Set a $size margin to all sides at $breakpoint */ - .m-md-0 { - margin: 0 !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-md-0 { - margin-top: 0 !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-md-0 { - margin-right: 0 !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-md-0 { - margin-bottom: 0 !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-md-0 { - margin-left: 0 !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-md-0 { - margin-right: 0 !important; - margin-left: 0 !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-md-0 { - margin-top: 0 !important; - margin-bottom: 0 !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-md-1 { - margin: 4px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-md-1 { - margin-top: 4px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-md-1 { - margin-right: 4px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-md-1 { - margin-bottom: 4px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-md-1 { - margin-left: 4px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-md-n1 { - margin-top: -4px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-md-n1 { - margin-right: -4px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-md-n1 { - margin-bottom: -4px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-md-n1 { - margin-left: -4px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-md-1 { - margin-right: 4px !important; - margin-left: 4px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-md-1 { - margin-top: 4px !important; - margin-bottom: 4px !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-md-2 { - margin: 8px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-md-2 { - margin-top: 8px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-md-2 { - margin-right: 8px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-md-2 { - margin-bottom: 8px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-md-2 { - margin-left: 8px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-md-n2 { - margin-top: -8px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-md-n2 { - margin-right: -8px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-md-n2 { - margin-bottom: -8px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-md-n2 { - margin-left: -8px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-md-2 { - margin-right: 8px !important; - margin-left: 8px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-md-2 { - margin-top: 8px !important; - margin-bottom: 8px !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-md-3 { - margin: 16px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-md-3 { - margin-top: 16px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-md-3 { - margin-right: 16px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-md-3 { - margin-bottom: 16px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-md-3 { - margin-left: 16px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-md-n3 { - margin-top: -16px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-md-n3 { - margin-right: -16px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-md-n3 { - margin-bottom: -16px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-md-n3 { - margin-left: -16px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-md-3 { - margin-right: 16px !important; - margin-left: 16px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-md-3 { - margin-top: 16px !important; - margin-bottom: 16px !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-md-4 { - margin: 24px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-md-4 { - margin-top: 24px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-md-4 { - margin-right: 24px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-md-4 { - margin-bottom: 24px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-md-4 { - margin-left: 24px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-md-n4 { - margin-top: -24px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-md-n4 { - margin-right: -24px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-md-n4 { - margin-bottom: -24px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-md-n4 { - margin-left: -24px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-md-4 { - margin-right: 24px !important; - margin-left: 24px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-md-4 { - margin-top: 24px !important; - margin-bottom: 24px !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-md-5 { - margin: 32px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-md-5 { - margin-top: 32px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-md-5 { - margin-right: 32px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-md-5 { - margin-bottom: 32px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-md-5 { - margin-left: 32px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-md-n5 { - margin-top: -32px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-md-n5 { - margin-right: -32px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-md-n5 { - margin-bottom: -32px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-md-n5 { - margin-left: -32px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-md-5 { - margin-right: 32px !important; - margin-left: 32px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-md-5 { - margin-top: 32px !important; - margin-bottom: 32px !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-md-6 { - margin: 40px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-md-6 { - margin-top: 40px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-md-6 { - margin-right: 40px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-md-6 { - margin-bottom: 40px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-md-6 { - margin-left: 40px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-md-n6 { - margin-top: -40px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-md-n6 { - margin-right: -40px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-md-n6 { - margin-bottom: -40px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-md-n6 { - margin-left: -40px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-md-6 { - margin-right: 40px !important; - margin-left: 40px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-md-6 { - margin-top: 40px !important; - margin-bottom: 40px !important; - } - /* responsive horizontal auto margins */ - .mx-md-auto { - margin-right: auto !important; - margin-left: auto !important; - } -} -@media (min-width: 1012px) { - /* Set a $size margin to all sides at $breakpoint */ - .m-lg-0 { - margin: 0 !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-lg-0 { - margin-top: 0 !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-lg-0 { - margin-right: 0 !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-lg-0 { - margin-bottom: 0 !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-lg-0 { - margin-left: 0 !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-lg-0 { - margin-right: 0 !important; - margin-left: 0 !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-lg-0 { - margin-top: 0 !important; - margin-bottom: 0 !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-lg-1 { - margin: 4px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-lg-1 { - margin-top: 4px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-lg-1 { - margin-right: 4px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-lg-1 { - margin-bottom: 4px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-lg-1 { - margin-left: 4px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-lg-n1 { - margin-top: -4px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-lg-n1 { - margin-right: -4px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-lg-n1 { - margin-bottom: -4px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-lg-n1 { - margin-left: -4px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-lg-1 { - margin-right: 4px !important; - margin-left: 4px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-lg-1 { - margin-top: 4px !important; - margin-bottom: 4px !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-lg-2 { - margin: 8px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-lg-2 { - margin-top: 8px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-lg-2 { - margin-right: 8px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-lg-2 { - margin-bottom: 8px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-lg-2 { - margin-left: 8px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-lg-n2 { - margin-top: -8px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-lg-n2 { - margin-right: -8px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-lg-n2 { - margin-bottom: -8px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-lg-n2 { - margin-left: -8px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-lg-2 { - margin-right: 8px !important; - margin-left: 8px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-lg-2 { - margin-top: 8px !important; - margin-bottom: 8px !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-lg-3 { - margin: 16px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-lg-3 { - margin-top: 16px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-lg-3 { - margin-right: 16px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-lg-3 { - margin-bottom: 16px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-lg-3 { - margin-left: 16px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-lg-n3 { - margin-top: -16px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-lg-n3 { - margin-right: -16px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-lg-n3 { - margin-bottom: -16px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-lg-n3 { - margin-left: -16px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-lg-3 { - margin-right: 16px !important; - margin-left: 16px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-lg-3 { - margin-top: 16px !important; - margin-bottom: 16px !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-lg-4 { - margin: 24px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-lg-4 { - margin-top: 24px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-lg-4 { - margin-right: 24px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-lg-4 { - margin-bottom: 24px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-lg-4 { - margin-left: 24px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-lg-n4 { - margin-top: -24px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-lg-n4 { - margin-right: -24px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-lg-n4 { - margin-bottom: -24px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-lg-n4 { - margin-left: -24px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-lg-4 { - margin-right: 24px !important; - margin-left: 24px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-lg-4 { - margin-top: 24px !important; - margin-bottom: 24px !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-lg-5 { - margin: 32px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-lg-5 { - margin-top: 32px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-lg-5 { - margin-right: 32px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-lg-5 { - margin-bottom: 32px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-lg-5 { - margin-left: 32px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-lg-n5 { - margin-top: -32px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-lg-n5 { - margin-right: -32px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-lg-n5 { - margin-bottom: -32px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-lg-n5 { - margin-left: -32px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-lg-5 { - margin-right: 32px !important; - margin-left: 32px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-lg-5 { - margin-top: 32px !important; - margin-bottom: 32px !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-lg-6 { - margin: 40px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-lg-6 { - margin-top: 40px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-lg-6 { - margin-right: 40px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-lg-6 { - margin-bottom: 40px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-lg-6 { - margin-left: 40px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-lg-n6 { - margin-top: -40px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-lg-n6 { - margin-right: -40px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-lg-n6 { - margin-bottom: -40px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-lg-n6 { - margin-left: -40px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-lg-6 { - margin-right: 40px !important; - margin-left: 40px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-lg-6 { - margin-top: 40px !important; - margin-bottom: 40px !important; - } - /* responsive horizontal auto margins */ - .mx-lg-auto { - margin-right: auto !important; - margin-left: auto !important; - } -} -@media (min-width: 1280px) { - /* Set a $size margin to all sides at $breakpoint */ - .m-xl-0 { - margin: 0 !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-xl-0 { - margin-top: 0 !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-xl-0 { - margin-right: 0 !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-xl-0 { - margin-bottom: 0 !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-xl-0 { - margin-left: 0 !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-xl-0 { - margin-right: 0 !important; - margin-left: 0 !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-xl-0 { - margin-top: 0 !important; - margin-bottom: 0 !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-xl-1 { - margin: 4px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-xl-1 { - margin-top: 4px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-xl-1 { - margin-right: 4px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-xl-1 { - margin-bottom: 4px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-xl-1 { - margin-left: 4px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-xl-n1 { - margin-top: -4px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-xl-n1 { - margin-right: -4px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-xl-n1 { - margin-bottom: -4px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-xl-n1 { - margin-left: -4px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-xl-1 { - margin-right: 4px !important; - margin-left: 4px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-xl-1 { - margin-top: 4px !important; - margin-bottom: 4px !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-xl-2 { - margin: 8px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-xl-2 { - margin-top: 8px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-xl-2 { - margin-right: 8px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-xl-2 { - margin-bottom: 8px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-xl-2 { - margin-left: 8px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-xl-n2 { - margin-top: -8px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-xl-n2 { - margin-right: -8px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-xl-n2 { - margin-bottom: -8px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-xl-n2 { - margin-left: -8px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-xl-2 { - margin-right: 8px !important; - margin-left: 8px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-xl-2 { - margin-top: 8px !important; - margin-bottom: 8px !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-xl-3 { - margin: 16px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-xl-3 { - margin-top: 16px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-xl-3 { - margin-right: 16px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-xl-3 { - margin-bottom: 16px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-xl-3 { - margin-left: 16px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-xl-n3 { - margin-top: -16px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-xl-n3 { - margin-right: -16px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-xl-n3 { - margin-bottom: -16px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-xl-n3 { - margin-left: -16px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-xl-3 { - margin-right: 16px !important; - margin-left: 16px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-xl-3 { - margin-top: 16px !important; - margin-bottom: 16px !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-xl-4 { - margin: 24px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-xl-4 { - margin-top: 24px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-xl-4 { - margin-right: 24px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-xl-4 { - margin-bottom: 24px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-xl-4 { - margin-left: 24px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-xl-n4 { - margin-top: -24px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-xl-n4 { - margin-right: -24px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-xl-n4 { - margin-bottom: -24px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-xl-n4 { - margin-left: -24px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-xl-4 { - margin-right: 24px !important; - margin-left: 24px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-xl-4 { - margin-top: 24px !important; - margin-bottom: 24px !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-xl-5 { - margin: 32px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-xl-5 { - margin-top: 32px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-xl-5 { - margin-right: 32px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-xl-5 { - margin-bottom: 32px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-xl-5 { - margin-left: 32px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-xl-n5 { - margin-top: -32px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-xl-n5 { - margin-right: -32px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-xl-n5 { - margin-bottom: -32px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-xl-n5 { - margin-left: -32px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-xl-5 { - margin-right: 32px !important; - margin-left: 32px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-xl-5 { - margin-top: 32px !important; - margin-bottom: 32px !important; - } - /* Set a $size margin to all sides at $breakpoint */ - .m-xl-6 { - margin: 40px !important; - } - /* Set a $size margin on the top at $breakpoint */ - .mt-xl-6 { - margin-top: 40px !important; - } - /* Set a $size margin on the right at $breakpoint */ - .mr-xl-6 { - margin-right: 40px !important; - } - /* Set a $size margin on the bottom at $breakpoint */ - .mb-xl-6 { - margin-bottom: 40px !important; - } - /* Set a $size margin on the left at $breakpoint */ - .ml-xl-6 { - margin-left: 40px !important; - } - /* Set a negative $size margin on top at $breakpoint */ - .mt-xl-n6 { - margin-top: -40px !important; - } - /* Set a negative $size margin on the right at $breakpoint */ - .mr-xl-n6 { - margin-right: -40px !important; - } - /* Set a negative $size margin on the bottom at $breakpoint */ - .mb-xl-n6 { - margin-bottom: -40px !important; - } - /* Set a negative $size margin on the left at $breakpoint */ - .ml-xl-n6 { - margin-left: -40px !important; - } - /* Set a $size margin on the left & right at $breakpoint */ - .mx-xl-6 { - margin-right: 40px !important; - margin-left: 40px !important; - } - /* Set a $size margin on the top & bottom at $breakpoint */ - .my-xl-6 { - margin-top: 40px !important; - margin-bottom: 40px !important; - } - /* responsive horizontal auto margins */ - .mx-xl-auto { - margin-right: auto !important; - margin-left: auto !important; - } -} -/* Set a $size padding to all sides at $breakpoint */ -.p-0 { - padding: 0 !important; -} - -/* Set a $size padding to the top at $breakpoint */ -.pt-0 { - padding-top: 0 !important; -} - -/* Set a $size padding to the right at $breakpoint */ -.pr-0 { - padding-right: 0 !important; -} - -/* Set a $size padding to the bottom at $breakpoint */ -.pb-0 { - padding-bottom: 0 !important; -} - -/* Set a $size padding to the left at $breakpoint */ -.pl-0 { - padding-left: 0 !important; -} - -/* Set a $size padding to the left & right at $breakpoint */ -.px-0 { - padding-right: 0 !important; - padding-left: 0 !important; -} - -/* Set a $size padding to the top & bottom at $breakpoint */ -.py-0 { - padding-top: 0 !important; - padding-bottom: 0 !important; -} - -/* Set a $size padding to all sides at $breakpoint */ -.p-1 { - padding: 4px !important; -} - -/* Set a $size padding to the top at $breakpoint */ -.pt-1 { - padding-top: 4px !important; -} - -/* Set a $size padding to the right at $breakpoint */ -.pr-1 { - padding-right: 4px !important; -} - -/* Set a $size padding to the bottom at $breakpoint */ -.pb-1 { - padding-bottom: 4px !important; -} - -/* Set a $size padding to the left at $breakpoint */ -.pl-1 { - padding-left: 4px !important; -} - -/* Set a $size padding to the left & right at $breakpoint */ -.px-1 { - padding-right: 4px !important; - padding-left: 4px !important; -} - -/* Set a $size padding to the top & bottom at $breakpoint */ -.py-1 { - padding-top: 4px !important; - padding-bottom: 4px !important; -} - -/* Set a $size padding to all sides at $breakpoint */ -.p-2 { - padding: 8px !important; -} - -/* Set a $size padding to the top at $breakpoint */ -.pt-2 { - padding-top: 8px !important; -} - -/* Set a $size padding to the right at $breakpoint */ -.pr-2 { - padding-right: 8px !important; -} - -/* Set a $size padding to the bottom at $breakpoint */ -.pb-2 { - padding-bottom: 8px !important; -} - -/* Set a $size padding to the left at $breakpoint */ -.pl-2 { - padding-left: 8px !important; -} - -/* Set a $size padding to the left & right at $breakpoint */ -.px-2 { - padding-right: 8px !important; - padding-left: 8px !important; -} - -/* Set a $size padding to the top & bottom at $breakpoint */ -.py-2 { - padding-top: 8px !important; - padding-bottom: 8px !important; -} - -/* Set a $size padding to all sides at $breakpoint */ -.p-3 { - padding: 16px !important; -} - -/* Set a $size padding to the top at $breakpoint */ -.pt-3 { - padding-top: 16px !important; -} - -/* Set a $size padding to the right at $breakpoint */ -.pr-3 { - padding-right: 16px !important; -} - -/* Set a $size padding to the bottom at $breakpoint */ -.pb-3 { - padding-bottom: 16px !important; -} - -/* Set a $size padding to the left at $breakpoint */ -.pl-3 { - padding-left: 16px !important; -} - -/* Set a $size padding to the left & right at $breakpoint */ -.px-3 { - padding-right: 16px !important; - padding-left: 16px !important; -} - -/* Set a $size padding to the top & bottom at $breakpoint */ -.py-3 { - padding-top: 16px !important; - padding-bottom: 16px !important; -} - -/* Set a $size padding to all sides at $breakpoint */ -.p-4 { - padding: 24px !important; -} - -/* Set a $size padding to the top at $breakpoint */ -.pt-4 { - padding-top: 24px !important; -} - -/* Set a $size padding to the right at $breakpoint */ -.pr-4 { - padding-right: 24px !important; -} - -/* Set a $size padding to the bottom at $breakpoint */ -.pb-4 { - padding-bottom: 24px !important; -} - -/* Set a $size padding to the left at $breakpoint */ -.pl-4 { - padding-left: 24px !important; -} - -/* Set a $size padding to the left & right at $breakpoint */ -.px-4 { - padding-right: 24px !important; - padding-left: 24px !important; -} - -/* Set a $size padding to the top & bottom at $breakpoint */ -.py-4 { - padding-top: 24px !important; - padding-bottom: 24px !important; -} - -/* Set a $size padding to all sides at $breakpoint */ -.p-5 { - padding: 32px !important; -} - -/* Set a $size padding to the top at $breakpoint */ -.pt-5 { - padding-top: 32px !important; -} - -/* Set a $size padding to the right at $breakpoint */ -.pr-5 { - padding-right: 32px !important; -} - -/* Set a $size padding to the bottom at $breakpoint */ -.pb-5 { - padding-bottom: 32px !important; -} - -/* Set a $size padding to the left at $breakpoint */ -.pl-5 { - padding-left: 32px !important; -} - -/* Set a $size padding to the left & right at $breakpoint */ -.px-5 { - padding-right: 32px !important; - padding-left: 32px !important; -} - -/* Set a $size padding to the top & bottom at $breakpoint */ -.py-5 { - padding-top: 32px !important; - padding-bottom: 32px !important; -} - -/* Set a $size padding to all sides at $breakpoint */ -.p-6 { - padding: 40px !important; -} - -/* Set a $size padding to the top at $breakpoint */ -.pt-6 { - padding-top: 40px !important; -} - -/* Set a $size padding to the right at $breakpoint */ -.pr-6 { - padding-right: 40px !important; -} - -/* Set a $size padding to the bottom at $breakpoint */ -.pb-6 { - padding-bottom: 40px !important; -} - -/* Set a $size padding to the left at $breakpoint */ -.pl-6 { - padding-left: 40px !important; -} - -/* Set a $size padding to the left & right at $breakpoint */ -.px-6 { - padding-right: 40px !important; - padding-left: 40px !important; -} - -/* Set a $size padding to the top & bottom at $breakpoint */ -.py-6 { - padding-top: 40px !important; - padding-bottom: 40px !important; -} - -@media (min-width: 544px) { - /* Set a $size padding to all sides at $breakpoint */ - .p-sm-0 { - padding: 0 !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-sm-0 { - padding-top: 0 !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-sm-0 { - padding-right: 0 !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-sm-0 { - padding-bottom: 0 !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-sm-0 { - padding-left: 0 !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-sm-0 { - padding-right: 0 !important; - padding-left: 0 !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-sm-0 { - padding-top: 0 !important; - padding-bottom: 0 !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-sm-1 { - padding: 4px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-sm-1 { - padding-top: 4px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-sm-1 { - padding-right: 4px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-sm-1 { - padding-bottom: 4px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-sm-1 { - padding-left: 4px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-sm-1 { - padding-right: 4px !important; - padding-left: 4px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-sm-1 { - padding-top: 4px !important; - padding-bottom: 4px !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-sm-2 { - padding: 8px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-sm-2 { - padding-top: 8px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-sm-2 { - padding-right: 8px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-sm-2 { - padding-bottom: 8px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-sm-2 { - padding-left: 8px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-sm-2 { - padding-right: 8px !important; - padding-left: 8px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-sm-2 { - padding-top: 8px !important; - padding-bottom: 8px !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-sm-3 { - padding: 16px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-sm-3 { - padding-top: 16px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-sm-3 { - padding-right: 16px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-sm-3 { - padding-bottom: 16px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-sm-3 { - padding-left: 16px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-sm-3 { - padding-right: 16px !important; - padding-left: 16px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-sm-3 { - padding-top: 16px !important; - padding-bottom: 16px !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-sm-4 { - padding: 24px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-sm-4 { - padding-top: 24px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-sm-4 { - padding-right: 24px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-sm-4 { - padding-bottom: 24px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-sm-4 { - padding-left: 24px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-sm-4 { - padding-right: 24px !important; - padding-left: 24px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-sm-4 { - padding-top: 24px !important; - padding-bottom: 24px !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-sm-5 { - padding: 32px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-sm-5 { - padding-top: 32px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-sm-5 { - padding-right: 32px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-sm-5 { - padding-bottom: 32px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-sm-5 { - padding-left: 32px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-sm-5 { - padding-right: 32px !important; - padding-left: 32px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-sm-5 { - padding-top: 32px !important; - padding-bottom: 32px !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-sm-6 { - padding: 40px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-sm-6 { - padding-top: 40px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-sm-6 { - padding-right: 40px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-sm-6 { - padding-bottom: 40px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-sm-6 { - padding-left: 40px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-sm-6 { - padding-right: 40px !important; - padding-left: 40px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-sm-6 { - padding-top: 40px !important; - padding-bottom: 40px !important; - } -} -@media (min-width: 768px) { - /* Set a $size padding to all sides at $breakpoint */ - .p-md-0 { - padding: 0 !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-md-0 { - padding-top: 0 !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-md-0 { - padding-right: 0 !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-md-0 { - padding-bottom: 0 !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-md-0 { - padding-left: 0 !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-md-0 { - padding-right: 0 !important; - padding-left: 0 !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-md-0 { - padding-top: 0 !important; - padding-bottom: 0 !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-md-1 { - padding: 4px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-md-1 { - padding-top: 4px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-md-1 { - padding-right: 4px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-md-1 { - padding-bottom: 4px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-md-1 { - padding-left: 4px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-md-1 { - padding-right: 4px !important; - padding-left: 4px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-md-1 { - padding-top: 4px !important; - padding-bottom: 4px !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-md-2 { - padding: 8px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-md-2 { - padding-top: 8px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-md-2 { - padding-right: 8px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-md-2 { - padding-bottom: 8px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-md-2 { - padding-left: 8px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-md-2 { - padding-right: 8px !important; - padding-left: 8px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-md-2 { - padding-top: 8px !important; - padding-bottom: 8px !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-md-3 { - padding: 16px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-md-3 { - padding-top: 16px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-md-3 { - padding-right: 16px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-md-3 { - padding-bottom: 16px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-md-3 { - padding-left: 16px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-md-3 { - padding-right: 16px !important; - padding-left: 16px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-md-3 { - padding-top: 16px !important; - padding-bottom: 16px !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-md-4 { - padding: 24px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-md-4 { - padding-top: 24px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-md-4 { - padding-right: 24px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-md-4 { - padding-bottom: 24px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-md-4 { - padding-left: 24px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-md-4 { - padding-right: 24px !important; - padding-left: 24px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-md-4 { - padding-top: 24px !important; - padding-bottom: 24px !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-md-5 { - padding: 32px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-md-5 { - padding-top: 32px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-md-5 { - padding-right: 32px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-md-5 { - padding-bottom: 32px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-md-5 { - padding-left: 32px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-md-5 { - padding-right: 32px !important; - padding-left: 32px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-md-5 { - padding-top: 32px !important; - padding-bottom: 32px !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-md-6 { - padding: 40px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-md-6 { - padding-top: 40px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-md-6 { - padding-right: 40px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-md-6 { - padding-bottom: 40px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-md-6 { - padding-left: 40px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-md-6 { - padding-right: 40px !important; - padding-left: 40px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-md-6 { - padding-top: 40px !important; - padding-bottom: 40px !important; - } -} -@media (min-width: 1012px) { - /* Set a $size padding to all sides at $breakpoint */ - .p-lg-0 { - padding: 0 !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-lg-0 { - padding-top: 0 !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-lg-0 { - padding-right: 0 !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-lg-0 { - padding-bottom: 0 !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-lg-0 { - padding-left: 0 !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-lg-0 { - padding-right: 0 !important; - padding-left: 0 !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-lg-0 { - padding-top: 0 !important; - padding-bottom: 0 !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-lg-1 { - padding: 4px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-lg-1 { - padding-top: 4px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-lg-1 { - padding-right: 4px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-lg-1 { - padding-bottom: 4px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-lg-1 { - padding-left: 4px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-lg-1 { - padding-right: 4px !important; - padding-left: 4px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-lg-1 { - padding-top: 4px !important; - padding-bottom: 4px !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-lg-2 { - padding: 8px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-lg-2 { - padding-top: 8px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-lg-2 { - padding-right: 8px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-lg-2 { - padding-bottom: 8px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-lg-2 { - padding-left: 8px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-lg-2 { - padding-right: 8px !important; - padding-left: 8px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-lg-2 { - padding-top: 8px !important; - padding-bottom: 8px !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-lg-3 { - padding: 16px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-lg-3 { - padding-top: 16px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-lg-3 { - padding-right: 16px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-lg-3 { - padding-bottom: 16px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-lg-3 { - padding-left: 16px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-lg-3 { - padding-right: 16px !important; - padding-left: 16px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-lg-3 { - padding-top: 16px !important; - padding-bottom: 16px !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-lg-4 { - padding: 24px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-lg-4 { - padding-top: 24px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-lg-4 { - padding-right: 24px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-lg-4 { - padding-bottom: 24px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-lg-4 { - padding-left: 24px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-lg-4 { - padding-right: 24px !important; - padding-left: 24px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-lg-4 { - padding-top: 24px !important; - padding-bottom: 24px !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-lg-5 { - padding: 32px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-lg-5 { - padding-top: 32px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-lg-5 { - padding-right: 32px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-lg-5 { - padding-bottom: 32px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-lg-5 { - padding-left: 32px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-lg-5 { - padding-right: 32px !important; - padding-left: 32px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-lg-5 { - padding-top: 32px !important; - padding-bottom: 32px !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-lg-6 { - padding: 40px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-lg-6 { - padding-top: 40px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-lg-6 { - padding-right: 40px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-lg-6 { - padding-bottom: 40px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-lg-6 { - padding-left: 40px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-lg-6 { - padding-right: 40px !important; - padding-left: 40px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-lg-6 { - padding-top: 40px !important; - padding-bottom: 40px !important; - } -} -@media (min-width: 1280px) { - /* Set a $size padding to all sides at $breakpoint */ - .p-xl-0 { - padding: 0 !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-xl-0 { - padding-top: 0 !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-xl-0 { - padding-right: 0 !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-xl-0 { - padding-bottom: 0 !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-xl-0 { - padding-left: 0 !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-xl-0 { - padding-right: 0 !important; - padding-left: 0 !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-xl-0 { - padding-top: 0 !important; - padding-bottom: 0 !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-xl-1 { - padding: 4px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-xl-1 { - padding-top: 4px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-xl-1 { - padding-right: 4px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-xl-1 { - padding-bottom: 4px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-xl-1 { - padding-left: 4px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-xl-1 { - padding-right: 4px !important; - padding-left: 4px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-xl-1 { - padding-top: 4px !important; - padding-bottom: 4px !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-xl-2 { - padding: 8px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-xl-2 { - padding-top: 8px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-xl-2 { - padding-right: 8px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-xl-2 { - padding-bottom: 8px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-xl-2 { - padding-left: 8px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-xl-2 { - padding-right: 8px !important; - padding-left: 8px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-xl-2 { - padding-top: 8px !important; - padding-bottom: 8px !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-xl-3 { - padding: 16px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-xl-3 { - padding-top: 16px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-xl-3 { - padding-right: 16px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-xl-3 { - padding-bottom: 16px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-xl-3 { - padding-left: 16px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-xl-3 { - padding-right: 16px !important; - padding-left: 16px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-xl-3 { - padding-top: 16px !important; - padding-bottom: 16px !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-xl-4 { - padding: 24px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-xl-4 { - padding-top: 24px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-xl-4 { - padding-right: 24px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-xl-4 { - padding-bottom: 24px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-xl-4 { - padding-left: 24px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-xl-4 { - padding-right: 24px !important; - padding-left: 24px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-xl-4 { - padding-top: 24px !important; - padding-bottom: 24px !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-xl-5 { - padding: 32px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-xl-5 { - padding-top: 32px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-xl-5 { - padding-right: 32px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-xl-5 { - padding-bottom: 32px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-xl-5 { - padding-left: 32px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-xl-5 { - padding-right: 32px !important; - padding-left: 32px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-xl-5 { - padding-top: 32px !important; - padding-bottom: 32px !important; - } - /* Set a $size padding to all sides at $breakpoint */ - .p-xl-6 { - padding: 40px !important; - } - /* Set a $size padding to the top at $breakpoint */ - .pt-xl-6 { - padding-top: 40px !important; - } - /* Set a $size padding to the right at $breakpoint */ - .pr-xl-6 { - padding-right: 40px !important; - } - /* Set a $size padding to the bottom at $breakpoint */ - .pb-xl-6 { - padding-bottom: 40px !important; - } - /* Set a $size padding to the left at $breakpoint */ - .pl-xl-6 { - padding-left: 40px !important; - } - /* Set a $size padding to the left & right at $breakpoint */ - .px-xl-6 { - padding-right: 40px !important; - padding-left: 40px !important; - } - /* Set a $size padding to the top & bottom at $breakpoint */ - .py-xl-6 { - padding-top: 40px !important; - padding-bottom: 40px !important; - } -} -.p-responsive { - padding-right: 16px !important; - padding-left: 16px !important; -} -@media (min-width: 544px) { - .p-responsive { - padding-right: 40px !important; - padding-left: 40px !important; - } -} -@media (min-width: 1012px) { - .p-responsive { - padding-right: 16px !important; - padding-left: 16px !important; - } -} - -/* Set the font size to 26px */ -.h1 { - font-size: 26px !important; -} -@media (min-width: 768px) { - .h1 { - font-size: 32px !important; - } -} - -/* Set the font size to 22px */ -.h2 { - font-size: 22px !important; -} -@media (min-width: 768px) { - .h2 { - font-size: 24px !important; - } -} - -/* Set the font size to 18px */ -.h3 { - font-size: 18px !important; -} -@media (min-width: 768px) { - .h3 { - font-size: 20px !important; - } -} - -/* Set the font size to 16px */ -.h4 { - font-size: 16px !important; -} - -/* Set the font size to 14px */ -.h5 { - font-size: 14px !important; -} - -/* Set the font size to 12px */ -.h6 { - font-size: 12px !important; -} - -.h1, -.h2, -.h3, -.h4, -.h5, -.h6 { - font-weight: 600 !important; -} - -/* Set the font size to 26px */ -.f1 { - font-size: 26px !important; -} -@media (min-width: 768px) { - .f1 { - font-size: 32px !important; - } -} - -/* Set the font size to 22px */ -.f2 { - font-size: 22px !important; -} -@media (min-width: 768px) { - .f2 { - font-size: 24px !important; - } -} - -/* Set the font size to 18px */ -.f3 { - font-size: 18px !important; -} -@media (min-width: 768px) { - .f3 { - font-size: 20px !important; - } -} - -/* Set the font size to 16px */ -.f4 { - font-size: 16px !important; -} -@media (min-width: 768px) { - .f4 { - font-size: 16px !important; - } -} - -/* Set the font size to 14px */ -.f5 { - font-size: 14px !important; -} - -/* Set the font size to 12px */ -.f6 { - font-size: 12px !important; -} - -/* Set the font size to 40px and weight to light */ -.f00-light { - font-size: 40px !important; - font-weight: 300 !important; -} -@media (min-width: 768px) { - .f00-light { - font-size: 48px !important; - } -} - -/* Set the font size to 32px and weight to light */ -.f0-light { - font-size: 32px !important; - font-weight: 300 !important; -} -@media (min-width: 768px) { - .f0-light { - font-size: 40px !important; - } -} - -/* Set the font size to 26px and weight to light */ -.f1-light { - font-size: 26px !important; - font-weight: 300 !important; -} -@media (min-width: 768px) { - .f1-light { - font-size: 32px !important; - } -} - -/* Set the font size to 22px and weight to light */ -.f2-light { - font-size: 22px !important; - font-weight: 300 !important; -} -@media (min-width: 768px) { - .f2-light { - font-size: 24px !important; - } -} - -/* Set the font size to 18px and weight to light */ -.f3-light { - font-size: 18px !important; - font-weight: 300 !important; -} -@media (min-width: 768px) { - .f3-light { - font-size: 20px !important; - } -} - -/* Set the font size to ${#h6-size} */ -.text-small { - font-size: 12px !important; -} - -/* Large leading paragraphs */ -.lead { - margin-bottom: 30px; - font-size: 20px; - font-weight: 300; - color: #586069; -} - -/* Set the line height to ultra condensed */ -.lh-condensed-ultra { - line-height: 1 !important; -} - -/* Set the line height to condensed */ -.lh-condensed { - line-height: 1.25 !important; -} - -/* Set the line height to default */ -.lh-default { - line-height: 1.5 !important; -} - -/* Set the line height to zero */ -.lh-0 { - line-height: 0 !important; -} - -/* Text align to the right */ -.text-right { - text-align: right !important; -} - -/* Text align to the left */ -.text-left { - text-align: left !important; -} - -/* Text align to the center */ -.text-center { - text-align: center !important; -} - -@media (min-width: 544px) { - /* Text align to the right */ - .text-sm-right { - text-align: right !important; - } - /* Text align to the left */ - .text-sm-left { - text-align: left !important; - } - /* Text align to the center */ - .text-sm-center { - text-align: center !important; - } -} -@media (min-width: 768px) { - /* Text align to the right */ - .text-md-right { - text-align: right !important; - } - /* Text align to the left */ - .text-md-left { - text-align: left !important; - } - /* Text align to the center */ - .text-md-center { - text-align: center !important; - } -} -@media (min-width: 1012px) { - /* Text align to the right */ - .text-lg-right { - text-align: right !important; - } - /* Text align to the left */ - .text-lg-left { - text-align: left !important; - } - /* Text align to the center */ - .text-lg-center { - text-align: center !important; - } -} -@media (min-width: 1280px) { - /* Text align to the right */ - .text-xl-right { - text-align: right !important; - } - /* Text align to the left */ - .text-xl-left { - text-align: left !important; - } - /* Text align to the center */ - .text-xl-center { - text-align: center !important; - } -} -/* Set the font weight to normal */ -.text-normal { - font-weight: 400 !important; -} - -/* Set the font weight to bold */ -.text-bold { - font-weight: 600 !important; -} - -/* Set the font to italic */ -.text-italic { - font-style: italic !important; -} - -/* Make text uppercase */ -.text-uppercase { - text-transform: uppercase !important; -} - -/* Underline text */ -.text-underline { - text-decoration: underline !important; -} - -/* Don't underline text */ -.no-underline { - text-decoration: none !important; -} - -/* Don't wrap white space */ -.no-wrap { - white-space: nowrap !important; -} - -/* Normal white space */ -.ws-normal { - white-space: normal !important; -} - -/* Allow long lines with no spaces to line break */ -.wb-break-all { - word-break: break-all !important; -} - -.text-emphasized { - font-weight: 600; - color: #24292e; -} - -.list-style-none { - list-style: none !important; -} - -/* Add a dark text shadow */ -.text-shadow-dark { - text-shadow: 0 1px 1px rgba(27, 31, 35, 0.25), 0 1px 25px rgba(27, 31, 35, 0.75); -} - -/* Add a light text shadow */ -.text-shadow-light { - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); -} - -/* Set to monospace font */ -.text-mono { - font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; -} - -/* Disallow user from selecting text */ -.user-select-none { - user-select: none !important; -} - -.d-block { - display: block !important; -} - -.d-flex { - display: flex !important; -} - -.d-inline { - display: inline !important; -} - -.d-inline-block { - display: inline-block !important; -} - -.d-inline-flex { - display: inline-flex !important; -} - -.d-none { - display: none !important; -} - -.d-table { - display: table !important; -} - -.d-table-cell { - display: table-cell !important; -} - -@media (min-width: 544px) { - .d-sm-block { - display: block !important; - } - .d-sm-flex { - display: flex !important; - } - .d-sm-inline { - display: inline !important; - } - .d-sm-inline-block { - display: inline-block !important; - } - .d-sm-inline-flex { - display: inline-flex !important; - } - .d-sm-none { - display: none !important; - } - .d-sm-table { - display: table !important; - } - .d-sm-table-cell { - display: table-cell !important; - } -} -@media (min-width: 768px) { - .d-md-block { - display: block !important; - } - .d-md-flex { - display: flex !important; - } - .d-md-inline { - display: inline !important; - } - .d-md-inline-block { - display: inline-block !important; - } - .d-md-inline-flex { - display: inline-flex !important; - } - .d-md-none { - display: none !important; - } - .d-md-table { - display: table !important; - } - .d-md-table-cell { - display: table-cell !important; - } -} -@media (min-width: 1012px) { - .d-lg-block { - display: block !important; - } - .d-lg-flex { - display: flex !important; - } - .d-lg-inline { - display: inline !important; - } - .d-lg-inline-block { - display: inline-block !important; - } - .d-lg-inline-flex { - display: inline-flex !important; - } - .d-lg-none { - display: none !important; - } - .d-lg-table { - display: table !important; - } - .d-lg-table-cell { - display: table-cell !important; - } -} -@media (min-width: 1280px) { - .d-xl-block { - display: block !important; - } - .d-xl-flex { - display: flex !important; - } - .d-xl-inline { - display: inline !important; - } - .d-xl-inline-block { - display: inline-block !important; - } - .d-xl-inline-flex { - display: inline-flex !important; - } - .d-xl-none { - display: none !important; - } - .d-xl-table { - display: table !important; - } - .d-xl-table-cell { - display: table-cell !important; - } -} -.v-hidden { - visibility: hidden !important; -} - -.v-visible { - visibility: visible !important; -} - -@media (max-width: 544px) { - .hide-sm { - display: none !important; - } -} -@media (min-width: 544px) and (max-width: 768px) { - .hide-md { - display: none !important; - } -} -@media (min-width: 768px) and (max-width: 1012px) { - .hide-lg { - display: none !important; - } -} -@media (min-width: 1012px) { - .hide-xl { - display: none !important; - } -} -/* Set the table-layout to fixed */ -.table-fixed { - table-layout: fixed !important; -} - -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - overflow: hidden; - clip: rect(0, 0, 0, 0); - word-wrap: normal; - border: 0; -} - -.show-on-focus { - position: absolute; - width: 1px; - height: 1px; - margin: 0; - overflow: hidden; - clip: rect(1px, 1px, 1px, 1px); -} -.show-on-focus:focus { - z-index: 20; - width: auto; - height: auto; - clip: auto; -} - -.container { - width: 980px; - margin-right: auto; - margin-left: auto; -} -.container::before { - display: table; - content: ""; -} -.container::after { - display: table; - clear: both; - content: ""; -} - -.container-md { - max-width: 768px; - margin-right: auto; - margin-left: auto; -} - -.container-lg { - max-width: 1012px; - margin-right: auto; - margin-left: auto; -} - -.container-xl { - max-width: 1280px; - margin-right: auto; - margin-left: auto; -} - -.columns { - margin-right: -10px; - margin-left: -10px; -} -.columns::before { - display: table; - content: ""; -} -.columns::after { - display: table; - clear: both; - content: ""; -} - -.column { - float: left; - padding-right: 10px; - padding-left: 10px; -} - -.one-third { - width: 33.333333%; -} - -.two-thirds { - width: 66.666667%; -} - -.one-fourth { - width: 25%; -} - -.one-half { - width: 50%; -} - -.three-fourths { - width: 75%; -} - -.one-fifth { - width: 20%; -} - -.four-fifths { - width: 80%; -} - -.centered { - display: block; - float: none; - margin-right: auto; - margin-left: auto; -} - -.col-1 { - width: 8.3333333333%; -} - -.col-2 { - width: 16.6666666667%; -} - -.col-3 { - width: 25%; -} - -.col-4 { - width: 33.3333333333%; -} - -.col-5 { - width: 41.6666666667%; -} - -.col-6 { - width: 50%; -} - -.col-7 { - width: 58.3333333333%; -} - -.col-8 { - width: 66.6666666667%; -} - -.col-9 { - width: 75%; -} - -.col-10 { - width: 83.3333333333%; -} - -.col-11 { - width: 91.6666666667%; -} - -.col-12 { - width: 100%; -} - -@media (min-width: 544px) { - .col-sm-1 { - width: 8.3333333333%; - } - .col-sm-2 { - width: 16.6666666667%; - } - .col-sm-3 { - width: 25%; - } - .col-sm-4 { - width: 33.3333333333%; - } - .col-sm-5 { - width: 41.6666666667%; - } - .col-sm-6 { - width: 50%; - } - .col-sm-7 { - width: 58.3333333333%; - } - .col-sm-8 { - width: 66.6666666667%; - } - .col-sm-9 { - width: 75%; - } - .col-sm-10 { - width: 83.3333333333%; - } - .col-sm-11 { - width: 91.6666666667%; - } - .col-sm-12 { - width: 100%; - } -} -@media (min-width: 768px) { - .col-md-1 { - width: 8.3333333333%; - } - .col-md-2 { - width: 16.6666666667%; - } - .col-md-3 { - width: 25%; - } - .col-md-4 { - width: 33.3333333333%; - } - .col-md-5 { - width: 41.6666666667%; - } - .col-md-6 { - width: 50%; - } - .col-md-7 { - width: 58.3333333333%; - } - .col-md-8 { - width: 66.6666666667%; - } - .col-md-9 { - width: 75%; - } - .col-md-10 { - width: 83.3333333333%; - } - .col-md-11 { - width: 91.6666666667%; - } - .col-md-12 { - width: 100%; - } -} -@media (min-width: 1012px) { - .col-lg-1 { - width: 8.3333333333%; - } - .col-lg-2 { - width: 16.6666666667%; - } - .col-lg-3 { - width: 25%; - } - .col-lg-4 { - width: 33.3333333333%; - } - .col-lg-5 { - width: 41.6666666667%; - } - .col-lg-6 { - width: 50%; - } - .col-lg-7 { - width: 58.3333333333%; - } - .col-lg-8 { - width: 66.6666666667%; - } - .col-lg-9 { - width: 75%; - } - .col-lg-10 { - width: 83.3333333333%; - } - .col-lg-11 { - width: 91.6666666667%; - } - .col-lg-12 { - width: 100%; - } -} -@media (min-width: 1280px) { - .col-xl-1 { - width: 8.3333333333%; - } - .col-xl-2 { - width: 16.6666666667%; - } - .col-xl-3 { - width: 25%; - } - .col-xl-4 { - width: 33.3333333333%; - } - .col-xl-5 { - width: 41.6666666667%; - } - .col-xl-6 { - width: 50%; - } - .col-xl-7 { - width: 58.3333333333%; - } - .col-xl-8 { - width: 66.6666666667%; - } - .col-xl-9 { - width: 75%; - } - .col-xl-10 { - width: 83.3333333333%; - } - .col-xl-11 { - width: 91.6666666667%; - } - .col-xl-12 { - width: 100%; - } -} -.gutter { - margin-right: -16px; - margin-left: -16px; -} -.gutter > [class*="col-"] { - padding-right: 16px !important; - padding-left: 16px !important; -} - -.gutter-condensed { - margin-right: -8px; - margin-left: -8px; -} -.gutter-condensed > [class*="col-"] { - padding-right: 8px !important; - padding-left: 8px !important; -} - -.gutter-spacious { - margin-right: -24px; - margin-left: -24px; -} -.gutter-spacious > [class*="col-"] { - padding-right: 24px !important; - padding-left: 24px !important; -} - -@media (min-width: 544px) { - .gutter-sm { - margin-right: -16px; - margin-left: -16px; - } - .gutter-sm > [class*="col-"] { - padding-right: 16px !important; - padding-left: 16px !important; - } - .gutter-sm-condensed { - margin-right: -8px; - margin-left: -8px; - } - .gutter-sm-condensed > [class*="col-"] { - padding-right: 8px !important; - padding-left: 8px !important; - } - .gutter-sm-spacious { - margin-right: -24px; - margin-left: -24px; - } - .gutter-sm-spacious > [class*="col-"] { - padding-right: 24px !important; - padding-left: 24px !important; - } -} -@media (min-width: 768px) { - .gutter-md { - margin-right: -16px; - margin-left: -16px; - } - .gutter-md > [class*="col-"] { - padding-right: 16px !important; - padding-left: 16px !important; - } - .gutter-md-condensed { - margin-right: -8px; - margin-left: -8px; - } - .gutter-md-condensed > [class*="col-"] { - padding-right: 8px !important; - padding-left: 8px !important; - } - .gutter-md-spacious { - margin-right: -24px; - margin-left: -24px; - } - .gutter-md-spacious > [class*="col-"] { - padding-right: 24px !important; - padding-left: 24px !important; - } -} -@media (min-width: 1012px) { - .gutter-lg { - margin-right: -16px; - margin-left: -16px; - } - .gutter-lg > [class*="col-"] { - padding-right: 16px !important; - padding-left: 16px !important; - } - .gutter-lg-condensed { - margin-right: -8px; - margin-left: -8px; - } - .gutter-lg-condensed > [class*="col-"] { - padding-right: 8px !important; - padding-left: 8px !important; - } - .gutter-lg-spacious { - margin-right: -24px; - margin-left: -24px; - } - .gutter-lg-spacious > [class*="col-"] { - padding-right: 24px !important; - padding-left: 24px !important; - } -} -@media (min-width: 1280px) { - .gutter-xl { - margin-right: -16px; - margin-left: -16px; - } - .gutter-xl > [class*="col-"] { - padding-right: 16px !important; - padding-left: 16px !important; - } - .gutter-xl-condensed { - margin-right: -8px; - margin-left: -8px; - } - .gutter-xl-condensed > [class*="col-"] { - padding-right: 8px !important; - padding-left: 8px !important; - } - .gutter-xl-spacious { - margin-right: -24px; - margin-left: -24px; - } - .gutter-xl-spacious > [class*="col-"] { - padding-right: 24px !important; - padding-left: 24px !important; - } -} -.offset-1 { - margin-left: 8.3333333333% !important; -} - -.offset-2 { - margin-left: 16.6666666667% !important; -} - -.offset-3 { - margin-left: 25% !important; -} - -.offset-4 { - margin-left: 33.3333333333% !important; -} - -.offset-5 { - margin-left: 41.6666666667% !important; -} - -.offset-6 { - margin-left: 50% !important; -} - -.offset-7 { - margin-left: 58.3333333333% !important; -} - -.offset-8 { - margin-left: 66.6666666667% !important; -} - -.offset-9 { - margin-left: 75% !important; -} - -.offset-10 { - margin-left: 83.3333333333% !important; -} - -.offset-11 { - margin-left: 91.6666666667% !important; -} - -@media (min-width: 544px) { - .offset-sm-1 { - margin-left: 8.3333333333% !important; - } - .offset-sm-2 { - margin-left: 16.6666666667% !important; - } - .offset-sm-3 { - margin-left: 25% !important; - } - .offset-sm-4 { - margin-left: 33.3333333333% !important; - } - .offset-sm-5 { - margin-left: 41.6666666667% !important; - } - .offset-sm-6 { - margin-left: 50% !important; - } - .offset-sm-7 { - margin-left: 58.3333333333% !important; - } - .offset-sm-8 { - margin-left: 66.6666666667% !important; - } - .offset-sm-9 { - margin-left: 75% !important; - } - .offset-sm-10 { - margin-left: 83.3333333333% !important; - } - .offset-sm-11 { - margin-left: 91.6666666667% !important; - } -} -@media (min-width: 768px) { - .offset-md-1 { - margin-left: 8.3333333333% !important; - } - .offset-md-2 { - margin-left: 16.6666666667% !important; - } - .offset-md-3 { - margin-left: 25% !important; - } - .offset-md-4 { - margin-left: 33.3333333333% !important; - } - .offset-md-5 { - margin-left: 41.6666666667% !important; - } - .offset-md-6 { - margin-left: 50% !important; - } - .offset-md-7 { - margin-left: 58.3333333333% !important; - } - .offset-md-8 { - margin-left: 66.6666666667% !important; - } - .offset-md-9 { - margin-left: 75% !important; - } - .offset-md-10 { - margin-left: 83.3333333333% !important; - } - .offset-md-11 { - margin-left: 91.6666666667% !important; - } -} -@media (min-width: 1012px) { - .offset-lg-1 { - margin-left: 8.3333333333% !important; - } - .offset-lg-2 { - margin-left: 16.6666666667% !important; - } - .offset-lg-3 { - margin-left: 25% !important; - } - .offset-lg-4 { - margin-left: 33.3333333333% !important; - } - .offset-lg-5 { - margin-left: 41.6666666667% !important; - } - .offset-lg-6 { - margin-left: 50% !important; - } - .offset-lg-7 { - margin-left: 58.3333333333% !important; - } - .offset-lg-8 { - margin-left: 66.6666666667% !important; - } - .offset-lg-9 { - margin-left: 75% !important; - } - .offset-lg-10 { - margin-left: 83.3333333333% !important; - } - .offset-lg-11 { - margin-left: 91.6666666667% !important; - } -} -@media (min-width: 1280px) { - .offset-xl-1 { - margin-left: 8.3333333333% !important; - } - .offset-xl-2 { - margin-left: 16.6666666667% !important; - } - .offset-xl-3 { - margin-left: 25% !important; - } - .offset-xl-4 { - margin-left: 33.3333333333% !important; - } - .offset-xl-5 { - margin-left: 41.6666666667% !important; - } - .offset-xl-6 { - margin-left: 50% !important; - } - .offset-xl-7 { - margin-left: 58.3333333333% !important; - } - .offset-xl-8 { - margin-left: 66.6666666667% !important; - } - .offset-xl-9 { - margin-left: 75% !important; - } - .offset-xl-10 { - margin-left: 83.3333333333% !important; - } - .offset-xl-11 { - margin-left: 91.6666666667% !important; - } -} -.markdown-body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", - "Segoe UI Emoji", "Segoe UI Symbol"; - font-size: 16px; - line-height: 1.5; - word-wrap: break-word; -} -.markdown-body::before { - display: table; - content: ""; -} -.markdown-body::after { - display: table; - clear: both; - content: ""; -} -.markdown-body > *:first-child { - margin-top: 0 !important; -} -.markdown-body > *:last-child { - margin-bottom: 0 !important; -} -.markdown-body a:not([href]) { - color: inherit; - text-decoration: none; -} -.markdown-body .absent { - color: #cb2431; -} -.markdown-body .anchor { - float: left; - padding-right: 4px; - margin-left: -20px; - line-height: 1; -} -.markdown-body .anchor:focus { - outline: none; -} -.markdown-body p, -.markdown-body blockquote, -.markdown-body ul, -.markdown-body ol, -.markdown-body dl, -.markdown-body table, -.markdown-body pre { - margin-top: 0; - margin-bottom: 16px; -} -.markdown-body hr { - height: 0.25em; - padding: 0; - margin: 24px 0; - background-color: #e1e4e8; - border: 0; -} -.markdown-body blockquote { - padding: 0 1em; - color: #6a737d; - border-left: 0.25em solid #dfe2e5; -} -.markdown-body blockquote > :first-child { - margin-top: 0; -} -.markdown-body blockquote > :last-child { - margin-bottom: 0; -} -.markdown-body kbd { - display: inline-block; - padding: 3px 5px; - font-size: 11px; - line-height: 10px; - color: #444d56; - vertical-align: middle; - background-color: #fafbfc; - border: solid 1px #c6cbd1; - border-bottom-color: #959da5; - border-radius: 3px; - box-shadow: inset 0 -1px 0 #959da5; -} - -.markdown-body h1, -.markdown-body h2, -.markdown-body h3, -.markdown-body h4, -.markdown-body h5, -.markdown-body h6 { - margin-top: 24px; - margin-bottom: 16px; - font-weight: 600; - line-height: 1.25; -} -.markdown-body h1 .octicon-link, -.markdown-body h2 .octicon-link, -.markdown-body h3 .octicon-link, -.markdown-body h4 .octicon-link, -.markdown-body h5 .octicon-link, -.markdown-body h6 .octicon-link { - color: #1b1f23; - vertical-align: middle; - visibility: hidden; -} -.markdown-body h1:hover .anchor, -.markdown-body h2:hover .anchor, -.markdown-body h3:hover .anchor, -.markdown-body h4:hover .anchor, -.markdown-body h5:hover .anchor, -.markdown-body h6:hover .anchor { - text-decoration: none; -} -.markdown-body h1:hover .anchor .octicon-link, -.markdown-body h2:hover .anchor .octicon-link, -.markdown-body h3:hover .anchor .octicon-link, -.markdown-body h4:hover .anchor .octicon-link, -.markdown-body h5:hover .anchor .octicon-link, -.markdown-body h6:hover .anchor .octicon-link { - visibility: visible; -} -.markdown-body h1 tt, -.markdown-body h1 code, -.markdown-body h2 tt, -.markdown-body h2 code, -.markdown-body h3 tt, -.markdown-body h3 code, -.markdown-body h4 tt, -.markdown-body h4 code, -.markdown-body h5 tt, -.markdown-body h5 code, -.markdown-body h6 tt, -.markdown-body h6 code { - font-size: inherit; -} -.markdown-body h1 { - padding-bottom: 0.3em; - font-size: 2em; - border-bottom: 1px solid #eaecef; -} -.markdown-body h2 { - padding-bottom: 0.3em; - font-size: 1.5em; - border-bottom: 1px solid #eaecef; -} -.markdown-body h3 { - font-size: 1.25em; -} -.markdown-body h4 { - font-size: 1em; -} -.markdown-body h5 { - font-size: 0.875em; -} -.markdown-body h6 { - font-size: 0.85em; - color: #6a737d; -} - -.markdown-body ul, -.markdown-body ol { - padding-left: 2em; -} -.markdown-body ul.no-list, -.markdown-body ol.no-list { - padding: 0; - list-style-type: none; -} -.markdown-body ul ul, -.markdown-body ul ol, -.markdown-body ol ol, -.markdown-body ol ul { - margin-top: 0; - margin-bottom: 0; -} -.markdown-body li { - word-wrap: break-all; -} -.markdown-body li > p { - margin-top: 16px; -} -.markdown-body li + li { - margin-top: 0.25em; -} -.markdown-body dl { - padding: 0; -} -.markdown-body dl dt { - padding: 0; - margin-top: 16px; - font-size: 1em; - font-style: italic; - font-weight: 600; -} -.markdown-body dl dd { - padding: 0 16px; - margin-bottom: 16px; -} - -.markdown-body table { - display: block; - width: 100%; - overflow: auto; -} -.markdown-body table th { - font-weight: 600; -} -.markdown-body table th, -.markdown-body table td { - padding: 6px 13px; - border: 1px solid #dfe2e5; -} -.markdown-body table tr { - background-color: #fff; - border-top: 1px solid #c6cbd1; -} -.markdown-body table tr:nth-child(2n) { - background-color: #f6f8fa; -} -.markdown-body table img { - background-color: transparent; -} - -.markdown-body img { - max-width: 100%; - box-sizing: content-box; - background-color: #fff; -} -.markdown-body img[align="right"] { - padding-left: 20px; -} -.markdown-body img[align="left"] { - padding-right: 20px; -} -.markdown-body .emoji { - max-width: none; - vertical-align: text-top; - background-color: transparent; -} -.markdown-body span.frame { - display: block; - overflow: hidden; -} -.markdown-body span.frame > span { - display: block; - float: left; - width: auto; - padding: 7px; - margin: 13px 0 0; - overflow: hidden; - border: 1px solid #dfe2e5; -} -.markdown-body span.frame span img { - display: block; - float: left; -} -.markdown-body span.frame span span { - display: block; - padding: 5px 0 0; - clear: both; - color: #24292e; -} -.markdown-body span.align-center { - display: block; - overflow: hidden; - clear: both; -} -.markdown-body span.align-center > span { - display: block; - margin: 13px auto 0; - overflow: hidden; - text-align: center; -} -.markdown-body span.align-center span img { - margin: 0 auto; - text-align: center; -} -.markdown-body span.align-right { - display: block; - overflow: hidden; - clear: both; -} -.markdown-body span.align-right > span { - display: block; - margin: 13px 0 0; - overflow: hidden; - text-align: right; -} -.markdown-body span.align-right span img { - margin: 0; - text-align: right; -} -.markdown-body span.float-left { - display: block; - float: left; - margin-right: 13px; - overflow: hidden; -} -.markdown-body span.float-left span { - margin: 13px 0 0; -} -.markdown-body span.float-right { - display: block; - float: right; - margin-left: 13px; - overflow: hidden; -} -.markdown-body span.float-right > span { - display: block; - margin: 13px auto 0; - overflow: hidden; - text-align: right; -} - -.markdown-body code, -.markdown-body tt { - padding: 0.2em 0.4em; - margin: 0; - font-size: 85%; - background-color: rgba(27, 31, 35, 0.05); - border-radius: 3px; -} -.markdown-body code br, -.markdown-body tt br { - display: none; -} -.markdown-body del code { - text-decoration: inherit; -} -.markdown-body pre { - word-wrap: normal; -} -.markdown-body pre > code { - padding: 0; - margin: 0; - font-size: 100%; - word-break: normal; - white-space: pre; - background: transparent; - border: 0; -} -.markdown-body .highlight { - margin-bottom: 16px; -} -.markdown-body .highlight pre { - margin-bottom: 0; - word-break: normal; -} -.markdown-body .highlight pre, -.markdown-body pre { - padding: 16px; - overflow: auto; - font-size: 85%; - line-height: 1.45; - background-color: #f6f8fa; - border-radius: 3px; -} -.markdown-body pre code, -.markdown-body pre tt { - display: inline; - max-width: auto; - padding: 0; - margin: 0; - overflow: visible; - line-height: inherit; - word-wrap: normal; - background-color: transparent; - border: 0; -} - -.markdown-body .csv-data td, -.markdown-body .csv-data th { - padding: 5px; - overflow: hidden; - font-size: 12px; - line-height: 1; - text-align: left; - white-space: nowrap; -} -.markdown-body .csv-data .blob-num { - padding: 10px 8px 9px; - text-align: right; - background: #fff; - border: 0; -} -.markdown-body .csv-data tr { - border-top: 0; -} -.markdown-body .csv-data th { - font-weight: 600; - background: #f6f8fa; - border-top: 0; -} - -.highlight table td { - padding: 5px; -} - -.highlight table pre { - margin: 0; -} - -.highlight .cm { - color: #999988; - font-style: italic; -} - -.highlight .cp { - color: #999999; - font-weight: bold; -} - -.highlight .c1 { - color: #999988; - font-style: italic; -} - -.highlight .cs { - color: #999999; - font-weight: bold; - font-style: italic; -} - -.highlight .c, -.highlight .cd { - color: #999988; - font-style: italic; -} - -.highlight .err { - color: #a61717; - background-color: #e3d2d2; -} - -.highlight .gd { - color: #000000; - background-color: #ffdddd; -} - -.highlight .ge { - color: #000000; - font-style: italic; -} - -.highlight .gr { - color: #aa0000; -} - -.highlight .gh { - color: #999999; -} - -.highlight .gi { - color: #000000; - background-color: #ddffdd; -} - -.highlight .go { - color: #888888; -} - -.highlight .gp { - color: #555555; -} - -.highlight .gs { - font-weight: bold; -} - -.highlight .gu { - color: #aaaaaa; -} - -.highlight .gt { - color: #aa0000; -} - -.highlight .kc { - color: #000000; - font-weight: bold; -} - -.highlight .kd { - color: #000000; - font-weight: bold; -} - -.highlight .kn { - color: #000000; - font-weight: bold; -} - -.highlight .kp { - color: #000000; - font-weight: bold; -} - -.highlight .kr { - color: #000000; - font-weight: bold; -} - -.highlight .kt { - color: #445588; - font-weight: bold; -} - -.highlight .k, -.highlight .kv { - color: #000000; - font-weight: bold; -} - -.highlight .mf { - color: #009999; -} - -.highlight .mh { - color: #009999; -} - -.highlight .il { - color: #009999; -} - -.highlight .mi { - color: #009999; -} - -.highlight .mo { - color: #009999; -} - -.highlight .m, -.highlight .mb, -.highlight .mx { - color: #009999; -} - -.highlight .sb { - color: #d14; -} - -.highlight .sc { - color: #d14; -} - -.highlight .sd { - color: #d14; -} - -.highlight .s2 { - color: #d14; -} - -.highlight .se { - color: #d14; -} - -.highlight .sh { - color: #d14; -} - -.highlight .si { - color: #d14; -} - -.highlight .sx { - color: #d14; -} - -.highlight .sr { - color: #009926; -} - -.highlight .s1 { - color: #d14; -} - -.highlight .ss { - color: #990073; -} - -.highlight .s { - color: #d14; -} - -.highlight .na { - color: #008080; -} - -.highlight .bp { - color: #999999; -} - -.highlight .nb { - color: #0086b3; -} - -.highlight .nc { - color: #445588; - font-weight: bold; -} - -.highlight .no { - color: #008080; -} - -.highlight .nd { - color: #3c5d5d; - font-weight: bold; -} - -.highlight .ni { - color: #800080; -} - -.highlight .ne { - color: #990000; - font-weight: bold; -} - -.highlight .nf { - color: #990000; - font-weight: bold; -} - -.highlight .nl { - color: #990000; - font-weight: bold; -} - -.highlight .nn { - color: #555555; -} - -.highlight .nt { - color: #000080; -} - -.highlight .vc { - color: #008080; -} - -.highlight .vg { - color: #008080; -} - -.highlight .vi { - color: #008080; -} - -.highlight .nv { - color: #008080; -} - -.highlight .ow { - color: #000000; - font-weight: bold; -} - -.highlight .o { - color: #000000; - font-weight: bold; -} - -.highlight .w { - color: #bbbbbb; -} - -.highlight { - background-color: #f8f8f8; -} diff --git a/docs/_site/assets/event-subscriptions-page.png b/docs/_site/assets/event-subscriptions-page.png deleted file mode 100644 index 713469575..000000000 Binary files a/docs/_site/assets/event-subscriptions-page.png and /dev/null differ diff --git a/docs/_site/assets/interactivity-and-shortcuts-page.png b/docs/_site/assets/interactivity-and-shortcuts-page.png deleted file mode 100644 index 1e8853004..000000000 Binary files a/docs/_site/assets/interactivity-and-shortcuts-page.png and /dev/null differ diff --git a/docs/_site/assets/ngrok.gif b/docs/_site/assets/ngrok.gif deleted file mode 100644 index c7c94d51a..000000000 Binary files a/docs/_site/assets/ngrok.gif and /dev/null differ diff --git a/docs/_site/assets/request-url-config.png b/docs/_site/assets/request-url-config.png deleted file mode 100644 index 5315298a8..000000000 Binary files a/docs/_site/assets/request-url-config.png and /dev/null differ diff --git a/docs/_site/assets/signing-secret.png b/docs/_site/assets/signing-secret.png deleted file mode 100644 index d32afa03e..000000000 Binary files a/docs/_site/assets/signing-secret.png and /dev/null differ diff --git a/docs/_site/assets/style.css b/docs/_site/assets/style.css deleted file mode 100644 index 3b6807783..000000000 --- a/docs/_site/assets/style.css +++ /dev/null @@ -1,629 +0,0 @@ -/* Color variables */ -:root { - --light-grey: #f8f8f8; - --grey: #868686; - --dark-grey: #616061; - --soft-grey: #ececec; - --blue: #1264a3; - --green: #00b073; - --light-blue: #b8d1e3; - --white: #ffffff; - --black: #1d1c1d; - --yellowish: #ff9e00; -} - -body { - background-color: var(--white); - font-family: "Noto Sans JP", "Slack-Lato", sans-serif; -} - -.content { - grid-area: content; -} - -/* Sidebar */ -.panel { - position: fixed; - width: 20%; - height: 100%; - overflow: auto; - top: 0; - left: 0; - background-color: var(--light-grey); -} - -.panel .sidebar-content { - width: 75%; - margin: 30px auto 20px auto; -} - -.panel .sidebar-content .logo { - padding-top: 1em; - position: relative; -} - -.panel .sidebar-content .logo .icon img { - width: 30px; - margin-right: 6px; -} - -.panel .sidebar-content .logo .name { - font-weight: 800; - font-size: 1.7em; - vertical-align: bottom; -} - -.panel .sidebar-content .logo .version { - line-height: 1em; - vertical-align: bottom; -} - -.panel .sidebar-content .logo .version a { - color: var(--dark-grey); - background-color: var(--soft-grey); - font-size: 0.5em; - font-weight: 800; - padding: 4px 10px; - border-radius: 12px; - margin-left: 10px; -} - -.panel .sidebar-content ul.sidebar-section { - list-style: none; - list-style-position: inside; - padding-top: 0.9em; - margin: 0 0 0 -8px; - font-size: 0.80em; -} - -.panel .sidebar-content ul.sidebar-section li { - border-radius: 8px; - padding: 2px 0 2px 8px; - margin: 3px 0; - color: var(--black); -} - -.panel .sidebar-content ul.sidebar-section li:hover { - background-color: #d7d7d7; -} - -.panel .sidebar-content ul.sidebar-section li.madeby:hover { - background-color: transparent; -} - -.panel .sidebar-content a:hover { - text-decoration: none; -} - -.panel .sidebar-content ul.sidebar-section li.active { - background-color: var(--blue); - color: var(--white); -} - -.panel .sidebar-content ul.sidebar-section li.title { - font-weight: 600; -} - -.panel .sidebar-content ul.sidebar-section .label-deprecated { - line-height: 1em; - vertical-align: middle; - color: var(--white); - background-color: var(--dark-grey); - font-size: 0.5em; - font-weight: 800; - padding: 4px 10px; - border-radius: 12px; -} - -/* Main page */ -.header { - width: 95%; - margin: 0 auto 1em auto; - height: 5rem; - padding-top: 1.5em; -} - -.header a:hover { - text-decoration: none; -} - -.header a.language-switcher { - color: var(--grey); - font-weight: 700; - padding: 6px 14px 9px; - font-size: 15px; -} - -.header a.language-switcher:hover { - color: var(--black); -} - -.wrapper { - width: 100%; - margin: 0 auto; -} - -/* Main page content */ -.section-wrapper { - width: 90%; - margin: 0 auto 30px auto; - display: grid; - grid-gap: 25px; - grid-template-areas: - "head" - "body" - "code" - "secondary" - "divider"; -} - -.tutorial-nav { - width: 20%; - position: fixed; -} - -.tutorial-nav ul { - margin-left: 3em; - padding-left: 1em; - border-width: 4px; - border-left-style: solid; - border-color: #f2f2f2; - border-image: linear-gradient(to bottom, #ffffff 0%, #f2f2f2 6% 92%, #ffffff 100%) 1 100%; - list-style: none; - padding-top: 1.5em; -} - -.circle { - background: #ddd; - border-radius: 50%; - height: 1em; - width: 1em; - float: left; - margin: 5px 0 0 -1.6em; -} - -.completed { - background: #58af7f; -} - -.tutorial-nav ul li { - padding-bottom: 2.5em; -} - -.tutorial-nav a { - font-weight: 700; - font-size: 0.8em; - color: #757575; -} - -.tutorial-nav a:hover { - color: #000; - text-decoration: none; -} - -.tutorial { - width: 55%; - margin: 1em 0 0 33%; - padding-bottom: 2em; - font-size: 1em; - line-height: 1.75em; -} - -.tutorial img, -.reference img { - width: 85%; - margin: 0.2em auto; - display: block; - box-shadow: 0 0 15px #dddddd; -} - -.tutorial blockquote, -.reference blockquote { - margin: 0 0 0 1em; - padding: 0 6em 0 1.5em; - border-radius: 6px; - border-left: 6px solid #ddd; - font-size: 0.95em; -} - -.tutorial h3 { - padding-bottom: 1em; -} - -.tutorial .label-deprecated { - line-height: 1em; - vertical-align: middle; - color: var(--white); - background-color: var(--dark-grey); - font-size: 0.45em; - font-weight: 800; - padding: 4px 10px; - border-radius: 12px; - margin-left: 10px; -} - -.reference { - width: 80%; - margin: 1em auto 3em auto; - padding-bottom: 2em; - font-size: 1em; - line-height: 1.75em; -} - -.reference h3 { - padding-top: 1em; -} - -.content .section-wrapper .language-javascript { - grid-area: code; -} - -pre { - background-color: var(--light-grey) !important; - background-image: none; - padding: 1em 1.5em; - border: 1px solid var(--soft-grey); - margin: 0; - border-radius: 1em; -} - -/* Code block with column numbers */ -pre.highlight { - line-height: 2em; - overflow-x: auto; -} - -pre.highlight code span { - padding: 0; - margin: 0; - height: 0; -} - -pre.highlight code pre { - padding: 0; - border: 0; - font-size: 0.9em; - overflow: visible; -} - -table.rouge-table, -td.rouge-code, -td.rouge-gutter { - padding: 0; - border: 0; - margin: 0; -} - -td.rouge-gutter { - padding-right: 1em; - user-select: none; - color: var(--dark-grey); -} -/* End Code block with column numbers */ - -.content .section-wrapper .section-content { - grid-area: body; - font-size: 1em; - line-height: 2em; -} - -.content .section-wrapper h3 { - grid-area: head; - font-size: 1.4em; - font-weight: 600; -} - -.content .section-wrapper hr { - grid-area: divider; - height: 1px; - border-top: 1px solid #ddd; - width: 100%; -} - -.content .section-wrapper .label-legacy { - line-height: 1em; - vertical-align: middle; - color: var(--white); - background-color: var(--dark-grey); - font-size: 0.5em; - font-weight: 800; - padding: 4px 10px; - border-radius: 12px; - margin-left: 10px; -} - -a:hover { - text-decoration: underline; -} - -/* Secondary content */ -.secondary-wrapper { - width: 100%; - grid-area: secondary; - margin: 1em auto 0 auto; - font-size: 1em; - line-height: 1.75em; -} - -.secondary-wrapper .language-javascript { - width: 50%; - float: left; - margin-top: 1em; -} - -.content .section-wrapper .secondary-content { - width: 45%; - float: left; - margin-right: 5%; - margin-top: 1em; -} - -summary h4 { - display: inline; -} - -/* Responsive */ -@media (min-width: 1024px) { - .tutorial-nav ul { - margin-left: 5em; - } -} - -@media (min-width: 768px) { - .wrapper { - display: grid; - grid-template-columns: 20% 75%; - grid-template-areas: "sidebar content"; - } - - .section-wrapper { - grid-template-columns: 50% 50%; - grid-template-areas: - "head head" - "body code" - "secondary secondary" - "divider divider"; - } -} - -@media (max-width: 768px) { - .panel { - display: none; - } - - .language-switcher { - display: none; - } - - .tutorial-nav { - display: none; - } - - .tutorial { - width: 85%; - margin: 1em auto; - } - - .wrapper { - display: grid; - grid-template-columns: 100%; - grid-template-areas: "content"; - } - - .section-wrapper { - grid-template-columns: 100%; - grid-template-areas: - "head" - "body" - "code" - "secondary" - "divider"; - } -} - -/* - * GitHub theme stylesheet from: http://jwarby.github.io/jekyll-pygments-themes/languages/javascript.html - */ -.highlight .hll { - background-color: #ffffcc; -} -.highlight .c { - color: #999988; -} /* Comment */ -.highlight .err { - color: #a61717; - background-color: #e3d2d2; -} /* Error */ -.highlight .k { - color: #000000; - font-weight: bold; -} /* Keyword */ -.highlight .o { - color: #000000; - font-weight: bold; -} /* Operator */ -.highlight .cm { - color: #999988; -} /* Comment.Multiline */ -.highlight .cp { - color: #999999; - font-weight: bold; -} /* Comment.Preproc */ -.highlight .c1 { - color: #999988; -} /* Comment.Single */ -.highlight .cs { - color: #999999; - font-weight: bold; -} /* Comment.Special */ -.highlight .gd { - color: #000000; - background-color: #ffdddd; -} /* Generic.Deleted */ -.highlight .ge { - color: #000000; - font-style: italic; -} /* Generic.Emph */ -.highlight .gr { - color: #aa0000; -} /* Generic.Error */ -.highlight .gh { - color: #999999; -} /* Generic.Heading */ -.highlight .gi { - color: #000000; - background-color: #ddffdd; -} /* Generic.Inserted */ -.highlight .go { - color: #888888; -} /* Generic.Output */ -.highlight .gp { - color: #555555; -} /* Generic.Prompt */ -.highlight .gs { - font-weight: bold; -} /* Generic.Strong */ -.highlight .gu { - color: #aaaaaa; -} /* Generic.Subheading */ -.highlight .gt { - color: #aa0000; -} /* Generic.Traceback */ -.highlight .kc { - color: #000000; - font-weight: bold; -} /* Keyword.Constant */ -.highlight .kd { - color: #000000; - font-weight: bold; -} /* Keyword.Declaration */ -.highlight .kn { - color: #000000; - font-weight: bold; -} /* Keyword.Namespace */ -.highlight .kp { - color: #000000; - font-weight: bold; -} /* Keyword.Pseudo */ -.highlight .kr { - color: #000000; - font-weight: bold; -} /* Keyword.Reserved */ -.highlight .kt { - color: #445588; - font-weight: bold; -} /* Keyword.Type */ -.highlight .m { - color: #009999; -} /* Literal.Number */ -.highlight .s { - color: #d01040; -} /* Literal.String */ -.highlight .na { - color: #008080; -} /* Name.Attribute */ -.highlight .nb { - color: #0086b3; -} /* Name.Builtin */ -.highlight .nc { - color: #445588; - font-weight: bold; -} /* Name.Class */ -.highlight .no { - color: #008080; -} /* Name.Constant */ -.highlight .nd { - color: #3c5d5d; - font-weight: bold; -} /* Name.Decorator */ -.highlight .ni { - color: #800080; -} /* Name.Entity */ -.highlight .ne { - color: #990000; - font-weight: bold; -} /* Name.Exception */ -.highlight .nf { - color: #990000; - font-weight: bold; -} /* Name.Function */ -.highlight .nl { - color: #990000; - font-weight: bold; -} /* Name.Label */ -.highlight .nn { - color: #555555; -} /* Name.Namespace */ -.highlight .nt { - color: #000080; -} /* Name.Tag */ -.highlight .nv { - color: #008080; -} /* Name.Variable */ -.highlight .ow { - color: #000000; - font-weight: bold; -} /* Operator.Word */ -.highlight .w { - color: #bbbbbb; -} /* Text.Whitespace */ -.highlight .mf { - color: #009999; -} /* Literal.Number.Float */ -.highlight .mh { - color: #009999; -} /* Literal.Number.Hex */ -.highlight .mi { - color: #009999; -} /* Literal.Number.Integer */ -.highlight .mo { - color: #009999; -} /* Literal.Number.Oct */ -.highlight .sb { - color: #d01040; -} /* Literal.String.Backtick */ -.highlight .sc { - color: #d01040; -} /* Literal.String.Char */ -.highlight .sd { - color: #d01040; -} /* Literal.String.Doc */ -.highlight .s2 { - color: #d01040; -} /* Literal.String.Double */ -.highlight .se { - color: #d01040; -} /* Literal.String.Escape */ -.highlight .sh { - color: #d01040; -} /* Literal.String.Heredoc */ -.highlight .si { - color: #d01040; -} /* Literal.String.Interpol */ -.highlight .sx { - color: #d01040; -} /* Literal.String.Other */ -.highlight .sr { - color: #009926; -} /* Literal.String.Regex */ -.highlight .s1 { - color: #d01040; -} /* Literal.String.Single */ -.highlight .ss { - color: #990073; -} /* Literal.String.Symbol */ -.highlight .bp { - color: #999999; -} /* Name.Builtin.Pseudo */ -.highlight .vc { - color: #008080; -} /* Name.Variable.Class */ -.highlight .vg { - color: #008080; -} /* Name.Variable.Global */ -.highlight .vi { - color: #008080; -} /* Name.Variable.Instance */ -.highlight .il { - color: #009999; -} /* Literal.Number.Integer.Long */ diff --git a/docs/_site/concepts.html b/docs/_site/concepts.html deleted file mode 100644 index e1fa4c9c7..000000000 --- a/docs/_site/concepts.html +++ /dev/null @@ -1,4051 +0,0 @@ - - - - - - Slack | Bolt for JavaScript - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
- -
- - -
- -
-

Listening to messages

- -
-

To listen to messages that your app has access to receive, you can use the message() method which filters out events that aren’t of type message.

- -

message() accepts an optional pattern parameter of type string or RegExp object which filters out any messages that don’t match the pattern.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-
// This will match any message that contains 👋
-app.message(':wave:', async ({ message, say }) => {
-  // Handle only newly posted messages here
-  if (message.subtype === undefined
-    || message.subtype === 'bot_message'
-    || message.subtype === 'file_share'
-    || message.subtype === 'thread_broadcast') {
-    await say(`Hello, <@${message.user}>`);
-  }
-});
-
-
-
- -
- -

Using a RegExp pattern

-
- -
-

A RegExp pattern can be used instead of a string for more granular matching.

- -

All of the results of the RegExp match will be in context.matches.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-
app.message(/^(hi|hello|hey).*/, async ({ context, say }) => {
-  // RegExp matches are inside of context.matches
-  const greeting = context.matches[0];
-
-  await say(`${greeting}, how are you?`);
-});
-
-
-
- -
- - -
-
- -
-

Sending messages

- -
-

Within your listener function, say() is available whenever there is an associated conversation (for example, a conversation where the event or action which triggered the listener occurred). say() accepts a string to post simple messages and JSON payloads to send more complex messages. The message payload you pass in will be sent to the associated conversation.

- -

In the case that you’d like to send a message outside of a listener or you want to do something more advanced (like handle specific errors), you can call chat.postMessage using the client attached to your Bolt instance.

-
- -
-
-

-
-
-
1
-2
-3
-4
-
// Listens for messages containing "knock knock" and responds with an italicized "who's there?"
-app.message('knock knock', async ({ message, say }) => {
-  await say(`_Who's there?_`);
-});
-
-
-
- -
- -

Sending a message with blocks

-
- -
-

say() accepts more complex message payloads to make it easy to add functionality and structure to your messages.

- -

To explore adding rich message layouts to your app, read through the guide on our API site and look through templates of common app flows in the Block Kit Builder.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-23
-
// Sends a section block with datepicker when someone reacts with a 📅 emoji
-app.event('reaction_added', async ({ event, say }) => {
-  if (event.reaction === 'calendar') {
-    await say({
-      blocks: [{
-        "type": "section",
-        "text": {
-          "type": "mrkdwn",
-          "text": "Pick a date for me to remind you"
-        },
-        "accessory": {
-          "type": "datepicker",
-          "action_id": "datepicker_remind",
-          "initial_date": "2019-04-28",
-          "placeholder": {
-            "type": "plain_text",
-            "text": "Select a date"
-          }
-        }
-      }]
-    });
-  }
-});
-
-
-
-
- - -
-
- -
-

Listening to events

- -
-

You can listen to any Events API event using the event() method after subscribing to it in your app configuration. This allows your app to take action when something happens in Slack, like a user reacting to a message or joining a channel.

- -

The event() method requires an eventType of type string.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-
const welcomeChannelId = 'C12345';
-
-// When a user joins the team, send a message in a predefined channel asking them to introduce themselves
-app.event('team_join', async ({ event, client, logger }) => {
-  try {
-    // Call chat.postMessage with the built-in client
-    const result = await client.chat.postMessage({
-      channel: welcomeChannelId,
-      text: `Welcome to the team, <@${event.user.id}>! 🎉 You can introduce yourself in this channel.`
-    });
-    logger.info(result);
-  }
-  catch (error) {
-    logger.error(error);
-  }
-});
-
-
-
- -
- -

Filtering on message subtypes

-
- -
-

A message() listener is equivalent to event('message')

- -

You can filter on subtypes of events by using the built-in subtype() middleware. Common message subtypes like message_changed and message_replied can be found on the message event page.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-
// Import subtype from the package
-const { App, subtype } = require('@slack/bolt');
-
-// Matches all message changes from users
-app.message(subtype('message_changed'), ({ event, logger }) => {
-  // This if statement is required in TypeScript code
-  if (event.subtype === 'message_changed'
-    && !event.message.subtype
-    && !event.previous_message.subtype) {
-    logger.info(`The user ${event.message.user} changed their message from ${event.previous_message.text} to ${event.message.text}`);
-  }
-});
-
-
-
- -
- - -
-
- -
-

Using the Web API

- -
-

You can call any Web API method using the WebClient provided to your app’s listeners as client. This uses either the token that initialized your app or the token that is returned from the authorize function for the incoming event. The built-in OAuth support handles the second case by default.

- -

Your Bolt app also has a top-level app.client which you can manually pass the token parameter. If the incoming request is not authorized or you’re calling a method from outside of a listener, use the top-level app.client.

- -

Calling one of the WebClient’s methods will return a Promise containing the response from Slack, regardless of whether you use the top-level or listener’s client.

- -

Since the introduction of org wide app installations, some web-api methods now require team_id to indicate which workspace to act on. Bolt for JavaScript will attempt to infer the team_id based on incoming payloads and pass it along to client. This is handy for existing applications looking to add support for org wide installations and not spend time updating all of these web-api calls.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-
// Unix Epoch time for September 30, 2019 11:59:59 PM
-const whenSeptemberEnds = 1569887999;
-
-app.message('wake me up', async ({ message, client, logger }) => {
-  try {
-    // Call chat.scheduleMessage with the built-in client
-    const result = await client.chat.scheduleMessage({
-      channel: message.channel,
-      post_at: whenSeptemberEnds,
-      text: 'Summer has come and passed'
-    });
-  }
-  catch (error) {
-    logger.error(error);
-  }
-});
-
-
-
- - -
-
- -
-

Listening to actions

- -
-

Your app can listen to user actions like button clicks, and menu selects, using the action method.

- -

Actions can be filtered on an action_id of type string or RegExp object. action_ids act as unique identifiers for interactive components on the Slack platform.

- -

You’ll notice in all action() examples, ack() is used. It is required to call the ack() function within an action listener to acknowledge that the request was received from Slack. This is discussed in the acknowledging requests section.

- -

Note: Since v2, message shortcuts (previously message actions) now use the shortcut() method instead of the action() method. View the migration guide for V2 to learn about the changes.

- -

Learn more about the block_actions payload, here. To access the full payload of a view from within a listener, reference the body argument within your callback function.

- -
- -
-
-

-
-
-
1
-2
-3
-4
-5
-
// Your listener function will be called every time an interactive component with the action_id "approve_button" is triggered
-app.action('approve_button', async ({ ack }) => {
-  await ack();
-  // Update the message to reflect the action
-});
-
-
-
- -
- -

Listening to actions using a constraint object

-
- -
-

You can use a constraints object to listen to callback_ids, block_ids, and action_ids (or any combination of them). Constraints in the object can be of type string or RegExp object.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-
// Your listener function will only be called when the action_id matches 'select_user' AND the block_id matches 'assign_ticket'
-app.action({ action_id: 'select_user', block_id: 'assign_ticket' },
-  async ({ body, client, ack, logger }) => {
-    await ack();
-    try {
-      // Make sure the action isn't from a view (modal or app home)
-      if (body.message) {
-        const result = await client.reactions.add({
-          name: 'white_check_mark',
-          timestamp: body.message.ts,
-          channel: body.channel.id
-        });
-
-        logger.info(result);
-      }
-    }
-    catch (error) {
-      logger.error(error);
-    }
-  });
-
-
-
- -
- - -
-
- -
-

Responding to actions

- -
-

There are two main ways to respond to actions. The first (and most common) way is to use the say function. The say function sends a message back to the conversation where the incoming request took place.

- -

The second way to respond to actions is using respond(), which is a simple utility to use the response_url associated with an action.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-
// Your middleware will be called every time an interactive component with the action_id “approve_button” is triggered
-app.action('approve_button', async ({ ack, say }) => {
-  // Acknowledge action request
-  await ack();
-  await say('Request approved 👍');
-});
-
-
-
- -
- -

Using respond()

-
- -
-

Since respond() is a utility for calling the response_url, it behaves in the same way. You can pass a JSON object with a new message payload that will be published back to the source of the original interaction with optional properties like response_type (which has a value of in_channel or ephemeral), replace_original, and delete_original.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-
// Listens to actions triggered with action_id of “user_select”
-app.action('user_select', async ({ action, ack, respond }) => {
-  await ack();
-  if (action.type === 'users_select') {
-    await respond(`You selected <@${action.selected_user}>`);
-  }
-});
-
-
-
- -
- - -
-
- -
-

Acknowledging requests

- -
-

Actions, commands, and options requests must always be acknowledged using the ack() function. This lets Slack know that the request was received and updates the Slack user interface accordingly. Depending on the type of request, your acknowledgement may be different. For example, when acknowledging a modal submission you will call ack() with validation errors if the submission contains errors, or with no parameters if the submission is valid.

- -

We recommend calling ack() right away before sending a new message or fetching information from your database since you only have 3 seconds to respond.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-
// Regex to determine if this is a valid email
-let isEmail = /^[\w\-\.]+@([\w\-]+\.)+[\w\-]+$/;
-// This uses a constraint object to listen for modal submissions with a callback_id of ticket_submit 
-app.view('ticket_submit', async ({ ack, view }) => {
-  // get the email value from the input block with `email_address` as the block_id
-  const email = view.state.values['email_address']['input_a'].value;
-
-  // if it’s a valid email, accept the submission
-  if (email && isEmail.test(email)) {
-    await ack();
-  } else {
-    // if it isn’t a valid email, acknowledge with an error
-    await ack({
-      "response_action": "errors",
-      errors: {
-        "email_address": "Sorry, this isn’t a valid email"
-      }
-    });
-  }
-});
-
-
-
- - -
-
- -
-

Listening and responding to shortcuts

- -
- -

The shortcut() method supports both global shortcuts and message shortcuts.

- -

Shortcuts are invokable entry points to apps. Global shortcuts are available from within search in Slack. Message shortcuts are available in the context menus of messages. Your app can use the shortcut() method to listen to incoming shortcut requests. The method requires a callback_id parameter of type string or RegExp.

- -

⚠️ Note that if you use shortcut() multiple times with overlapping RegExp matches, all matching listeners will run. Design your regular expressions to avoid this possibility.

- -

Shortcuts must be acknowledged with ack() to inform Slack that your app has received the request.

- -

Shortcuts include a trigger_id which an app can use to open a modal that confirms the action the user is taking.

- -

When configuring shortcuts within your app configuration, you’ll continue to append /slack/events to your request URL.

- -

⚠️ Note that global shortcuts do not include a channel ID. If your app needs access to a channel ID, you may use a conversations_select element within a modal. Message shortcuts do include channel ID.

- -
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-23
-24
-25
-26
-27
-28
-29
-30
-31
-32
-33
-34
-35
-36
-37
-38
-39
-40
-41
-42
-43
-44
-45
-46
-47
-
// The open_modal shortcut opens a plain old modal
-app.shortcut('open_modal', async ({ shortcut, ack, client, logger }) => {
-
-  try {
-    // Acknowledge shortcut request
-    await ack();
-
-    // Call the views.open method using one of the built-in WebClients
-    const result = await client.views.open({
-      trigger_id: shortcut.trigger_id,
-      view: {
-        type: "modal",
-        title: {
-          type: "plain_text",
-          text: "My App"
-        },
-        close: {
-          type: "plain_text",
-          text: "Close"
-        },
-        blocks: [
-          {
-            type: "section",
-            text: {
-              type: "mrkdwn",
-              text: "About the simplest modal you could conceive of :smile:\n\nMaybe <https://api.slack.com/reference/block-kit/interactive-components|*make the modal interactive*> or <https://api.slack.com/surfaces/modals/using#modifying|*learn more advanced modal use cases*>."
-            }
-          },
-          {
-            type: "context",
-            elements: [
-              {
-                type: "mrkdwn",
-                text: "Psssst this modal was designed using <https://api.slack.com/tools/block-kit-builder|*Block Kit Builder*>"
-              }
-            ]
-          }
-        ]
-      }
-    });
-
-    logger.info(result);
-  }
-  catch (error) {
-    logger.error(error);
-  }
-});
-
-
-
- -
- -

Listening to shortcuts using a constraint object

-
- -
-

You can use a constraints object to listen to callback_ids, and types. Constraints in the object can be of type string or RegExp object.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-23
-24
-25
-26
-27
-28
-29
-30
-31
-32
-33
-34
-35
-36
-37
-38
-39
-40
-41
-42
-43
-44
-45
-46
-
  // Your middleware will only be called when the callback_id matches 'open_modal' AND the type matches 'message_action'
-  app.shortcut({ callback_id: 'open_modal', type: 'message_action' }, async ({ shortcut, ack, client, logger }) => {
-    try {
-      // Acknowledge shortcut request
-      await ack();
-
-      // Call the views.open method using one of the built-in WebClients
-      const result = await client.views.open({
-        trigger_id: shortcut.trigger_id,
-        view: {
-          type: "modal",
-          title: {
-            type: "plain_text",
-            text: "My App"
-          },
-          close: {
-            type: "plain_text",
-            text: "Close"
-          },
-          blocks: [
-            {
-              type: "section",
-              text: {
-                type: "mrkdwn",
-                text: "About the simplest modal you could conceive of :smile:\n\nMaybe <https://api.slack.com/reference/block-kit/interactive-components|*make the modal interactive*> or <https://api.slack.com/surfaces/modals/using#modifying|*learn more advanced modal use cases*>."
-              }
-            },
-            {
-              type: "context",
-              elements: [
-                {
-                  type: "mrkdwn",
-                  text: "Psssst this modal was designed using <https://api.slack.com/tools/block-kit-builder|*Block Kit Builder*>"
-                }
-              ]
-            }
-          ]
-        }
-      });
-
-      logger.info(result);
-    }
-    catch (error) {
-      logger.error(error);
-    }
-  });
-
-
-
- -
- - -
-
- -
-

Listening and responding to commands

- -
-

Your app can use the command() method to listen to incoming slash command requests. The method requires a commandName of type string or RegExp.

- -

⚠️ Note that if you use command() multiple times with overlapping RegExp matches, all matching listeners will run. Design your regular expressions to avoid this possibility.

- -

Commands must be acknowledged with ack() to inform Slack your app has received the request.

- -

There are two ways to respond to slash commands. The first way is to use say(), which accepts a string or JSON payload. The second is respond() which is a utility for the response_url. These are explained in more depth in the responding to actions section.

- -

When configuring commands within your app configuration, you’ll continue to append /slack/events to your request URL.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-
// The echo command simply echoes on command
-app.command('/echo', async ({ command, ack, respond }) => {
-  // Acknowledge command request
-  await ack();
-
-  await respond(`${command.text}`);
-});
-
-
-
- - -
-
- -
-

Opening modals

- -
-

Modals are focused surfaces that allow you to collect user data and display dynamic information. You can open a modal by passing a valid trigger_id and a view payload to the built-in client’s views.open method.

- -

Your app receives trigger_ids in payloads sent to your Request URL triggered user invocation like a slash command, button press, or interaction with a select menu.

- -

Read more about modal composition in the API documentation.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-23
-24
-25
-26
-27
-28
-29
-30
-31
-32
-33
-34
-35
-36
-37
-38
-39
-40
-41
-42
-43
-44
-45
-46
-47
-48
-49
-50
-51
-52
-53
-54
-55
-56
-57
-58
-59
-60
-61
-
// Listen for a slash command invocation
-app.command('/ticket', async ({ ack, body, client, logger }) => {
-  // Acknowledge the command request
-  await ack();
-
-  try {
-    // Call views.open with the built-in client
-    const result = await client.views.open({
-      // Pass a valid trigger_id within 3 seconds of receiving it
-      trigger_id: body.trigger_id,
-      // View payload
-      view: {
-        type: 'modal',
-        // View identifier
-        callback_id: 'view_1',
-        title: {
-          type: 'plain_text',
-          text: 'Modal title'
-        },
-        blocks: [
-          {
-            type: 'section',
-            text: {
-              type: 'mrkdwn',
-              text: 'Welcome to a modal with _blocks_'
-            },
-            accessory: {
-              type: 'button',
-              text: {
-                type: 'plain_text',
-                text: 'Click me!'
-              },
-              action_id: 'button_abc'
-            }
-          },
-          {
-            type: 'input',
-            block_id: 'input_c',
-            label: {
-              type: 'plain_text',
-              text: 'What are your hopes and dreams?'
-            },
-            element: {
-              type: 'plain_text_input',
-              action_id: 'dreamy_input',
-              multiline: true
-            }
-          }
-        ],
-        submit: {
-          type: 'plain_text',
-          text: 'Submit'
-        }
-      }
-    });
-    logger.info(result);
-  }
-  catch (error) {
-    logger.error(error);
-  }
-});
-
-
-
- - -
-
- -
-

Updating and pushing views

- -
-

Modals contain a stack of views. When you call views.open, you add the root view to the modal. After the initial call, you can dynamically update a view by calling views.update, or stack a new view on top of the root view by calling views.push.

- -

views.update
-To update a view, you can use the built-in client to call views.update with the view_id that was generated when you opened the view, and a new view including the updated blocks array. If you’re updating the view when a user interacts with an element inside of an existing view, the view_id will be available in the body of the request.

- -

views.push
-To push a new view onto the view stack, you can use the built-in client to call views.push with a valid trigger_id a new view payload. The arguments for views.push is the same as opening modals. After you open a modal, you may only push two additional views onto the view stack.

- -

Learn more about updating and pushing views in our API documentation.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-23
-24
-25
-26
-27
-28
-29
-30
-31
-32
-33
-34
-35
-36
-37
-38
-39
-40
-41
-42
-43
-44
-45
-46
-
// Listen for a button invocation with action_id `button_abc` (assume it's inside of a modal)
-app.action('button_abc', async ({ ack, body, client, logger }) => {
-  // Acknowledge the button request
-  await ack();
-
-  try {
-    if (body.type !== 'block_actions' || !body.view) {
-      return;
-    }
-    // Call views.update with the built-in client
-    const result = await client.views.update({
-      // Pass the view_id
-      view_id: body.view.id,
-      // Pass the current hash to avoid race conditions
-      hash: body.view.hash,
-      // View payload with updated blocks
-      view: {
-        type: 'modal',
-        // View identifier
-        callback_id: 'view_1',
-        title: {
-          type: 'plain_text',
-          text: 'Updated modal'
-        },
-        blocks: [
-          {
-            type: 'section',
-            text: {
-              type: 'plain_text',
-              text: 'You updated the modal!'
-            }
-          },
-          {
-            type: 'image',
-            image_url: 'https://media.giphy.com/media/SVZGEcYt7brkFUyU90/giphy.gif',
-            alt_text: 'Yay! The modal was updated'
-          }
-        ]
-      }
-    });
-    logger.info(result);
-  }
-  catch (error) {
-    logger.error(error);
-  }
-});
-
-
-
- - -
-
- -
-

Listening to views

- -
- -

You may listen for user interactions with views using the view method.

- -

Slack will send a view_submission request when a user submits a view. To receive the values submitted in view input blocks, you can access the state object. state contains a values object that uses the block_id and unique action_id to store the input values. -If the notify_on_close field of a view has been set to true, Slack will also send a view_closed request if a user clicks the close button. See the section on Handling views on close for more detail. -To listen to either a view_submission request or view_closed request, you can use the built-in view() method.

- -

view() requires a callback_id of type string or RegExp or a constraint object with properties type and callback_id.

- -
- -
Update views on submission
- -

To update a view in response to a view_submission request, you may pass a response_action of type update with a newly composed view to display in your acknowledgement.

- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-
// Update the view on submission 
-app.view('modal-callback-id', async ({ ack, body }) => {
-  await ack({
-    response_action: 'update',
-    view: buildNewModalView(body),
-  });
-});
-
-
-
-

Similarly, there are options for displaying errors in response to view submissions.

- -

Read more about view submissions in our API documentation.

- -
- -
Handling views on close
- -

💡 When listening for view_closed requests, you must pass an object containing type: 'view_closed' and the view callback_id. See below for an example of this:

- -

See the API documentation for more information about view_closed.

- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-
// Handle a view_closed request
-app.view({ callback_id: 'view_b', type: 'view_closed' }, async ({ ack, body, view, client }) => {
-  // Acknowledge the view_closed request
-  await ack();
-  // react on close request
-});
-
-
-
-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-23
-24
-25
-26
-27
-28
-29
-30
-31
-32
-33
-34
-35
-
// Handle a view_submission request
-app.view('view_b', async ({ ack, body, view, client, logger }) => {
-  // Acknowledge the view_submission request
-  await ack();
-
-  // Do whatever you want with the input data - here we're saving it to a DB then sending the user a verification of their submission
-
-  // Assume there's an input block with `block_1` as the block_id and `input_a`
-  const val = view['state']['values']['block_1']['input_a'];
-  const user = body['user']['id'];
-
-  // Message to send user
-  let msg = '';
-  // Save to DB
-  const results = await db.set(user.input, val);
-
-  if (results) {
-    // DB save was successful
-    msg = 'Your submission was successful';
-  } else {
-    msg = 'There was an error with your submission';
-  }
-
-  // Message the user
-  try {
-    await client.chat.postMessage({
-      channel: user,
-      text: msg
-    });
-  }
-  catch (error) {
-    logger.error(error);
-  }
-
-});
-
-
-
- - -
-
- -
-

Publishing views to App Home

- -
-

Home tabs are customizable surfaces accessible via the sidebar and search that allow apps to display views on a per-user basis. After enabling App Home within your app configuration, home tabs can be published and updated by passing a user_id and view payload to the views.publish method.

- -

You can subscribe to the app_home_opened event to listen for when users open your App Home.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-23
-24
-25
-26
-27
-28
-29
-30
-31
-32
-33
-34
-35
-
// Listen for users opening your App Home
-app.event('app_home_opened', async ({ event, client, logger }) => {
-  try {
-    // Call views.publish with the built-in client
-    const result = await client.views.publish({
-      // Use the user ID associated with the event
-      user_id: event.user,
-      view: {
-        // Home tabs must be enabled in your app configuration page under "App Home"
-        type: "home",
-        blocks: [
-          {
-            type: "section",
-            text: {
-              type: "mrkdwn",
-              text: "*Welcome home, <@" + event.user + "> :house:*"
-            }
-          },
-          {
-            type: "section",
-            text: {
-              type: "mrkdwn",
-              text: "Learn how home tabs can be more useful and interactive <https://api.slack.com/surfaces/tabs/using|*in the documentation*>."
-            }
-          }
-        ]
-      }
-    });
-
-    logger.info(result);
-  }
-  catch (error) {
-    logger.error(error);
-  }
-});
-
-
-
- - -
-
- -
-

Listening and responding to options

- -
-

The options() method listens for incoming option request payloads from Slack. Similar to action(), -an action_id or constraints object is required.

- -

While it’s recommended to use action_id for external_select menus, dialogs do not yet support Block Kit so you’ll have to -use the constraints object to filter on a callback_id.

- -

To respond to options requests, you’ll need to ack() with valid options. Both external select response examples and dialog response examples can be found on our API site.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-23
-24
-25
-
// Example of responding to an external_select options request
-app.options('external_action', async ({ options, ack }) => {
-  // Get information specific to a team or channel
-  const results = await db.get(options.team.id);
-
-  if (results) {
-    let options = [];
-    // Collect information in options array to send in Slack ack response
-    for (const result of results) {
-      options.push({
-        text: {
-          type: "plain_text",
-          text: result.label
-        },
-        value: result.value
-      });
-    }
-
-    await ack({
-      options: options
-    });
-  } else {
-    await ack();
-  }
-});
-
-
-
- - -
-
- -
-

Authenticating with OAuth

- -
-

To prepare your Slack app for distribution, you will need to enable Bolt OAuth and store installation information securely. Bolt supports OAuth and will handle the rest of the work; this includes setting up OAuth routes, state verification, and passing your app an installation object which you must store.

- -

To enable OAuth, you must provide:

-
    -
  • -clientId, clientSecret, stateSecret and scopes (required) -
  • -
  • An installationStore option with handlers that store and fetch installations to your database (optional, strongly recommended in production) -
  • -
- -
- -
Development and Testing
- -

We’ve provided a default implementation of the installationStore FileInstallationStore which you can use during app development and testing.

- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-
const { App } = require('@slack/bolt');
-const { FileInstallationStore } = require('@slack/oauth');
-const app = new App({
-  signingSecret: process.env.SLACK_SIGNING_SECRET,
-  clientId: process.env.SLACK_CLIENT_ID,
-  clientSecret: process.env.SLACK_CLIENT_SECRET,
-  stateSecret: 'my-state-secret',
-  scopes: ['channels:history', 'chat:write', 'commands'],
-  installationStore: new FileInstallationStore(),
-});
-
-
-
-

:warning: This is not recommended for use in production - you should implement your own production store. Please see the example code to the right and our other examples.

- -
- -
Installing your App
- -
    -
  • -Initiating an installation: Bolt for JavaScript provides an Install Path /slack/install out-of-the-box. This endpoint returns a simple page with an Add to Slack button which initiates a direct install of your app (with a valid state parameter). An app hosted at www.example.com would serve the install page at www.example.com/slack/install. -
      -
    • 💡 You can skip rendering the provided default webpage and navigate users directly to Slack authorize URL by settinginstallerOptions.directInstall: true in the App constructor (example).
    • -
    -
  • -
  • -

    Add to Slack: The Add to Slack button initiates the OAuth process with Slack. After users have clicked Allow to grant your app permissions, Slack will call your app’s Redirect URI (provided out-of-the-box), and prompt users to Open Slack. See the Redirect URI section below for customization options.

    -
  • -
  • -

    Open Slack: After users Open Slack, and here after as your app processes events from Slack, your provided installationStore’s fetchInstallation and storeInstallation handlers will execute. See the Installation Object section below for more detail on arguments passed to those handlers.

    -
  • -
  • -

    If you need additional authorizations (user tokens) from users inside a team when your app is already installed, or have a reason to dynamically generate an install URL, manually instantiate an ExpressReceiver, assign the instance to a variable named receiver, and then call receiver.installer.generateInstallUrl(). Read more about generateInstallUrl() in the OAuth docs.

    -
  • -
  • 💡 Bolt for JavaScript does not support OAuth for custom receivers. If you’re implementing a custom receiver, you can use our Slack OAuth library, which is what Bolt for JavaScript uses under the hood.
  • -
- -
- -
Redirect URI
-

Bolt for JavaScript provides a Redirect URI Path /slack/oauth_redirect. Slack uses the Redirect URI to redirect users after they complete an app’s installation flow.

- -

💡 You will need to add the full Redirect URI including your app domain in your Slack app configuration settings under OAuth and Permissions, e.g. https://example.com/slack/oauth_redirect.

- -

To supply your own custom Redirect URI, you can set redirectUri in the App options and installerOptions.redirectUriPath. You must supply both, and the path must be consistent with the full URI.

- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-
const app = new App({
-  signingSecret: process.env.SLACK_SIGNING_SECRET,
-  clientId: process.env.SLACK_CLIENT_ID,
-  clientSecret: process.env.SLACK_CLIENT_SECRET,
-  stateSecret: 'my-state-secret',
-  scopes: ['chat:write'],
-  redirectUri: 'https://example.com/slack/redirect', // here
-  installerOptions: {
-    redirectUriPath: '/slack/redirect', // and here!
-  },
-});
-
-
-
- -
- -
Installation object
-

Bolt will pass your installationStore’s storeInstallation handler an installation. This can be a source of confusion for developers who aren’t sure what shape of object to expect. The installation object should resemble:

- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-
{
-  team: { id: 'T012345678', name: 'example-team-name' },
-  enterprise: undefined,
-  user: { token: undefined, scopes: undefined, id: 'U01234567' },
-  tokenType: 'bot',
-  isEnterpriseInstall: false,
-  appId: 'A01234567',
-  authVersion: 'v2',
-  bot: {
-    scopes: [
-      'chat:write',
-    ],
-    token: 'xoxb-244493-28*********-********************',
-    userId: 'U012345678',
-    id: 'B01234567'
-  }
-}
-
-
-
-

Bolt will pass your fetchInstallation and deleteInstallation handlers an installQuery object:

- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-
{
-  userId: 'U012345678',
-  isEnterpriseInstall: false,
-  teamId: 'T012345678',
-  enterpriseId: undefined,
-  conversationId: 'D02345678'
-}
-
-
-
- -
- -
Org-wide installation
-

To add support for org-wide installations, you will need Bolt for JavaScript version 3.0.0 or later. Make sure you have enabled org-wide installation in your app configuration settings under Org Level Apps.

- -

Installing an org-wide app from admin pages requires additional configuration to work with Bolt. In that scenario, the recommended state parameter is not supplied. Bolt will try to verify state and stop the installation from progressing.

- -

You may disable state verification in Bolt by setting the stateVerification option to false. See the example setup below:

- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-
const app = new App({
-  signingSecret: process.env.SLACK_SIGNING_SECRET,
-  clientId: process.env.SLACK_CLIENT_ID,
-  clientSecret: process.env.SLACK_CLIENT_SECRET,
-  scopes: ['chat:write'],
-  installerOptions: {
-    stateVerification: false,
-  },
-});
-
-
-
- -

To learn more about the OAuth installation flow with Slack, read the API documentation.

- -
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-23
-24
-25
-26
-27
-28
-29
-30
-31
-32
-33
-34
-35
-36
-37
-38
-39
-40
-41
-42
-43
-44
-45
-46
-47
-48
-49
-50
-51
-52
-53
-54
-
const database = {
-  async get(key) {},
-  async delete(key) {},
-  async set(key, value) {}
-};
-
-const app = new App({
-  signingSecret: process.env.SLACK_SIGNING_SECRET,
-  clientId: process.env.SLACK_CLIENT_ID,
-  clientSecret: process.env.SLACK_CLIENT_SECRET,
-  stateSecret: 'my-secret',
-  scopes: ['chat:write', 'commands'],
-  installationStore: {
-    storeInstallation: async (installation) => {
-      // Bolt will pass your handler an installation object
-      // Change the lines below so they save to your database
-      if (installation.isEnterpriseInstall && installation.enterprise !== undefined) {
-        // handle storing org-wide app installation
-        return await database.set(installation.enterprise.id, installation);
-      }
-      if (installation.team !== undefined) {
-        // single team app installation
-        return await database.set(installation.team.id, installation);
-      }
-      throw new Error('Failed saving installation data to installationStore');
-    },
-    fetchInstallation: async (installQuery) => {
-      // Bolt will pass your handler an installQuery object
-      // Change the lines below so they fetch from your database
-      if (installQuery.isEnterpriseInstall && installQuery.enterpriseId !== undefined) {
-        // handle org wide app installation lookup
-        return await database.get(installQuery.enterpriseId);
-      }
-      if (installQuery.teamId !== undefined) {
-        // single team app installation lookup
-        return await database.get(installQuery.teamId);
-      }
-      throw new Error('Failed fetching installation');
-    },
-    deleteInstallation: async (installQuery) => {
-      // Bolt will pass your handler  an installQuery object
-      // Change the lines below so they delete from your database
-      if (installQuery.isEnterpriseInstall && installQuery.enterpriseId !== undefined) {
-        // org wide app installation deletion
-        return await database.delete(installQuery.enterpriseId);
-      }
-      if (installQuery.teamId !== undefined) {
-        // single team app installation deletion
-        return await database.delete(installQuery.teamId);
-      }
-      throw new Error('Failed to delete installation');
-    },
-  },
-});
-
-
-
- -
- -

Customizing OAuth defaults

-
- -
-

We provide several options for customizing default OAuth using the installerOptions object, which can be passed in during the initialization of App. You can override the following:

- -
    -
  • -authVersion: Used to toggle between new Slack Apps and Classic Slack Apps
  • -
  • -metadata: Used to pass around session related information
  • -
  • -installPath: Override default path for “Add to Slack” button
  • -
  • -redirectUriPath: This relative path must match the redirectUri provided in the App options
  • -
  • -callbackOptions: Provide custom success and failure pages at the end of the OAuth flow
  • -
  • -stateStore: Provide a custom state store instead of using the built in ClearStateStore -
  • -
  • -userScopes: Array of user scopes needed when the user installs the app, similar to scopes attribute at the parent level.
  • -
- -
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-23
-24
-25
-26
-27
-28
-29
-30
-31
-32
-33
-34
-35
-36
-37
-38
-39
-40
-41
-42
-43
-44
-
const app = new App({
-  signingSecret: process.env.SLACK_SIGNING_SECRET,
-  clientId: process.env.SLACK_CLIENT_ID,
-  clientSecret: process.env.SLACK_CLIENT_SECRET,
-  scopes: ['channels:read', 'groups:read', 'channels:manage', 'chat:write', 'incoming-webhook'],
-  installerOptions: {
-      authVersion: 'v1', // default  is 'v2', 'v1' is used for classic slack apps
-      metadata: 'some session data',
-      installPath: '/slack/installApp',
-      redirectUriPath: '/slack/redirect',
-      userScopes: ['chat:write'],
-      callbackOptions: {
-        success: (installation, installOptions, req, res) => {
-          // Do custom success logic here
-          res.send('successful!');
-        }, 
-        failure: (error, installOptions , req, res) => {
-          // Do custom failure logic here
-          res.send('failure');
-        }
-      },
-      stateStore: {
-        // Do not need to provide a `stateSecret` when passing in a stateStore
-        // generateStateParam's first argument is the entire InstallUrlOptions object which was passed into generateInstallUrl method
-        // the second argument is a date object
-        // the method is expected to return a string representing the state
-        generateStateParam: async (installUrlOptions, date) => {
-          // generate a random string to use as state in the URL
-          const randomState = randomStringGenerator();
-          // save installOptions to cache/db
-          await myDB.set(randomState, installUrlOptions);
-          // return a state string that references saved options in DB
-          return randomState;
-        },
-        // verifyStateParam's first argument is a date object and the second argument is a string representing the state
-        // verifyStateParam is expected to return an object representing installUrlOptions
-        verifyStateParam:  async (date, state) => {
-          // fetch saved installOptions from DB using state reference
-          const installUrlOptions = await myDB.get(randomState);
-          return installUrlOptions;
-        }
-      },
-  }
-});
-
-
-
- -
- - -
-
- -
-

Using Socket Mode

- -
-

Socket Mode allows your app to connect and receive data from Slack via a WebSocket connection. To handle the connection, Bolt for JavaScript includes a SocketModeReceiver (in @slack/bolt@3.0.0 and higher). Before using Socket Mode, be sure to enable it within your app configuration.

- -

To use the SocketModeReceiver, just pass in socketMode:true and appToken:YOUR_APP_TOKEN when initializing App. You can get your App Level Token in your app configuration under the Basic Information section.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-
const { App } = require('@slack/bolt');
-
-const app = new App({
-  token: process.env.BOT_TOKEN,
-  socketMode: true,
-  appToken: process.env.APP_TOKEN,
-});
-
-(async () => {
-  await app.start();
-  console.log('⚡️ Bolt app started');
-})();
-
-
-
- -
- -

Custom SocketMode Receiver

-
- -
-

You can define a custom SocketModeReceiver by importing it from @slack/bolt.

- -
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-
const { App, SocketModeReceiver } = require('@slack/bolt');
-
-const socketModeReceiver = new SocketModeReceiver({
-  appToken: process.env.APP_TOKEN,
-
-  // enable the following if you want to use OAuth
-  // clientId: process.env.CLIENT_ID,
-  // clientSecret: process.env.CLIENT_SECRET,
-  // stateSecret: 'my-state-secret',
-  // scopes: ['channels:read', 'chat:write', 'app_mentions:read', 'channels:manage', 'commands'],
-});
-
-const app = new App({
-  receiver: socketModeReceiver,
-  // disable token line below if using OAuth
-  token: process.env.BOT_TOKEN
-});
-
-(async () => {
-  await app.start();
-  console.log('⚡️ Bolt app started');
-})();
-
-
-
- -
- - -
-
- -
- -
- -
-

Handling errors

- -
-

Note: Since v2, error handling has improved! View the migration guide for V2 to learn about the changes.

- -

If an error occurs in a listener, it’s recommended you handle it directly with a try/catch. However, there still may be cases where errors slip through the cracks. By default, these errors will be logged to the console. To handle them yourself, you can attach a global error handler to your app with the app.error(fn) method.

- -

You can also define more focussed and specific error handlers for a variety of error paths directly on the HTTPReceiver:

- -
    -
  • -dispatchErrorHandler: triggered if an incoming request is to an unexpected path.
  • -
  • -processEventErrorHandler: triggered when processing a request (i.e. middleware, authorization) throws an exception.
  • -
  • -unhandledRequestHandler: triggered when a request from Slack goes unacknowledged.
  • -
  • -unhandledRequestTimeoutMillis: the amount of time in milliseconds to wait for request acknowledgement from the application before triggering the unhandledRequestHandler. Default is 3001.
  • -
- -

NOTE: It is imperative that any custom Error Handlers defined in your app respond to the underlying Slack request that led to the error, using response.writeHead() to set the HTTP status code of the response and response.end() to dispatch the response back to Slack. See the example for details.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-23
-24
-25
-26
-27
-28
-29
-30
-31
-32
-33
-34
-
import { App, HTTPReceiver } from '@slack/bolt';
-
-const app = new App({
-  receiver: new HTTPReceiver({
-    signingSecret: process.env.SLACK_SIGNING_SECRET,
-    // more specific, focussed error handlers
-    dispatchErrorHandler: async ({ error, logger, response }) => {
-      logger.error(`dispatch error: ${error}`);
-      response.writeHead(404);
-      response.write("Something is wrong!");
-      response.end();
-    },
-    processEventErrorHandler: async ({ error, logger, response }) => {
-      logger.error(`processEvent error: ${error}`);
-      // acknowledge it anyway!
-      response.writeHead(200);
-      response.end();
-      return true;
-    },
-    unhandledRequestHandler: async ({ logger, response }) => {
-      logger.info('Acknowledging this incoming request because 2 seconds already passed...');
-      // acknowledge it anyway!
-      response.writeHead(200);
-      response.end();
-    },
-    unhandledRequestTimeoutMillis: 2000, // the default is 3001
-  }),
-});
-
-// A more generic, global error handler
-app.error(async (error) => {
-  // Check the details of the error to handle cases where you should retry sending a message or stop the app
-  console.error(error);
-});
-
-
-
- -
- -

Accessing more data in the error handler

-
- -
-

There may be cases where you need to log additional data from a request in the global error handler. Or you may simply wish to have access to the logger you’ve passed into Bolt.

- -

Starting with version 3.8.0, when passing extendedErrorHandler: true to the constructor, the error handler will receive an object with error, logger, context, and the body of the request.

- -

It is recommended to check whether a property exists on the context or body objects before accessing its value, as the data available in the body object differs from event to event, and because errors can happen at any point in a request’s lifecycle (i.e. before a certain property of context has been set).

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-
const { App } = require('@slack/bolt');
-
-const app = new App({
-  signingSecret: process.env.SLACK_SIGNING_SECRET,
-  token: process.env.SLACK_BOT_TOKEN,
-  extendedErrorHandler: true,
-});
-
-app.error(async ({ error, logger, context, body }) => {
-  // Log the error using the logger passed into Bolt
-  logger.error(error);
-
-  if (context.teamId) {
-    // Do something with the team's ID for debugging purposes
-  }
-});
-
-
-
- -
- - -
-
- -
-

Authorization

- -
-

Authorization is the process of deciding which Slack credentials (such as a bot token) should be available while processing a specific incoming request.

- -

Custom apps installed on a single workspace can simply use the token option at the time of App initialization. However, when your app needs to handle several tokens, such as cases where it will be installed on multiple workspaces or needs access to more than one user token, the authorize option should be used instead. If you’re using the built-in OAuth support authorization is handled by default, so you do not need to pass in an authorize option.

- -

The authorize option can be set to a function that takes an event source as its input, and should return a Promise for an object containing the authorized credentials. The source contains information about who and where the request is coming from by using properties like teamId (always available), userId, conversationId, and enterpriseId.

- -

The authorized credentials should also have a few specific properties: botToken, userToken, botId (required for an app to ignore messages from itself), and botUserId. You can also include any other properties you’d like to make available on the context object.

- -

You should always provide either one or both of the botToken and userToken properties. At least one of them is necessary to make helpers like say() work. If they are both given, then botToken will take precedence.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-23
-24
-25
-26
-27
-28
-29
-30
-31
-32
-33
-34
-35
-36
-37
-38
-39
-
const app = new App({ authorize: authorizeFn, signingSecret: process.env.SLACK_SIGNING_SECRET });
-
-// NOTE: This is for demonstration purposes only.
-// All sensitive data should be stored in a secure database
-// Assuming this app only uses bot tokens, the following object represents a model for storing the credentials as the app is installed into multiple workspaces.
-
-const installations = [
-  {
-    enterpriseId: 'E1234A12AB',
-    teamId: 'T12345',
-    botToken: 'xoxb-123abc',
-    botId: 'B1251',
-    botUserId: 'U12385',
-  },
-  {
-    teamId: 'T77712',
-    botToken: 'xoxb-102anc',
-    botId: 'B5910',
-    botUserId: 'U1239',
-  },
-];
-
-const authorizeFn = async ({ teamId, enterpriseId }) => {
-  // Fetch team info from database
-  for (const team of installations) {
-    // Check for matching teamId and enterpriseId in the installations array
-    if ((team.teamId === teamId) && (team.enterpriseId === enterpriseId)) {
-      // This is a match. Use these installation credentials.
-      return {
-        // You could also set userToken instead
-        botToken: team.botToken,
-        botId: team.botId,
-        botUserId: team.botUserId
-      };
-    }
-  }
-
-  throw new Error('No matching authorizations');
-}
-
-
-
- - -
-
- -
-

Token rotation

- -
-

Supported in Bolt for JavaScript as of v3.5.0, token rotation provides an extra layer of security for your access tokens and is defined by the OAuth V2 RFC.

- -

Instead of an access token representing an existing installation of your Slack app indefinitely, with token rotation enabled, access tokens expire. A refresh token acts as a long-lived way to refresh your access tokens.

- -

Bolt for JavaScript supports and will handle token rotation automatically so long as the built-in OAuth functionality is used.

- -

For more information about token rotation, please see the documentation.

-
- - -
-
- -
-

Conversation stores

- -
-

Bolt for JavaScript includes support for a store, which sets and retrieves state related to a conversation. Conversation stores have two methods:

-
    -
  • -set() modifies conversation state. set() requires a conversationId of type string, value of any type, and an optional expiresAt of type number. set() returns a Promise.
  • -
  • -get() fetches conversation state from the store. get() requires a conversationId of type string and returns a Promise with the conversation’s state.
  • -
- -

conversationContext() is a built-in global middleware that allows conversations to be updated by other middleware. When receiving an event, middleware functions can use context.updateConversation() to set state and context.conversation to retrieve it.

- -

The built-in conversation store simply stores conversation state in memory. While this is sufficient for some situations, if there is more than one instance of your app running, the state will not be shared among the processes so you’ll want to implement a conversation store that fetches conversation state from a database.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-23
-24
-25
-26
-27
-28
-29
-30
-31
-32
-33
-
const app = new App({
-  token,
-  signingSecret,
-  // It's more likely that you'd create a class for a convo store
-  convoStore: new simpleConvoStore()
-});
-
-// A simple implementation of a conversation store with a Firebase-like database
-class simpleConvoStore {
-  set(conversationId, value, expiresAt) {
-    // Returns a Promise
-    return db().ref('conversations/' + conversationId).set({ value, expiresAt });
-  }
-
-  get(conversationId) {
-    // Returns a Promise
-    return new Promise((resolve, reject) => {
-      db().ref('conversations/' + conversationId).once('value').then((result) => {
-        if (result !== undefined) {
-          if (result.expiresAt !== undefined && Date.now() > result.expiresAt) {
-            db().ref('conversations/' + conversationId).delete();
-
-            reject(new Error('Conversation expired'));
-          }
-          resolve(result.value)
-        } else {
-          // Conversation not found
-          reject(new Error('Conversation not found'));
-        }
-      });
-    });
-  }
-}
-
-
-
- - -
-
- -
-

Global middleware

- -
-

Global middleware is run for all incoming requests before any listener middleware. You can add any number of global middleware to your app by utilizing app.use(fn). The middleware function fn is called with the same arguments as listeners and an additional next function.

- -

Both global and listener middleware must call await next() to pass control of the execution chain to the next middleware, or call throw to pass an error back up the previously-executed middleware chain.

- -

As an example, let’s say your app should only respond to users identified with a corresponding internal authentication service (an SSO provider or LDAP, for example). You may define a global middleware that looks up a user record in the authentication service and errors if the user is not found.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-23
-24
-25
-26
-27
-28
-29
-30
-31
-32
-
// Authentication middleware that associates incoming request with user in Acme identity provider
-async function authWithAcme({ payload, client, context, next }) {
-  const slackUserId = payload.user;
-  const helpChannelId = 'C12345';
-
-  // Assume we have a function that accepts a Slack user ID to find user details from Acme
-  try {
-    // Assume we have a function that can take a Slack user ID as input to find user details from the provider
-    const user = await acme.lookupBySlackId(slackUserId);
-      
-    // When the user lookup is successful, add the user details to the context
-    context.user = user;
-  } catch (error) {
-    // This user wasn't found in Acme. Send them an error and don't continue processing request
-    if (error.message === 'Not Found') {
-        await client.chat.postEphemeral({
-          channel: payload.channel,
-          user: slackUserId,
-          text: `Sorry <@${slackUserId}>, you aren't registered in Acme. Please post in <#${helpChannelId}> for assistance.`
-        });
-        return;
-    }
-    
-    // Pass control to previous middleware (if any) or the global error handler
-    throw error;
-  }
-  
-  // Pass control to the next middleware (if there are any) and the listener functions
-  // Note: You probably don't want to call this inside a `try` block, or any middleware
-  //       after this one that throws will be caught by it. 
-  await next();
-}
-
-
-
- - -
-
- -
-

Listener middleware

- -
-

Listener middleware is used for logic across many listener functions (but usually not all of them). They are added as arguments before the listener function in one of the built-in methods. You can add any number of listener middleware before the listener function.

- -

There’s a collection of built-in listener middleware that you can use like directMention which filters out any message that doesn’t directly @-mention your bot at the start of a message.

- -

But of course, you can write your own middleware for more custom functionality. While writing your own middleware, your function must call await next() to pass control to the next middleware, or throw to pass an error back up the previously-executed middleware chain.

- -

As an example, let’s say your listener should only deal with messages from humans. You can write a listener middleware that excludes any bot messages.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-
// Listener middleware that filters out messages with 'bot_message' subtype
-async function noBotMessages({ message, next }) {
-  if (!message.subtype || message.subtype !== 'bot_message') {
-    await next();
-  }
-}
-
-// The listener only receives messages from humans
-app.message(noBotMessages, async ({ message, logger }) => logger.info(
-  // Handle only newly posted messages
-  if (message.subtype === undefined
-    // || message.subtype === 'bot_message'
-    || message.subtype === 'file_share'
-    || message.subtype === 'thread_broadcast') {
-    logger.info(`(MSG) User: ${message.user} Message: ${message.text}`)
-  }
-));
-
-
-
- - -
-
- -
-

Adding context

- -
-

All listeners have access to a context object, which can be used to enrich requests with additional information. For example, perhaps you want to add user information from a third party system or add temporary state for the next middleware in the chain.

- -

context is just an object, so you can add to it by setting it to a modified version of itself.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-23
-24
-25
-26
-27
-28
-29
-30
-31
-32
-33
-34
-35
-36
-37
-38
-39
-40
-41
-42
-43
-44
-45
-46
-47
-48
-49
-50
-51
-52
-
async function addTimezoneContext({ payload, client, context, next }) {
-  const user = await client.users.info({
-    user: payload.user_id,
-    include_locale: true
-  });
-
-  // Add user's timezone context
-  context.tz_offset = user.tz_offset;
-
-  // Pass control to the next middleware function
-  await next();
-}
-
-app.command('/request', addTimezoneContext, async ({ command, ack, client, context, logger }) => {
-  // Acknowledge command request
-  await ack();
-  // Get local hour of request
-  const localHour = (Date.UTC(2020, 3, 31) + context.tz_offset).getHours();
-
-  // Request channel ID
-  const requestChannel = 'C12345';
-
-  const requestText = `:large_blue_circle: *New request from <@${command.user_id}>*: ${command.text}`;
-
-  // If request not inbetween 9AM and 5PM, send request tomorrow
-  if (localHour > 17 || localHour < 9) {
-    // Assume function exists to get local tomorrow 9AM from offset
-    const localTomorrow = getLocalTomorrow(context.tz_offset);
-
-    try {
-      // Schedule message
-      const result = await client.chat.scheduleMessage({
-        channel: requestChannel,
-        text: requestText,
-        post_at: localTomorrow
-      });
-    }
-    catch (error) {
-      logger.error(error);
-    }
-  } else {
-    try {
-      // Post now
-      const result = await client.chat.postMessage({
-        channel: requestChannel,
-        text: requestText
-      });
-    } catch (error) {
-      logger.error(error);
-    }
-  }
-});
-
-
-
- - -
-
- -
-

Deferring App initialization

- -
-

Bolt offers a way to defer full initialization via the deferInitialization option and to call the equivalent App#init() in your code, putting more control over asynchronous execution required for initialization into your hands as the developer.

- -

Note: If you call start() before init(), Bolt will raise an exception.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-
const { App } = require('@slack/bolt');
-
-// deferInitialization is one of the options you can set in the constructor
-const app = new App({
-  token,
-  signingSecret,
-  deferInitialization: true,
-});
-
-(async () => {
-  try {
-    // Must call init() before start() within an async function
-    await app.init();
-    // Now safe to call start()
-    await app.start(process.env.PORT || 3000);
-  } catch (e) {
-    console.log(e);
-    process.exit(1);
-  }
-})()
-
-
-
- - -
-
- -
-

Logging

- -
-

By default, Bolt for JavaScript will log information from your app to the console. You can customize how much logging occurs by passing a logLevel in the constructor. The available log levels in order of most to least logs are DEBUG, INFO, WARN, and ERROR.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-
// Import LogLevel from the package
-const { App, LogLevel } = require('@slack/bolt');
-
-// Log level is one of the options you can set in the constructor
-const app = new App({
-  token,
-  signingSecret,
-  logLevel: LogLevel.DEBUG,
-});
-
-
-
- -
- -

Sending log output somewhere besides the console

-
- -
-

If you want to send logs to somewhere besides the console or want more control over the logger, you can implement a custom logger. A custom logger must implement specific methods (known as the Logger interface):

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
MethodParametersReturn type
setLevel()level: LogLevelvoid
getLevel()None -string with value error, warn, info, or debug -
setName()name: stringvoid
debug()...msgs: any[]void
info()...msgs: any[]void
warn()...msgs: any[]void
error()...msgs: any[]void
- -

A very simple custom logger might ignore the name and level, and write all messages to a file.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-
const { App } = require('@slack/bolt');
-const { createWriteStream } = require('fs');
-const logWritable = createWriteStream('/var/my_log_file'); // Not shown: close this stream
-
-const app = new App({
-  token,
-  signingSecret,
-  // Creating a logger as a literal object. It's more likely that you'd create a class.
-  logger: {
-    debug: (...msgs) => { logWritable.write('debug: ' + JSON.stringify(msgs)); },
-    info: (...msgs) => { logWritable.write('info: ' + JSON.stringify(msgs)); },
-    warn: (...msgs) => { logWritable.write('warn: ' + JSON.stringify(msgs)); },
-    error: (...msgs) => { logWritable.write('error: ' + JSON.stringify(msgs)); },
-    setLevel: (level) => { },
-    getLevel: () => { },
-    setName: (name) => { },
-  },
-});
-
-
-
- -
- - -
-
- -
-

Customizing a receiver

- -
- -

Writing a custom receiver

- -

A receiver is responsible for handling and parsing any incoming requests from Slack then sending it to the app, so that the app can add context and pass the request to your listeners. Receivers must conform to the Receiver interface:

- - - - - - - - - - - - - - - - - - - - - - - - - - -
MethodParametersReturn type
init()app: Appunknown
start()NonePromise
stop()NonePromise
- -

init() is called after Bolt for JavaScript app is created. This method gives the receiver a reference to an App to store so that it can call:

-
    -
  • -await app.processEvent(event) whenever your app receives a request from Slack. It will throw if there is an unhandled error.
  • -
- -

To use a custom receiver, you can pass it into the constructor when initializing your Bolt for JavaScript app. Here is what a basic custom receiver might look like.

- -

For a more in-depth look at a receiver, read the source code for the built-in ExpressReceiver

- -
- -

Customizing built-in receivers

- -

The built-in HTTPReceiver, ExpressReceiver, AwsLambdaReceiver and SocketModeReceiver accept several configuration options. For a full list of options, see the Receiver options reference.

- -
Extracting custom properties
- -

Use the customPropertiesExtractor option to extract custom properties from incoming events. The event type depends on the type of receiver you are using, e.g. HTTP requests for HTTPReceivers, websocket messages for SocketModeReceivers.

- -

This is particularly useful for extracting HTTP headers that you want to propagate to other services, for example, if you need to propagate a header for distributed tracing.

- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-23
-24
-25
-26
-
const { App, HTTPReceiver } = require('@slack/bolt');
-
-const app = new App({
-  token: process.env.SLACK_BOT_TOKEN,
-  receiver: new HTTPReceiver({
-    signingSecret: process.env.SLACK_SIGNING_SECRET,
-    customPropertiesExtractor: (req) => {
-      return {
-        "headers": req.headers,
-        "foo": "bar",
-      };
-    }
-  }),
-});
-
-app.use(async ({ logger, context, next }) => {
-  logger.info(context);
-  await next();
-});
-
-(async () => {
-  // Start your app
-  await app.start(process.env.PORT || 3000);
-
-  console.log('⚡️ Bolt app is running!');
-})();
-
-
-
- -

You can find more examples of extracting custom properties from different types of receivers here.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-23
-24
-25
-26
-27
-28
-29
-30
-31
-32
-33
-34
-35
-36
-37
-38
-39
-40
-41
-42
-43
-44
-45
-46
-47
-48
-49
-50
-51
-52
-53
-54
-55
-56
-57
-58
-59
-60
-61
-62
-63
-64
-65
-66
-67
-68
-69
-70
-
import { createServer } from 'http';
-import express from 'express';
-
-class SimpleReceiver  {
-  constructor(signingSecret, endpoints) {
-    this.app = express();
-    this.server = createServer(this.app);
-
-    for (const endpoint of endpoints) {
-      this.app.post(endpoint, this.requestHandler.bind(this));
-    }
-  }
-
-  init(app) {
-    this.bolt = app;
-  }
-
-  start(port) {
-    return new Promise((resolve, reject) => {
-      try {
-        this.server.listen(port, () => {
-          resolve(this.server);
-        });
-      } catch (error) {
-        reject(error);
-      }
-    });
-  }
-
-  stop() {
-    return new Promise((resolve, reject) => {
-      this.server.close((error) => {
-        if (error) {
-          reject(error);
-          return;
-        }
-        resolve();
-      })
-    })
-  }
-
-  // This is a very simple implementation. Look at the ExpressReceiver source for more detail
-  async requestHandler(req, res) {
-    let ackCalled = false;
-    // Assume parseBody function exists to parse incoming requests
-    const parsedReq = parseBody(req);
-    const event = {
-      body: parsedReq.body,
-      // Receivers are responsible for handling acknowledgements
-      // `ack` should be prepared to be called multiple times and
-      // possibly with `response` as an error
-      ack: (response) => {
-        if (ackCalled) {
-          return;
-        }
-
-        if (response instanceof Error) {
-          res.status(500).send();
-        } else if (!response) {
-          res.send('')
-        } else {
-          res.send(response);
-        }
-
-        ackCalled = true;
-      }
-    };
-    await this.bolt.processEvent(event);
-  }
-}
-
-
-
- - -
-
- -
-

Adding Custom HTTP routes

- -
-

As of v3.7.0, custom HTTP routes can be easily added by passing in an array of routes as customRoutes when initializing App.

- -

Each CustomRoute object must contain three properties: path, method, and handler. method, which corresponds to the HTTP verb, can be either a string or an array of strings.

- -

Since v3.13.0, the default built-in receivers (HTTPReceiver and SocketModeReceiver) support dynamic route parameters like Express.js does. With this, you can capture positional values in the URL for use in your route’s handler via req.params.

- -

To determine what port the custom HTTP route will be available on locally, you can specify an installerOptions.port property in the App constructor. Otherwise, it will default to port 3000.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-23
-24
-25
-26
-27
-28
-29
-30
-31
-32
-33
-
const { App } = require('@slack/bolt');
-
-// Initialize Bolt app, using the default HTTPReceiver
-const app = new App({
-  token: process.env.SLACK_BOT_TOKEN,
-  signingSecret: process.env.SLACK_SIGNING_SECRET,
-  customRoutes: [
-    {
-      path: '/health-check',
-      method: ['GET'],
-      handler: (req, res) => {
-        res.writeHead(200);
-        res.end(`Things are going just fine at ${req.headers.host}!`);
-      },
-    },
-    {
-      path: '/music/:genre',
-      method: ['GET'],
-      handler: (req, res) => {
-        res.writeHead(200);
-        res.end(`Oh? ${req.params.genre}? That slaps!`);
-      },
-    },
-  ],
-  installerOptions: {
-    port: 3001,
-  },
-});
-
-(async () => {
-  await app.start();
-  console.log('⚡️ Bolt app started');
-})();
-
-
-
- -
- -

Custom ExpressReceiver routes

-
- -
-

Adding custom HTTP routes is quite straightforward when using Bolt’s built-in ExpressReceiver. Since v2.1.0, ExpressReceiver added a router property, which exposes the Express Router on which additional routes and middleware can be added.

-
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-23
-24
-25
-26
-27
-28
-29
-30
-31
-32
-33
-
const { App, ExpressReceiver } = require('@slack/bolt');
-
-// Create a Bolt Receiver
-const receiver = new ExpressReceiver({ signingSecret: process.env.SLACK_SIGNING_SECRET });
-
-// Create the Bolt App, using the receiver
-const app = new App({
-  token: process.env.SLACK_BOT_TOKEN,
-  receiver
-});
-
-// Slack interactions are methods on app
-app.event('message', async ({ event, client }) => {
-  // Do some slack-specific stuff here
-  await client.chat.postMessage(...);
-});
-
-// Middleware methods execute on every web request
-receiver.router.use((req, res, next) => {
-  console.log(`Request time: ${Date.now()}`);
-  next();
-});
-
-// Other web requests are methods on receiver.router
-receiver.router.post('/secret-page', (req, res) => {
-  // You're working with an express req and res now.
-  res.send('yay!');
-});
-
-(async () => {
-  await app.start();
-  console.log('⚡️ Bolt app started');
-})();
-
-
-
-
- - -
-
- -
- -
- -
-

- Overview of Workflow Steps for apps - - Deprecated - -

-
- -

⚠️ Workflow Steps from Apps are a deprecated feature, not to be confused with workflows that are part of the next generation Slack platform. They are not interchangeable features. We encourage those who are currently publishing Workflow Steps from apps to consider the new automation features and custom steps for bolt.

- -

Workflow Steps from apps allow your app to create and process custom workflow steps that users can add using Workflow Builder.

- -

A workflow step is made up of three distinct user events:

- -
    -
  • Adding or editing the step in a Workflow
  • -
  • Saving or updating the step’s configuration
  • -
  • The end user’s execution of the step
  • -
- -

All three events must be handled for a workflow step to function.

- -

Read more about workflow steps from apps in the API documentation.

- -
- -
-
- -
-

- Creating workflow steps - - Deprecated - -

-
- -

To create a workflow step, Bolt provides the WorkflowStep class.

- -

When instantiating a new WorkflowStep, pass in the step’s callback_id and a configuration object.

- -

The configuration object contains three properties: edit, save, and execute. Each of these properties must be a single callback or an array of callbacks. All callbacks have access to a step object that contains information about the workflow step event.

- -

After instantiating a WorkflowStep, you can pass it into app.step(). Behind the scenes, your app will listen and respond to the workflow step’s events using the callbacks provided in the configuration object.

- -
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-
const { App, WorkflowStep } = require('@slack/bolt');
-
-// Initiate the Bolt app as you normally would
-const app = new App({
-  signingSecret: process.env.SLACK_SIGNING_SECRET,
-  token: process.env.SLACK_BOT_TOKEN,
-});
-
-// Create a new WorkflowStep instance
-const ws = new WorkflowStep('add_task', {
-  edit: async ({ ack, step, configure }) => {},
-  save: async ({ ack, step, update }) => {},
-  execute: async ({ step, complete, fail }) => {},
-});
-
-app.step(ws);
-
-
-
- -
-
- -
-

- Adding or editing workflow steps - - Deprecated - -

-
- -

When a builder adds (or later edits) your step in their workflow, your app will receive a workflow_step_edit event. The edit callback in your WorkflowStep configuration will be run when this event is received.

- -

Whether a builder is adding or editing a step, you need to send them a workflow step configuration modal. This modal is where step-specific settings are chosen, and it has more restrictions than typical modals—most notably, it cannot include title​, submit​, or close​ properties. By default, the configuration modal’s callback_id will be the same as the workflow step.

- -

Within the edit callback, the configure() utility can be used to easily open your step’s configuration modal by passing in an object with your view’s blocks. To disable saving the configuration before certain conditions are met, pass in submit_disabled with a value of true.

- -

To learn more about opening configuration modals, read the documentation.

- -
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-23
-24
-25
-26
-27
-28
-29
-30
-31
-32
-33
-34
-35
-36
-37
-38
-39
-40
-41
-42
-43
-44
-
const ws = new WorkflowStep('add_task', {
-  edit: async ({ ack, step, configure }) => {
-    await ack();
-
-    const blocks = [
-      {
-        type: 'input',
-        block_id: 'task_name_input',
-        element: {
-          type: 'plain_text_input',
-          action_id: 'name',
-          placeholder: {
-            type: 'plain_text',
-            text: 'Add a task name',
-          },
-        },
-        label: {
-          type: 'plain_text',
-          text: 'Task name',
-        },
-      },
-      {
-        type: 'input',
-        block_id: 'task_description_input',
-        element: {
-          type: 'plain_text_input',
-          action_id: 'description',
-          placeholder: {
-            type: 'plain_text',
-            text: 'Add a task description',
-          },
-        },
-        label: {
-          type: 'plain_text',
-          text: 'Task description',
-        },
-      },
-    ];
-
-    await configure({ blocks });
-  },
-  save: async ({ ack, step, update }) => {},
-  execute: async ({ step, complete, fail }) => {},
-});
-
-
-
- -
-
- -
-

- Saving step configurations - - Deprecated - -

-
- -

After the configuration modal is opened, your app will listen for the view_submission event. The save callback in your WorkflowStep configuration will be run when this event is received.

- -

Within the save callback, the update() method can be used to save the builder’s step configuration by passing in the following arguments:

- -
    -
  • -inputs is an object representing the data your app expects to receive from the user upon workflow step execution.
  • -
  • -outputs is an array of objects containing data that your app will provide upon the workflow step’s completion. Outputs can then be used in subsequent steps of the workflow.
  • -
  • -step_name overrides the default Step name
  • -
  • -step_image_url overrides the default Step image
  • -
- -

To learn more about how to structure these parameters, read the documentation.

- -
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-23
-24
-25
-26
-27
-28
-29
-30
-31
-
const ws = new WorkflowStep('add_task', {
-  edit: async ({ ack, step, configure }) => {},
-  save: async ({ ack, step, view, update }) => {
-    await ack();
-
-    const { values } = view.state;
-    const taskName = values.task_name_input.name;
-    const taskDescription = values.task_description_input.description;
-                
-    const inputs = {
-      taskName: { value: taskName.value },
-      taskDescription: { value: taskDescription.value }
-    };
-
-    const outputs = [
-      {
-        type: 'text',
-        name: 'taskName',
-        label: 'Task name',
-      },
-      {
-        type: 'text',
-        name: 'taskDescription',
-        label: 'Task description',
-      }
-    ];
-
-    await update({ inputs, outputs });
-  },
-  execute: async ({ step, complete, fail }) => {},
-});
-
-
-
- -
-
- -
-

- Executing workflow steps - - Deprecated - -

-
- -

When your workflow step is executed by an end user, your app will receive a workflow_step_execute event. The execute callback in your WorkflowStep configuration will be run when this event is received.

- -

Using the inputs from the save callback, this is where you can make third-party API calls, save information to a database, update the user’s Home tab, or decide the outputs that will be available to subsequent workflow steps by mapping values to the outputs object.

- -

Within the execute callback, your app must either call complete() to indicate that the step’s execution was successful, or fail() to indicate that the step’s execution failed.

- -
- -
-
-

-
-
-
1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-11
-12
-13
-14
-15
-16
-17
-18
-19
-20
-21
-22
-23
-24
-25
-
const ws = new WorkflowStep('add_task', {
-  edit: async ({ ack, step, configure }) => {},
-  save: async ({ ack, step, update }) => {},
-  execute: async ({ step, complete, fail }) => {
-    const { inputs } = step;
-
-    const outputs = {
-      taskName: inputs.taskName.value,
-      taskDescription: inputs.taskDescription.value,
-    };
-
-    // signal back to Slack that everything was successful
-    await complete({ outputs });
-    // NOTE: If you run your app with processBeforeResponse: true option,
-    // `await complete()` is not recommended because of the slow response of the API endpoint
-    // which could result in not responding to the Slack Events API within the required 3 seconds
-    // instead, use:
-    // complete({ outputs }).then(() => { console.log('workflow step execution complete registered'); });
-
-    // let Slack know if something went wrong
-    // await fail({ error: { message: "Just testing step failure!" } });
-    // NOTE: If you run your app with processBeforeResponse: true, use this instead:
-    // fail({ error: { message: "Just testing step failure!" } }).then(() => { console.log('workflow step execution failure registered'); });
-  },
-});
-
-
-
- -
-
- -
- -
-
- - - - - - diff --git a/docs/_site/deployments/aws-lambda.html b/docs/_site/deployments/aws-lambda.html deleted file mode 100644 index 54b85dd46..000000000 --- a/docs/_site/deployments/aws-lambda.html +++ /dev/null @@ -1,845 +0,0 @@ - - - - - - - Slack | Bolt for JavaScript - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
- - -
- - -
-
    -
    - -
    -

    Deploying to AWS Lambda

    - -
    -

    This guide walks you through preparing and deploying a Slack app using Bolt for JavaScript, the Serverless Framework, and AWS Lambda.

    -
    - -

    When you’re finished, you’ll have this ⚡️Deploying to AWS Lambda app to run, modify, and make your own.

    - -
    - -

    Set up AWS Lambda

    - -

    AWS Lambda is a serverless, Function-as-a-Service (FaaS) platform that allows you to run code without managing servers. In this section, we’ll configure your local machine to access AWS Lambda.

    - -
    -

    💡 Skip this section if you have already configured a profile on your local machine to access AWS Lambda.

    -
    - -

    1. Sign up for an AWS account

    - -

    If you don’t already have an account, you should sign up for AWS and follow the on-screen instructions.

    - -
    -

    💡 You may be asked for payment information during the sign up. Don’t worry, this guide only uses the free tier.

    -
    - -

    2. Create an AWS access key

    - -

    Next, you’ll need programmatic access to your AWS account to deploy onto Lambda. In the world of AWS, this requires an Access Key ID and Secret Access Key.

    - -

    We recommend watching this short, step-by-step video to 🍿 create an IAM user and download the access keys.

    - -
    -

    💡 Do you already have an IAM user? Follow the official AWS guide to create access keys for existing IAM users.

    -
    - -

    3. Install the AWS CLI

    - -

    The AWS tools are available as a Command Line Interface (CLI) and can be installed on macOS, Windows, or Linux.

    - -

    On macOS, you can install the AWS CLI by downloading the latest package installer.

    - -

    4. Configure an AWS profile

    - -

    You can use the AWS CLI to configure a profile that stores your access key pair on your local machine. This profile is used by the CLI and other tools to access AWS.

    - -

    The quickest way to configure your profile is to run this command and follow the prompts:

    - -
    -
    -
    
    -
    -
    -
    1
    -2
    -3
    -4
    -5
    -
    aws configure
    -# AWS Access Key ID [None]: <your-aws-access-key>
    -# AWS Secret Access Key [None]: <your-aws-secret>
    -# Default region name [None]: us-east-1
    -# Default output format [None]: json
    -
    -
    -
    - -
    -

    💡 Customize the region and output format best for you.

    -
    - -

    That wraps up configuring your local machine to access AWS. 👏 Next, let’s do the same with the Serverless Framework.

    - -
    - -

    Set up Serverless Framework

    - -

    The Serverless Framework includes tools that let you easily configure, debug, and deploy your app to AWS Lambda.

    - -

    1. Install the Serverless Framework CLI

    - -

    The Serverless tools are available as a Command Line Interface (CLI) and can be installed on macOS, Windows, or Linux. Check out the Serverless Getting Started documentation for instructions on how to install.

    - -

    Once the installation is complete, test the Serverless CLI by displaying the commands available to you:

    - -
    -
    -
    
    -
    -
    -
    1
    -
    serverless help
    -
    -
    -
    - -

    You’re now set up with the Serverless tools! Let’s move on to preparing your Bolt app to run as an AWS Lambda function.

    - -
    - -

    Get a Bolt Slack app

    - -

    If you haven’t already built your own Bolt app, you can use our Getting Started guide or clone the template app below:

    - -
    -
    -
    
    -
    -
    -
    1
    -
    git clone https://github.com/slackapi/bolt-js-getting-started-app.git
    -
    -
    -
    - -

    After you have a Bolt app, navigate to its directory:

    - -
    -
    -
    
    -
    -
    -
    1
    -
    cd bolt-js-getting-started-app/
    -
    -
    -
    - -

    Now that you have an app, let’s prepare it for AWS Lambda and the Serverless Framework.

    - -
    - -

    Prepare the app

    - -

    1. Prepare the app for AWS Lambda

    - -

    By default, our Bolt Getting Started app sample is configured to use SocketMode. Let’s update the setup in app.js to have our app listen for HTTP requests instead.

    - -
    -
    -
    
    -
    -
    -
    1
    -2
    -3
    -4
    -5
    -6
    -
    // Initializes your app with your bot token
    -const app = new App({
    -  token: process.env.SLACK_BOT_TOKEN,
    -  socketMode: true, // delete this line
    -  appToken: process.env.SLACK_APP_TOKEN, // delete this line
    -});
    -
    -
    -
    - -

    Next, we’ll customize your Bolt app’s receiver to respond to Lambda function events.

    - -

    Update the source code that imports your modules in app.js to require Bolt’s AwsLambdaReceiver:

    - -
    -
    -
    
    -
    -
    -
    1
    -
    const { App, AwsLambdaReceiver } = require('@slack/bolt');
    -
    -
    -
    - -
    -

    💡 If implementing authentication with OAuth, you must use the ExpressReceiver. Please note that when using ExpressReceiver, the processBeforeResponse: true property is required during initialization to avoid latency issues.

    -
    - -

    Then update the source code that initializes your Bolt app to create a custom receiver using AwsLambdaReceiver:

    - -
    -
    -
    
    -
    -
    -
    1
    -2
    -3
    -4
    -5
    -6
    -7
    -8
    -9
    -10
    -11
    -12
    -13
    -14
    -15
    -16
    -17
    -18
    -19
    -
    // Initialize your custom receiver
    -const awsLambdaReceiver = new AwsLambdaReceiver({
    -    signingSecret: process.env.SLACK_SIGNING_SECRET,
    -});
    -
    -// Initializes your app with your bot token and the AWS Lambda ready receiver
    -const app = new App({
    -    token: process.env.SLACK_BOT_TOKEN,
    -    receiver: awsLambdaReceiver,
    -
    -    // When using the AwsLambdaReceiver, processBeforeResponse can be omitted.
    -    // If you use other Receivers, such as ExpressReceiver for OAuth flow support
    -    // then processBeforeResponse: true is required. This option will defer sending back
    -    // the acknowledgement until after your handler has run to ensure your handler
    -    // isn't terminated early by responding to the HTTP request that triggered it.
    -
    -    // processBeforeResponse: true
    -
    -});
    -
    -
    -
    - -

    Finally, at the bottom of your app, update the source code that starts the HTTP server to now respond to an AWS Lambda function event:

    - -
    -
    -
    
    -
    -
    -
    1
    -2
    -3
    -4
    -5
    -
    // Handle the Lambda function event
    -module.exports.handler = async (event, context, callback) => {
    -    const handler = await awsLambdaReceiver.start();
    -    return handler(event, context, callback);
    -}
    -
    -
    -
    - -

    When you’re done, your app should look similar to the ⚡️Deploying to AWS Lambda app.

    - -

    2. Add a serverless.yml

    - -

    Serverless Framework projects use a serverless.yml file to configure and deploy apps.

    - -

    Create a new file called serverless.yml in your app’s root directory and paste the following:

    - -
    -
    -
    
    -
    -
    -
    1
    -2
    -3
    -4
    -5
    -6
    -7
    -8
    -9
    -10
    -11
    -12
    -13
    -14
    -15
    -16
    -17
    -
    service: serverless-bolt-js
    -frameworkVersion: '3'
    -provider:
    -  name: aws
    -  runtime: nodejs14.x
    -  environment:
    -    SLACK_SIGNING_SECRET: ${env:SLACK_SIGNING_SECRET}
    -    SLACK_BOT_TOKEN: ${env:SLACK_BOT_TOKEN}
    -functions:
    -  slack:
    -    handler: app.handler
    -    events:
    -      - http:
    -          path: slack/events
    -          method: post
    -plugins:
    -  - serverless-offline
    -
    -
    -
    - -
    -

    💡 SLACK_SIGNING_SECRET and SLACK_BOT_TOKEN must be environment variables on your local machine. -You can learn how to export Slack environment variables in our Getting Started guide.

    -
    - -

    3. Install Serverless Offline

    - -

    To make local development a breeze, we’ll use the serverless-offline module to emulate a deployed function.

    - -

    Run the following command to install it as a development dependency:

    - -
    -
    -
    
    -
    -
    -
    1
    -
    npm install --save-dev serverless-offline
    -
    -
    -
    - -

    Congratulations, you’ve just prepared your Bolt app for AWS Lambda and Serverless! Now let’s run and deploy your app.

    - -
    - -

    Run the app locally

    - -

    Now that your app is configured to respond to an AWS Lambda function, we’ll set up your environment to run the app locally.

    - -

    1. Start your local servers

    - -

    First, use the serverless offline command to start your app and listen to AWS Lambda function events:

    - -
    -
    -
    
    -
    -
    -
    1
    -
    serverless offline --noPrependStageInUrl
    -
    -
    -
    - -
    -

    🏌️ Pro-tip: you can make code changes to your app in one terminal while running the above command in another terminal, and as you save code changes your app will reload automatically.

    -
    - -

    Next, use ngrok to forward Slack events to your local machine:

    - -
    -
    -
    
    -
    -
    -
    1
    -
    ngrok http 3000
    -
    -
    -
    - -
    -

    💡 Learn how to use ngrok to create a public URL and forward requests to your local machine.

    -
    - -

    2. Update your Request URL

    - -

    Next, visit your Slack app’s settings to update your Request URL to use the ngrok web address.

    - -
    -

    💡 Your Request URL ends with /slack/events, such as https://abc123.ngrok.io/slack/events.

    -
    - -

    First, select Interactivity & Shortcuts from the side and update the Request URL:

    - -

    Interactivity & Shortcuts page

    - -

    Second, select Event Subscriptions from the side and update the Request URL:

    - -

    Event Subscriptions page

    - -

    3. Test your Slack app

    - -

    Now you can test your Slack app by inviting your app to a channel then saying “hello” (lower-case). Just like in the Getting Started guide, your app should respond back:

    - -
    -

    👩‍💻 hello
    -🤖 Hey there @Jane!

    -
    - -

    If you don’t receive a response, check your Request URL and try again.

    - -
    -

    💡 How does this work? -The ngrok and Serverless commands are configured on the same port (default: 3000). When a Slack event is sent to your Request URL, it’s received on your local machine by ngrok. The request is then forwarded to Serverless Offline, which emulates an AWS Lambda function event and triggers your Bolt app’s receiver. 🛫🛬 Phew, what a trip!

    -
    - -
    - -

    Deploy the app

    - -

    In the previous section of this tutorial, you ran your app locally and tested it in a live Slack workspace. Now that you have a working app, let’s deploy it!

    - -

    You can use the Serverless Framework tools to provision, package, and deploy your app onto AWS Lambda. After your app is deployed, you’ll need to update your app’s request URL to say “hello” to your app. ✨

    - -

    1. Deploy the app to AWS Lambda

    - -

    Now, deploy your app to AWS Lambda with the following command:

    - -
    -
    -
    
    -
    -
    -
    1
    -2
    -3
    -4
    -5
    -6
    -
    serverless deploy
    -# Serverless: Packaging service...
    -# ...
    -# endpoints:
    -#   POST - https://atuzelnkvd.execute-api.us-east-1.amazonaws.com/dev/slack/events
    -# ...
    -
    -
    -
    - -

    After your app is deployed, you’ll be given an endpoint which you’ll use as your app’s Request URL. Go ahead and copy this endpoint to use in the next section.

    - -
    -

    💡 The endpoint should end in /slack/events.

    -
    - -

    2. Update your Slack app’s settings

    - -

    Now we need to use your AWS Lambda endpoint as your Request URL, which is where Slack will send events and actions. -With your endpoint copied, navigate to your Slack app’s configuration to update your app’s Request URLs.

    - -

    First, select Interactivity & Shortcuts from the side and update the Request URL:

    - -

    Interactivity & Shortcuts page

    - -

    Second, select Event Subscriptions from the side and update the Request URL:

    - -

    Event Subscriptions page

    - -

    3. Test your Slack app

    - -

    Your app is now deployed and Slack is updated, so let’s try it out!

    - -

    Just like the running the app locally section, open a Slack channel that your app is in and say “hello”. You app should once again respond with a greeting:

    - -
    -

    👩‍💻 hello
    -🤖 Hey there @Jane!

    -
    - -

    4. Deploy an update

    - -

    As you continue to build your Slack app, you’ll need to deploy the updates. Let’s get a feel for this by updating your app to respond to a “goodbye” message.

    - -

    Add the following code to app.js (source code on GitHub):

    - -
    -
    -
    
    -
    -
    -
    1
    -2
    -3
    -4
    -5
    -
    // Listens to incoming messages that contain "goodbye"
    -app.message('goodbye', async ({ message, say }) => {
    -  // say() sends a message to the channel where the event was triggered
    -  await say(`See ya later, <@${message.user}> :wave:`);
    -});
    -
    -
    -
    - -

    Deploy the update using the same command as before:

    - -
    -
    -
    
    -
    -
    -
    1
    -
    serverless deploy
    -
    -
    -
    - -

    When the deploy is complete, you can open a Slack channel that your app has joined and say “goodbye” (lower-case). You should see a friendly farewell from your Slack app.

    - -
    -

    ⛳️ If you are making small changes to single functions, you can deploy only a single function using serverless deploy function -f my-function which is much faster. Run serverless help deploy function for more detailed help.

    -
    - -
    - -

    Next steps

    - -

    You just deployed your first ⚡️Bolt for JavaScript app to AWS Lambda! 🚀

    - -

    Now that you’ve built and deployed a basic app, here are some ideas you can explore to extend, customize, and monitor it:

    - - - - -
    -
    - -
    - - - - - - - - \ No newline at end of file diff --git a/docs/_site/deployments/heroku.html b/docs/_site/deployments/heroku.html deleted file mode 100644 index 319fea272..000000000 --- a/docs/_site/deployments/heroku.html +++ /dev/null @@ -1,766 +0,0 @@ - - - - - - - Slack | Bolt for JavaScript - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - - -
    - - -
    - - -
    -
      -
      - -
      -

      Deploying to Heroku

      - -
      -

      ⚠️ Using Heroku dynos to complete this tutorial counts towards your usage. Delete your app as soon as you are done to control costs.

      -
      - -
      -

      This guide will walk you through preparing and deploying a Slack app using Bolt for JavaScript and the Heroku platform. Along the way, we’ll download a Bolt Slack app, prepare it for Heroku, and deploy it.

      -
      - -

      When you’re finished, you’ll have this ⚡️Deploying to Heroku app to run, modify, and make your own.

      - -
      - -

      Get a Bolt Slack app

      - -

      If you haven’t already built your own Bolt app, you can use our Getting Started guide or clone the template app below:

      - -
      -
      -
      
      -
      -
      -
      1
      -
      git clone https://github.com/slackapi/bolt-js-getting-started-app.git
      -
      -
      -
      - -

      After you have a Bolt app, navigate to its directory:

      - -
      -
      -
      
      -
      -
      -
      1
      -
      cd bolt-js-getting-started-app/
      -
      -
      -
      - -

      Now that you have an app, let’s prepare it for Heroku.

      - -
      - -

      Prepare the app for Heroku

      - -

      Heroku is a flexible platform that requires some configuration to host your app. In this section, we’ll update your Bolt app to support Heroku.

      - -

      1. Use a Git repository

      - -

      Before you can deploy your app to Heroku, you’ll need a Git repository. If you aren’t already using Git, you’ll need to install Git and create a Git repository.

      - -
      -

      💡 Skip this step if you used git clone in the previous section because you already have a Git repository.

      -
      - -

      2. Add a Procfile

      - -

      Every Heroku app uses a special file called Procfile that tells Heroku how to start your app. The contents of the file will depend on whether or not you are using Socket Mode.

      - -

      Create a new file called Procfile (without any extension) in your app’s root directory and paste in one of the following, depending on how you’re running your app.

      - -

      By default, a Bolt Slack app will be started as a web server with a public web address:

      - -
      -
      -
      
      -
      -
      -
      1
      -
      web: node app.js
      -
      -
      -
      - -

      Apps using Socket Mode are started as workers that do not listen to a port:

      - -
      -
      -
      
      -
      -
      -
      1
      -
      worker: node app.js
      -
      -
      -
      - -

      Once you’ve saved the file, let’s commit it to your Git repository:

      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -
      git add Procfile
      -git commit -m "Add Procfile"
      -
      -
      -
      - -
      -

      💡 Are you following this guide with an existing Bolt app? If so, please review the guide on preparing a codebase for Heroku to listen on the correct port.

      -
      - -
      - -

      Set up the Heroku tools

      - -

      Now we can set up the Heroku tools on your local machine. These tools will help you manage, deploy, and debug your app on Heroku’s platform.

      - -

      1. Install the Heroku CLI

      - -

      The Heroku tools are available as a Command Line Interface (CLI). Go ahead and install the Heroku CLI for macOS, Windows, or Linux. On macOS, you can run the command:

      - -
      -
      -
      
      -
      -
      -
      1
      -
      brew install heroku/brew/heroku
      -
      -
      -
      - -

      Once the install is complete, we can test the Heroku CLI by displaying all of the wonderful commands available to you:

      - -
      -
      -
      
      -
      -
      -
      1
      -
      heroku help
      -
      -
      -
      - -
      -

      💡 If the heroku command is not found, refresh your path by opening a new terminal session/tab.

      -
      - -

      2. Log into the Heroku CLI

      - -

      The Heroku CLI connects your local machine with your Heroku account. Sign up for a free Heroku account and then log into the Heroku CLI with the following command:

      - -
      -
      -
      
      -
      -
      -
      1
      -
      heroku login
      -
      -
      -
      -
      -

      💡 If you’re behind a firewall, you may need to set the proxy environment variables for the Heroku CLI.

      -
      - -

      3. Confirm you’re logged into the Heroku CLI

      - -

      Check that you’re logged in by displaying the account currently connected to your Heroku CLI:

      - -
      -
      -
      
      -
      -
      -
      1
      -
      heroku auth:whoami
      -
      -
      -
      - -

      You should now be set up with the Heroku tools! Let’s move on to the exciting step of creating an app on Heroku.

      - -
      - -

      Create an app on Heroku

      - -

      It’s time to create a Heroku app using the tools that you just installed. When you create an app, you can choose a unique name or have it randomly generated.

      - -

      Creating new Heroku apps will use your existing Heroku plan subscription. When getting started or deploying many small apps, we recommend starting with Heroku’s low-cost Eco Dyno plan.

      - -
      -

      💡 Eligible students can apply for platform credits through the Heroku for GitHub Student program.

      -
      - -

      1. Create an app on Heroku

      - -

      Create an app on Heroku with a unique name:

      - -
      -
      -
      
      -
      -
      -
      1
      -
      heroku create my-unique-bolt-app-name
      -
      -
      -
      - -

      or, have some fun with a random name:

      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -
      heroku create
      -# Creating sharp-rain-871... done, stack is heroku-18
      -# https://sharp-rain-871.herokuapp.com/ | https://git.heroku.com/sharp-rain-871.git
      -
      -
      -
      - -
      -

      💡 You can rename a Heroku app at any time, but you may change your Git remote and public web address.

      -
      - -

      After your app is created, you’ll be given some information that we’ll use in the upcoming sections. In the example above:

      - -
        -
      • App name is sharp-rain-871 -
      • -
      • Web address is https://sharp-rain-871.herokuapp.com/ -
      • -
      • Empty Git remote is https://git.heroku.com/sharp-rain-871.git -
      • -
      - -

      2. Confirm Heroku Git remote

      - -

      The Heroku CLI automatically adds a Git remote called heroku to your local repository. You can list your Git remotes to confirm heroku exists:

      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -
      git remote -v
      -# heroku	https://git.heroku.com/sharp-rain-871.git (fetch)
      -# heroku	https://git.heroku.com/sharp-rain-871.git (push)
      -
      -
      -
      - -

      3. Set environment variables on Heroku

      - -

      Now you’ll need to add your Slack app credentials to your Heroku app:

      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -
      heroku config:set SLACK_SIGNING_SECRET=<your-signing-secret>
      -heroku config:set SLACK_BOT_TOKEN=xoxb-<your-bot-token>
      -
      -
      -
      - -
      -

      💡 If you don’t know where to find your credentials, please read about exporting your signing secret and token in the Getting Started guide.

      -
      - -

      Now that we have prepared your local app and created a Heroku app, the next step is to deploy it!

      - -
      - -

      Deploy the app

      - -

      To deploy the app, we’re going to push your local code to Heroku, update your Slack app’s settings, and say “hello” to your Heroku app. ✨

      - -

      1. Deploy the app to Heroku

      - -

      When deploying an app to Heroku, you’ll typically use the git push command. This will push your code from your local repository to your heroku remote repository.

      - -

      You can now deploy your app with the command:

      - -
      -
      -
      
      -
      -
      -
      1
      -
      git push heroku main
      -
      -
      -
      - -
      -

      💡 Heroku deploys code that’s pushed to the master or main branches. Pushing to other branches will not trigger a deployment.

      -
      - -

      Finally, we need to start a web server instance using the Heroku CLI:

      - -
      -
      -
      
      -
      -
      -
      1
      -
      heroku ps:scale web=1
      -
      -
      -
      - -

      2. Update your Slack app’s settings

      - -

      Now we need to use your Heroku web address as your Request URL, which is where Slack will send events and actions.

      - -

      Get your Heroku web address with the following command:

      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -
      heroku info
      -# ...
      -# Web URL: https://sharp-rain-871.herokuapp.com/
      -
      -
      -
      - -

      In our example, the web address is https://sharp-rain-871.herokuapp.com/.

      - -

      Head over to the Slack App page and select your app name. Next, we’ll update your Request URL in two locations to be your web address.

      - -
      -

      💡 Your Request URL ends with /slack/events, such as https://sharp-rain-871.herokuapp.com/slack/events.

      -
      - -

      First, select Interactivity & Shortcuts from the side and update the Request URL:

      - -

      Interactivity & Shortcuts page

      - -

      Second, select Event Subscriptions from the side and update the Request URL:

      - -

      Event Subscriptions page

      - -
      -

      💡 Heroku Eco Dyno apps sleep when inactive. 💤 If your verification fails, please try it again immediately.

      -
      - -

      3. Test your Slack app

      - -

      Your app is now deployed and Slack is updated, so let’s try it out!

      - -

      Open a Slack channel that your app has joined and say “hello” (lower-case). Just like in the Getting Started guide, your app should respond back. If you don’t receive a response, check your Request URL and try again.

      - -
      - -

      Deploy an update

      - -

      As you continue building your Slack app, you’ll need to deploy updates. A common flow is to make a change, commit it, and then push it to Heroku.

      - -

      Let’s get a feel for this by updating your app to respond to a “goodbye” message. Add the following code to app.js (source code on GitHub):

      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -
      // Listens to incoming messages that contain "goodbye"
      -app.message('goodbye', async ({ message, say }) => {
      -  // say() sends a message to the channel where the event was triggered
      -  await say(`See ya later, <@${message.user}> :wave:`);
      -});
      -
      -
      -
      - -

      Commit the changes to your local Git repository:

      - -
      -
      -
      
      -
      -
      -
      1
      -
      git commit -am "Say 'goodbye' to a person"
      -
      -
      -
      - -

      Deploy the update by pushing to your heroku remote:

      - -
      -
      -
      
      -
      -
      -
      1
      -
      git push heroku main
      -
      -
      -
      - -

      When the deploy is complete, you can open a Slack channel that your app has joined and say “goodbye” (lower-case). You should see a friendly farewell from your Slack app.

      - -
      - -

      Next steps

      - -

      You just deployed your first ⚡️Bolt for JavaScript app to Heroku! 🚀

      - -

      Now that you’ve deployed a basic app, you can start exploring how to customize and monitor it. Here are some ideas of what to explore next:

      - - - - -
      -
      - -
      - - - - - - - - \ No newline at end of file diff --git a/docs/_site/getting-started-http.html b/docs/_site/getting-started-http.html deleted file mode 100644 index 33ba95f28..000000000 --- a/docs/_site/getting-started-http.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - Redirecting… - - - - -

      Redirecting…

      - Click here if you are not redirected. - diff --git a/docs/_site/getting-started.html b/docs/_site/getting-started.html deleted file mode 100644 index b1c17461f..000000000 --- a/docs/_site/getting-started.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - Redirecting… - - - - -

      Redirecting…

      - Click here if you are not redirected. - diff --git a/docs/_site/getting-started/ja-jp.html b/docs/_site/getting-started/ja-jp.html deleted file mode 100644 index e17d7b8c0..000000000 --- a/docs/_site/getting-started/ja-jp.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - Redirecting… - - - - -

      Redirecting…

      - Click here if you are not redirected. - diff --git a/docs/_site/hubot-migration.html b/docs/_site/hubot-migration.html deleted file mode 100644 index ca6be0130..000000000 --- a/docs/_site/hubot-migration.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - Redirecting… - - - - -

      Redirecting…

      - Click here if you are not redirected. - diff --git a/docs/_site/index.html b/docs/_site/index.html deleted file mode 100644 index 8b91487ce..000000000 --- a/docs/_site/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - Redirecting… - - - - -

      Redirecting…

      - Click here if you are not redirected. - diff --git a/docs/_site/ja-jp.html b/docs/_site/ja-jp.html deleted file mode 100644 index 0a9803a13..000000000 --- a/docs/_site/ja-jp.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - Redirecting… - - - - -

      Redirecting…

      - Click here if you are not redirected. - diff --git a/docs/_site/ja-jp/concepts.html b/docs/_site/ja-jp/concepts.html deleted file mode 100644 index f5d4d8cad..000000000 --- a/docs/_site/ja-jp/concepts.html +++ /dev/null @@ -1,3895 +0,0 @@ - - - - - - Slack | Bolt for JavaScript - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      -
      - - -
      - -
      - - -
      - -
      -

      メッセージ・イベントのリスニング

      - -
      -

      アプリが受信可能なメッセージをリッスンするには、message 型でないイベントを除外する message() メソッドを使用します。

      - -

      message() は、string 型か RegExp 型の、指定パターンに一致しないメッセージを除外する pattern パラメーター(指定は必須ではありません)を受け付けます。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -
      // 特定の文字列、この場合 👋絵文字を含むメッセージと一致
      -app.message(':wave:', async ({ message, say }) => {
      -  // 新しく投稿されたメッセージだけを処理
      -  if (message.subtype === undefined
      -    || message.subtype === 'bot_message'
      -    || message.subtype === 'file_share'
      -    || message.subtype === 'thread_broadcast') {
      -    await say(`Hello, <@${message.user}>`);
      -  }
      -});
      -
      -
      -
      - -
      - -

      正規表現(RegExp) パターンの使用

      -
      - -
      -

      文字列の代わりに 正規表現(RegExp) パターンを使用すると、より細やかなマッチングが可能です。

      - -

      RegExp の一致結果はすべて context.matches に保持されます。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -
      app.message(/^(hi|hello|hey).*/, async ({ context, say }) => {
      -  // context.matches の内容が特定の正規表現と一致
      -  const greeting = context.matches[0];
      -
      -  await say(`${greeting}, how are you?`);
      -});
      -
      -
      -
      - -
      - - -
      -
      - -
      -

      メッセージの送信

      - -
      -

      リスナー関数内では、その実行に関連付けられた会話 (例:リスナー実行のトリガーが発生したイベント・アクションが発生したチャンネル) があるとき say() を使用できます。 say() は、シンプルなメッセージを送信するための文字列か、もっと複雑なメッセージを送信するための JSON ペイロードを受け付けます。渡されたメッセージのペイロードは、関連付けられた会話へ送信されます。

      - -

      リスナー関数以外の場所でメッセージを送信したい場合や、より高度な操作 (特定のエラーの処理など) を実行したい場合は、Bolt インスタンスにアタッチされた client を使用して chat.postMessage を呼び出します。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -
      // "knock knock" を含むメッセージをリッスンし、 "who's there?" というメッセージをイタリック体で送信
      -app.message('knock knock', async ({ message, say }) => {
      -  await say(`_Who's there?_`);
      -});
      -
      -
      -
      - -
      - -

      ブロックを用いたメッセージの送信

      -
      - -
      -

      say() は、より複雑なメッセージペイロードを受け付けるので、メッセージに機能やリッチな構造を与えることが容易です。

      - -

      リッチなメッセージレイアウトをアプリに追加する方法については、API サイトのガイドを参照し、Block Kit ビルダーの一般的なアプリフローのテンプレートを確認してください。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -21
      -22
      -23
      -
      // 誰かが 📅 絵文字でリアクションした時に、日付ピッカー block を送信
      -app.event('reaction_added', async ({ event, say }) => {
      -  if (event.reaction === 'calendar') {
      -    await say({
      -      blocks: [{
      -        "type": "section",
      -        "text": {
      -          "type": "mrkdwn",
      -          "text": "Pick a date for me to remind you"
      -        },
      -        "accessory": {
      -          "type": "datepicker",
      -          "action_id": "datepicker_remind",
      -          "initial_date": "2019-04-28",
      -          "placeholder": {
      -            "type": "plain_text",
      -            "text": "Select a date"
      -          }
      -        }
      -      }]
      -    });
      -  }
      -});
      -
      -
      -
      -
      - - -
      -
      - -
      -

      イベントのリスニング

      - -
      -

      Events API イベントのリスニングは、Slack アプリの設定画面でサブスクリプション設定を行った上で event() メソッドを使用します。これにより、Slack で何かが発生した (例:ユーザーがメッセージにリアクションした、チャンネルに参加した) ときに Bolt アプリ側で処理を実行できます。

      - -

      event() メソッドは、文字列型の eventType を指定する必要があります。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -
      const welcomeChannelId = 'C12345';
      -
      -// 新しいユーザーがワークスペースに加入したタイミングで、指定のチャンネルにメッセージを送信して自己紹介を促す
      -app.event('team_join', async ({ event, client, logger }) => {
      -  try {
      -    // 組み込みの client で chat.postMessage を呼び出す
      -    const result = await client.chat.postMessage({
      -      channel: welcomeChannelId,
      -      text: `Welcome to the team, <@${event.user.id}>! 🎉 You can introduce yourself in this channel.`
      -    });
      -    logger.info(result);
      -  }
      -  catch (error) {
      -    logger.error(error);
      -  }
      -});
      -
      -
      -
      - -
      - -

      メッセージのサブタイプのフィルタリング

      -
      - -
      -

      message() リスナーは event('message') と等価の機能を提供します。

      - -

      イベントのサブタイプをフィルタリングしたい場合、組み込みの subtype() ミドルウェアを使用できます。 message_changedmessage_replied のような一般的なメッセージサブタイプの情報は、メッセージイベントのドキュメントを参照してください。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -
      // パッケージから subtype をインポート
      -const { App, subtype } = require('@slack/bolt');
      -
      -// user からのメッセージの編集と一致
      -app.message(subtype('message_changed'), ({ event, logger }) => {
      -  // この if 文は TypeScript でコードを書く際に必要
      -  if (event.subtype === 'message_changed'
      -    && !event.message.subtype
      -    && !event.previous_message.subtype) {
      -    logger.info(`The user ${event.message.user} changed their message from ${event.previous_message.text} to ${event.message.text}`);
      -  }
      -});
      -
      -
      -
      - -
      - - -
      -
      - -
      -

      Web API の使用

      - -
      -

      Web API メソッドを呼び出すには、リスナー関数の引数に client として提供されている WebClient を使用します。このインスタンスが使用するトークンは、Bolt アプリの初期化時に指定されたもの もしくは Slack からのリクエストに対して authorize 関数から返されたものが設定されます。組み込みの OAuth サポートは、この後者のケースをデフォルトでハンドリングします。

      - -

      Bolt アプリケーションは、トップレベルに app.client も持っています。このインスタンスには、トークンをメソッド呼び出しのパラメーターとして都度指定します。Slack からのリクエストが authorize されないユースケースや、リスナー関数の外で Web API を呼び出したい場合は、このトップレベルの app.client を使用します。

      - -

      トップレベルのクライアントを使ってもリスナー関数でのクライアントを使っても、WebClient が提供するメソッドを呼び出すと、それへの Slack からのレスポンスを含む Promise の値が返されます。

      - -

      OrG 全体へのインストール機能の導入により、いくつかの Web API は、動作しているワークスペースを伝えるために team_id パラメーターを必要とします。Bolt for JavaScript は、この team_id を Slack から受け取ったペイロードを元に判定し、client インスタンスに設定します。これは、既存のアプリケーションにとっても OrG 全体へのインストールに対応する上で有用です。既存の Web API 呼び出しの処理をアップデートする必要はありません。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -
      // September 30, 2019 11:59:59 PM を Unix エポックタイムで表示
      -const whenSeptemberEnds = 1569887999;
      -
      -app.message('wake me up', async ({ message, context, logger }) => {
      -  try {
      -    // トークンを用いて chat.scheduleMessage 関数を呼び出す
      -    const result = await app.client.chat.scheduleMessage({
      -      // アプリの初期化に用いたトークンを `context` オブジェクトに保存
      -      token: context.botToken,
      -      channel: message.channel,
      -      post_at: whenSeptemberEnds,
      -      text: 'Summer has come and passed'
      -    });
      -  }
      -  catch (error) {
      -    logger.error(error);
      -  }
      -});
      -
      -
      -
      - - -
      -
      - -
      -

      アクションのリスニング

      - -
      -

      Bolt アプリは action メソッドを用いて、ボタンのクリック、メニューの選択、メッセージショートカットなどのユーザーのアクションをリッスンすることができます。

      - -

      アクションは文字列型の action_id または RegExp オブジェクトでフィルタリングできます。 action_id は、Slack プラットフォーム上のインタラクティブコンポーネントの一意の識別子として機能します。

      - -

      すべての action() の例で ack() が使用されていることに注目してください。Slack からリクエストを受信したことを確認するために、アクションリスナー内で ack() 関数を呼び出す必要があります。これについては、「リクエストの確認」 セクションで説明しています。

      - -

      注: Bolt 2.x からメッセージショートカット(以前はメッセージアクションと呼ばれていました)は action() ではなく shortcut() メソッドを使用するようになりました。この変更については 2.x マイグレーションガイドを参照してください。

      - -

      block_actions ペイロードの詳細については、こちら をご覧ください。リスナー内からビューの完全なペイロードにアクセスするには、コールバック関数内で body 引数を参照します。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -
      // action_id が "approve_button" のインタラクティブコンポーネントがトリガーされる毎にミドルウェアが呼び出される
      -app.action('approve_button', async ({ ack }) => {
      -  await ack();
      -  // アクションを反映してメッセージをアップデート
      -});
      -
      -
      -
      - -
      - -

      制約付きオブジェクトを使用したアクションのリスニング

      -
      - -
      -

      制約付きのオブジェクトを使って、 callback_idblock_id 、および action_id (またはそれらの組み合わせ) をリッスンすることができます。オブジェクト内の制約には、文字列型または RegExp オブジェクトを使用できます。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -
      // action_id が 'select_user' と一致し、block_id が 'assign_ticket' と一致する場合のみミドルウェアが呼び出される
      -app.action({ action_id: 'select_user', block_id: 'assign_ticket' },
      -  async ({ body, client, ack, logger }) => {
      -    await ack();
      -    try {
      -      if (body.message) {
      -        const result = await client.reactions.add({
      -          name: 'white_check_mark',
      -          timestamp: body.message.ts,
      -          channel: body.channel.id
      -        });
      -
      -        logger.info(result);
      -      }
      -    }
      -    catch (error) {
      -      logger.error(error);
      -    }
      -  });
      -
      -
      -
      - -
      - - -
      -
      - -
      -

      アクションへの応答

      - -
      -

      アクションへの応答には、主に 2 つのやり方があります。1 つ目の (最も一般的な) やり方は say 関数の利用です。 say 関数は、Slack 内のリクエストが発生した会話(チャンネルや DM)へメッセージを返します。

      - -

      アクションに応答する 2 つ目の方法は respond() です。これはアクションに紐付けられている response_url を用いたメッセージの送信をシンプルに行うためのユーティリティです。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -
      // action_id が "approve_button" のインタラクティブコンポーネントがトリガーされる毎にミドルウェアが呼び出される
      -app.action('approve_button', async ({ ack, say }) => {
      -  // アクションリクエストの確認
      -  await ack();
      -  await say('Request approved 👍');
      -});
      -
      -
      -
      - -
      - -

      respond() の使用

      -
      - -
      -

      respond()response_url を呼び出すためのユーティリティであるため、それを直接使うときと同様に動作します。新しいメッセージのペイロードと、オプショナルな引数である response_type (値は in_channel または ephemeral )、 replace_originaldelete_original を含む JSON オブジェクトを渡すことができます。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -
      // "user_select" の action_id がトリガーされたアクションをリッスン
      -app.action('user_select', async ({ action, ack, respond }) => {
      -  await ack();
      -  if (action.type === 'users_select') {
      -    await respond(`You selected <@${action.selected_user}>`);
      -  }
      -});
      -
      -
      -
      - -
      - - -
      -
      - -
      -

      リクエストの確認

      - -
      -

      アクション(action)、コマンド(command)、およびオプション(options)リクエストは、必ず ack() 関数を用いて確認する必要があります。これにより Slack 側にリクエストが正常に受信されたことを知らせることができ、それに応じて Slack のユーザーインターフェイスが更新されます。リクエストのタイプによっては、確認の通知方法が異なる場合があります。たとえば、モーダルの送信を確認するとき、送信内容にエラーがあればバリデーションエラーとともに ack() を呼び出しますが、送信内容が問題なければ、そのようなパラメータなしで ack() を呼び出します。

      - -

      この ack() による応答は 3 秒以内に行う必要があります。新しいメッセージの送信や、データベースからの情報の取得などを行う前に、リクエストを受けてすぐに ack() を呼び出して応答を返してしまうことをおすすめします。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -
      // Regex でメールアドレスが有効かチェック
      -let isEmail = /^[\w\-\.]+@([\w\-]+\.)+[\w\-]+$/;
      -// 制約付きのオブジェクト を使用して ticket_submit という callback_id を持つモーダル送信をリッスン
      -app.view('ticket_submit', async ({ ack, view }) => {
      -  // block_id が `email_address` の input ブロックからメールアドレスを取得
      -  const email = view.state.values['email_address']['input_a'].value;
      -
      -  // メールアドレスが有効。モーダルを受信
      -  if (email && isEmail.test(email)) {
      -    await ack();
      -  } else {
      -    // メールアドレスが無効。エラーを確認
      -    await ack({
      -      "response_action": "errors",
      -      errors: {
      -        "email_address": "Sorry, this isn’t a valid email"
      -      }
      -    });
      -  }
      -});
      -
      -
      -
      - - -
      -
      - -
      -

      ショートカットのリスニング

      - -
      -

      shortcut() メソッドは、グローバルショートカットメッセージショートカットの両方をサポートします。

      - -

      ショートカットは、テキスト入力エリアや検索バーから起動できる Slack クライアント内の UI エレメントです。グローバルショートカットは、コンポーザーメニューまたは検索メニューから呼び出すことができます。メッセージショートカットは、メッセージのコンテキストメニュー内にあります。shortcut() メソッドを使って、これらのショートカットのリクエストをリッスンすることができます。このメソッドには callback_id を文字列または正規表現のデータ型で設定します。

      - -

      ⚠️ 同じ対象にマッチする正規表現の shortcut() を複数使用する場合、マッチする 全ての リスナーが実行されることに注意してください。そのような挙動を意図しない場合は、これが発生しないよう正規表現をデザインしてください。

      - -

      グローバルショートカットのリクエストは Slack へリクエストを受信したことを知らせるために ack() メソッドで確認する必要があります。

      - -

      グローバルショートカットのペイロードは、ユーザーの実行アクションの確認のためにモーダルを開くなどの用途に使用できる trigger_id を含んでいます。

      - -

      ⚠️ グローバルショートカットのペイロードは チャンネル ID は含んでいない ことに注意してください。もしあなたのアプリがチャンネル ID を知る必要があれば、モーダル内で conversations_select エレメントを使用できます。 -メッセージショートカットのペイロードはチャンネル ID を含みます。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -21
      -22
      -23
      -24
      -25
      -26
      -27
      -28
      -29
      -30
      -31
      -32
      -33
      -34
      -35
      -36
      -37
      -38
      -39
      -40
      -41
      -42
      -43
      -44
      -45
      -46
      -47
      -48
      -
      // open_modal というグローバルショートカットはシンプルなモーダルを開く
      -app.shortcut('open_modal', async ({ shortcut, ack, context, logger }) => {
      -  // グローバルショートカットリクエストの確認
      -  ack();
      -
      -  try {
      -    // 組み込みの WebClient を使って views.open API メソッドを呼び出す
      -    const result = await app.client.views.open({
      -      // `context` オブジェクトに保持されたトークンを使用
      -      token: context.botToken,
      -      trigger_id: shortcut.trigger_id,
      -      view: {
      -        "type": "modal",
      -        "title": {
      -          "type": "plain_text",
      -          "text": "My App"
      -        },
      -        "close": {
      -          "type": "plain_text",
      -          "text": "Close"
      -        },
      -        "blocks": [
      -          {
      -            "type": "section",
      -            "text": {
      -              "type": "mrkdwn",
      -              "text": "About the simplest modal you could conceive of :smile:\n\nMaybe <https://api.slack.com/reference/block-kit/interactive-components|*make the modal interactive*> or <https://api.slack.com/surfaces/modals/using#modifying|*learn more advanced modal use cases*>."
      -            }
      -          },
      -          {
      -            "type": "context",
      -            "elements": [
      -              {
      -                "type": "mrkdwn",
      -                "text": "Psssst this modal was designed using <https://api.slack.com/tools/block-kit-builder|*Block Kit Builder*>"
      -              }
      -            ]
      -          }
      -        ]
      -      }
      -    });
      -
      -    logger.info(result);
      -  }
      -  catch (error) {
      -    logger.error(error);
      -  }
      -});
      -
      -
      -
      - -
      - -

      制約付きオブジェクトを使用したショートカットのリスニング

      -
      - -
      -

      制約付きオブジェクトを使って callback_idtype によるリスニングができます。オブジェクト内の制約は文字列型または RegExp オブジェクトを使用できます。

      - -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -21
      -22
      -23
      -24
      -25
      -26
      -27
      -28
      -29
      -30
      -31
      -32
      -33
      -34
      -35
      -36
      -37
      -38
      -39
      -40
      -41
      -42
      -43
      -44
      -45
      -46
      -47
      -48
      -
        // callback_id が 'open_modal' と一致し type が 'message_action' と一致する場合のみミドルウェアが呼び出される
      -  app.shortcut({ callback_id: 'open_modal', type: 'message_action' }, async ({ shortcut, ack, context, client, logger }) => {
      -    try {
      -      // ショートカットリクエストの確認
      -      await ack();
      -
      -      // 組み込みの WebClient を使って views.open API メソッドを呼び出す
      -      const result = await app.client.views.open({
      -        // `context` オブジェクトに保持されたトークンを使用
      -        token: context.botToken,
      -        trigger_id: shortcut.trigger_id,
      -        view: {
      -          type: "modal",
      -          title: {
      -            type: "plain_text",
      -            text: "My App"
      -          },
      -          close: {
      -            type: "plain_text",
      -            text: "Close"
      -          },
      -          blocks: [
      -            {
      -              type: "section",
      -              text: {
      -                type: "mrkdwn",
      -                text: "About the simplest modal you could conceive of :smile:\n\nMaybe <https://api.slack.com/reference/block-kit/interactive-components|*make the modal interactive*> or <https://api.slack.com/surfaces/modals/using#modifying|*learn more advanced modal use cases*>."
      -              }
      -            },
      -            {
      -              type: "context",
      -              elements: [
      -                {
      -                  type: "mrkdwn",
      -                  text: "Psssst this modal was designed using <https://api.slack.com/tools/block-kit-builder|*Block Kit Builder*>"
      -                }
      -              ]
      -            }
      -          ]
      -        }
      -      });
      -
      -      logger.info(result);
      -    }
      -    catch (error) {
      -      logger.error(error);
      -    }
      -  });
      -
      -
      -
      - -
      - - -
      -
      - -
      -

      コマンドのリスニングと応答

      - -
      -

      スラッシュコマンドが実行されたリクエストをリッスンするには、アプリで command() メソッドを使用します。メソッドの使用には文字列か正規表現の commandName の指定が必要です。

      - -

      ⚠️ 同じ対象にマッチする正規表現の command() を複数使用する場合、マッチする 全ての リスナーが実行されることに注意してください。そのような挙動を意図しない場合は、これが発生しないよう正規表現をデザインしてください。

      - -

      アプリがスラッシュコマンドのリクエストを受け取ったことを ack() の実行によって Slack に通知する必要があります。

      - -

      スラッシュコマンドへの応答には 2 つのやり方があります。1 つ目の方法は、文字列または JSON ペイロードを受け取る say() で、2 つ目は response_url を簡単に利用するためのユーティリティである respond() です。これらについては、「アクションへの応答」セクションで詳しく説明しています。

      - -

      Slack アプリの管理画面でスラッシュコマンドを設定するとき、そのスラッシュコマンドの Request URL に(https://{ドメイン} に続いて) /slack/events を指定するようにしてください。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -
      // この echo コマンドは ただ、その引数を(やまびこのように)おうむ返しする
      -app.command('/echo', async ({ command, ack, respond }) => {
      -  // コマンドリクエストを確認
      -  await ack();
      -
      -  await respond(`${command.text}`);
      -});
      -
      -
      -
      - - -
      -
      - -
      -

      モーダルの開始

      - -
      - -

      モーダルは、ユーザー情報を収集したり、動的な表示を実現するためのインターフェースです。モーダルは、有効な trigger_idビュー部分のペイロード を組み込みの API クライアントによる views.open メソッドの呼び出しに渡すことで開始することができます。

      - -

      trigger_id はスラッシュコマンド、ボタンの押下、メニューの選択などによって Request URL に送信されたペイロードの項目として入手することができます。

      - -

      モーダルの生成についてのより詳細な情報は API ドキュメントを参照してください。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -21
      -22
      -23
      -24
      -25
      -26
      -27
      -28
      -29
      -30
      -31
      -32
      -33
      -34
      -35
      -36
      -37
      -38
      -39
      -40
      -41
      -42
      -43
      -44
      -45
      -46
      -47
      -48
      -49
      -50
      -51
      -52
      -53
      -54
      -55
      -56
      -57
      -58
      -59
      -60
      -
      // コマンド起動をリッスン
      -app.command('/ticket', async ({ ack, body, client, logger }) => {
      -  // コマンドのリクエストを確認
      -  await ack();
      -
      -  try {
      -    const result = await client.views.open({
      -      // 適切な trigger_id を受け取ってから 3 秒以内に渡す
      -      trigger_id: body.trigger_id,
      -      // view の値をペイロードに含む
      -      view: {
      -        type: 'modal',
      -        // callback_id が view を特定するための識別子
      -        callback_id: 'view_1',
      -        title: {
      -          type: 'plain_text',
      -          text: 'Modal title'
      -        },
      -        blocks: [
      -          {
      -            type: 'section',
      -            text: {
      -              type: 'mrkdwn',
      -              text: 'Welcome to a modal with _blocks_'
      -            },
      -            accessory: {
      -              type: 'button',
      -              text: {
      -                type: 'plain_text',
      -                text: 'Click me!'
      -              },
      -              action_id: 'button_abc'
      -            }
      -          },
      -          {
      -            type: 'input',
      -            block_id: 'input_c',
      -            label: {
      -              type: 'plain_text',
      -              text: 'What are your hopes and dreams?'
      -            },
      -            element: {
      -              type: 'plain_text_input',
      -              action_id: 'dreamy_input',
      -              multiline: true
      -            }
      -          }
      -        ],
      -        submit: {
      -          type: 'plain_text',
      -          text: 'Submit'
      -        }
      -      }
      -    });
      -    logger.info(result);
      -  }
      -  catch (error) {
      -    logger.error(error);
      -  }
      -});
      -
      -
      -
      - - -
      -
      - -
      -

      モーダルの更新と多重表示

      - -
      -

      モーダルでは、複数のモーダルをスタックのように積み重ねて表示できます。views.open という API を呼び出すと、まず親の(最初の)モーダルが表示されます。この最初の呼び出しの後、views.update を実行することでそのビューを書き換えることもできますし、最初に述べたように views.push で新しいモーダルを積み重ねて表示することもできます。

      - -

      views.update
      -モーダルの更新には、組み込みの API クライアントを使って views.update を呼び出します。この API 呼び出しには、そのモーダルを開いたときに生成された view_id と、更新後の内容を表現する blocks の配列を含む新しい view を渡します。ユーザーが既存のモーダル内の要素とインタラクションを行なった(例:ボタンを押す、メニューから選択する)ことをトリガーにビューを更新する場合、そのリクエストの bodyview_id が含まれます。

      - -

      views.push
      -モーダルのスタックに新しいモーダルを積み重ねるためには、組み込みの API クライアントを用いて views.push を呼び出します。この API 呼び出しには、有効な trigger_id と、新しく生成する ビュー部分のペイロードを渡します。views.push の引数は モーダルを開始するときと同様です。最初のモーダルを開いた後、その上にさらに二つまで追加のモーダルをスタックに積み重ねることができます。

      - -

      より詳細な情報は API ドキュメントを参照してください。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -21
      -22
      -23
      -24
      -25
      -26
      -27
      -28
      -29
      -30
      -31
      -32
      -33
      -34
      -35
      -36
      -37
      -38
      -39
      -40
      -41
      -42
      -43
      -44
      -45
      -46
      -
      // action_id: button_abc のボタンを押すイベントをリッスン
      -// (そのボタンはモーダルの中にあるという想定)
      -app.action('button_abc', async ({ ack, body, client, logger }) => {
      -  // ボタンを押したイベントを確認
      -  await ack();
      -
      -  try {
      -    if (body.type !== 'block_actions' || !body.view) {
      -      return;
      -    }
      -    const result = await client.views.update({
      -      // リクエストに含まれる view_id を渡す
      -      view_id: body.view.id,
      -      // 競合状態を防ぐために更新前の view に含まれる hash を指定
      -      hash: body.view.hash,
      -      // 更新された view の値をペイロードに含む
      -      view: {
      -        type: 'modal',
      -        // callback_id が view を特定するための識別子
      -        callback_id: 'view_1',
      -        title: {
      -          type: 'plain_text',
      -          text: 'Updated modal'
      -        },
      -        blocks: [
      -          {
      -            type: 'section',
      -            text: {
      -              type: 'plain_text',
      -              text: 'You updated the modal!'
      -            }
      -          },
      -          {
      -            type: 'image',
      -            image_url: 'https://media.giphy.com/media/SVZGEcYt7brkFUyU90/giphy.gif',
      -            alt_text: 'Yay! The modal was updated'
      -          }
      -        ]
      -      }
      -    });
      -    logger.info(result);
      -  }
      -  catch (error) {
      -    logger.error(error);
      -  }
      -});
      -
      -
      -
      - - -
      -
      - -
      -

      モーダルでの送信のリスニング

      - -
      - -

      view メソッドを使うと、ユーザーのビューとのインタラクションをリッスンすることができます。

      - -

      ユーザーがモーダルからデータ送信したとき、Slack から view_submission のリクエストが送信されます。送信された input ブロックの値は state オブジェクトから取得できます。state 内には values というオブジェクトがあり、これは block_id と一意な action_id に紐づける形で入力値を保持しています。 -モーダルでの notify_on_close プロパティを true に設定した場合、ユーザーが Close ボタンを押したときに Slack から view_closed リクエストが送信されます。 より詳細な情報は以下の モーダルを閉じるときのハンドリング を参照してください。 -view_submissionview_closed リクエストをリッスンするには、組み込みの view() メソッドを使用できます。

      - -

      view() メソッドでは、文字列か正規表現の callback_id の指定が必要です。typecallback_id を含む制約付きオブジェクトを渡すこともできます。

      - -
      - -
      モーダル送信でのビューの更新
      - -

      view_submission リクエストに対してモーダルを更新するには、リクエストの確認の中で update という response_action と新しく作成した view を指定します。

      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -
      // モーダル送信でのビューの更新
      -app.view('modal-callback-id', async ({ ack, body }) => {
      -  await ack({
      -    response_action: 'update',
      -    view: buildNewModalView(body),
      -  });
      -});
      -
      -
      -
      -

      この例と同様に、モーダルでの送信リクエストに対して、エラーを表示する ためのオプションもあります。

      - -

      より詳細な情報は API ドキュメントを参照してください。

      - -
      - -
      モーダルを閉じるときのハンドリング
      - -

      💡 view_closed リクエストをリッスンするとき、callback_idtype: 'view_closed' を含むオブジェクトの指定が必要です。以下の例を参照してください。

      - -

      view_closed に関するより詳細な情報は API ドキュメントを参照してください。

      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -
      // view_closed リクエストの処理
      -app.view({ callback_id: 'view_b', type: 'view_closed' }, async ({ ack, body, view, client }) => {
      -  // view_closed リクエストの確認
      -  await ack();
      -  // close リクエストについて何らかの処理
      -});
      -
      -
      -
      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -21
      -22
      -23
      -24
      -25
      -26
      -27
      -28
      -29
      -30
      -31
      -32
      -33
      -34
      -35
      -
      // モーダルでのデータ送信リクエストを処理します
      -app.view('view_b', async ({ ack, body, view, client, logger }) => {
      -  // モーダルでのデータ送信リクエストを確認
      -  await ack();
      -
      -  // 入力値を使ってやりたいことをここで実装 - ここでは DB に保存して送信内容の確認を送っている
      -
      -  // block_id: block_1 という input ブロック内で action_id: input_a の場合の入力
      -  const val = view['state']['values']['block_1']['input_a'];
      -  const user = body['user']['id'];
      -
      -  // ユーザーに対して送信するメッセージ
      -  let msg = '';
      -  // DB に保存
      -  const results = await db.set(user.input, val);
      -
      -  if (results) {
      -    // DB への保存が成功
      -    msg = 'Your submission was successful';
      -  } else {
      -    msg = 'There was an error with your submission';
      -  }
      -
      -  // ユーザーにメッセージを送信
      -  try {
      -    await client.chat.postMessage({
      -      channel: user,
      -      text: msg
      -    });
      -  }
      -  catch (error) {
      -    logger.error(error);
      -  }
      -
      -});
      -
      -
      -
      - - -
      -
      - -
      -

      ホームタブの更新

      - -
      -

      ホームタブは、サイドバーや検索画面からアクセス可能なサーフェスエリアです。アプリはこのエリアを使ってユーザーごとのビューを表示することができます。アプリ設定ページで App Home の機能を有効にすると、views.publish API メソッドの呼び出しで user_idビューのペイロードを指定して、ホームタブを公開・更新することができるようになります。

      - -

      エンドユーザーが App Home(ホームタブやアプリとの DM など)にアクセスしたことを知るために、app_home_opened イベントをサブスクライブすることができます。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -21
      -22
      -23
      -24
      -25
      -26
      -27
      -28
      -29
      -30
      -31
      -32
      -33
      -34
      -35
      -
      // ユーザーが App Home にアクセスしたことを伝えるイベントをリッスン
      -app.event('app_home_opened', async ({ event, client, logger }) => {
      -  try {
      -    // 組み込みの API クライアントを使って views.publish を呼び出す
      -    const result = await client.views.publish({
      -      // イベントに紐づけられたユーザー ID を指定
      -      user_id: event.user,
      -      view: {
      -        // ホームタブはあらかじめアプリ設定ページで有効にしておく必要があります
      -        type: "home",
      -        blocks: [
      -          {
      -            type: "section",
      -            text: {
      -              type: "mrkdwn",
      -              text: "*Welcome home, <@" + event.user + "> :house:*"
      -            }
      -          },
      -          {
      -            type: "section",
      -            text: {
      -              type: "mrkdwn",
      -              text: "Learn how home tabs can be more useful and interactive <https://api.slack.com/surfaces/tabs/using|*in the documentation*>."
      -            }
      -          }
      -        ]
      -      }
      -    });
      -
      -    logger.info(result);
      -  }
      -  catch (error) {
      -    logger.error(error);
      -  }
      -});
      -
      -
      -
      - - -
      -
      - -
      -

      オプションのリスニングと応答

      - -
      -

      options() メソッドは、Slack からのオプション(セレクトメニュー内の動的な選択肢)をリクエストするペイロードをリッスンします。 action() と同様に、文字列型の action_id または制約付きオブジェクトが必要です。

      - -

      external_select メニューには action_id を使用することをおすすめしますが、ダイアログはまだ Block Kit をサポートしていないため、制約オブジェクトを用いて callback_id でフィルタリングする必要があります。

      - -

      オプションのリクエストへの応答には、適切なオプションを指定して ack() を実行する必要があります。API サイトに掲載されているexternal_select の応答の例ダイアログ応答の例を参考にしてください。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -21
      -22
      -23
      -24
      -25
      -
      // external_select オプションリクエストに応答する例
      -app.options('external_action', async ({ options, ack }) => {
      -  // チームまたはチャンネル情報を取得
      -  const results = await db.get(options.team.id);
      -
      -  if (results) {
      -    let options = [];
      -    // ack 応答 するために options 配列に情報をプッシュ
      -    for (const result of results) {
      -      options.push({
      -        text: {
      -          type: "plain_text",
      -          text: result.label
      -        },
      -        value: result.value
      -      });
      -    }
      -
      -    await ack({
      -      options: options
      -    });
      -  } else {
      -    await ack();
      -  }
      -});
      -
      -
      -
      - - -
      -
      - -
      -

      OAuth フローの実装

      - -
      -

      Slack アプリの配布を行うには Bolt による OAuth フローを実装し、インストール時に取得した情報をセキュアな方法で保存しておく必要があります。 -Bolt は OAuth フローそのものに加えて OAuth のためのルーティング、 state パラメーターの検証、保存するためのインストール情報をアプリに受け渡す、などの処理をハンドリングします。

      - -

      OAuth を有効にするために、以下を提供する必要があります:

      -
        -
      • -clientId, clientSecret, stateSecret, scopes (必須) -
      • -
      • -installationStore オプションは、インストール情報の保存と取得を行うハンドラーを提供します (必須とはなっていませんが、本番環境では設定することを強く推奨します) -
      • -
      - -
      開発とテスト
      - -

      開発・テストの際に利用することを想定して installationStore オプションのデフォルト実装である FileInstallationStore を提供しています。

      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -
      const { App } = require('@slack/bolt');
      -const { FileInstallationStore } = require('@slack/oauth');
      -const app = new App({
      -  signingSecret: process.env.SLACK_SIGNING_SECRET,
      -  clientId: process.env.SLACK_CLIENT_ID,
      -  clientSecret: process.env.SLACK_CLIENT_SECRET,
      -  stateSecret: 'my-state-secret',
      -  scopes: ['channels:history', 'chat:write', 'commands'],
      -  installationStore: new FileInstallationStore(),
      -});
      -
      -
      -
      -

      :warning: 本番運用での利用は 推奨しません ので、本番向けのデータストアはご自身で実装する必要があります。サンプルコードとして OAuth の他の実装例を参照してください。

      - -
      アプリのインストール
      - -
        -
      • -インストールの開始: Bolt for JavaScript は /slack/install という インストール用のパス を生成します。これは、有効な state パラメータを生成した上で Slack アプリの直接のインストールを開始するための Add to Slack ボタンを含むページを応答する URL です。 www.example.com でホスティングされているアプリの場合、インストールページは www.example.com/slack/install となります。 -
          -
        • 💡 App コンストラクタ内で installerOptions.directInstall: true を設定すると、デフォルトのウェブページを描画する代わりに、ユーザーを直接 Slack の authorize URL に誘導することができます()。
        • -
        -
      • -
      • -

        Add to Slack (Slack へ追加): Add to Slack ボタンを押すと Slack との OAuth プロセスを開始します。ユーザーがアプリへの権限付与を許可すると、Slack はアプリの Redirect URI (あらかじめ設定されています)へユーザーを誘導し、処理が正常に完了したらユーザーに Slack で開く よう促します。これらの設定をカスタマイズする方法については、後述の Redirect URI セクションを参照してください。

        -
      • -
      • -

        Slack で開く: ユーザーが Slack で開く を選択した後、アプリが Slack からのイベントをするときに installationStorefetchInstallationstoreInstallation ハンドラーが実行されます。ハンドラーに渡す引数に関するより詳しい情報は Installation Object セクションを参照してください。

        -
      • -
      • -

        アプリがすでにインストールされていて、さらにユーザーから追加の認可情報(例:ユーザートークンの発行)な場合や、何らかの理由で動的にインストール用の URL を生成したい場合は、ExpressReceiver を自前でインスタンス化し、それを receiver という変数に代入した上で receiver.installer.generateInstallUrl() を呼び出してください。詳しくは OAuth ライブラリのドキュメントgenerateInstallUrl() を参照してください。

        -
      • -
      • 💡 Bolt for JavaScript は カスタムのレシーバーでの OAuth をサポートしていません。カスタムのレシーバーで OAuth フローを実装したい場合は、私たちが提供している OAuth ライブラリ を使うことができます。Bolt for JavaScript の組み込みのモジュールもこれを内部的に利用しています。
      • -
      - -
      Redirect URI
      - -

      Bolt for JavaScript は、アプリのインストールフローを完了した後の遷移先の URL である Redirect URI のためのパスとして /slack/oauth_redirect を有効にします。

      - -

      💡 アプリのドメインを含んだ Redirect URI (絶対 URI)を Slack アプリの設定画面の OAuth and Permissions セクション内で設定してください。(例 https://example.com/slack/oauth_redirect )。

      - -

      カスタムの Redirect URI を使う場合、 App クラスの引数 redirectUriinstallerOptions.redirectUriPath にも設定してください。 両方とも設定する必要があり、また、矛盾のないフル URI である必要があります。

      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -
      const app = new App({
      -  signingSecret: process.env.SLACK_SIGNING_SECRET,
      -  clientId: process.env.SLACK_CLIENT_ID,
      -  clientSecret: process.env.SLACK_CLIENT_SECRET,
      -  stateSecret: 'my-state-secret',
      -  scopes: ['chat:write'],
      -  redirectUri: 'https://example.com/slack/redirect', // ここに設定します
      -  installerOptions: {
      -    redirectUriPath: '/slack/redirect', // ここにも!
      -  },
      -});
      -
      -
      -
      - -
      Installation オブジェクト
      - -

      Bolt は installationStorestoreInstallation ハンドラーに installation オブジェクトを渡します。どのようなオブジェクトの形式となるか想像しづらいと開発時に混乱の元になるかもしれません。installation オブジェクトはこのような形式となります:

      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -
      {
      -  team: { id: 'T012345678', name: 'example-team-name' },
      -  enterprise: undefined,
      -  user: { token: undefined, scopes: undefined, id: 'U01234567' },
      -  tokenType: 'bot',
      -  isEnterpriseInstall: false,
      -  appId: 'A01234567',
      -  authVersion: 'v2',
      -  bot: {
      -    scopes: [
      -      'chat:write',
      -    ],
      -    token: 'xoxb-244493-28*********-********************',
      -    userId: 'U012345678',
      -    id: 'B01234567'
      -  }
      -}
      -
      -
      -
      - -

      Bolt は fetchInstallationdeleteInstallation ハンドラーに installQuery オブジェクトを渡します:

      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -
      {
      -  userId: 'U012345678',
      -  isEnterpriseInstall: false,
      -  teamId: 'T012345678',
      -  enterpriseId: undefined,
      -  conversationId: 'D02345678'
      -}
      -
      -
      -
      - -
      OrG 全体へのインストール
      - -

      Enterprise Grid の OrG 全体へのインストールへの対応を追加する場合、Bolt for JavaScript のバージョン 3.0.0 以上を利用してください。また Slack アプリの設定画面で Org Level Apps の設定が有効になっていることを確認してください。

      - -

      管理者画面からの Enterprise Grid の OrG 全体へのインストール の場合、 Bolt で動作させるために追加の設定が必要です。この利用シナリオでは、推奨の state パラメータが提供されず、Bolt アプリでは state を検証しようとするため、インストールを継続することができません。

      - -

      Bolt アプリ側で stateVerification オプションを false に設定することで、 state パラメーターの検証を無効することができます。以下の例を参考にしてください。

      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -
      const app = new App({
      -  signingSecret: process.env.SLACK_SIGNING_SECRET,
      -  clientId: process.env.SLACK_CLIENT_ID,
      -  clientSecret: process.env.SLACK_CLIENT_SECRET,
      -  scopes: ['chat:write'],
      -  installerOptions: {
      -    stateVerification: false,
      -  },
      -});
      -
      -
      -
      - -

      Slack の OAuth インストールフローについてのより詳細な情報は API ドキュメントを参照してください。

      - -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -21
      -22
      -23
      -24
      -25
      -26
      -27
      -28
      -29
      -30
      -31
      -32
      -33
      -34
      -35
      -36
      -37
      -38
      -39
      -40
      -41
      -42
      -43
      -44
      -45
      -
      const app = new App({
      -  signingSecret: process.env.SLACK_SIGNING_SECRET,
      -  clientId: process.env.SLACK_CLIENT_ID,
      -  clientSecret: process.env.SLACK_CLIENT_SECRET,
      -  stateSecret: 'my-state-secret',
      -  scopes: ['channels:read', 'groups:read', 'channels:manage', 'chat:write', 'incoming-webhook'],
      -  installationStore: {
      -    storeInstallation: async (installation) => {
      -      // 実際のデータベースに保存するために、ここのコードを変更
      -      if (installation.isEnterpriseInstall && installation.enterprise !== undefined) {
      -        // OrG 全体へのインストールに対応する場合
      -        return await database.set(installation.enterprise.id, installation);
      -      }
      -      if (installation.team !== undefined) {
      -        // 単独のワークスペースへのインストールの場合
      -        return await database.set(installation.team.id, installation);
      -      }
      -      throw new Error('Failed saving installation data to installationStore');
      -    },
      -    fetchInstallation: async (installQuery) => {
      -      // 実際のデータベースから取得するために、ここのコードを変更
      -      if (installQuery.isEnterpriseInstall && installQuery.enterpriseId !== undefined) {
      -        // OrG 全体へのインストール情報の参照
      -        return await database.get(installQuery.enterpriseId);
      -      }
      -      if (installQuery.teamId !== undefined) {
      -        // 単独のワークスペースへのインストール情報の参照
      -        return await database.get(installQuery.teamId);
      -      }
      -      throw new Error('Failed fetching installation');
      -    },
      -    deleteInstallation: async (installQuery) => {
      -      // 実際のデータベースから削除するために、ここのコードを変更
      -      if (installQuery.isEnterpriseInstall && installQuery.enterpriseId !== undefined) {
      -        // OrG 全体へのインストール情報の削除
      -        return await myDB.delete(installQuery.enterpriseId);
      -      }
      -      if (installQuery.teamId !== undefined) {
      -        // 単独のワークスペースへのインストール情報の削除
      -        return await myDB.delete(installQuery.teamId);
      -      }
      -      throw new Error('Failed to delete installation');
      -    },
      -  },
      -});
      -
      -
      -
      - -
      - -

      OAuth デフォルト設定をカスタマイズ

      -
      - -
      - -

      installerOptions を使って OAuth モジュールのデフォルト設定を上書きすることができます。このカスタマイズされた設定は App の初期化時に渡します。以下の情報を変更可能です:

      - -
        -
      • -authVersion: 新しい Slack アプリとクラシック Slack アプリの切り替えに使用
      • -
      • -metadata: セッションに関連する情報の指定に使用
      • -
      • -installPath: “Add to Slack” ボタンのためのパスを変更するために使用
      • -
      • -redirectUriPath: Redirect URL を変更するために使用
      • -
      • -callbackOptions: OAuth フロー完了時の成功・エラー完了画面をカスタマイズするために使用
      • -
      • -stateStore: 組み込みの ClearStateStore の代わりにカスタムのデータストアを有効にするために使用
      • -
      • -userScopes: 親の階層にある scopes プロパティと同様、ユーザがアプリをインストールする際に必要となるユーザスコープのリストの指定に使用
      • -
      - -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -21
      -22
      -23
      -24
      -25
      -26
      -27
      -28
      -29
      -30
      -31
      -32
      -33
      -34
      -35
      -36
      -37
      -38
      -39
      -40
      -41
      -42
      -43
      -44
      -45
      -46
      -47
      -48
      -49
      -50
      -51
      -
      const database = {
      -  async get(key) {},
      -  async delete(key) {},
      -  async set(key, value) {}
      -};
      -
      -const app = new App({
      -  signingSecret: process.env.SLACK_SIGNING_SECRET,
      -  clientId: process.env.SLACK_CLIENT_ID,
      -  clientSecret: process.env.SLACK_CLIENT_SECRET,
      -  scopes: ['channels:read', 'groups:read', 'channels:manage', 'chat:write', 'incoming-webhook'],
      -  installerOptions: {
      -      authVersion: 'v1', // デフォルトは 'v2' (クラシック Slack アプリは 'v1')
      -      metadata: 'some session data',
      -      installPath: '/slack/installApp',
      -      redirectUriPath: '/slack/redirect',
      -      userScopes: ['chat:write'],
      -      callbackOptions: {
      -        success: (installation, installOptions, req, res) => {
      -          // ここで成功時のカスタムロジックを実装
      -          res.send('successful!');
      -        }, 
      -        failure: (error, installOptions , req, res) => {
      -          // ここでエラー時のカスタムロジックを実装
      -          res.send('failure');
      -        }
      -      },
      -      stateStore: {
      -        // `stateStore` を指定する場合は `stateSecret` の設定が不要
      -
      -        // 第一引数は `generateInstallUrl` メソッドに渡される `InstallUrlOptions` オブジェクト、第二引数は日付オブジェクト
      -        // state の文字列を応答
      -        generateStateParam: async (installUrlOptions, date) => {
      -          // URL の state パラメーターとして使用するランダムな文字列を生成
      -          const randomState = randomStringGenerator();
      -          // その値をキャッシュ、データベースに保存
      -          await myDB.set(randomState, installUrlOptions);
      -          // データベースに保存されたものを利用可能な値として返却
      -          return randomState;
      -        },
      -
      -        // 第一引数は日付オブジェクトで、第二引数は state を表現する文字列
      -        // `installUrlOptions` オブジェクトを応答
      -        verifyStateParam: async (date, state) => {
      -          // state をキーに、データベースから保存された installOptions を取得
      -          const installUrlOptions = await myDB.get(randomState);
      -          return installUrlOptions;
      -        }
      -      },
      -  }
      -});
      -
      -
      -
      - -
      - - -
      -
      - -
      -

      ソケットモードの使用

      - -
      - -

      ソケットモード は、アプリに WebSocket での接続と、そのコネクション経由でのデータ受信を可能とします。コネクションをハンドリングするために @slack/bolt@3.0.0 以上では SokcetModeReceiver というレシーバーが提供されています。ソケットモードを使う前に、アプリの管理画面でソケットモードの機能が有効になっていることを確認しておいてください。

      - -

      SocketModeReceiver を使う方法は App インスタンスの初期化時にコンストラクターに socketMode: trueappToken: YOUR_APP_TOKEN を渡すだけです。App Level Token は、アプリ管理画面の Basic Information セクションから取得できます。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -
      const { App } = require('@slack/bolt');
      -
      -const app = new App({
      -  token: process.env.BOT_TOKEN,
      -  socketMode: true,
      -  appToken: process.env.APP_TOKEN,
      -});
      -
      -(async () => {
      -  await app.start();
      -  console.log('⚡️ Bolt app started');
      -})();
      -
      -
      -
      - -
      - -

      ソケットモードレシーバーのカスタム初期化

      -
      - -
      - -

      以下のように @slack/bolt から SocketModeReceiver を import して、カスタムされたインスタンスとして定義することができます。

      - -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -21
      -22
      -
      const { App, SocketModeReceiver } = require('@slack/bolt');
      -
      -const socketModeReceiver = new SocketModeReceiver({
      -  appToken: process.env.APP_TOKEN,
      -
      -  // OAuth フローの実装を合わせて使う場合は、以下を有効にしてください
      -  // clientId: process.env.CLIENT_ID,
      -  // clientSecret: process.env.CLIENT_SECRET,
      -  // stateSecret: 'my-state-secret',
      -  // scopes: ['channels:read', 'chat:write', 'app_mentions:read', 'channels:manage', 'commands'],
      -});
      -
      -const app = new App({
      -  receiver: socketModeReceiver,
      -  // OAuth を使うなら以下の token 指定は不要です
      -  token: process.env.BOT_TOKEN
      -});
      -
      -(async () => {
      -  await app.start();
      -  console.log('⚡️ Bolt app started');
      -})();
      -
      -
      -
      - -
      - - -
      -
      - -
      - -
      - -
      -

      エラーの処理

      - -
      -

      注: Bolt 2.x からエラーハンドリングが改善されました!この変更については 2.x マイグレーションガイドを参照してください。

      - -

      リスナーでエラーが発生した場合は try/catch を使って直接ハンドリングすることをおすすめします。しかし、それでもなおすり抜けてしまうエラーのパターンもあるでしょう。デフォルトでは、このようなエラーはコンソールにログ出力されます。ご自身でこれらをハンドリングするには、app.error(fn) メソッドによって、グローバルエラーハンドラーを定義してください。

      -
      - -

      また、様々なエラーパターンにより特化したエラーハンドラーを HTTPReceiver に直接設定することができます。

      - -
        -
      • -dispatchErrorHandler: 想定しないパスにリクエストが来たときに実行されます
      • -
      • -processEventErrorHandler: リクエストを処理するとき(例:ミドルウェアや認可プロセス)に発生した例外に対して実行されます
      • -
      • -unhandledRequestHandler: Slack からのリクエストが確認(ack())されなかったときに実行されます
      • -
      • -unhandledRequestTimeoutMillis: リクエストが受信されてから unhandledRequestHandler が実行されるまでの待機時間(ミリ秒単位)。 デフォルトは 3001 です。
      • -
      - -

      : あなたのアプリ内に定義されたカスタムのエラーハンドラーは、エラーとなった Slack からのリクストに応答するために response.writeHead() を呼び出して応答の HTTP ステータスコードを設定し、かつ response.end() を呼び出して Slack へのレスポンスを送信する必要があります。詳細は以下の例を参考にしてください。 -</div>

      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -21
      -22
      -23
      -24
      -25
      -26
      -27
      -28
      -29
      -30
      -31
      -32
      -33
      -34
      -
      import { App, HTTPReceiver } from '@slack/bolt';
      -
      -const app = new App({
      -  receiver: new HTTPReceiver({
      -    signingSecret: process.env.SLACK_SIGNING_SECRET,
      -    // より特定のパターンに特化したエラーハンドラー
      -    dispatchErrorHandler: async ({ error, logger, response }) => {
      -      logger.error(`dispatch error: ${error}`);
      -      response.writeHead(404);
      -      response.write("Something is wrong!");
      -      response.end();
      -    },
      -    processEventErrorHandler: async ({ error, logger, response }) => {
      -      logger.error(`processEvent error: ${error}`);
      -      // とにかく ack する
      -      response.writeHead(200);
      -      response.end();
      -      return true;
      -    },
      -    unhandledRequestHandler: async ({ logger, response }) => {
      -      logger.info('Acknowledging this incoming request because 2 seconds already passed...');
      -      // とにかく ack する
      -      response.writeHead(200);
      -      response.end();
      -    },
      -    unhandledRequestTimeoutMillis: 2000, // デフォルトは 3001
      -  }),
      -});
      -
      -// より一般的なグローバルのエラーハンドラー
      -app.error(async (error) => {
      -  // メッセージ送信をリトライすべきか、アプリを停止すべきか判断するためにエラーの詳細を確認
      -  console.error(error);
      -});
      -
      -
      -
      - -
      - -

      エラーハンドラーでのさらなるデータの参照

      -
      - -
      -

      グローバルエラーハンドラーの中で、リクエストからのデータをログ出力したい場合もあるでしょう。あるいは単に Bolt に設定した logger を利用したい場合もあるでしょう。

      - -

      バージョン 3.8.0 からは、コンストラクターに extendedErrorHandler: true を渡すと、エラーハンドラーはリクエストの errorloggercontextbody を含むオブジェクトを受け取ります。

      - -

      contextbody オブジェクト内にアクセスしたいプロパティが存在するかどうかをチェックすることをおすすめします。なぜなら body オブジェクト内に存在するデータはイベント毎に異なりますし、エラーはリクエストのライフサイクルの中のどんなタイミング(例えば context のプロパティが設定される前)でも発生しうるからです。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -
      const { App } = require('@slack/bolt');
      -
      -const app = new App({
      -  signingSecret: process.env.SLACK_SIGNING_SECRET,
      -  token: process.env.SLACK_BOT_TOKEN,
      -  extendedErrorHandler: true,
      -});
      -
      -app.error(async ({ error, logger, context, body }) => {
      -  // Bolt で指定した logger を使ってエラー内容をログ出力
      -  logger.error(error);
      -
      -  if (context.teamId) {
      -    // デバッグのために teamId を使ってなんらかの処理
      -  }
      -});
      -
      -
      -
      - -
      - - -
      -
      - -
      -

      認可(Authorization)

      - -
      -

      認可(Authorization)は、Slack からのリクエストを処理するにあたって、どの Slack クレデンシャル (ボットトークンなど) を使用可能にするかを決定するプロセスです。

      - -

      1 つだけのワークスペースにインストールされたカスタムアプリであれば App 初期化時に単に token オプションを使用するだけで OK です。一方で、複数のワークスペースにインストールされる、複数のユーザートークンを使用するといったケースのように、アプリが複数のトークンを処理しなければならない場合があります。このようなケースでは token の代わりに authorize オプションを使用する必要があります。

      - -

      authorize オプションには、イベントソースを入力値として受け取り、許可された認可されたクレデンシャルを含むオブジェクトを Promise の値として返す関数を指定します。このイベントソースの情報には、 teamId (常に存在します)、 userIdconversationIdenterpriseId のような、リクエストが誰によって発生させられたか、どこで発生したかに関する情報が含まれます。

      - -

      許可されたクレデンシャルには、botTokenuserTokenbotId (アプリがボット自体からのメッセージを無視するために必要です)、 botUserId が含まれます。context オブジェクトに、これ以外の他のプロパティを自由に設定することもできます。

      - -

      botTokenuserToken は、どちらか、またはその両方を必ず設定してください。say() のようなユーティリティを動作させるには、どちらか一方が存在している必要があります。両方指定した場合、say() では botToken が優先されます。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -21
      -22
      -23
      -24
      -25
      -26
      -27
      -28
      -29
      -30
      -31
      -32
      -33
      -34
      -35
      -36
      -37
      -38
      -
      const app = new App({ authorize: authorizeFn, signingSecret: process.env.SLACK_SIGNING_SECRET });
      -
      -// 注: これはデモの目的のみの例です
      -// 実際は重要なデータはセキュリティの高いデータベースに保存してください。このアプリは bot トークンのみを使用すると仮定しています。ここで使われるオブジェクトは、複数ワークスペースにアプリをインストールした場合のクレデンシャルを保管するモデルです。
      -
      -const installations = [
      -  {
      -    enterpriseId: 'E1234A12AB',
      -    teamId: 'T12345',
      -    botToken: 'xoxb-123abc',
      -    botId: 'B1251',
      -    botUserId: 'U12385',
      -  },
      -  {
      -    teamId: 'T77712',
      -    botToken: 'xoxb-102anc',
      -    botId: 'B5910',
      -    botUserId: 'U1239',
      -  },
      -];
      -
      -const authorizeFn = async ({ teamId, enterpriseId }) => {
      -  // データベースから team(ワークスペース)を取得
      -  for (const team of installations) {
      -    // installations 配列から teamId と enterpriseId(Enterprise Grid の OrG の ID)が一致するかチェック
      -    if ((team.teamId === teamId) && (team.enterpriseId === enterpriseId)) {
      -      // 一致したワークスペースのクレデンシャルを使用
      -      return {
      -        // 代わりに userToken をセットしても OK
      -        botToken: team.botToken,
      -        botId: team.botId,
      -        botUserId: team.botUserId
      -      };
      -    }
      -  }
      -
      -  throw new Error('No matching authorizations');
      -}
      -
      -
      -
      - - -
      -
      - -
      -

      トークンのローテーション

      - -
      -

      Bolt for JavaScript v3.5.0 から、アクセストークンのさらなるセキュリティ強化のレイヤーであるトークンローテーションの機能に対応しています。トークンローテーションは OAuth V2 の RFC で規定されているものです。

      - -

      既存の Slack アプリではアクセストークンが無期限に存在し続けるのに対して、トークンローテーションを有効にしたアプリではアクセストークンが失効するようになります。リフレッシュトークンを利用して、アクセストークンを長期間にわたって更新し続けることができます。

      - -

      Bolt for JavaScript の組み込みの OAuth 機能 を使用していれば、Bolt for JavaScript が自動的にトークンローテーションの処理をハンドリングします。

      - -

      トークンローテーションに関する詳細は API ドキュメントを参照してください。

      -
      - - -
      -
      - -
      -

      会話ストア

      - -
      -

      Bolt は、会話 (conversation) に関連する state を設定および取得する store をサポートしています。conversation store には以下の 2 つのメソッドがあります。

      -
        -
      • -set() は会話の state を変更します。set() は、文字列型の conversationId、任意の型の value、およびオプションの数値型の expiresAt を必要とします。set()Promise を返します。
      • -
      • -get() は store から会話の state を取得します。get() は文字列型の conversationId を必要とし、その会話の state とともに Promise を返します。
      • -
      - -

      conversationContext() は、他のミドルウェアによる会話の更新を可能にする組み込みのグローバルミドルウェアです。イベントを受け取ると、ミドルウェア関数は context.updateConversation() を使用して状態を設定でき、context.conversation を使用してその state を取得できます。

      - -

      組み込みの conversation store は、シンプルに会話の state をメモリーに格納します。状況によってはこれで十分ですが、アプリのインスタンスが複数実行されている場合、状態はプロセス間で共有されないため、データベースを使用して会話の state を取得する conversation store を実装することをおすすめします。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -21
      -22
      -23
      -24
      -25
      -26
      -27
      -28
      -29
      -30
      -31
      -32
      -33
      -
      const app = new App({
      -  token,
      -  signingSecret,
      -  // クラスを作成する感じで
      -  convoStore: new simpleConvoStore()
      -});
      -
      -// Firebaseのようなデータベースを使い conversation store を実装
      -class simpleConvoStore {
      -  set(conversationId, value, expiresAt) {
      -    // Promise を返す
      -    return db().ref('conversations/' + conversationId).set({ value, expiresAt });
      -  }
      -
      -  get(conversationId) {
      -    // Promise を返す
      -    return new Promise((resolve, reject) => {
      -      db().ref('conversations/' + conversationId).once('value').then((result) => {
      -        if (result !== undefined) {
      -          if (result.expiresAt !== undefined && Date.now() > result.expiresAt) {
      -            db().ref('conversations/' + conversationId).delete();
      -
      -            reject(new Error('Conversation expired'));
      -          }
      -          resolve(result.value)
      -        } else {
      -          // Conversation が存在しないエラー
      -          reject(new Error('Conversation not found'));
      -        }
      -      });
      -    });
      -  }
      -}
      -
      -
      -
      - - -
      -
      - -
      -

      グローバルミドルウェア

      - -
      -

      グローバルミドルウェアは、すべての受信リクエストに対して、リスナーミドルウェアより前に実行されます。app.use(fn({payload,...,next})) を使用すると、グローバルミドルウェアをいくつでもアプリに追加できます。

      - -

      グローバルミドルウェアとリスナーミドルウェアは、いずれも、await next() を呼び出して実行チェーンの制御を次のミドルウェアに渡すか、throw を呼び出して以前に実行したミドルウェアチェーンにエラーを渡す必要があります。

      - -

      たとえば、アプリが、対応する内部認証サービス (SSO プロバイダ、LDAP など) で識別されたユーザーにのみ応答する必要があるとします。この場合、グローバルミドルウェアを使用して認証サービス内のユーザーレコードを検索し、ユーザーが見つからない場合はエラーとなるように定義するのがよいでしょう。

      - -

      注: Bolt 2.x からグローバルミドルウェアが async 関数をサポートしました!この変更については 2.x マイグレーションガイドを参照してください。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -21
      -22
      -23
      -24
      -25
      -26
      -27
      -28
      -29
      -
      //  Acme ID情報管理プロバイダ上のユーザからの着信リクエストと紐つけた認証ミドルウェア
      -async function authWithAcme({ payload, client, context, next }) {
      -  const slackUserId = payload.user;
      -  const helpChannelId = 'C12345';
      -
      -  // Slack ユーザ ID を使って Acmeシステム上にあるユーザ情報を検索できる関数があるとと仮定
      -  try {
      -    const user = await acme.lookupBySlackId(slackUserId)
      -    
      -    // 検索できたらそのユーザ情報でコンテクストを生成
      -    context.user = user;
      -  } catch (error) {
      -      // Acme システム上にユーザが存在しないパターン。エラーを伝えることとし、リクエストの処理は継続しない
      -      if (error.message === 'Not Found') {
      -        await client.chat.postEphemeral({
      -          channel: payload.channel,
      -          user: slackUserId,
      -          text: `Sorry <@${slackUserId}>, you aren't registered in Acme. Please post in <#${helpChannelId}> for assistance.`
      -        });
      -        return;
      -      }
      -
      -      // 制御とリスナー関数を(もしあれば)前のミドルウェア渡す、もしくはグローバルエラーハンドラに引き渡し
      -      throw error;
      -  }
      -  
      -  // 制御とリスナー関数を次のミドルウェアに引き渡し
      -  await next();
      -}
      -
      -
      -
      - - -
      -
      - -
      -

      リスナーミドルウェア

      - -
      -

      リスナーミドルウェアは、多くのリスナー関数を対象(つまり、複数のリスナー関数を対象としますが、全てのリスナーに実行するわけではないものです)としたロジックの適用に使用でき、リスナーを追加する組み込みメソッドの引数リスト内で、リスナー関数より先に引数として追加されます。ここでは任意の数のリスナーミドルウェアを追加することができます。

      - -

      組み込みリスナーミドルウェアはいくつか用意されており、例えば、メッセージのサブタイプをフィルタリングする subtype() や、メッセージのはじまりでボットに直接 @ メンションしないメッセージを除外する directMention() のように使用することができます。

      - -

      もちろん、よりカスタマイズされた機能を追加するために独自のミドルウェアを実装することもできます。カスタムミドルウェアとして動作する関数の実装は await next() を呼び出して制御を次のミドルウェアに渡すか、throw を呼び出して以前に実行されたミドルウェアチェーンにエラーを投げる必要があります。

      - -

      例として、リスナーが人(ボットではないユーザー)からのメッセージのみを扱うケースを考えてみましょう。このためには、全てのボットメッセージを除外するリスナーミドルウェアを実装します。

      - -

      注: Bolt 2.x からミドルウェアが async 関数をサポートしました!この変更については 2.x マイグレーションガイドを参照してください。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -
      // 'bot_message' サブタイプを持つメッセージをフィルタリングするリスナーミドルウェア
      -async function noBotMessages({ message, next }) {
      -  if (!message.subtype || message.subtype !== 'bot_message') {
      -    await next();
      -  }
      -}
      -
      -// ボットではなく人間からのメッセージのみを受信するリスナー
      -app.message(noBotMessages, async ({ message, logger }) => logger.info(
      -  // 新規で投稿されたメッセージのみを処理
      -  if (message.subtype === undefined
      -    // || message.subtype === 'bot_message'
      -    || message.subtype === 'file_share'
      -    || message.subtype === 'thread_broadcast') {
      -    logger.info(`(MSG) User: ${message.user} Message: ${message.text}`)
      -  }
      -));
      -
      -
      -
      - - -
      -
      - -
      -

      context の追加

      - -
      -

      context オブジェクトは、受信リクエストに付加情報を提供するために使用されるもので、全てのリスナーがこれを使用できます。例えば、3rd party のシステムからユーザー情報を追加したり、ミドルウェアのチェインの中で次のミドルウェアが必要とする一時的な状態を追加したりといった用途に利用できます。

      - -

      context は、ただのオブジェクトなので、いくらでも属性を追加、編集することができます。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -21
      -22
      -23
      -24
      -25
      -26
      -27
      -28
      -29
      -30
      -31
      -32
      -33
      -34
      -35
      -36
      -37
      -38
      -39
      -40
      -41
      -42
      -43
      -44
      -45
      -46
      -47
      -48
      -49
      -50
      -51
      -52
      -
      async function addTimezoneContext({ payload, client, context, next }) {
      -  const user = await client.users.info({
      -    user: payload.user_id,
      -    include_locale: true
      -  });
      -
      -  // ユーザのタイムゾーン情報を追加
      -  context.tz_offset = user.tz_offset;
      -
      -  // 制御とリスナー関数を次のミドルウェアに引き渡し
      -  await next();
      -}
      -
      -app.command('/request', addTimezoneContext, async ({ command, ack, client, context, logger }) => {
      -  // コマンドリクエストの確認
      -  await ack();
      -  // リクエスト時のローカル時間を取得
      -  const localHour = (Date.UTC(2020, 3, 31) + context.tz_offset).getHours();
      -
      -  // リクエストに使用するチャンネル ID
      -  const requestChannel = 'C12345';
      -
      -  const requestText = `:large_blue_circle: *New request from <@${command.user_id}>*: ${command.text}`;
      -
      -  // 午前 9 時〜午後 5 時以外のリクエストの場合は明日
      -  if (localHour > 17 || localHour < 9) {
      -    // ローカル時間の明日午前 9 時までの差分を取得する関数があると仮定
      -    const localTomorrow = getLocalTomorrow(context.tz_offset);
      -
      -    try {
      -      // メッセージ送信スケジュールを調整
      -      const result = await client.chat.scheduleMessage({
      -        channel: requestChannel,
      -        text: requestText,
      -        post_at: localTomorrow
      -      });
      -    }
      -    catch (error) {
      -      logger.error(error);
      -    }
      -  } else {
      -    try {
      -      // 送信
      -      const result = await client.chat.postMessage({
      -        channel: requestChannel,
      -        text: requestText
      -      });
      -    } catch (error) {
      -      logger.error(error);
      -    }
      -  }
      -});
      -
      -
      -
      - - -
      -
      - -
      -

      ログの表示

      - -
      -

      Bolt はデフォルトの設定では、標準出力のコンソールにログを出力します。どれくらいのログが出力されるかは、コンストラクターの引数の logLevel を指定して、カスタマイズできます。使用可能なログレベルは、頻度の高い方から順に、DEBUGINFOWARNERROR です。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -
      // パッケージから LogLevel をインポート
      -const { App, LogLevel } = require('@slack/bolt');
      -
      -// オプションとして、コンストラクタで Log level を設定可能
      -const app = new App({
      -  token,
      -  signingSecret,
      -  logLevel: LogLevel.DEBUG,
      -});
      -
      -
      -
      - -
      - -

      コンソール以外へのログ出力の送信

      -
      - -
      -

      ログの送信先をコンソール以外に設定したり、よりロガーを細かくコントロールしたい場合は、カスタムロガーを実装します。カスタムロガーは、以下のメソッド (Logger インターフェイスに定義されているもの) を実装する必要があります。

      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      メソッドパラメーター戻り値の型
      setLevel()level: LogLevelvoid
      getLevel()なし -string (値は error, warn, info, debug のいずれか)
      setName()name: stringvoid
      debug()...msgs: any[]void
      info()...msgs: any[]void
      warn()...msgs: any[]void
      error()...msgs: any[]void
      - -

      非常に単純なカスタム logger では、名前やレベルが無視され、すべてのメッセージがファイルに書き込まれることがあります。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -
      const { App } = require('@slack/bolt');
      -const { createWriteStream } = require('fs');
      -const logWritable = createWriteStream('/var/my_log_file');
      -
      -const app = new App({
      -  token,
      -  signingSecret,
      -  // リテラルオブジェクトとして logger を設定(必要なメソッドを持つクラスを指定するイメージで)
      -  logger: {
      -    debug: (...msgs) => { logWritable.write('debug: ' + JSON.stringify(msgs)); },
      -    info: (...msgs) => { logWritable.write('info: ' + JSON.stringify(msgs)); },
      -    warn: (...msgs) => { logWritable.write('warn: ' + JSON.stringify(msgs)); },
      -    error: (...msgs) => { logWritable.write('error: ' + JSON.stringify(msgs)); },
      -    setLevel: (level) => { },
      -    getLevel: () => { },
      -    setName: (name) => { },
      -  },
      -});
      -
      -
      -
      - -
      - - -
      -
      - -
      -

      レシーバーのカスタマイズ

      - -
      -

      レシーバーは、Slack からのイベントを受け付けてパースした後、それを Bolt アプリに伝える責務を担っています。Bolt アプリは、context 情報やリスナーへのイベントの引き渡しを行います。レシーバーの実装は Receiver インターフェイスに準拠している必要があります。

      - - - - - - - - - - - - - - - - - - - - - - - - - - -
      メソッドパラメーター戻り値の型
      init()app: Appunknown
      start()NonePromise
      stop()NonePromise
      - -

      init() メソッドは Bolt for JavaScript アプリが生成されたときに呼び出されます。このメソッドはレシーバーに App インスタンスへの参照を与えます。レシーバーはこれを保持して、イベント受信時に呼び出します。

      - -
        -
      • -await app.processEvent(event) は Slack から送信されてくるイベントを受け取るたびに呼び出されます。ハンドリングされなかったエラーが発生した場合はそれを throw します。
      • -
      - -

      カスタムのレシーバーを使用する場合は、それを App のコンストラクターに渡します。ここで紹介しているコード例は、基本的なカスタムレシーバーの実装例です。

      - -

      レシーバーについてより深く知りたい場合は、組み込み ExpressReceiver のソースコードを参照してください。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -21
      -22
      -23
      -24
      -25
      -26
      -27
      -28
      -29
      -30
      -31
      -32
      -33
      -34
      -35
      -36
      -37
      -38
      -39
      -40
      -41
      -42
      -43
      -44
      -45
      -46
      -47
      -48
      -49
      -50
      -51
      -52
      -53
      -54
      -55
      -56
      -57
      -58
      -59
      -60
      -61
      -62
      -63
      -64
      -65
      -66
      -67
      -68
      -69
      -70
      -71
      -
      import { EventEmitter } from 'events';
      -import { createServer } from 'http';
      -import express from 'express';
      -
      -// EventEmitter は on() 関数を操作
      -// https://nodejs.org/api/events.html#events_emitter_on_eventname_listener
      -class simpleReceiver extends EventEmitter {
      -  constructor(signingSecret, endpoints) {
      -    super();
      -    this.app = express();
      -    this.server = createServer(this.app);
      -
      -    for (const endpoint of endpoints) {
      -      this.app.post(endpoint, this.requestHandler.bind(this));
      -    }
      -  }
      -  
      -  init(app) {
      -    this.bolt = app;
      -  }
      -
      -  start(port) {
      -    return new Promise((resolve, reject) => {
      -      try {
      -        this.server.listen(port, () => {
      -          resolve(this.server);
      -        });
      -      } catch (error) {
      -        reject(error);
      -      }
      -    });
      -  }
      -
      -  stop() {
      -    return new Promise((resolve, reject) => {
      -      this.server.close((error) => {
      -        if (error) {
      -          reject(error);
      -          return;
      -        }
      -        resolve();
      -      })
      -    })
      -  }
      -
      -  async requestHandler(req, res) {
      -    let ackCalled = false;
      -    // 着信リクエストをパースするparseBody 関数があると仮定
      -    const parsedReq = parseBody(req);
      -    const event = {
      -      body: parsedReq.body,
      -      // レシーバーが確認作業に重要
      -      ack: (response) => {
      -        if (ackCalled) {
      -          return;
      -        }
      -        
      -        if (response instanceof Error) {
      -          res.status(500).send();
      -        } else if (!response) {
      -          res.send('')
      -        } else {
      -          res.send(response);
      -        }
      -        
      -        ackCalled = true;
      -      }
      -    };
      -    await this.bolt.processEvent(event);
      -  }
      -}
      -
      -
      -
      - - -
      -
      - -
      -

      カスタム HTTP ルートの追加

      - -
      -

      v3.7.0 から App を初期化する際に customRoutes というルートの配列を渡すことでカスタムの HTTP ルートを簡単に追加できるようになりました。

      - -

      CustomRoute オブジェクトには pathmethodhandler という三つのプロパティが含まれていなければなりません。 HTTP メソッドに相当する method は文字列または文字列の配列です。

      - -

      v3.13.0 からデフォルトの組み込みレシーバーである HTTPReceiverSocketModeReceiver が、Express.js が提供するものと同様な動的なルートパラメーターをサポートするようになりました。これによって URL 内に含まれる値を req.params の値として利用できるようになりました。

      - -

      カスタムの HTTP ルートがローカル環境でどのポートからアクセスできるかを指定するために App コンストラクターに installerOptions.port というプロパティを渡すことができます。指定しない場合は、デフォルトの 3000 ポートとなります。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -21
      -22
      -23
      -24
      -25
      -26
      -27
      -28
      -29
      -30
      -31
      -32
      -33
      -
      const { App } = require('@slack/bolt');
      -
      -// デフォルトの HTTPReceiver を使って Bolt アプリを初期化します
      -const app = new App({
      -  token: process.env.SLACK_BOT_TOKEN,
      -  signingSecret: process.env.SLACK_SIGNING_SECRET,
      -  customRoutes: [
      -    {
      -      path: '/health-check',
      -      method: ['GET'],
      -      handler: (req, res) => {
      -        res.writeHead(200);
      -        res.end(`Things are going just fine at ${req.headers.host}!`);
      -      },
      -    },
      -    {
      -      path: '/music/:genre',
      -      method: ['GET'],
      -      handler: (req, res) => {
      -        res.writeHead(200);
      -        res.end(`Oh? ${req.params.genre}? That slaps!`);
      -      },
      -    },
      -  ],
      -  installerOptions: {
      -    port: 3001,
      -  },
      -});
      -
      -(async () => {
      -  await app.start();
      -  console.log('⚡️ Bolt app started');
      -})();
      -
      -
      -
      - -
      - -

      カスタム ExpressReceiver ルート

      -
      - -
      -

      Bolt の組み込みの ExpressReceiver を使っているなら、カスタムの HTTP ルートを追加するのはとても簡単です。v2.1.0 から ExpressReceiver には router というプロパティが追加されています。これは、さらにルートを追加できるように App 内部で保持している Express の Router を public にしたものです。

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -21
      -22
      -23
      -24
      -25
      -26
      -27
      -28
      -29
      -30
      -31
      -32
      -
      const { App, ExpressReceiver } = require('@slack/bolt');
      -
      -// Bolt の Receiver を明に生成
      -const receiver = new ExpressReceiver({ signingSecret: process.env.SLACK_SIGNING_SECRET });
      -
      -// App をこのレシーバーを指定して生成
      -const app = new App({
      -  token: process.env.SLACK_BOT_TOKEN,
      -  receiver
      -});
      -
      -// Slack とのやりとりは App のメソッドで定義
      -app.event('message', async ({ event, client }) => {
      -  // Do some slack-specific stuff here
      -  await client.chat.postMessage(...);
      -});
      -
      -receiver.router.use((req, res, next) => {
      -  console.log(`Request time: ${Date.now()}`);
      -  next();
      -});
      -
      -// それ以外の Web リクエストの処理は receiver.router のメソッドで定義
      -receiver.router.post('/secret-page', (req, res) => {
      -  // ここでは Express のリクエストやレスポンスをそのまま扱う
      -  res.send('yay!');
      -});
      -
      -(async () => {
      -  await app.start();
      -  console.log('⚡️ Bolt app started');
      -})();
      -
      -
      -
      -
      - - -
      -
      - -
      - -
      - -
      -

      - ワークフローステップの概要 - - 非推奨 - -

      -
      -

      (アプリによる)ワークフローステップ(Workflow Steps from Apps) は、ワークフロービルダーにおけるワークフローに組み込み可能なカスタムのワークフローステップを任意の Slack アプリが提供することを可能とします。

      - -

      ワークフローステップは、三つの異なるユーザーイベントから構成されます:

      - -
        -
      • ワークフロー作成者がワークフローにカスタムステップを追加・または編集する
      • -
      • ワークフロー作成者がステップの設定を保存・更新する
      • -
      • ワークフローの利用者がそのワークフローステップを実行する
      • -
      - -

      ワークフローステップを機能させるためには、これら三つのイベント全てを適切にハンドリングする必要があります。

      - -

      ワークフローステップのさらなる詳細については API ドキュメントを参考にしてください。

      - -
      - -
      -
      - -
      -

      - ステップの定義 - - 非推奨 - -

      -
      - -

      ワークフローステップを作るための手段として Bolt は WorkflowStep というクラスを提供しています。

      - -

      新しい WorkflowStep インスタンスの生成には、そのステップの callback_id と設定オブジェクトを渡します。

      - -

      設定オブジェクトには editsaveexecute という三つのプロパティがあります。これらのそれぞれは単一のコールバック関数、またはコールバック関数の配列である必要があります。すべてのコールバック関数は、ワークフローステップのイベントに関する情報を保持しする step オブジェクトにアクセスすることができます。

      - -

      WorkflowStep インスタンスを生成したら、それを app.step() メソッドに渡します。これによって、Bolt アプリは対象のワークフローステップのイベントをリッスンしたり、設定オブジェクトが提供するコールバック関数を使ってイベントに応答したりすることができるようになります。 -

      -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -
      const { App, WorkflowStep } = require('@slack/bolt');
      -
      -// いつも通り Bolt アプリを初期化
      -const app = new App({
      -  signingSecret: process.env.SLACK_SIGNING_SECRET,
      -  token: process.env.SLACK_BOT_TOKEN,
      -});
      -
      -// WorkflowStep インスタンスを生成
      -const ws = new WorkflowStep('add_task', {
      -  edit: async ({ ack, step, configure }) => {},
      -  save: async ({ ack, step, update }) => {},
      -  execute: async ({ step, complete, fail }) => {},
      -});
      -
      -app.step(ws);
      -
      -
      -
      - -
      -
      - -
      -

      - ステップの追加・編集 - - 非推奨 - -

      -
      - -

      ワークフローの作成者が、アプリが提供するステップをワークフローに追加(またはその設定を変更)するタイミングで、アプリは workflow_step_edit というイベントを受信します。このイベントの受信時に WorkflowStep 設定オブジェクト内の edit コールバック関数が実行されます。

      - -

      このとき、ワークフロー作成・変更のどちらの場合でも、アプリはワークフローステップ設定のためのモーダルを応答する必要があります。このモーダルは、ワークフローステップに固有の設定である必要があり、通常のモーダルにはない制約があります。最もわかりやすいものとしては、title​submit​close プロパティを設定することができません。また、デフォルトの設定では、この設定モーダルの callback_id はワークフローステップのものと同じものが使用されます。

      - -

      edit コールバック関数の中では モーダルの view のうち blocks だけを渡すだけで簡単にステップ設定モーダルをオープンすることができる configure() というユーティリティ関数が利用できます。これは、必要な入力内容が揃うまで設定の保存を無効にする submit_disabled というオプションを true に設定します。

      - -

      設定モーダルを開く処理に関するさらなる詳細は、ドキュメントを参考にしてください。

      - -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -21
      -22
      -23
      -24
      -25
      -26
      -27
      -28
      -29
      -30
      -31
      -32
      -33
      -34
      -35
      -36
      -37
      -38
      -39
      -40
      -41
      -42
      -43
      -44
      -
      const ws = new WorkflowStep('add_task', {
      -  edit: async ({ ack, step, configure }) => {
      -    await ack();
      -
      -    const blocks = [
      -      {
      -        type: 'input',
      -        block_id: 'task_name_input',
      -        element: {
      -          type: 'plain_text_input',
      -          action_id: 'name',
      -          placeholder: {
      -            type: 'plain_text',
      -            text: 'Add a task name',
      -          },
      -        },
      -        label: {
      -          type: 'plain_text',
      -          text: 'Task name',
      -        },
      -      },
      -      {
      -        type: 'input',
      -        block_id: 'task_description_input',
      -        element: {
      -          type: 'plain_text_input',
      -          action_id: 'description',
      -          placeholder: {
      -            type: 'plain_text',
      -            text: 'Add a task description',
      -          },
      -        },
      -        label: {
      -          type: 'plain_text',
      -          text: 'Task description',
      -        },
      -      },
      -    ];
      -
      -    await configure({ blocks });
      -  },
      -  save: async ({ ack, step, update }) => {},
      -  execute: async ({ step, complete, fail }) => {},
      -});
      -
      -
      -
      - -
      -
      - -
      -

      - ステップの設定の保存 - - 非推奨 - -

      -
      - -

      ワークフローステップの設定モーダルが開いたら、アプリはワークフロー作成者がモーダルを送信するイベントである view_submission イベントを待ち受けます。このイベントを受信すると WorkflowStep 設定オブジェクト内の save コールバック関数が実行されます。

      - -

      save コールバック関数の中では、以下の引数を渡してステップの設定を保存するための update() 関数を利用できます。

      - -
        -
      • -inputs は、ワークフローステップ実行時にアプリが受け取ることを期待するデータの内容を表現するオブジェクトです
      • -
      • -outputs は、ステップの実行が正常に完了したとき、同一ワークフロー内の後続のステップに提供するデータの内容を表現するオブジェクトの配列です。
      • -
      • -step_name は、デフォルトのステップ名を上書きするために使用します
      • -
      • -step_image_url は、デフォルトのステップのイメージ画像を上書きするために使用します
      • -
      - -

      これら引数をどのように構成するかの詳細は、ドキュメントを参考にしてください。

      - -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -21
      -22
      -23
      -24
      -25
      -26
      -27
      -28
      -29
      -30
      -31
      -
      const ws = new WorkflowStep('add_task', {
      -  edit: async ({ ack, step, configure }) => {},
      -  save: async ({ ack, step, view, update }) => {
      -    await ack();
      -
      -    const { values } = view.state;
      -    const taskName = values.task_name_input.name;
      -    const taskDescription = values.task_description_input.description;
      -                
      -    const inputs = {
      -      taskName: { value: taskName.value },
      -      taskDescription: { value: taskDescription.value }
      -    };
      -
      -    const outputs = [
      -      {
      -        type: 'text',
      -        name: 'taskName',
      -        label: 'Task name',
      -      },
      -      {
      -        type: 'text',
      -        name: 'taskDescription',
      -        label: 'Task description',
      -      }
      -    ];
      -
      -    await update({ inputs, outputs });
      -  },
      -  execute: async ({ step, complete, fail }) => {},
      -});
      -
      -
      -
      - -
      -
      - -
      -

      - ステップの実行 - - 非推奨 - -

      -
      - -

      ワークフローの利用者によって、アプリが提供するカスタムのワークフローステップが実行されるとき、アプリはworkflow_step_execute というイベントを受信します。このイベントの受信時に WorkflowStep 設定オブジェクト内の execute コールバック関数が実行されます。

      - -

      save コールバック関数で予め規定された inputs の情報を使って、ここでの処理は、サードパーティの API を呼び出したり、データベースに情報を保存したり、そのユーザーのホームタブを更新したり、outputs オブジェクトを構築することで後続のワークフローステップが利用できる情報を設定したりします。

      - -

      execute コールバック関数内では、ステップの実行が成功であることを Slack 側に伝える complete() 関数、失敗であることを伝える fail() 関数のいずれかを呼び出す必要があります。

      - -
      - -
      -
      -
      
      -
      -
      -
      1
      -2
      -3
      -4
      -5
      -6
      -7
      -8
      -9
      -10
      -11
      -12
      -13
      -14
      -15
      -16
      -17
      -18
      -19
      -20
      -21
      -22
      -23
      -
      const ws = new WorkflowStep('add_task', {
      -  edit: async ({ ack, step, configure }) => {},
      -  save: async ({ ack, step, update }) => {},
      -  execute: async ({ step, complete, fail }) => {
      -    const { inputs } = step;
      -
      -    const outputs = {
      -      taskName: inputs.taskName.value,
      -      taskDescription: inputs.taskDescription.value,
      -    };
      -
      -    // もし全て OK なら
      -    await complete({ outputs });
      -    // 注意: processBeforeResponse: true を指定している場合
      -    // ここでは await complete() はおすすめしません。呼び出す API の応答が遅いためです。
      -    // これにより、3 秒以内に Slack のイベント API に応答することができなくなる場合があります。
      -    // 代わりに以下のようにすることができます:
      -    // complete({ outputs }).then(() => { console.log('workflow step execution complete registered'); });
      -
      -    // もし何か問題が起きたら
      -    // fail({ error: { message: "Just testing step failure!" } }).then(() => { console.log('workflow step execution failure registered'); });
      -  },
      -});
      -
      -
      -
      - -
      -
      - -
      - -
      -
      - - - - - - diff --git a/docs/_site/ja-jp/deployments/aws-lambda.html b/docs/_site/ja-jp/deployments/aws-lambda.html deleted file mode 100644 index e88d3f657..000000000 --- a/docs/_site/ja-jp/deployments/aws-lambda.html +++ /dev/null @@ -1,841 +0,0 @@ - - - - - - - Slack | Bolt for JavaScript - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      -
      - - -
      - - -
      - - -
      -
        -
        - -
        -

        AWS Lambda へのデプロイ

        - -
        -

        このガイドでは、Bolt for JavaScriptServerless FrameworkAWS Lambda を使った Slack アプリの準備とデプロイの方法について説明します。

        -
        - -

        この手順を全て終わらせたら、あなたはきっと⚡️ Deploying to AWS Lambda のサンプルアプリを動作させたり、それに変更を加えたり、自分のアプリを作ったりすることができるようになるでしょう。

        - -
        - -

        AWS Lambda のセットアップ

        - -

        AWS Lambda はサーバーレスの Function-as-a-Service(FaaS)プラットフォームです。AWS Lambda を利用すると、サーバーを管理することなく、コードを実行することができます。このセクションでは、ローカルマシンから AWS Lambda にアクセスするための設定を行います。

        - -
        -

        💡 すでにローカルマシンから AWS Lambda へのアクセスに必要なプロファイルの構成が済んでいる場合、このセクションはスキップできます。

        -
        - -

        1. AWS アカウントを作成する

        - -

        AWS アカウントをまだ持っていない場合は、アカウントを作成する必要があります。画面に表示される案内に沿って作成しましょう。

        - -
        -

        💡 作成手順の中で請求情報の入力を求められる場合がありますが、ご心配なく。このガイドでは無料利用枠のみを使用します。

        -
        - -

        2. AWS のアクセスキーを作成する

        - -

        Lambda へのデプロイでは、プログラムから AWS アカウントにアクセスする手段が必要になります。AWS の世界では、このためにアクセスキー IDシークレットアクセスキーが必要です。

        - -

        🍿 IAM ユーザーを作成してアクセスキーをダウンロードする手順を紹介する短い動画を参考にしてみてください。

        - -
        -

        💡 すでに IAM ユーザーの作成が完了している場合は、AWS の公式ガイドに従って IAM ユーザーのアクセスキーを作成してください。

        -
        - -

        3. AWS CLI をインストールする

        - -

        AWS では macOS、Windows、Linux にインストールして利用できるコマンドラインインターフェイス(CLI)のツールが用意されています。

        - -

        macOS では、最新の .pkg インストーラーをダウンロードして AWS CLI をインストールできます。

        - -

        4. AWS プロファイルを構成する

        - -

        AWS CLI を使ってプロファイルを構成します。プロファイルはローカルマシンに置かれ、アクセスキーのペアを保管します。この CLI やその他のツールは、このプロファイルを使って AWS にアクセスします。

        - -

        プロファイルを構成する最も簡単な方法は、次のコマンドを実行し、プロンプトに従って入力する方法です。

        - -
        -
        -
        
        -
        -
        -
        1
        -2
        -3
        -4
        -5
        -
        aws configure
        -# AWS Access Key ID [None]: <AWS のアクセスキー>
        -# AWS Secret Access Key [None]: <AWS のシークレットアクセスキー>
        -# Default region name [None]: us-east-1
        -# Default output format [None]: json
        -
        -
        -
        - -
        -

        💡 Default region nameDefault output format は最も望ましい設定でカスタマイズしてください。

        -
        - -

        これでローカルマシンから AWS にアクセスするための設定が完了しました。👏 次は、同じように Serverless Framework も設定していきましょう。

        - -
        - -

        Serverless Framework をセットアップする

        - -

        Serverless Framework では、AWS Lambda 向けのアプリの設定、デバッグ、デプロイを簡単に行うためのツールが用意されています。

        - -

        1. Serverless Framework CLI をインストールする

        - -

        Serverless でも macOS、Windows、Linux にインストールして利用できるコマンドラインインターフェイス(CLI)のツールが用意されています。インストールするには Serverless の入門ガイド(英語) をお読みください。

        - -

        インストールが完了したら Serverless CLI をテストするため、利用可能なコマンドを表示してみましょう。

        - -
        -
        -
        
        -
        -
        -
        1
        -
        serverless help
        -
        -
        -
        - -

        Serverless のツールのセットアップが完了しました。次に、AWS Lambda 関数として実行する Bolt アプリの準備へと進みましょう。

        - -
        - -

        Bolt Slack アプリを入手する

        - -

        まだ Bolt アプリを自分で作成したことがない場合は、入門ガイドを参照してください。テンプレートのアプリをクローンするには、以下のコマンドを実行します。

        - -
        -
        -
        
        -
        -
        -
        1
        -
        git clone https://github.com/slackapi/bolt-js-getting-started-app.git
        -
        -
        -
        - -

        用意した Bolt アプリのディレクトリに移動します。

        - -
        -
        -
        
        -
        -
        -
        1
        -
        cd bolt-js-getting-started-app/
        -
        -
        -
        - -

        Bolt アプリを用意できました。次に AWS Lambda と Serverless Framework に対応するための準備をします。

        - -
        - -

        アプリをセットアップする

        - -

        1. アプリを AWS Lambda に対応させる

        - -

        デフォルトでは、入門ガイドの Bolt サンプルアプリはソケットモードを使用しています。WebSocket イベントの代わりに HTTP リクエストをリッスンするため、 app.js の設定を変更しましょう。

        - -
        -
        -
        
        -
        -
        -
        1
        -2
        -3
        -4
        -5
        -6
        -
        // ボットトークンを使ってアプリを初期化します
        -const app = new App({
        -  token: process.env.SLACK_BOT_TOKEN,
        -  socketMode: true, // この行を削除します
        -  appToken: process.env.SLACK_APP_TOKEN, // この行を削除します
        -});
        -
        -
        -
        - -

        次に Lambda 関数のイベントに応答するよう、Bolt アプリの receiver をカスタマイズします。

        - -

        app.js のソースコードの中でモジュールのインポートを行う部分を編集し、Bolt の AwsLambdaReceiver モジュールを require します。

        - -
        -
        -
        
        -
        -
        -
        1
        -
        const { App, AwsLambdaReceiver } = require('@slack/bolt');
        -
        -
        -
        - -
        -

        💡 OAuth フローを実装するなら、現時点では ExpressReceiver を使用する必要があります。

        -
        - -

        その後、ソースコードの中で Bolt アプリの初期化を行う部分を編集して、AwsLambdaReceiver を使ったカスタムのレシーバーを作成します。

        - -
        -
        -
        
        -
        -
        -
        1
        -2
        -3
        -4
        -5
        -6
        -7
        -8
        -9
        -10
        -11
        -12
        -13
        -14
        -15
        -16
        -17
        -18
        -
        // カスタムのレシーバーを初期化します
        -const awsLambdaReceiver = new AwsLambdaReceiver({
        -  signingSecret: process.env.SLACK_SIGNING_SECRET,
        -});
        -
        -// ボットトークンと、AWS Lambda に対応させたレシーバーを使ってアプリを初期化します。
        -const app = new App({
        -    token: process.env.SLACK_BOT_TOKEN,
        -    receiver: awsLambdaReceiver,
        -    
        -    // AwsLambdaReceiver を利用する場合は  `processBeforeResponse` は省略可能です。
        -    // OAuth フローに対応した ExpressReceiver など、他のレシーバーを使用する場合、
        -    // `processBeforeResponse: true` が必要になります。
        -    // このオプションは、ハンドラーの実行が完了するまで応答を返すのを遅延させます。
        -    // これによってハンドラーがトリガーとなった HTTP リクエストに応答を返すことでただちに終了されることを防ぐことができます。
        -    
        -    //processBeforeResponse: true
        -});
        -
        -
        -
        - -

        最後に、アプリのソースコードの末尾にある HTTP サーバーを起動する部分を編集して、AWS Lambda 関数のイベントに応答するようにします。

        - -
        -
        -
        
        -
        -
        -
        1
        -2
        -3
        -4
        -5
        -
        // Lambda 関数のイベントを処理します
        -module.exports.handler = async (event, context, callback) => {
        -  const handler = await awsLambdaReceiver.start();
        -  return handler(event, context, callback);
        -}
        -
        -
        -
        - -

        完成したアプリのソースコードは、⚡️deploy-aws-lambda のサンプルのようになります。

        - -

        2. serverless.yml を追加する

        - -

        Serverless Framework のプロジェクトでは、アプリの設定とデプロイに serverless.yml ファイルを使用します。

        - -

        アプリのルートディレクトリに serverless.yml という名前のファイルを新規作成し、次の内容を貼りつけます。

        - -
        -
        -
        
        -
        -
        -
        1
        -2
        -3
        -4
        -5
        -6
        -7
        -8
        -9
        -10
        -11
        -12
        -13
        -14
        -15
        -16
        -17
        -
        service: serverless-bolt-js
        -frameworkVersion: '3'
        -provider:
        -  name: aws
        -  runtime: nodejs14.x
        -  environment:
        -    SLACK_SIGNING_SECRET: ${env:SLACK_SIGNING_SECRET}
        -    SLACK_BOT_TOKEN: ${env:SLACK_BOT_TOKEN}
        -functions:
        -  slack:
        -    handler: app.handler
        -    events:
        -      - http:
        -          path: slack/events
        -          method: post
        -plugins:
        -  - serverless-offline
        -
        -
        -
        - -
        -

        💡 SLACK_SIGNING_SECRETSLACK_BOT_TOKEN の環境変数は、ローカルマシンで設定しておく必要があります。Slack の環境変数をエクスポートする方法を入門ガイドで参照してください。

        -
        - -

        3. serverless-offline モジュールをインストールする

        - -

        ローカルでの開発を容易にするため、serverless-offline モジュールを使ってデプロイ対象の関数をエミュレートできるようにしましょう。

        - -

        次のコマンドを実行して、開発用の devDependencies としてインストールします。

        - -
        -
        -
        
        -
        -
        -
        1
        -
        npm install --save-dev serverless-offline
        -
        -
        -
        - -

        これで Bolt アプリを AWS Lambda と Serverless に対応させることができました。次はアプリの実行とデプロイに進みます。

        - -
        - -

        アプリをローカルで実行する

        - -

        アプリを AWS Lambda 関数に応答させるための準備が完了したので、次にローカルでアプリを実行できるように環境を設定します。

        - -

        1. ローカルのサーバーを起動する

        - -

        まず、アプリの起動と AWS Lambda 関数のイベントをリッスンするため、serverless offline コマンドを実行します。

        - -
        -
        -
        
        -
        -
        -
        1
        -
        serverless offline --noPrependStageInUrl
        -
        -
        -
        - -
        -

        🏌️ Pro-tip: 別のターミナルで上記のコマンドを実行しておくことで、ターミナル上でアプリのコードを変更することができます。コードの変更を保存する度、アプリは自動的にリロードされます。

        -
        - -

        次に、ngrok を使って Slack のイベントをローカルマシンに転送します。

        - -
        -
        -
        
        -
        -
        -
        1
        -
        ngrok http 3000
        -
        -
        -
        - -
        -

        💡 パブリック URL の作成方法と、ローカルマシンへのリクエストの転送方法については、ngrok の使い方を参照してください。

        -
        - -

        2. リクエスト URL を変更する

        - -

        次に、Slack アプリの設定を開き、リクエスト URL を ngrok のウェブアドレスに変更します。

        - -
        -

        💡 リクエスト URL/slack/events で終わる文字列で、例えば https://abc123.ngrok.io/slack/events のようになります。

        -
        - -

        まず、サイドバーの「Interactivity & Shortcuts」を選択し、リクエスト URL を更新します。

        - -

        「Interactivity & Shortcuts」ページ

        - -

        次に、サイドバーの「Event Subscriptions」を選択し、リクエスト URL を更新します。

        - -

        「Event Subscriptions」ページ

        - -

        3. Slack アプリをテストする

        - -

        Slack アプリをテストします。今作った Bolt アプリを Slack のチャンネルに招待し、半角の小文字で「hello」と入力してみましょう。入門ガイドのとおり、アプリから応答があるはずです。

        - -
        -

        👩‍💻 hello
        -🤖 Hey there @Jane!

        -
        - -

        応答がない場合、リクエスト URL を確認してからもう一度試してみてください。

        - -
        -

        💡 動作の仕組み : ngrok と Serverless のコマンドは同じポートを使用するように設定されています(デフォルトでは「3000」)。リクエスト URL に向けて送信された Slack イベントは、ローカルマシンの ngrok で受信されます。このリクエストはさらに Serverless Offline に転送されます。Serverless Offline は AWS Lambda 関数のイベントをエミュレートしていて、Bolt アプリのレシーバーをトリガーさせます。 🛫🛬 長旅ですね。

        -
        - -
        - -

        アプリをデプロイする

        - -

        今までローカルでアプリを実行し、 Slack ワークスペースでテストをしてきました。さて、動作するアプリができたので、デプロイしてみましょう!

        - -

        AWS Lambda 向けのアプリのプロビジョニング、パッケージング、デプロイには、Serverless Framework のツールが利用できます。アプリのデプロイが完了したら、アプリのリクエスト URL を更新して、「hello」と入力した時にアプリが応答できるようにします。✨

        - -

        1. AWS Lambda にアプリをデプロイする

        - -

        次のコマンドを使って AWS Lambda にアプリをデプロイします。

        - -
        -
        -
        
        -
        -
        -
        1
        -2
        -3
        -4
        -5
        -6
        -
        serverless deploy
        -# Serverless:Packaging service...
        -# ...
        -# endpoints:
        -#   POST - https://atuzelnkvd.execute-api.us-east-1.amazonaws.com/dev/slack/events
        -# ...
        -
        -
        -
        - -

        アプリのデプロイが成功すると、エンドポイントが発行されます。これをアプリのリクエスト URL に指定します。発行されたエンドポイントをコピーして、次のセクションで使います。

        - -
        -

        💡 エンドポイントは、/slack/events で終わる文字列です。

        -
        - -

        2. Slack アプリの設定を更新する

        - -

        Slack からのイベントやアクションの送信先となるリクエスト URL に、発行された AWS Lambda のエンドポイントを指定します。Slack アプリの構成を開き、先ほどコピーしたエンドポイントをリクエスト URL に貼りつけます。

        - -

        まず、サイドバーの「Interactivity & Shortcuts」を選択し、リクエスト URL を更新します。

        - -

        「Interactivity & Shortcuts」ページ

        - -

        次に、サイドバーの「Event Subscriptions」を選択し、リクエスト URL を更新します。

        - -

        「Event Subscriptions」ページ

        - -

        3. Slack アプリをテストする

        - -

        アプリのデプロイと、Slack の設定の更新が完了しました。動作を試してみましょう。

        - -

        アプリをローカルで実行する」のセクションで行った操作と同様に、アプリを招待した Slack チャンネルを開いて「hello」と入力します。アプリが応答し、同じように挨拶してくれるはずです。

        - -
        -

        👩‍💻 hello
        -🤖 Hey there @Jane!

        -
        - -

        4. 更新をデプロイする

        - -

        Slack アプリの開発を継続していくなら、更新したアプリをデプロイする必要が出てくるでしょう。それをやってみるために、「goodbye」というメッセージに応答するようにアプリを変更してみましょう。

        - -

        次のコードを app.js に追加します(GitHub でソースコードを確認できます)。

        - -
        -
        -
        
        -
        -
        -
        1
        -2
        -3
        -4
        -5
        -
        // 「goodbye」というメッセージの着信をリッスンします
        -app.message('goodbye', async ({ message, say }) => {
        -  // イベントがトリガーされたチャンネルに向けて say() でメッセージを送信します
        -  await say(`See ya later, <@${message.user}> :wave:`);
        -});
        -
        -
        -
        - -

        先ほどと同じコマンドを使って更新をデプロイします。

        - -
        -
        -
        
        -
        -
        -
        1
        -
        serverless deploy
        -
        -
        -
        - -

        デプロイが完了したら、アプリを参加させた Slack チャンネルを開いて、半角の小文字で「goodbye」と入力してみましょう。Slack アプリに「See you later」と表示されるはずです。

        - -
        -

        ⛳️ 一つの関数に小さな変更を加える場合、その関数だけをデプロイするためにより高速な serverless deploy function -f my-function を実行することができます。より詳細なヘルプを見るには serverless help deploy function を実行してください。

        -
        - -
        - -

        次のステップ

        - -

        ⚡️AWS Lambda を使った最初の Bolt for JavaScript アプリをデプロイできました。🚀

        - -

        基本的なアプリのデプロイができましたので、次はアプリのカスタマイズやモニタリングを行う方法を調べてみましょう。

        - - - - -
        -
        - -
        - - - - - - - - \ No newline at end of file diff --git a/docs/_site/ja-jp/deployments/heroku.html b/docs/_site/ja-jp/deployments/heroku.html deleted file mode 100644 index 4766b83f4..000000000 --- a/docs/_site/ja-jp/deployments/heroku.html +++ /dev/null @@ -1,759 +0,0 @@ - - - - - - - Slack | Bolt for JavaScript - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        -
        - - -
        - - -
        - - -
        -
          -
          - -
          -

          Heroku へのデプロイ

          - -
          -

          このガイドでは、Bolt for JavaScriptHeroku プラットフォームを使ってSlack アプリを用意して、デプロイするまでの手順を説明します。全体の流れとしては、Bolt Slack アプリをダウンロードし、Heroku 用の準備を済ませ、デプロイする流れになります。

          -
          - -

          この手順を全て終わらせたら、あなたはきっと️⚡️getting-started-with-herokuのサンプルアプリを動作させたり、それに変更を加えたり、自分のアプリを作ったりすることができるようになるでしょう。

          - -
          - -

          Bolt Slack アプリを入手する

          - -

          Bolt アプリを作るのが初めてという場合は、まずBolt 入門ガイドに沿って進めてみましょう。または、以下のテンプレートアプリをクローンしてもよいでしょう。

          - -
          -
          -
          
          -
          -
          -
          1
          -
          git clone https://github.com/slackapi/bolt-js-getting-started-app.git
          -
          -
          -
          - -

          ダウンロードしたBolt アプリのディレクトリに移動します。

          - -
          -
          -
          
          -
          -
          -
          1
          -
          cd bolt-js-getting-started-app/
          -
          -
          -
          - -

          次に、このアプリをHeroku で動かすための準備をします。

          - -
          - -

          アプリをHeroku で動かすための準備する

          - -

          Heroku は、作ったアプリをホストできる柔軟性の高いプラットフォームで、少し設定が必要です。このセクションでは、Bolt アプリに変更を加え、Heroku に対応させます。

          - -

          1. Git リポジトリを使用する

          - -

          Heroku にアプリをデプロイするには、まずGit リポジトリが必要です。まだGit を使ったことがない場合は、Git をインストールし、Git リポジトリを作成します

          - -
          -

          💡 前のセクションでgit cloneを使用した場合、Git リポジトリはすでに存在しますので、この手順はスキップできます

          -
          - -

          2. Procfile を追加する

          - -

          Heroku アプリでは、必ずProcfileという専用のファイルが必要です。このファイルを使ってHeroku にアプリの起動方法を伝えます。Bolt Slack アプリは、公開されたWeb アドレスを持つWeb サーバーとして起動します。

          - -

          アプリのルートディレクトリに、拡張子なしのProcfileという名前のファイルを作成し、次の内容を貼りつけます。内容はどのようにアプリを動かすかによって変わります。

          - -

          デフォルトでは Bolt アプリは公開された Web アドレスを持つ Web サーバーとして起動するので、以下のように指定します:

          - -
          -
          -
          
          -
          -
          -
          1
          -
          web: node app.js
          -
          -
          -
          - -

          ソケットモードを使ったアプリをデプロイするときは、ポートをリッスンしない worker として起動します:

          - -
          -
          -
          
          -
          -
          -
          1
          -
          worker: node app.js
          -
          -
          -
          - -

          ファイルを保存したら、ローカルのGit リポジトリにコミットします。

          - -
          -
          -
          
          -
          -
          -
          1
          -2
          -
          git add Procfile
          -git commit -m "Add Procfile"
          -
          -
          -
          - -
          -

          💡 既存のBolt アプリを使ってこのガイドに沿って進めている場合は、Preparing a Codebase for Heroku Deploymentのガイドを参考に、適切なポートをリッスンするようにしてください。

          -
          - -
          - -

          Heroku ツールをセットアップする

          - -

          ローカルマシンでHeroku ツールのセットアップを行います。このツールは、Heroku プラットフォームを使用するアプリの管理、デプロイ、デバッグを行う場合に便利です。

          - -

          1. Heroku CLI をインストールする

          - -

          Heroku ツールは、コマンドラインインターフェイス(CLI)の形で提供されています。さっそくmacOS、Windows、Linux 用のHeroku CLIをインストールしましょう。macOS では次のコマンドを実行します。

          - -
          -
          -
          
          -
          -
          -
          1
          -
          brew install heroku/brew/heroku
          -
          -
          -
          - -

          インストールが完了したら、Heroku CLI を試してみましょう。どのようなコマンドが使えるかを一覧表示してみます。

          - -
          -
          -
          
          -
          -
          -
          1
          -
          heroku help
          -
          -
          -
          - -
          -

          💡 herokuコマンドが見つからない場合は、パスを更新するため新しいターミナルセッションまたはターミナルタブを開いてください。

          -
          - -

          2. Heroku CLI にログインする

          - -

          Heroku CLI では、ローカルマシンからHeroku アカウントに接続します。無料のHeroku アカウントを新規登録して、次のコマンドでHeroku CLI にログインします。

          - -
          -
          -
          
          -
          -
          -
          1
          -
          heroku login
          -
          -
          -
          -
          -

          💡 ファイアウォールを使っている場合、Heroku CLI で使用されるプロキシ環境変数の設定が必要なことがあります。

          -
          - -

          3. Heroku CLI へのログインが成功したか確認する

          - -

          ログインできたかどうか確認しましょう。次のコマンドを実行すると、Heroku CLI に現在接続されているアカウント名が表示されます。

          - -
          -
          -
          
          -
          -
          -
          1
          -
          heroku auth:whoami
          -
          -
          -
          - -

          これでHeroku ツールのセットアップが完了しました。それではHeroku アプリの作成の本編に進みましょう。

          - -
          - -

          Heroku アプリを作成する

          - -

          先ほどインストールしたツールを使って、Heroku アプリを作成します。アプリを作成するときは、ユニークな名前を自分で指定するか、ランダムな名前を生成することができます。

          - -
          -

          💡 Heroku アプリはあとから名前を変更することもできますが、リモートのGit アドレスとパブリックのWeb アドレスも変更になります。

          -
          - -

          1. Heroku アプリを作成する

          - -

          ユニークな名前を指定してHeroku アプリを作成します。

          - -
          -
          -
          
          -
          -
          -
          1
          -
          heroku create my-unique-bolt-app-name
          -
          -
          -
          - -

          または、ランダムな名前を楽しむならこちらで。

          - -
          -
          -
          
          -
          -
          -
          1
          -2
          -3
          -
          heroku create
          -# Creating sharp-rain-871... done, stack is heroku-18
          -# https://sharp-rain-871.herokuapp.com/ | https://git.heroku.com/sharp-rain-871.git
          -
          -
          -
          - -

          Heroku アプリが作成されると、いくつかの情報が表示されます。これらの情報は次のセクションで使用します。この例では、次のようになります。

          - -
            -
          • アプリ名: sharp-rain-871 -
          • -
          • Web アドレス: https://sharp-rain-871.herokuapp.com/ -
          • -
          • 空のリモートGit リポジトリ: https://git.heroku.com/sharp-rain-871.git -
          • -
          - -

          2. Heroku のリモートGit リポジトリを確認する

          - -

          Heroku CLI は、自動的にherokuという名前のリモートGit リポジトリをローカルに追加します。リモートGit リポジトリを一覧して、herokuが存在することを確認しましょう。

          - -
          -
          -
          
          -
          -
          -
          1
          -2
          -3
          -
          git remote -v
          -# heroku	https://git.heroku.com/sharp-rain-871.git (fetch)
          -# heroku	https://git.heroku.com/sharp-rain-871.git (push)
          -
          -
          -
          - -

          3. アプリをデプロイする

          - -

          Slack アプリの認証情報をHeroku アプリに設定します。

          - -
          -
          -
          
          -
          -
          -
          1
          -2
          -
          heroku config:set SLACK_SIGNING_SECRET=<your-signing-secret>
          -heroku config:set SLACK_BOT_TOKEN=xoxb-<your-bot-token>
          -
          -
          -
          - -
          -

          💡 認証情報の入手場所がわからない場合、Bolt 入門ガイドで署名シークレットとトークンのエクスポートについて参照してください。

          -
          - -

          ローカルでのアプリの準備と、Heroku アプリの作成が完了しました。次のステップは、デプロイです。

          - -
          - -

          アプリをデプロイする

          - -

          アプリをデプロイするため、ローカルのコードをHeroku にプッシュします。その後Slack アプリの設定を更新し、Heroku アプリに”hello” と声をかけてみましょう。 ✨

          - -

          1. Heroku にアプリをデプロイする

          - -

          Heroku へのアプリのデプロイには、通常git pushコマンドを使用します。これにより、ローカルリポジトリのコードがリモートのherokuリポジトリにプッシュされます。

          - -

          次のコマンドでアプリをデプロイしましょう。

          - -
          -
          -
          
          -
          -
          -
          1
          -
          git push heroku main
          -
          -
          -
          -

          Heroku でデプロイされるのは、master またはmain ブランチのコードです。それ以外のブランチにプッシュした場合、デプロイ処理はトリガーされません

          - -
          -

          💡 Heroku deploys code that’s pushed to the master or main branches. Pushing to other branches will not trigger a deployment.

          -
          - -

          最後に、Heroku CLI を使ってWeb サーバーインスタンスを起動します。

          - -
          -
          -
          
          -
          -
          -
          1
          -
          heroku ps:scale web=1
          -
          -
          -
          - -

          2. Slack アプリの設定を更新する

          - -

          次に、Heroku のWeb アドレスをリクエストURL に指定し、Slack からのイベントやアクションがこのURL に送信されるようにします。

          - -

          次のコマンドを使ってHeroku のWeb アドレスを取得します。

          - -
          -
          -
          
          -
          -
          -
          1
          -2
          -3
          -
          heroku info
          -# ...
          -# Web URL: https://sharp-rain-871.herokuapp.com/
          -
          -
          -
          - -

          この例では、https://sharp-rain-871.herokuapp.com/がWeb アドレスとなります。

          - -

          Slack アプリのページを開き、アプリ名を選択します。次に、リクエストURLを自分で確認したWeb アドレスに変更します。設定する場所は2 か所あります。

          - -
          -

          💡 リクエストURL の末尾は/slack/eventsです。例えばhttps://sharp-rain-871.herokuapp.com/slack/eventsのようになります。

          -
          - -

          つ目の場所は、サイドパネルの「Interactivity & Shortcuts」です。これを選択し、リクエストURLを更新します。

          - -

          Interactivity & Shortcuts page

          - -

          2 つ目の場所は、サイドパネルの「Event Subscriptions」です。これを選択し、リクエストURLを更新します。

          - -

          Event Subscriptions page

          - -
          -

          💡 無料プランで使用するHeroku アプリは、非アクティブな状態が続くとスリープします。💤 認証が失敗した場合、すぐに再試行してみてください。

          -
          - -

          3. Slack アプリをテストする

          - -

          アプリのデプロイが完了し、Slack の設定変更も行いました。アプリを試してみましょう。

          - -

          アプリが参加しているSlack チャンネルを開き、半角の小文字で”hello” と書き込みます。Bolt 入門ガイドのとおり、アプリから応答があるはずです。応答がない場合、リクエストURLを確認し、もう一度試してください。

          - -
          - -

          変更をデプロイする

          - -

          Slack アプリを構築するなかで、変更を加えてデプロイする必要があります。一般的な流れでは、変更を加え、コミットし、Heroku にプッシュするという順番です。

          - -

          この流れをつかむため、アプリが”goodbye” というメッセージに応答するように変更を加えてみましょう。次のコードをapp.js に追加します(GitHub のソースコードはこちら)。

          - -
          -
          -
          
          -
          -
          -
          1
          -2
          -3
          -4
          -5
          -
          // "goodbye" が含まれるメッセージの着信をリッスン
          -app.message('goodbye', async ({ message, say }) => {
          -  // say() で、イベントがトリガーされたチャンネルにメッセージを送信する
          -  await say(`See ya later, <@${message.user}> :wave:`);
          -});
          -
          -
          -
          - -

          変更内容をローカルのGit リポジトリにコミットします。

          - -
          -
          -
          
          -
          -
          -
          1
          -
          git commit -am "ユーザーに'goodbye' を返す"
          -
          -
          -
          - -

          変更内容をリモートのherokuリポジトリにプッシュし、デプロイします。

          - -
          -
          -
          
          -
          -
          -
          1
          -
          git push heroku main
          -
          -
          -
          - -

          デプロイ処理が完了したら、アプリが参加しているSlack チャンネルを開き、半角の小文字で”goodbye” と書き込みます。Slack アプリから、さよならの挨拶が返ってくるはずです。

          - -
          - -

          次のステップ

          - -

          これではじめて️⚡Bolt for JavaScript アプリをHerokuへデプロイすることに成功しました。🚀

          - -

          基本的なアプリのデプロイができましたので、次はアプリのカスタマイズやモニタリングを行う方法を調べてみましょう。おすすめのステップをいくつか紹介します。

          - - - - -
          -
          - -
          - - - - - - - - \ No newline at end of file diff --git a/docs/_site/ja-jp/getting-started-http.html b/docs/_site/ja-jp/getting-started-http.html deleted file mode 100644 index 865440080..000000000 --- a/docs/_site/ja-jp/getting-started-http.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - Redirecting… - - - - -

          Redirecting…

          - Click here if you are not redirected. - diff --git a/docs/_site/ja-jp/getting-started.html b/docs/_site/ja-jp/getting-started.html deleted file mode 100644 index e17d7b8c0..000000000 --- a/docs/_site/ja-jp/getting-started.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - Redirecting… - - - - -

          Redirecting…

          - Click here if you are not redirected. - diff --git a/docs/_site/ja-jp/hubot-migration.html b/docs/_site/ja-jp/hubot-migration.html deleted file mode 100644 index 4dd6ba323..000000000 --- a/docs/_site/ja-jp/hubot-migration.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - Redirecting… - - - - -

          Redirecting…

          - Click here if you are not redirected. - diff --git a/docs/_site/ja-jp/reference.html b/docs/_site/ja-jp/reference.html deleted file mode 100644 index 7c691d132..000000000 --- a/docs/_site/ja-jp/reference.html +++ /dev/null @@ -1,743 +0,0 @@ - - - - - - - Slack | Bolt for JavaScript - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          -
          - - -
          - -
          - - -
          -

          リファレンス(Appインターフェイスと設定)

          - -
          -

          このガイドでは、Bolt インターフェイスのリスナー関数、リスナー関数の引数、初期化オプション、エラーについて詳しく説明します。⚡入門ガイドをまだ完了していない場合は、先にそちらで Bolt for JavaScript アプリ開発の基本を確認しておきましょう。

          -
          - - - -
          - -

          リスナー関数

          -

          Slack アプリは通常、Slack からのイベント情報を受け取ったり、それに応答を返したりします。受信するイベントは 1 つの場合もあれば、多数の場合もあります。例えば、Events API のイベント(アプリに関連するリンクが共有されたときなど)や、ユーザーがアプリのショートカットを実行するイベントを受け取ったりします。Slack からのリクエストの種類に応じて、それぞれ異なるメソッドが用意されています。これらのメソッドに、それらのイベントを処理したり応答を返したりするためのリスナー関数を渡します。

          - -

          メソッド

          -

          以下の表は、現在提供しているリスナー関数を渡すためのメソッドの一覧です。これらのメソッドを使って、Slack から送信された各種のイベントを処理します。各メソッドの一般的な設定は、まずイベントを判別するためのパラメーターがあり、リスナー関数がそれに続く形になっています。イベント判定のためのパラメーターとは、以下の説明にある、特定の callback_id やメッセージ中の部分一致の文字列を指定する部分などのことです。これにより、リスナー関数が処理するイベントを条件に合致するものだけに絞り込むことができます。

          - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          メソッド説明
          app.event(eventType, fn);Events API のイベントをリッスンします。eventType は、処理対象のイベントを指定するための文字列 です。この値は、Slackアプリの設定画面でサブスクライブの設定がされている必要があります。
          app.message([pattern ,] fn);message イベントのリッスンに特化した、便利なメソッドです。pattern パラメーターには、部分一致させる文字列、または正規表現を指定します。これによって処理対象のメッセージを判別します。
          app.action(actionId, fn);Block Kit エレメントから送信される action イベントをリッスンします。このイベントにはユーザーのボタン操作、メニュー選択、日付ピッカーの操作などがあります。actionId は文字列型で、アプリがビュー内に含めたブロックエレメントに指定した一意の action_id の値と一致する必要があります。ここでいう「ビュー」とは、メッセージ、モーダル、アプリのホームタブのことを指します。アクションエレメントを input ブロックに配置した場合はイベントがトリガーされないことに注意してください。
          app.shortcut(callbackId, fn);グローバルショートカットまたはメッセージショートカットの呼び出しをリッスンします。callbackId は文字列または正規表現で、アプリの設定で指定したショートカットの callback_id にマッチする必要があります。
          app.view(callbackId, fn);view_submission イベントと view_closed イベントをリッスンします。view_submission イベントは、アプリが開いたモーダルでユーザーがデータ送信の操作をしたときに発生します。view_closed イベントは、ユーザーがデータ送信を実行せずにモーダルを閉じたときに発生します。
          app.step(workflowStep)WorkflowStep のインスタンスに渡されたコールバックを使用して、ワークフローステップイベントのリッスンと応答を行います。コールバックには editsaveexecute の 3 種類があります。ワークフローステップについて詳しくは、ドキュメントを参照してください。
          app.command(commandName, fn);Slash コマンドの呼び出しをリッスンします。commandName は文字列型で、アプリの設定で指定したスラッシュコマンドと一致する必要があります。スラッシュコマンドの名前では / を最初に配置します(例 : /helpdesk)。
          app.options(actionId, fn);外部データソースを使用するセレクトメニューなどから送られる選択肢読み込みのリクエストをリッスンします。使う機会は多くありませんが、app.action と混同しないようにしましょう。actionId は文字列型で、アプリがビュー内に外部データソースを使用するセレクトメニューを含めるときに指定したaction_id と一致する必要があります。
          - -

          制約オブジェクト

          -

          一部のメソッドでは、さまざまな制約オブジェクトを指定することができます。制約オブジェクトを使用すると、リスナー関数で扱うイベントをさらに絞り込んだり、特定のケースに対応することができます。制約オブジェクトは、上で説明した識別子の代わりとしてメソッドに渡すことができます。さまざまな制約オブジェクトとそれを渡せるメソッドを以下の表にまとめます。

          - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          メソッドオプション詳細
          app.action(constraints, fn)block_id, action_id, callback_id, (,type)action_id だけでなく、他の制約指定でもリッスンします。block_id は、エレメントの親ブロックの ID です。callback_id は、ビューの初期化時に指定したビューの ID です(モーダルに配置したアクションエレメントのみで使用できます)。type を指定することで、blocks内のアクションのみを処理するか、あるいは attachments 内のアクションのみなのかを選択できます。type に block_actions を指定すると、blocks内のアクションエレメントのみを処理します。interactive_message を指定すると、旧来の attachments 内のインタラクティブなアクションのみを処理します。
          app.shortcut(constraints, fn)type, callback_id対象のショートカットの種類を指定できます。typeshortcutを指定するとグローバルショートカットmessage_actionの場合はメッセージショートカット)となります。callbackId には文字列か正規表現を指定します。
          app.view(constraints, fn)type, callback_idtype には view_closed または view_submission のいずれかを指定します。ここで指定した種別のイベントの場合のみリスナー関数にイベントが渡されます。callback_id は、アプリでモーダルを開く際に設定したビューの callback_id です。
          app.options(constraints, fn)block_id, action_id, callback_id必須ではない設定として、action_id の他に block_idcallback_id もリッスンする条件に追加することができます。callback_id はモーダル内の options エレメントを処理する場合にのみ指定できます。
          - -

          リスナー関数の引数

          -

          リスナー関数がアクセスできる引数は、リスナー関数が渡されるメソッドによって決まります。以下の表は、これらの引数の説明です。この表は、それぞれの引数とそれにアクセスできるメソッドの詳細をカバーします。

          - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          引数説明 
          payloadすべてのリスナー受信したイベントペイロードから装飾部分などが取り除かれた本質的な内容部分。この内容や構造はイベントの種別によって異なります。このペイロード情報は下記の bodyのサブセットです。また、payload には、リスナーが渡されたメソッド名と同じ名前のエイリアスを使ってアクセスすることもできます(messageeventactionshortcutviewcommandoptions)。payload の内容を簡単に確認する方法は、それを実際にログに出力してみることです。または、TypeScript を使うのもよいでしょう。
          saymessage, event, action, command受信したイベントが紐づいているチャンネルにメッセージを送信する関数。この引数が使用できるのは、リスナーをトリガーしたイベントにチャンネル ID が含まれる場合のみです(message イベントが最も一般的です)。say は、シンプルな文字列(プレーンテキストのメッセージ)またはオブジェクト(ブロックを含むメッセージ)を受け付けます。say は Promise を返します。この Promise は chat.postMessage の応答でresolveされます。もしaction メソッドや、message 以外のイベントを使用する場合は、イベントの payload にチャンネル ID が含まれているかを確認するようにしてください
          ackaction, shortcut, view, command, optionsアプリが受信イベントを受け取ったことを確認するために呼び出す必要のある関数ack は応答の完了時にresolveする Promise を返します。詳しくは、イベントの確認を参照してください。
          clientすべてのリスナーイベントに関連づけられたトークンを使用する Web API クライアント。単一のワークスペースへのインストールでは、トークンは Appのコンストラクターに提供されます。複数のワークスペースへのインストールでは、トークンは authorize 関数から返されます。
          respondaction, shortcut, view, command受信イベントに response_url含まれる場合に、受信イベントに応答を返す関数。respond は Promise を返します。この Promise は、response_url の応答結果に resolveされます。ショートカットに関しては、respond はメッセージショートカットでのみ動作します(グローバルショートカットでは動作しません)。ビューでの respond は モーダル内の input ブロックの conversations listchannels list のセレクトメニューで response_url_enabled: true というオプションが指定されている場合のみ動作します。
          contextすべてのリスナーイベントのコンテキスト。このオブジェクトは、botId など、イベントやアプリに関するデータを保持します。イベントがリスナーに渡される前に、ミドルウェアで他のコンテキスト情報を追加することもできます。
          bodyすべてのリスナーリクエストの body 全体を保持するオブジェクト(payload のスーパーセット)。trigger_idauthorizations など、一部の付帯的なデータは payload の外側でのみ利用できます。
          - -

          body と payload について

          - -

          payloadbody の構造は、API サイトで説明しています。

          - - - -

          リスナーミドルウェアとの違い

          -

          リスナーミドルウェアは、多くのリスナー関数で利用するロジックを実装したい場合に使用します(全てのリスナーでは使わないようなケースで)。リスナーミドルウェアは、上で説明したリスナー関数と同じ引数を持ちますが、唯一異なるのは next() 関数を持っている点です。この関数は、実行のチェインを切らないために、必ず呼び出される必要があります。リスナーミドルウェアについて詳しくは、ドキュメントを参照してください。

          - -

          初期化オプション

          -

          Bolt には、アプリをカスタマイズするためのさまざまな初期化オプションが用意されています。主なオプションには、Bolt アプリのオプションとレシーバーのオプションの 2 種類があります。レシーバーのオプションは、アプリで使用するレシーバーによって異なります。デフォルトの HTTPReceiver では以下のレシーバーオプションが利用できます(カスタムのレシーバーを使わない限りはこれらが利用できます)。

          - -

          レシーバーオプション

          -

          HTTPReceiver オプションは、Bolt App オプションと同様に、App のコンストラクターに渡すことができます。渡したオプションは、初期化の際に HTTPReceiver のインスタンスに渡されます。

          - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          オプション説明
          signingSecretアプリの設定の「Basic Information」から取得した 文字列。受信イベントが Slack から送信されたものであることを検証するために使用されます。
          endpointsレシーバーが Slack からの受信リクエストをリッスンするエンドポイントを指定する文字列または オブジェクト。現在、オブジェクトに指定できるキーはラベルとしての任意の文字列のみで、値にはカスタムのエンドポイントを指定します(例 : /myapp/events)。デフォルトでは /slack/events というエンドポイントにすべてのイベントが送信されます。
          processBeforeResponseイベントに対して即座に確認の応答を返すかどうかを指定する真偽値。リクエストへの応答が完了するとリスナーはただちに終了してしまうため、FaaS プラットフォームでアプリを実行する場合に有用な設定です。 true に設定すると早期終了を防ぐためにハンドラーが実行されるまで応答を返すのを遅らせます。デフォルトは false です。
          clientIdアプリの設定で指定した、クライアントの ID を示す文字列。OAuth の設定を行うために必要です
          clientSecretアプリの設定で指定した、クライアントのシークレットキーを示す 文字列。OAuth の設定を行うために必要です
          stateSecretCSRF 攻撃を防ぐために OAuth の設定時に渡すことができる、推奨のパラメーター(文字列)。
          installationStoreOAuth の設定時に、インストールデータの保存・取得・削除の手段を定義します。fetchInstallationstoreInstallationdeleteInstallation という 3 つのメソッドが含まれます。デフォルトの installationStore はインメモリストアです。
          scopesアプリが OAuth のプロセスの中でアクセス許可を求めるスコープのリスト。
          installerOptionsデフォルトの OAuth サポートをカスタマイズする場合に指定するオブジェクト(必須ではない)。詳しくは、OAuth のドキュメントを参照してください。
          dispatchErrorHandler受信リクエストが想定しないパスへのリクエストを受信したときに実行されるエラーハンドラー。 詳細はエラー処理のドキュメントを参照してください。
          processEventErrorHandlerイベントの処理中に例外がスローされたときに実行されるエラーハンドラー。 詳細はドキュメントを参照してください。
          unhandledRequestHandlerSlack からのリクエストが Bolt アプリによって確認(ack())されなかったときに実行されるエラーハンドラー。 詳細はドキュメントを参照してください。
          unhandledRequestTimeoutMillisリクエストが受信されてから unhandledRequestHandler が実行されるまでの待機時間(ミリ秒単位)。 デフォルトは 3001 です。 詳細はドキュメントを参照してください。
          signatureVerificationBolt が Slack からの受信リクエストの署名を検証するかどうかを指定する真偽値。 デフォルトは true です。
          - -

          App オプション

          -

          App オプションは、App のコンストラクターに渡します。receiver 引数が設定されない場合 App コンストラクターは上記の receiver オプションを受け取り、それを用いて socketMode の値に応じて HttpReceiver または SocketModeReceiver を初期化します。

          - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          オプション説明
          receiverReceiver のインスタンス。受信イベントのパースとその処理を行います。Receiver インターフェイスに準拠して、init(app)start()stop()を持つ必要があります。receiver について詳しくは、ドキュメントを参照してください。
          agentオプションの HTTP エージェント。プロキシのサポートを設定する場合に使用します。カスタムの agent について詳しくは、Node Slack SDK のドキュメントを参照してください。
          clientTls設定必須ではない文字列。HTTP クライアントリクエストにカスタムの TLS を設定する場合に指定します。"pfx""key""passphrase""cert""ca" のいずれかを指定します。
          convoStoreステートに関連する会話のデータを設定・取得するためのデータストア実装。set() で会話のステートを設定し、get() で取得します。デフォルトでは、アプリはインメモリのストアを利用できます。詳細とサンプルについては、ドキュメントを参照してください。
          tokenアプリの設定(「Settings」>「Install App」)で指定した 文字列。Web API の呼び出しに必要です。authorizeorgAuthorizeやOAuth 設定を使用する場合には指定しないでください。
          botIdauthorize が定義されていない場合に限り指定できる、設定必須ではないbotId(例 :B12345)。ボットトークンの ID で、アプリ自身によって送信されたメッセージを無視するために使用されます。xoxb トークンがアプリに渡されている場合、アプリは auth.test メソッドを呼び出して、この値を自動的に取得します。
          botUserIdauthorize が定義されていない場合に限り指定できる、設定必須ではないbotUserIdbotId とは異なり、ボットユーザーに関連づけられたユーザー ID を指します。ダイレクトメンションを識別するために使用します。xoxb トークンがアプリに渡されている場合、アプリは auth.test メソッドを呼び出して、この値を自動的に取得します。
          authorize複数のチームでのインストールをサポートする場合に使用する関数。どのトークンが受信イベントに関連づけられているかを判断するのに使用します。authorize 関数に渡される元データには、場合によって userIdconversationIdenterpriseIdteamIdisEnterpriseInstall が含まれます(受信イベントによって異なります)。authorize 関数は、botTokenbotIdbotUserId、または userTokenを返します。ビルトインの OAuth サポートを使用する場合、authorize 関数は自動的に作成されるため、自分で渡す必要はありません。authorize 関数について詳しくは、こちらを参照してください。
          loggerビルトインのロガーの代わりにカスタムのロガーを渡すためのオプション。ロガーには特定のメソッドが実装されている必要があります。これには Logger インターフェイスで定義されている setLevel(level:LogLevel)getLevel()setName(name: string)debug(...msgs: any\[])info(...msgs: any\[])warn(...msgs: any\[])error(...msgs: any\[]) があります。ログ出力の詳細については、ドキュメントを参照してください。
          logLevel出力するログのレベルを指定するオプション。LogLevel の出力に含まれる情報のレベルには、重要度の低い順から高い順に DEBUGINFOWARNERROR があります。デフォルトの logLevelINFO に設定されています。ログ出力の詳細については、ドキュメントを参照してください。
          extendedErrorHandler真偽値を指定するオプションで、 true に設定するとさらなるリクエストのコンテキスト情報を含んだオブジェクトがグローバルエラーハンドラーに渡されます。 バージョン 3.8.0 から利用することができます。 デフォルトは false です。 より高度なエラーの処理に関する詳細は API ドキュメントを参照してください。
          ignoreSelfアプリ自身から発信されたメッセージをミドルウェアの関数で無視するかどうかを指定する真偽値。botId が必要です。デフォルトは true です。
          clientOptions.slackApiUrlSlack Web API で使用するエンドポイントをカスタマイズできます。これが使用されるのはほとんどがテスト用途です。
          socketMode真偽値を指定するオプションで、true に設定するとアプリはソケットモードで起動します。ソケットモードは WebSocket のコネクションを通して Slack からのデータを受信する機能です。デフォルトは false です。
          developerModeデベロッパーモードを有効にする真偽値です。 true に設定したとき、logLevelDEBUGsocketModetrue に自動的に設定されます。しかし、 これらの二つのプロパティを明示的に設定した場合、それらの設定が developerMode による設定よりも優先されます。さらに、デバッグをしやすくするためのカスタムの OAuth エラーハンドラーも提供されます。また、全ての Slack からのリクエストのボディがログ出力されるため、トークンのようなセンシティブな情報がログに含まれる可能性があります。デフォルトは false です。
          deferInitializationアプリの初期化を遅延させる真偽値です。有効にすると非同期の App#init() メソッドを手動で呼び出す必要があります。 また init() メソッドは App#start() を実行する前に呼び出さなければなりません。 デフォルトは false です。
          signatureVerificationBoltが着信リクエストでSlackの署名を検証する必要があるかどうかを決定するブール値。 デフォルトはtrueです。
          - -
          -

          Bolt のclientは Node Slack SDKWebClient のインスタンスです。そのため、Node Slack SDK のドキュメントも合わせて参照すると、開発時の理解に役立つでしょう。

          -
          - -

          フレームワークのエラー

          - -

          Bolt では、さまざまなエラーが定義されています。これらにはより具体的なコンテキスト情報が含まるため、エラーのハンドリングが行いやすくなるでしょう。以下は、すべてのエラーコードを網羅しているわけではありませんが、開発中に目にすると思われるものを取り上げたエラーコードの一覧です。

          - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          エラーコード詳細
          AppInitializationError無効な初期化オプションが渡されたことを示します。原因として、署名シークレットが渡されていないことや、競合するオプションが指定されたことなどが考えられます(例 : tokenauthorize の両方を同時に指定することはできない)。original プロパティで詳細を確認できます。このエラーがスローされるのは、アプリのコンストラクターで行われる初期化時のみです。
          AuthorizationErrorインストール情報が取得できなかった、またはパースできなかったときにのみスローされるエラーです。このエラーは、ビルトインの OAuth サポートを使用しているときに発生する可能性があります。また、独自の authorize 関数を作成するときに、このエラーをインポートして使用することができます。
          ContextMissingPropertyErrorcontext オブジェクトに必要な情報が不足しているときにスローされるエラーです(例 : ignoreSelftrue に設定したのに botUserId または botId が含まれていない)。不足しているプロパティは、missingProperty プロパティで確認できます。
          ReceiverMultipleAckErrorReceiver 内で、すでに確認が済んでいるリクエストに対してアプリがさらに ack() を呼んだ場合にスローされるエラーです。現在、デフォルトの HTTPReceiver でのみ使用されます。
          ReceiverAuthenticityErrorアプリのリクエストの署名が検証できないときにスローされるエラーです。このエラーには、失敗した理由を示す情報が含まれます(例 : タイムスタンプが有効でない、ヘッダーに抜けがある、署名シークレットが有効でない)。
          MultipleListenerError単一のイベントに対して複数のリスナーでの処理中に複数のエラーが発生した場合にスローされるエラーです。個々のエラーを配列に収めた originals プロパティを持ちます。
          WorkflowStepInitializationError新しい WorkflowStep をインスタンス化する際に、設定オプションが無効な場合、または不足している場合にスローされるエラーです。原因として、callback_id が指定されていない、または設定オブジェクトが指定されていないことが考えられます。ワークフローステップについて詳しくは、ドキュメントを参照してください。
          UnknownErrorフレームワーク内でスローされる、特定のエラーコードを持たないエラーです。original プロパティで詳細を確認できます。
          - -
          -

          errors.ts のコードで、エラー定義の部分とコンストラクターの部分を読み、参考にしてみてください。

          -
          - -

          クライアント側のエラー

          -

          Bolt では、Slack API の呼び出しのため WebClient をインポートしています。クライアントで API 呼び出しを行う際に目にすると思われるエラーを以下に示します。より詳しい内容は、Web API のドキュメントを参照してください。クライアントのエラーをハンドリングする際、data プロパティの body で詳しい情報を確認できます。

          - - - - - - - - - - - - - - - - - - - - - - - - - - -
          エラーコード詳細
          PlatformErrorSlack API の呼び出し中に何らかの異常が発生したことを示すエラー。data プロパティを持ちます。
          RequestErrorリクエストが送信できなかったことを示すエラー。ネットワーク接続が利用できないことなどが原因として考えられます。original プロパティで詳細を確認できます。
          RateLimitedError短時間で送信したリクエストが多すぎることを示すエラー。retryAfter プロパティで、再送信まで待機する必要のある秒数を確認できます。WebClient は、デフォルトでレート制限エラーのハンドリングを行います。詳しくはドキュメントを参照してください
          HTTPErrorHTTP レスポンスに通常は想定されないステータスコードが設定されていたことを示すエラー。Web API が返す HTTP ステータスコードは、通常 200(エラー時を含む)または 429(レート制限時)のみです。
          - -
          -
          - -
          - - - - - - - \ No newline at end of file diff --git a/docs/_site/ja-jp/tutorial/getting-started-http.html b/docs/_site/ja-jp/tutorial/getting-started-http.html deleted file mode 100644 index 00853ab46..000000000 --- a/docs/_site/ja-jp/tutorial/getting-started-http.html +++ /dev/null @@ -1,808 +0,0 @@ - - - - - - - Slack | Bolt for JavaScript - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          -
          - - -
          - - -
          - - -
          -
            -
            - -
            -

            Bolt 入門ガイド (HTTP)

            - -
            -

            このガイドでは、Bolt を使用して Slack アプリを起動し実行する方法について説明します。その過程で、新しい Slack アプリを作成し、ローカル環境を設定し、Slack ワークスペースからのメッセージをリッスンして応答するアプリを開発します。

            -
            - -

            このガイドが終わったら、あなたはこの⚡️Getting Started appを実行したり、修正したり、自分で作ったりすることができます。

            - -
            - -

            アプリを作成する

            -

            最初にやるべきこと: Bolt で開発を始める前に、 Slack アプリを作成します。

            - -
            -

            💡 いつもの仕事のさまたげにならないように、別に開発用のワークスペースを使用することをおすすめします — 新しいワークスペースを無料で作成できます。

            -
            - -

            アプリ名を入力し (後で変更可能)、インストール先のワークスペースを選択したら、Create App ボタンをクリックすると、アプリの Basic Information ページが表示されます。

            - -

            このページには、後で必要になる重要な認証情報 (App Credentials ヘッダーの下の Signing Secret など) に加えて、アプリケーションの概要が表示されます。

            - -

            Basic Information page

            - -

            ひと通り確認し、アプリのアイコンと説明を追加してから、アプリの設定 🔩 を始めましょう。

            - -
            - -

            トークンとアプリのインストール

            -

            Slack アプリは、OAuth を使用して、Slack の API へのアクセスを管理します。アプリがインストールされると、トークンを受け取ります。そのトークンを使って、アプリは API メソッドを呼び出すことができます。

            - -

            Slack アプリで使用できるトークンには、ユーザートークン(xoxp)とボットトークン(xoxb)、アプリレベルトークン(xapp)の 3 種類があります。

            -
              -
            • ユーザートークン を使用すると、アプリをインストールまたは認証したユーザーに成り代わって API メソッドを呼び出すことができます。1 つのワークスペースに複数のユーザートークンが存在する可能性があります。
            • -
            • ボットトークン はボットユーザーに関連づけられ、1 つのワークスペースでは最初に誰かがそのアプリをインストールした際に一度だけ発行されます。どのユーザーがインストールを実行しても、アプリが使用するボットトークンは同じになります。 ほとんど のアプリで使用されるのは、ボットトークンです。
            • -
            • アプリレベルトークン は、全ての組織(とその配下のワークスペースでの個々のユーザーによるインストール)を横断して、あなたのアプリを代理するものです。アプリレベルトークンは、アプリの WebSocket コネクションを確立するためによく使われます。
            • -
            - -

            説明を簡潔にするために、このガイドではボットトークンを使用します。

            - -
              -
            1. -

              左側のサイドバーの OAuth & Permissions にアクセスして、Bot Token Scopes までスクロールします。そして、Add an OAuth Scope をクリックします。

              -
            2. -
            3. -

              とりあえずは、chat:write というスコープだけを追加してみましょう。これは、アプリにボットユーザがメンバーとして参加しているチャンネルへのメッセージの投稿を許可するスコープです。

              -
            4. -
            5. -

              ページ上部までスクロールして戻り、Install App to Workspace をクリックします。すると、開発用のワークスペースにこのアプリをインストールするための Slack の OAuth 確認画面へと誘導されます。

              -
            6. -
            7. -

              インストールを承認すると、OAuth & Permissions ページが表示され、Bot User OAuth Access Token を確認することができるはずです。

              -
            8. -
            - -

            OAuth Tokens

            - -
            -

            💡 トークンは、パスワードのように大切に扱い、安全に保管してください。アプリではそのトークンを使用して、Slack ワークスペースからの情報を投稿および取得します。

            -
            - -
            - -

            ローカルプロジェクトの設定

            -

            初期設定が完了したので、次は新しい Bolt プロジェクトを設定します。ここで、アプリのロジックを処理するコードを記述します。

            - -

            プロジェクトをまだ作成していない場合は、新しいプロジェクトを作成しましょう。次のように、空のディレクトリを作成して、新しいプロジェクトを初期化します。

            - -
            -
            -
            1
            -2
            -3
            -
            mkdir first-bolt-app
            -cd first-bolt-app
            -npm init
            -
            -
            -
            - -

            新しいプロジェクトを説明するための一連の質問が表示されます (特に問題がなければ、各プロンプトで Enter を押すと、デフォルトを受け入れることができます)。完了すると、ディレクトリ内に新しい package.json ファイルが作成されます。

            - -

            Bolt パッケージを新しいプロジェクトにインストールする前に、アプリの設定時に生成されたボットトークンと signing secret (サイン認証) を保存しましょう。これらは環境変数として保存する必要があります。バージョン管理では保存しないでください。

            - -
              -
            1. Basic Information ページから Signing Secret をコピーして、新しい環境変数に保存します。次の例は Linux と macOS で動作します。ただし、Windows でも同様のコマンドが利用可能です。 -
              1
              -
              export SLACK_SIGNING_SECRET=<your-signing-secret>
              -
              -
            2. -
            3. OAuth & Permissions ページからボット (xoxb) トークンをコピーし、それを別の環境変数に格納します。 -
              1
              -
              export SLACK_BOT_TOKEN=xoxb-<your-bot-token>
              -
              -
            4. -
            - -

            それでは、アプリを作成しましょう。次のコマンドを使用して、@slack/bolt パッケージをインストールし、 package.json 中で依存ファイルとして保存します。

            - -
            -
            -
            1
            -
            npm install @slack/bolt
            -
            -
            -
            - -

            このディレクトリ内に app.js という名前の新しいファイルを作成し、以下のコードを追加します。

            - -
            -
            -
            1
            -2
            -3
            -4
            -5
            -6
            -7
            -8
            -9
            -10
            -11
            -12
            -13
            -14
            -
            const { App } = require('@slack/bolt');
            -
            -// ボットトークンとソケットモードハンドラーを使ってアプリを初期化します
            -const app = new App({
            -  token: process.env.SLACK_BOT_TOKEN,
            -  signingSecret: process.env.SLACK_SIGNING_SECRET
            -});
            -
            -(async () => {
            -  // アプリを起動します
            -  await app.start(process.env.PORT || 3000);
            -
            -  console.log('⚡️ Bolt app is running!');
            -})();
            -
            -
            -
            - -

            まず実行してみましょう。 app.js ファイルを保存してから、以下のコマンドラインで動かします。

            - -
            node app.js
            -
            - -

            アプリから、起動し実行中であることが通知されます🎉

            - -
            - -

            イベントの設定 (HTTP)

            -

            アプリはボットとしてチームメンバーのように動作し、メッセージを投稿したり、絵文字リアクションを追加したりすることができます。

            - -

            Slack ワークスペースで発生するイベント (メッセージが投稿されたときや、メッセージに対するリアクションが投稿されたときなど) をリッスンするには、Events API を使用してイベントタイプに登録します。

            - -

            アプリのイベントを有効にしましょう。

            - -
              -
            1. -

              アプリのイベントを有効にするには、まずアプリ設定ページに戻ります (アプリ管理ページでアプリをクリックします)。左側のサイドバーにある Event Subscription をクリックします。Enable Events のスイッチをオンにします。

              -
            2. -
            3. -

              Request URLを追加します。Slackはイベントに対応するHTTP POSTリクエストをこのRequest URLエンドポイントに送信します。Boltは/slack/eventsのパスを使用して、すべての受信リクエスト(ショートカット、イベント、インタラクティビティのペイロードなど)をリッスンします。アプリの設定でRequest URLを設定する際には、https://<your-domain>/slack/eventsのように/slack/eventsを追加します。💡

              -
            4. -
            - -
            -

            ローカル開発では、ngrokのようなプロキシサービスを使って公開 URL を作成し、リクエストを開発環境にトンネリングすることができます。このトンネリングの方法については、ngrok のガイドを参照してください。

            -
            - -

            最後に、聞きたいイベントをSlackに伝えましょう。Event Subscriptionsの下にある、Enable Eventsというラベルの付いたスイッチを切り替えます。

            - -

            イベントが発生すると、Slack は、そのイベントをトリガーしたユーザーやイベントが発生したチャンネルなど、イベントに関する情報をアプリに送信します。アプリが詳細を処理し、それに応じて応答することができます。

            - -

            Request URL ボックスの Enable Events スイッチの下のフィールドにこの URL を貼り付けます。Bolt アプリが引き続き実行されている場合は、URL が検証されチェックマークが表示されます。

            - -

            Request URL が検証されたら、Subscribe to Bot Events までスクロールします。メッセージに関するイベントが4つあります:

            -
              -
            • message.channels あなたのアプリが追加されているパブリックチャンネルのメッセージをリッスン
            • -
            • message.groups あなたのアプリが追加されている🔒プライベートチャンネルのメッセージをリッスン
            • -
            • message.im あなたのアプリとユーザーのダイレクトメッセージをリッスン
            • -
            • message.mpim あなたのアプリが追加されているグループ DM をリッスン
            • -
            - -

            もしボットに参加しているすべての場所で全てのメッセージイベントをリッスンさせたいなら、これら4つ全てのイベントを選んでください。選択したら、緑の Save Changes ボタンをクリックします。

            - -
            - -

            メッセージのリスニングと応答

            -

            これで、アプリでいくつかのロジックを設定する準備が整いました。まずは message() メソッドを使用して、メッセージのリスナーをアタッチしましょう。

            - -

            次の例では、あなたのアプリが追加されているチャンネルや DM で hello という単語を含むすべてのメッセージをリッスンし、 Hey there @user! と応答します。

            - -
            -
            -
            1
            -2
            -3
            -4
            -5
            -6
            -7
            -8
            -9
            -10
            -11
            -12
            -13
            -14
            -15
            -16
            -17
            -18
            -19
            -
            const { App } = require('@slack/bolt');
            -
            -const app = new App({
            -  token: process.env.SLACK_BOT_TOKEN,
            -  signingSecret: process.env.SLACK_SIGNING_SECRET
            -});
            -
            -// "hello" を含むメッセージをリッスンします
            -app.message('hello', async ({ message, say }) => {
            -  // イベントがトリガーされたチャンネルに say() でメッセージを送信します
            -  await say(`Hey there <@${message.user}>!`);
            -});
            -
            -(async () => {
            -  // アプリを起動します
            -  await app.start(process.env.PORT || 3000);
            -
            -  console.log('⚡️ Bolt app is running!');
            -})();
            -
            -
            -
            - -

            アプリを再起動したら、ボットユーザーをチャンネル、 DM に追加し、 hello を含むメッセージを送信してみてください。アプリが応答したら成功です。

            - -

            これは基本的な例ですが、ここから自分の好きなようにアプリをカスタマイズしていくことができます。さらにインタラクティブな動作を試すために、プレーンテキストではなくボタンを送信してみましょう。

            - -
            - -

            アクションの送信と応答

            - -

            ボタン、選択メニュー、日付ピッカー、モーダルなどの機能を使用するには、インタラクティブ性を有効にする必要があります。イベントと同様に、Slack の URL を指定してアクション ( 「ボタン・クリック」など) を送信する必要があります。

            - -

            アプリ設定ページに戻り、左側の Interactivity & Shortcuts をクリックします。Request URL ボックスがもう 1 つあることがわかります。

            - -
            -

            💡 デフォルトでは、Bolt はイベントに使用しているのと同じエンドポイントをインタラクティブコンポーネントに使用するように設定されているため、上記と同じリクエスト URL (この例では https://8e8ec2d7.ngrok.io/slack/events) を使用します。右下隅にある Save Changes ボタンを押してください。これでアプリのインタラクティブなコンポーネントを利用する設定が有効になりました!

            -
            - -

            Configuring a Request URL

            - -

            インタラクティブ機能を有効にすると、ショートカット、モーダル、インタラクティブコンポーネント(ボタン、セレクトメニュー、データピッカーなど)とのやり取りがイベントとしてアプリに送信されます。

            - -

            それでは、アプリのコードに戻り、インタラクティブな処理を追加しましょう。この実装は以下の二つのステップで構成されます。

            -
              -
            • 最初に、アプリからボタンを含むメッセージを送信します。
            • -
            • 次に、ユーザーがボタンをクリックしたときの動作をアプリでリッスンし、応答します。
            • -
            - -

            以下は、前のセクションで記述したアプリコードを、文字列だけでなく、ボタン付きのメッセージを送信するように変更したものです。

            - -
            -
            -
            1
            -2
            -3
            -4
            -5
            -6
            -7
            -8
            -9
            -10
            -11
            -12
            -13
            -14
            -15
            -16
            -17
            -18
            -19
            -20
            -21
            -22
            -23
            -24
            -25
            -26
            -27
            -28
            -29
            -30
            -31
            -32
            -33
            -34
            -35
            -36
            -37
            -38
            -
            const { App } = require('@slack/bolt');
            -
            -const app = new App({
            -  token: process.env.SLACK_BOT_TOKEN,
            -  signingSecret: process.env.SLACK_SIGNING_SECRET
            -});
            -
            -// "hello" を含むメッセージをリッスンします
            -app.message('hello', async ({ message, say }) => {
            -  // イベントがトリガーされたチャンネルに say() でメッセージを送信します
            -  await say({
            -    blocks: [
            -      {
            -        "type": "section",
            -        "text": {
            -          "type": "mrkdwn",
            -          "text": `Hey there <@${message.user}>!`
            -        },
            -        "accessory": {
            -          "type": "button",
            -          "text": {
            -            "type": "plain_text",
            -            "text": "Click Me"
            -          },
            -          "action_id": "button_click"
            -        }
            -      }
            -    ],
            -    text: `Hey there <@${message.user}>!`
            -  });
            -});
            -
            -(async () => {
            -  // アプリを起動します
            -  await app.start(process.env.PORT || 3000);
            -
            -  console.log('⚡️ Bolt app is running!');
            -})();
            -
            -
            -
            - -

            say() に格納されている値が、 blocks の配列を含むオブジェクトになりました。このブロックは Slack メッセージを構成するコンポーネントであり、テキストや画像、日付ピッカーなど、さまざまなタイプがあります。この例では、アプリは、ボタンを accessory として含むセクションブロックを使用して応答します。blocks を使っている場合、 text は通知やアクセシビリティのためのフォールバックとして使用されます。

            - -

            このボタン accessory オブジェクトには、action_id が割り当てられています。これはボタンの一意の識別子として機能するため、アプリはどのアクションに応答するかを指定できます。

            - -
            -

            💡 Block Kit ビルダーを使うとインタラクティブメッセージを簡単にプロトタイプすることができます。ビルダーを使用すると、ユーザー (またはそのチームメンバー) はメッセージをモックアップして、対応する JSON を生成し、それをアプリに直接貼り付けることができます。

            -
            - -

            これで、アプリを再起動し、アプリが登録されているチャンネルで hello と入力すると、ボタン付きのメッセージが表示されます。ただしこのボタンをクリックしても、まだ何も起こりません。

            - -

            ボタンがクリックされるとフォローアップメッセージを送信するハンドラーを追加してみましょう。

            - -
            -
            -
            1
            -2
            -3
            -4
            -5
            -6
            -7
            -8
            -9
            -10
            -11
            -12
            -13
            -14
            -15
            -16
            -17
            -18
            -19
            -20
            -21
            -22
            -23
            -24
            -25
            -26
            -27
            -28
            -29
            -30
            -31
            -32
            -33
            -34
            -35
            -36
            -37
            -38
            -39
            -40
            -41
            -42
            -43
            -44
            -
            const { App } = require('@slack/bolt');
            -
            -const app = new App({
            -  token: process.env.SLACK_BOT_TOKEN,
            -  signingSecret: process.env.SLACK_SIGNING_SECRET
            -});
            -
            -// "hello" を含むメッセージをリッスンします
            -app.message('hello', async ({ message, say }) => {
            -  // イベントがトリガーされたチャンネルに say() でメッセージを送信します
            -  await say({
            -    blocks: [
            -      {
            -        "type": "section",
            -        "text": {
            -          "type": "mrkdwn",
            -          "text": `Hey there <@${message.user}>!`
            -        },
            -        "accessory": {
            -          "type": "button",
            -          "text": {
            -            "type": "plain_text",
            -            "text": "Click Me"
            -          },
            -          "action_id": "button_click"
            -        }
            -      }
            -    ],
            -    text: `Hey there <@${message.user}>!`
            -  });
            -});
            -
            -app.action('button_click', async ({ body, ack, say }) => {
            -  // アクションのリクエストを確認
            -  await ack();
            -  await say(`<@${body.user.id}> clicked the button`);
            -});
            -
            -(async () => {
            -  // アプリを起動します
            -  await app.start(process.env.PORT || 3000);
            -
            -  console.log('⚡️ Bolt app is running!');
            -})();
            -
            -
            -
            - -

            このように、app.action() を使うことで button_click という action_id のボタンアクションのリスナーを追加できるのです。アプリを再起動してボタンをクリックしてみましょう。すると、you clicked the button という新しいメッセージがアプリに表示されるはずです。

            - -
            - -

            次のステップ

            -

            これで最初の Bolt アプリが構築できました! 🎉

            - -

            基本的なアプリの作成ができましたので、次回は是非もっといろいろな、 Bolt の機能を使ってアプリを作ってみましょう。下記のリンクを辿っていろいろアイデアを模索してみてください!

            - -
              -
            • -

              基本的な概念をお読みください。Bolt アプリからアクセスできるさまざまなメソッドと機能について学ぶことができます。

              -
            • -
            • -

              ボットがevents() メソッドでリッスンできるさまざまなイベントを確認しましょう。イベントはすべてAPI サイトにリストされています。

              -
            • -
            • -

              Bolt を使用すると、アプリにアタッチされているクライアントで Web API メソッドを呼び出すことができます。API サイトに 220 を超えるメソッドを用意してあります。

              -
            • -
            • -

              異なるトークンの種類については、APIサイトを参照してください。アプリケーションが実行したいアクションに応じて、異なるトークンが必要になる場合があります。HTTPではなくSocket Modeを使用している場合は、connections:writeスコープを持つ追加の(xapp)トークンが必要です。

              -
            • -
            - -
            -
            - -
            - - - - - - - - \ No newline at end of file diff --git a/docs/_site/ja-jp/tutorial/getting-started.html b/docs/_site/ja-jp/tutorial/getting-started.html deleted file mode 100644 index 39feac420..000000000 --- a/docs/_site/ja-jp/tutorial/getting-started.html +++ /dev/null @@ -1,839 +0,0 @@ - - - - - - - Slack | Bolt for JavaScript - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            -
            - - -
            - - -
            - - -
            -
              -
              - -
              -

              Bolt 入門ガイド

              - -
              -

              このガイドでは、Bolt を使用して Slack アプリを起動し実行する方法について説明します。その過程で、新しい Slack アプリを作成し、ローカル環境を設定し、Slack ワークスペースからのメッセージをリッスンして応答するアプリを開発します。

              -
              - -
              -

              💡 このガイドではソケットモード を利用します。ソケットモードは、Slack アプリ開発をとりあえず始めてみるときやあなたのチームだけのためのアプリをつくるときにおすすめのやり方です。もしすでに HTTP をアプリのコミュニケーションプロトコルとしてするとわかっているなら、HTTP の方式に対応した同様のドキュメントである Bolt 入門ガイド(HTTP) を参照してください。

              -
              - -
              - -

              アプリを作成する

              -

              最初にやるべきこと: Bolt で開発を始める前に、 Slack アプリを作成します。

              - -
              -

              💡 いつもの仕事のさまたげにならないように、別に開発用のワークスペースを使用することをおすすめします — 新しいワークスペースを無料で作成できます。

              -
              - -

              アプリ名を入力し (後で変更可能)、インストール先のワークスペースを選択したら、Create App ボタンをクリックすると、アプリの Basic Information ページが表示されます。

              - -

              このページには、後で必要になる重要な認証情報 (App Credentials ヘッダーの下の Signing Secret など) に加えて、アプリケーションの概要が表示されます。

              - -

              Basic Information page

              - -

              ひと通り確認し、アプリのアイコンと説明を追加してから、アプリの設定 🔩 を始めましょう。

              - -
              - -

              トークンとアプリのインストール

              -

              Slack アプリは、OAuth を使用して、Slack の API へのアクセスを管理します。アプリがインストールされるとトークンが発行されます。そのトークンを使って、アプリは API メソッドを呼び出すことができます。

              - -

              Slack アプリで使用できるトークンには、ユーザートークン(xoxp)とボットトークン(xoxb)、アプリレベルトークン(xapp)の 3 種類があります。

              -
                -
              • ユーザートークン を使用すると、アプリをインストールまたは認証したユーザーに成り代わって API メソッドを呼び出すことができます。1 つのワークスペースに複数のユーザートークンが存在する可能性があります。
              • -
              • ボットトークン はボットユーザーに関連づけられ、1 つのワークスペースでは最初に誰かがそのアプリをインストールした際に一度だけ発行されます。どのユーザーがインストールを実行しても、アプリが使用するボットトークンは同じになります。 ほとんど のアプリで使用されるのは、ボットトークンです。
              • -
              • アプリレベルトークン は、全ての組織(とその配下のワークスペースでの個々のユーザーによるインストール)を横断して、あなたのアプリを代理するものです。アプリレベルトークンは、アプリの WebSocket コネクションを確立するためによく使われます。
              • -
              - -

              このガイドではボットトークンとアプリレベルトークンを使用します。

              - -
                -
              1. -

                左側のサイドバーの OAuth & Permissions にアクセスして、Bot Token Scopes までスクロールします。そして、Add an OAuth Scope をクリックします。

                -
              2. -
              3. -

                ここでは、chat:write というスコープだけを追加してみましょう。これは、アプリにボットユーザがメンバーとして参加しているチャンネルへのメッセージの投稿を許可するスコープです。

                -
              4. -
              5. -

                ページ上部までスクロールして戻り、Install App to Workspace をクリックします。すると、開発用のワークスペースにこのアプリをインストールするための Slack の OAuth 確認画面へと誘導されます。

                -
              6. -
              7. -

                インストールを承認すると、OAuth & Permissions ページが表示され、Bot User OAuth Access Token を確認することができるはずです。

                -
              8. -
              - -

              OAuth Tokens

              - -
              -

              💡 トークンは、パスワードのように大切に扱い、安全に保管してください。アプリではそのトークンを使用して、Slack ワークスペースからの情報を投稿および取得します。

              -
              - -
              - -

              ローカルプロジェクトの設定

              -

              初期設定が完了したので、次は新しい Bolt プロジェクトを設定します。ここで、アプリのロジックを処理するコードを記述します。

              - -

              プロジェクトをまだ作成していない場合は、新しいプロジェクトを作成しましょう。次のように、空のディレクトリを作成して、新しいプロジェクトを初期化します。

              - -
              -
              -
              1
              -2
              -3
              -
              mkdir first-bolt-app
              -cd first-bolt-app
              -npm init
              -
              -
              -
              - -

              新しいプロジェクトを説明するための一連の質問が表示されます (特に問題がなければ、各プロンプトで Enter を押すと、デフォルトを受け入れることができます)。完了すると、ディレクトリ内に新しい package.json ファイルが作成されます。

              - -

              Bolt パッケージを新しいプロジェクトにインストールする前に、アプリの設定時に生成されたボットトークンと signing secret (サイン認証) を保存しましょう。これらは環境変数として保存する必要があります。バージョン管理では保存しないでください。

              - -
                -
              1. Basic Information ページから Signing Secret をコピーして、新しい環境変数に保存します。次の例は Linux と macOS で動作します。ただし、Windows でも同様のコマンドが利用可能です。 -
                1
                -
                export SLACK_SIGNING_SECRET=<your-signing-secret>
                -
                -
              2. -
              3. OAuth & Permissions ページからボット (xoxb) トークンをコピーし、それを別の環境変数に格納します。 -
                1
                -
                export SLACK_BOT_TOKEN=xoxb-<your-bot-token>
                -
                -
              4. -
              - -
              -

              🔒 全てのトークンは安全に保管してください。少なくともパブリックなバージョン管理にチェックインするようなことは避けるべきでしょう。また、上にあった例のように環境変数を介してアクセスするようにしてください。詳細な情報は アプリのセキュリティのベストプラクティスのドキュメントを参照してください。

              -
              - -

              それでは、アプリを作成しましょう。次のコマンドを使用して、@slack/bolt パッケージをインストールし、 package.json 中で依存ファイルとして保存します。

              - -
              -
              -
              1
              -
              npm install @slack/bolt
              -
              -
              -
              - -

              このディレクトリ内に app.js という名前の新しいファイルを作成し、以下のコードを追加します。

              - -
              -
              -
              1
              -2
              -3
              -4
              -5
              -6
              -7
              -8
              -9
              -10
              -11
              -12
              -13
              -14
              -
              const { App } = require('@slack/bolt');
              -
              -// ボットトークンと Signing Secret を使ってアプリを初期化します
              -const app = new App({
              -  token: process.env.SLACK_BOT_TOKEN,
              -  signingSecret: process.env.SLACK_SIGNING_SECRET
              -});
              -
              -(async () => {
              -  // アプリを起動します
              -  await app.start(process.env.PORT || 3000);
              -
              -  console.log('⚡️ Bolt app is running!');
              -})();
              -
              -
              -
              - -

              まず実行してみましょう。 app.js ファイルを保存してから、以下のコマンドラインで動かします。

              - -
              node app.js
              -
              - -

              アプリから、起動し実行中であることが通知されます🎉

              - -
              - -

              イベントの設定

              - -

              アプリはワークスペース内の他のメンバーと同じように振る舞い、メッセージを投稿したり、絵文字リアクションを追加したり、イベントをリッスンして返答したりできます。

              - -

              Slack ワークスペースで発生するイベント(メッセージが投稿されたときや、メッセージに対するリアクションがつけられたときなど)をリッスンするには、Events API を使って特定の種類のイベントをサブスクライブします。このチュートリアルでは、ソケットモードを使用します。 Socket モードは、チームのために何かを作り始めたばかりの人にお勧めのオプションです。

              - -
              -

              💡 ソケットモードを使うことで、アプリが公開された HTTP エンドポイントを公開せずに Events API やインタラクティブコンポーネントを利用できるようになります。このことは、開発時やファイヤーウォールの裏からのリクエストを受ける際に便利です。HTTP での方式はホスティング環境(AWS or Herokuなど)にデプロイするアプリや Slack App Directory で配布されるアプリに適しています。 HTTP での情報についてはこちらのドキュメントを参照してください。

              -
              - -

              それではソケットモードを有効にします。

              - -
                -
              1. -

                アプリの設定ページに向かいます(アプリ管理ページからアプリをクリックします)。左側のメニューにある「Socket Mode」に移動し、有効に切り替えます。

                -
              2. -
              3. -

                Basic Information にアクセスし、「App Token」セクションの下にスクロールし、Generate Token and Scopes をクリックしてアプリトークンを生成します。このトークンに connections:write スコープを追加し、生成された xapp トークンを保存します。

                -
              4. -
              - -

              そして最後に、私たちがどのイベントをリッスンしたいかを Slack に伝えましょう。

              - -

              イベントが発生すると、そのイベントをトリガーしたユーザーやイベントが発生したチャンネルなど、イベントに関する情報が Slack からアプリに送信されます。アプリではこれらの情報を処理して、適切な応答を返します。

              - -

              Subscribe to Bot Events まで下にスクロールします。4つのメッセージに関するイベントがあります。

              -
                -
              • message.channels アプリが参加しているパブリックチャンネルのメッセージをリッスン
              • -
              • message.groups アプリが参加しているプライベートチャンネルのメッセージをリッスン
              • -
              • message.im あなたのアプリとユーザーのダイレクトメッセージをリッスン
              • -
              • message.mpim あなたのアプリが追加されているグループ DM をリッスン
              • -
              - -

              もしボットに参加しているすべての場所で全てのメッセージイベントをリッスンさせたいなら、これら4つ全てのイベントを選んでください。選択したら、緑の Save Changes ボタンをクリックします。

              - -
              - -

              メッセージのリスニングと応答

              -

              これで、アプリでいくつかのロジックを設定する準備が整いました。まずは message() メソッドを使用して、メッセージのリスナーをアタッチしましょう。

              - -

              次の例では、あなたのアプリが追加されているチャンネルや DM で hello という単語を含むすべてのメッセージをリッスンし、 Hey there @user! と応答します。

              - -
              -
              -
              1
              -2
              -3
              -4
              -5
              -6
              -7
              -8
              -9
              -10
              -11
              -12
              -13
              -14
              -15
              -16
              -17
              -18
              -19
              -20
              -21
              -22
              -23
              -24
              -
              const { App } = require('@slack/bolt');
              -
              -const app = new App({
              -  token: process.env.SLACK_BOT_TOKEN,
              -  signingSecret: process.env.SLACK_SIGNING_SECRET,
              -  socketMode: true,
              -  appToken: process.env.SLACK_APP_TOKEN,
              -  // ソケットモードではポートをリッスンしませんが、アプリを OAuth フローに対応させる場合、
              -  // 何らかのポートをリッスンする必要があります
              -  port: process.env.PORT || 3000
              -});
              -
              -// "hello" を含むメッセージをリッスンします
              -app.message('hello', async ({ message, say }) => {
              -  // イベントがトリガーされたチャンネルに say() でメッセージを送信します
              -  await say(`Hey there <@${message.user}>!`);
              -});
              -
              -(async () => {
              -  // アプリを起動します
              -  await app.start();
              -
              -  console.log('⚡️ Bolt app is running!');
              -})();
              -
              -
              -
              - -

              アプリを再起動したら、ボットユーザーをチャンネル、 DM に追加し、 hello を含むメッセージを送信してみてください。アプリが応答したら成功です。

              - -

              これは基本的な例ですが、ここから自分の好きなようにアプリをカスタマイズしていくことができます。さらにインタラクティブな動作を試すために、プレーンテキストではなくボタンを送信してみましょう。

              - -
              - -

              アクションの送信と応答

              - -

              ボタン、選択メニュー、日付ピッカー、モーダルなどの機能を使用するには、インタラクティブ機能を有効にする必要があります。イベントと同様に、Slack の URL を指定してアクション ( 「ボタン・クリック」など) を送信する必要があります。

              - -
              -

              💡 ソケットモードを有効にしているとき、デフォルトで基本的なインタラクティブ機能が有効になっていため、ここでは特に何もする必要はいありません。もし HTTP を使っている場合、Slack からのイベント送信先である Request URL を設定する必要があります。

              -
              - -

              インタラクティブ機能が有効化されていると、ショートカット、モーダル、インタラクティブコンポーネント (例:ボタン、選択メニュー、日付ピッカーなど) とのインタラクションがイベントとしてあなたのアプリに送信されます。

              - -

              それでは、アプリのコードに戻り、インタラクティブな処理を追加しましょう。この実装は以下の二つのステップで構成されます。

              -
                -
              • 最初に、アプリからボタンを含むメッセージを送信します。
              • -
              • 次に、ユーザーがボタンをクリックしたときの動作をアプリでリッスンし、応答します。
              • -
              - -

              以下は、前のセクションで記述したアプリコードを、文字列だけでなく、ボタン付きのメッセージを送信するように変更したものです。

              - -
              -
              -
              1
              -2
              -3
              -4
              -5
              -6
              -7
              -8
              -9
              -10
              -11
              -12
              -13
              -14
              -15
              -16
              -17
              -18
              -19
              -20
              -21
              -22
              -23
              -24
              -25
              -26
              -27
              -28
              -29
              -30
              -31
              -32
              -33
              -34
              -35
              -36
              -37
              -38
              -39
              -40
              -41
              -42
              -43
              -
              const { App } = require('@slack/bolt');
              -
              -const app = new App({
              -  token: process.env.SLACK_BOT_TOKEN,
              -  signingSecret: process.env.SLACK_SIGNING_SECRET,
              -  socketMode: true,
              -  appToken: process.env.SLACK_APP_TOKEN,
              -  // ソケットモードではポートをリッスンしませんが、アプリを OAuth フローに対応させる場合、
              -  // 何らかのポートをリッスンする必要があります
              -  port: process.env.PORT || 3000
              -});
              -
              -// "hello" を含むメッセージをリッスンします
              -app.message('hello', async ({ message, say }) => {
              -  // イベントがトリガーされたチャンネルに say() でメッセージを送信します
              -  await say({
              -    blocks: [
              -      {
              -        "type": "section",
              -        "text": {
              -          "type": "mrkdwn",
              -          "text": `Hey there <@${message.user}>!`
              -        },
              -        "accessory": {
              -          "type": "button",
              -          "text": {
              -            "type": "plain_text",
              -            "text": "Click Me"
              -          },
              -          "action_id": "button_click"
              -        }
              -      }
              -    ],
              -    text: `Hey there <@${message.user}>!`
              -  });
              -});
              -
              -(async () => {
              -  // アプリを起動します
              -  await app.start();
              -
              -  console.log('⚡️ Bolt app is running!');
              -})();
              -
              -
              -
              - -

              say() に格納されている値が、 blocks の配列を含むオブジェクトになりました。このブロックは Slack メッセージを構成するコンポーネントであり、テキストや画像、日付ピッカーなど、さまざまなタイプがあります。この例では、アプリは、ボタンを accessory として含むセクションブロックを使用して応答します。blocks を使っている場合、 text は通知やアクセシビリティのためのフォールバックとして使用されます。

              - -

              このボタン accessory オブジェクトには、action_id が割り当てられています。これはボタンの一意の識別子として機能するため、アプリはどのアクションに応答するかを指定できます。

              - -
              -

              💡 Block Kit ビルダーを使うとインタラクティブメッセージを簡単にプロトタイプすることができます。ビルダーを使用すると、ユーザー (またはそのチームメンバー) はメッセージをモックアップして、対応する JSON を生成し、それをアプリに直接貼り付けることができます。

              -
              - -

              これで、アプリを再起動し、アプリが登録されているチャンネルで hello と入力すると、ボタン付きのメッセージが表示されます。ただしこのボタンをクリックしても、まだ何も起こりません。

              - -

              ボタンがクリックされるとフォローアップメッセージを送信するハンドラーを追加してみましょう。

              - -
              -
              -
              1
              -2
              -3
              -4
              -5
              -6
              -7
              -8
              -9
              -10
              -11
              -12
              -13
              -14
              -15
              -16
              -17
              -18
              -19
              -20
              -21
              -22
              -23
              -24
              -25
              -26
              -27
              -28
              -29
              -30
              -31
              -32
              -33
              -34
              -35
              -36
              -37
              -38
              -39
              -40
              -41
              -42
              -43
              -44
              -45
              -46
              -47
              -48
              -49
              -
              const { App } = require('@slack/bolt');
              -
              -const app = new App({
              -  token: process.env.SLACK_BOT_TOKEN,
              -  signingSecret: process.env.SLACK_SIGNING_SECRET,
              -  socketMode: true,
              -  appToken: process.env.SLACK_APP_TOKEN,
              -  // ソケットモードではポートをリッスンしませんが、アプリを OAuth フローに対応させる場合、
              -  // 何らかのポートをリッスンする必要があります
              -  port: process.env.PORT || 3000
              -});
              -
              -// "hello" を含むメッセージをリッスンします
              -app.message('hello', async ({ message, say }) => {
              -  // イベントがトリガーされたチャンネルに say() でメッセージを送信します
              -  await say({
              -    blocks: [
              -      {
              -        "type": "section",
              -        "text": {
              -          "type": "mrkdwn",
              -          "text": `Hey there <@${message.user}>!`
              -        },
              -        "accessory": {
              -          "type": "button",
              -          "text": {
              -            "type": "plain_text",
              -            "text": "Click Me"
              -          },
              -          "action_id": "button_click"
              -        }
              -      }
              -    ],
              -    text: `Hey there <@${message.user}>!`
              -  });
              -});
              -
              -app.action('button_click', async ({ body, ack, say }) => {
              -  // アクションのリクエストを確認
              -  await ack();
              -  await say(`<@${body.user.id}> clicked the button`);
              -});
              -
              -(async () => {
              -  // アプリを起動します
              -  await app.start();
              -
              -  console.log('⚡️ Bolt app is running!');
              -})();
              -
              -
              -
              - -

              このように、app.action() を使うことで button_click という action_id のボタンアクションのリスナーを追加できるのです。アプリを再起動してボタンをクリックしてみましょう。すると、you clicked the button という新しいメッセージがアプリに表示されるはずです。

              - -
              - -

              次のステップ

              -

              これで最初の Bolt アプリをソケットモードを使って構築できました! 🎉

              - -

              基本的なアプリの作成ができましたので、次回は是非もっといろいろな、 Bolt の機能を使ってアプリを作ってみましょう。下記のリンクを辿っていろいろアイデアを模索してみてください!

              - -
                -
              • -

                基本的な概念をお読みください。Bolt アプリからアクセスできるさまざまなメソッドと機能について学ぶことができます。

                -
              • -
              • -

                ボットがevents() メソッドでリッスンできるさまざまなイベントを確認しましょう。イベントはすべてAPI サイトにリストされています。

                -
              • -
              • -

                Bolt を使用すると、アプリにアタッチされているクライアントで Web API メソッドを呼び出すことができます。API サイトに 220 を超えるメソッドを用意してあります。

                -
              • -
              • -

                API サイトでは、様々なトークンタイプの詳細を確認することができます。アプリには、実行するアクションに応じて異なるトークンが必要になる場合があります。ソケットモードを使わないアプリでは、通常はボットトークン (xoxb) と署名シークレットが必要です。ソケットモードを使わない場合の例については、 HTTP 方式のやり方としてこのチュートリアルと対になっている Bolt 入門ガイド(HTTP)を参照してください。

                -
              • -
              - -
              -
              - -
              - - - - - - - - \ No newline at end of file diff --git a/docs/_site/ja-jp/tutorial/hubot-migration.html b/docs/_site/ja-jp/tutorial/hubot-migration.html deleted file mode 100644 index a9374d934..000000000 --- a/docs/_site/ja-jp/tutorial/hubot-migration.html +++ /dev/null @@ -1,532 +0,0 @@ - - - - - - - Slack | Bolt for JavaScript - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              -
              - - -
              - - -
              - - -
              -
                -
                - -
                -

                Hubot のアプリを Bolt に移行する方法

                - -
                -

                Bolt は、Slack アプリを構築する時間と手間を減らすために作成されたフレームワークで、Slack 開発者のみなさんに最新機能とベストプラクティスを使用してアプリを構築できる単一のインターフェイスを提供します。このガイドでは、Hubot で作成されたアプリを Bolt アプリに移行するプロセスを順を追って説明します。

                - -

                すでに ボットユーザーがいるアプリ を持っている方、または Hubot コードを Bolt コードに変換するコードサンプルをお探しの方は、はじめにBolt リポジトリのサンプルスクリプト を読むとよいでしょう。

                -
                - -
                - -

                まずはじめに

                -

                Hubot アプリを Bolt に変換するとき、それぞれが内部的にどのように機能しているかを把握しているとさらに理解を深めることができるでしょう。Slack の Hubot アダプターは、 WebSocket をベースとした RTM API と接続するように実装されているので、Hubot アプリには一連のワークスペースイベントが一気にストリーミングされます。そして、RTM API は、新しいプラットフォーム機能をサポートしておらず、特にアプリが複数のまたは大規模な Slack チームにインストールされる場合には、膨大なリソースを消費する可能性があるため、ほとんどのユースケースでおすすめできません。

                - -

                デフォルトの Bolt レシーバーは、Events API をサポートするように構築されています。これは、HTTP ベースのイベントサブスクリプションを使用して Bolt アプリに JSON ペイロードを送信します。Events API には、RTM にはない新機能のイベントも含まれており、より細かい制御が可能でスケーラブルですのでほとんどのユースケースで推奨されています。しかし例外として、RTM API を使用し続けなければならない理由の 1 つに、アプリをホストしているサーバーにファイアウォールがあり、HTTP 送信リクエストのみを許可して、受信リクエストを許可しないというようなケースが挙げられます。

                - -

                Bolt アプリを作成する前に考慮に入れた方がよい違いがほかにもあります。

                -
                  -
                • Bolt は Node v10.0.0 以上で動作します。アプリをホストしているサーバーが、v10 をサポートできない場合は、現時点でアプリを Bolt に移行することはできません。
                • -
                • Bolt は、外部スクリプトをサポートしていません。Hubot アプリがアプリの機能または展開に必要な外部スクリプトを使用している場合、当面は Hubot のままでいいと思われます。アプリに外部スクリプトがあるかどうかわからない場合は、external-scripts.json ファイルをチェックしてください。Slack は Bolt の開発を続けていきますので、将来的にどう改良し続けていくかを常に検討しています。外部スクリプトでどうしても必要、というリクエストなどがある場合、専用の Github の Issues で要望を 聞かせてください
                • -
                • Hubot アプリは、CoffeeScript で書かれており、JavaScript にトランスパイルされます。Slack は、Bolt を TypeScript で書くことでリッチな型情報にアクセスできるようにしました。Bolt アプリは、TypeScript または JavaScript を使用して開発できます。こちらの サンプルスクリプト は、CoffeeScript がどのように JavaScript に変換されるかを示しています。あなたのアプリが比較的複雑なスクリプトである場合、Decaffeinate などのプロジェクトを調べて、CoffeeScript を JavaScript に変換するとよいかもしれません。
                • -
                - -
                - -

                ボットの設定

                -

                ボットユーザーを持つ既存の Slack アプリをお持ちの方は、次のセクションに進むことができます。わからない場合は、App Management ページ に移動し、自分の Hubot アプリがあるかどうかを確認してください。ある場合は、そのアプリの認証情報をそのまま使用できます (次のセクションに進んでください)。ない場合は、下記の手順通りに進めていきましょう。

                - -

                Slack アプリを作成する

                -

                まず最初に、Slack アプリを作成します。

                - -
                -

                💡ここでは普段の仕事の支障にならないように、開発専用のワークスペースを使用することをおすすめします — 新しいワークスペースの作成はここから

                -
                - -

                アプリ名を入力し、インストール先のワークスペースを選択したら、Create App ボタンをクリックします。そうすると、アプリの Basic Information ページが表示されます。

                - -

                このページには、後で必要になる重要な認証情報 (App Credentials ヘッダーの下の Signing Secret など) に加えて、アプリケーションの概要が表示されます。

                - -

                ひと通り確認し、アプリのアイコンと説明を追加したら、アプリの構成 🔩 を始めましょう。

                - -

                ボットユーザーを追加する

                -

                Slack では、Hubot アプリはユーザーとの対話型のボットユーザーを採用しています。

                - -

                新しいアプリにボットユーザーを追加するには、左側のサイドバーの Bot Users をクリックしてから、Add A Bot User をクリックします。表示名とユーザー名を指定して、Add Bot User をクリックします。その他のフィールドの詳しい情報は、API サイト をご覧ください。

                - -

                ボットの設定

                -

                Events API は、ボットの目と耳に相当します。これによりボットは、投稿されたメッセージ、チャンネルの変更、Slack で発生するその他のアクティビティに反応することができます。

                - -
                -

                ⚠️ボットのイベントを設定する前に、パブリック URL が必要です。Bolt アプリを作成したことがない場合、または Events API を使用したことがない場合は、『Getting Started ガイド』の ローカル Bolt プロジェクトの設定イベントの設定 を参考にしてください。

                -
                - -

                メッセージのリスニング

                -

                すべての Hubot アプリは、デフォルトでメッセージをリッスンできるので、ボットユーザーがそうするように設定する必要があります。

                - -

                イベントの設定 を行ってから、リクエスト URL を入力、そして検証されたことを確認したら、Subscribe to Bot Events にスクロールダウンします。メッセージに関連する次の 4 つのイベントがあります message channel (パブリックチャンネルのメッセージをリッスン)、message group (プライベートチャンネルのメッセージをリッスン)、message.im (アプリのホーム/DM スペースのメッセージをリッスン)、message.mpim (マルチパーソン DM のメッセージをリッスン)。

                - -

                ボットがチャンネルのメッセージをリッスンするだけでよい場合は、message.channelsmessage.groups をリッスンできます。または、ボットがすべての場所のメッセージをリッスンするようにするには、4 つのメッセージイベントすべてを選択します。

                - -

                ボットにリッスンさせるメッセージイベントの種類を追加して、Save Changes をクリックします。

                - -

                その他のイベントのリッスン

                -

                使用していた機能に応じて、Hubot アプリはほかのイベントにも応答していたかもしれません。スクリプトを調べて、reactrespondpresenceChange が使用されている箇所を特定してください。

                -
                  -
                • アプリで respond が使用されている場合、app_mention イベントをサブスクライブします。これで、ボットユーザーがメンションされる時をリッスンします。
                • -
                • アプリで react が使用されている場合、reaction_added イベントをサブスクライブします。これにより、ボットユーザーがいるチャンネルのメッセージにリアクションが追加される時をリッスンします。
                • -
                • アプリで presenceChange が使用されている場合、対応するイベントはありません。このイベントがあなたのボットの機能上重要な場合は、Hubot の使用を継続するか、アプリのロジックを変更する必要があるかもしれません。
                • -
                - -
                -

                💡Bolt に追加された利点として、どの Events API イベント でもリッスンできることが挙げられます。移行が完了すれば、ユーザーがワークスペースに参加したときユーザーがアプリで DM を開いたとき など、より多くのイベントをリッスンできます。

                -
                - -

                アプリの機能に対応するイベントを追加 し終えたら、Save Changes をクリックします。

                - -

                スクリプトインターフェイスの変更

                -

                Bolt のインターフェイスは、可能な限り Slack API 言語に適合するように設計されましたが、Hubot は複数のサービスを抽象化するために一般化された言語を使用して設計されました。インターフェイスは似ていますが、Hubot スクリプトを Bolt スクリプトに変換するには、いくらかコードを変更する必要があります。

                - -

                Bolt は、res を使用せず、Slack からの raw リクエストを公開しません。代わりに、payload 使ってペイロードボディを取得したり、say() を使ってメッセージを送信するといった一般的な機能を使用したりできます。

                - -
                -

                ⚙わかりやすくするために、サンプルスクリプトを Github 上に作成しました。このスクリプトは、Bolt 用に書かれた機能と同等のものを使用している Hubot のコア機能を紹介しています。

                -
                - -

                -message() を使用したパターンのリスニング

                -

                Hubot スクリプトは、hear() を使用して、一致するパターンを持つメッセージをリッスンします。代わりに、 Bolt は message() を使用して、そのパターンの string または RegExp を受け入れます。

                - -
                -

                👨‍💻👩‍💻コードで hear() を使用している箇所はすべて、message() を使用するように変更してください。

                -
                - -

                メッセージのリスニングについてもっと詳しく読む.

                - -

                -say() および respond() を使用したメッセージで応答する

                -

                Hubot スクリプトは、send() を使用してメッセージを同じ会話に送信し、reply() を使用して、元のメッセージを送信したユーザー宛の@メンションを付けて、メッセージを同じ会話上に送信します。

                - -

                Bolt は、send() の代わりに say() を使用し、respond() を使用して response_url で返信を送信します。返信の冒頭にメンションを追加するには、context オブジェクトにあるユーザー ID を使用できます。たとえば、メッセージイベントの場合は次のようにできます: say('<@${message.user}>Hello :wave:')

                - -

                Hubot の send() と Bolt の say() はほとんど同じですが、say() を使用すると ボタン、メニューの選択、デートピッカー といったインタラクティブなコンポーネントを付けてメッセージを送信できます。

                - -
                -

                👨‍💻👩‍💻コードで send() が使用されている箇所はすべて say() に変更してください

                -
                - -

                メッセージへの応答についてもっと詳しく読む.

                - -

                -respondreact -

                -

                前のセクションで、Hubot スクリプトで respond() が使用されている場合は app_mention イベントを、react() が使用されている場合は reaction_added をサブスクライブするようにアプリを設定しました。

                - -

                Bolt は、event() と呼ばれるメソッドを使用して、任意の Events API イベント をリッスンできます。コードを変更するには、respond() を app.event(‘app_mention’) に、react()app.event(‘reaction_added’) に変更するだけです。この点は、サンプルスクリプト で詳しく説明されています。

                - -
                -

                👨‍💻👩‍💻コードで respond() が使用されている箇所はすべて、app.event (‘app_mention’) を使用するように変更してください。react が使用されている箇所はすべて app.event('reaction_added') に変更してください。

                -
                - -

                イベントのリッスンについてもっと詳しく読む.

                - -

                Bolt で Web API メソッドを使用する

                -

                Hubot では、@slack/client から WebClient パッケージをインポートする必要がありました。Bolt では、app.client からアクセスできる WebClient インスタンスがデフォルトでインポートされます。

                - -

                組み込みの WebClient を使用するには、アプリをインスタンス化するために使用されるトークン、またはリクエストの送信元のチームに関連付けられているトークンを渡す必要があります。これは、リスナー関数に渡された context オブジェクトにあります。たとえば、メッセージにリアクションを追加するには、次を使用します:

                - -
                -
                -
                
                -
                -
                -
                1
                -2
                -3
                -4
                -5
                -6
                -7
                -8
                -9
                -10
                -11
                -12
                -13
                -
                app.message('react', async ({ message, context, logger }) => {
                -  try {
                -    const result = await app.client.reactions.add({
                -      token: context.botToken,
                -      name: 'star',
                -      channel: message.channel,
                -      timestamp: message.ts
                -    });
                -  }
                -  catch (error) {
                -    logger.error(error);
                -  }
                -});
                -
                -
                -
                - -
                -

                👨‍💻👩‍💻app.client で組み込みのクライアントを使用するように、Web API 呼び出しを変更してください。

                -
                - -

                Bolt での Web API の使用についてもっと詳しく読む。

                - -

                Bolt でのミドルウェアの使用

                -

                Hubot には、受信 (リスナーが呼び出される前に実行される)、リスナー (一致するすべてのリスナーに対して実行される)、応答 (送信されるすべての応答に対して実行される) という 3 種類のミドルウェアがあります。

                - -

                Bolt には、グローバルとリスナーという 2 種類のミドルウェアしかありません。

                - - -

                Bolt では、グローバルとリスナーというミドルウェアはいずれも、await next() を呼び出して実行の制御を次のミドルウェアに渡す必要があります。ミドルウェアが実行中にエラーを検出した場合、Errornext() に渡すことができ、エラーはその前に実行されたミドルウェアチェーンにバブルアップされます。

                - -

                既存のミドルウェア関数を移行するには、Hubot の受信ミドルウェアは、Bolt のグローバルミドルウェアのユースケースと対応しています。Hubot と Bolt のリスナーミドルウェアは、ほぼ同じです。Hubot の応答ミドルウェアを移行するには、後処理関数と呼ばれる Bolt のコンセプトを使用します。

                - -

                ミドルウェアがイベントの後処理を実行する必要がある場合、undefined で呼び出すのではなく、後処理関数を使用して await next() を呼び出すことができます。後処理関数は、ミドルウェア関数が await next() を呼び出すのと同じ方法で done() を呼び出す必要があります(Error で呼び出すことも可能) 。

                - -

                Brain を conversation store に移行する

                -

                Hubot には、brain と呼ばれるメモリ内ストレージがあります。これによって、Hubot スクリプトはデータの基本部分を get および set することができます。Bolt は、conversation store と呼ばれる、get()/set() インターフェイスを含むグローバルミドルウェアを使用します。

                - -

                デフォルトの組み込み conversation store は Hubot に似たメモリ内ストレージを使用し、ミリ秒単位で有効期限を設定できます。conversation の状態情報を get および set する方法は 2 つあります。

                -
                  -
                • conversation ID を使用して app.convoStore.get() を呼び出して conversation の状態情報を取得する方法と、conversation ID、 conversation の状態情報 (キーと値のペア) 、オプションで expriesAt 時間 (ミリ秒) を使用して app.convoStore.set() を呼び出す方法です。
                • -
                • リスナーミドルウェアでは、context.updateConversation() を呼び出して更新されたconversation の状態情報を得るか、context.conversation を使用して現在のconversation の状態情報にアクセスします。
                • -
                - -

                アプリのインスタンスが複数実行されている場合、組み込みの conversation store はプロセス間で共有されないため、データベースから conversation の状態を取得する conversation store を実装することをおすすめします。

                - -

                会話ストアについてもっと詳しく読む.

                - -

                次のステップ

                -

                ここまで来れば、きっと Hubot アプリを Bolt アプリに変換できているはずです!✨⚡

                - -

                新しくなってよりクールになった Bolt アプリを、さらにパワーアップしていくこともできます。

                -
                  -
                • -ボタンやメニュー選択 などの双方向のインタラクションを追加することを検討してください。これらの機能は、Hubot ではサポートされていませんでしたが、アプリが Slack にメッセージを送信するときにコンテキストアクションを含めることができるようになります。
                • -
                • こちらの ドキュメント を読んで、Bolt でほかに何ができるか探してみてください。
                • -
                • イベントやインタラクティブコンポーネントの使用方法を示す サンプルアプリ をチェックしてみてください。
                • -
                - -

                開発中に問題が発生した場合は、Slack の開発者サポートチームdevelopers@slack.comまでお問合せください。フレームワークで問題が発生した場合は、Githubで issues を開いてください

                - -
                -
                - -
                - - - - - - - - \ No newline at end of file diff --git a/docs/_site/ja-jp/tutorial/migration-v2.html b/docs/_site/ja-jp/tutorial/migration-v2.html deleted file mode 100644 index 5ea17265f..000000000 --- a/docs/_site/ja-jp/tutorial/migration-v2.html +++ /dev/null @@ -1,521 +0,0 @@ - - - - - - - Slack | Bolt for JavaScript - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                -
                - - -
                - - -
                - - -
                -
                  -
                  - -
                  -

                  2.x マイグレーションガイド

                  - -
                  -

                  このガイドは Bolt 1.x を利用しているアプリを 2.x にアップグレードするための手順について説明します。いくつかの変更が必要とはなりますが、ほとんどのアプリの場合で、おそらく対応に必要な時間は 5 〜 15 分程度です。

                  - -

                  注: もしすぐにアップグレードをしない場合は、Bolt 1.x に関するサポートスケジュールをご確認ください

                  -
                  - -
                  - -

                  リスナー関数を async 関数に変更

                  - -

                  Bolt アプリ内のリスナー関数は、全て async 関数に変更する必要があります。そして、そのリスナー関数内の say()respond()ack() メソッドの呼び出しも全て await を呼び出しの前につける必要があります。

                  - -

                  これまで:

                  - -
                  -
                  -
                  1
                  -2
                  -3
                  -4
                  -
                  app.action('some-action-id', ({action, ack, say}) => {
                  -  ack();
                  -  say('hello world');
                  -})
                  -
                  -
                  -
                  - -

                  これから:

                  - -
                  -
                  -
                  1
                  -2
                  -3
                  -4
                  -
                  app.action('some-action-id', async ({action, ack, say}) => {
                  -  await ack();
                  -  await say('hello world');
                  -})
                  -
                  -
                  -
                  - -

                  エラーハンドリング

                  - -

                  Bolt for JavaScript 2.x では、より多くのユースケースで、必要に応じてエラーをキャッチし、グローバルエラーハンドラーにそれを送るかを制御できるよう改善されました。これまでと同様、グローバルエラーハンドラーに全て任せるよりは、可能な限り、リスナー関数の内部でエラーに対処することをおすすめします。

                  - -

                  リスナー関数内で try/catch 節を用いたエラーハンドリング

                  - -
                  -
                  -
                  1
                  -2
                  -3
                  -4
                  -5
                  -6
                  -7
                  -8
                  -9
                  -
                  app.action('some-action-id', async ({action, ack, say, logger}) => {
                  -  try {
                  -    await ack();
                  -    await say('hello world');
                  -  } catch (error) {
                  -    // ここでエラーに対処
                  -    logger.error(error);
                  -  }
                  -})
                  -
                  -
                  -
                  - -

                  グローバルエラーハンドラーによるエラーハンドリング

                  - -
                  -
                  -
                  1
                  -2
                  -3
                  -4
                  -
                  app.error(async (error) => {
                  -  // エラーの詳細をチェックして、メッセージ送信のリトライやアプリの停止などの対処を行う
                  -  console.error(error);
                  -});
                  -
                  -
                  -
                  - -

                  その他のエラーに関する変更:

                  - -
                    -
                  • リスナー関数が ack() メソッドを 3 秒間のうちに呼び出さなかった場合、これまでのように例外を投げるのではなくログを出力するようになりました
                  • -
                  • もし一つのイベントに対して複数のリスナー関数を実行中に複数のエラーが発生した場合、Bolt for JavaScript は ErrorCode.MultipleListenerError の値での code と、発生した個々のエラーの配列を含む originals というパラメーターをラップしたエラーを返します
                  • -
                  - -

                  メッセージショートカット

                  - -

                  メッセージショートカット (以前はメッセージアクションと呼ばれていました)は、これまで action() メソッドでハンドリングしていましたが shortcut() メソッドを使うようになりました。

                  - -

                  これまで:

                  - -
                  -
                  -
                  1
                  -2
                  -3
                  -4
                  -
                  app.action({ callback_id: 'message-action-callback' }, ({action, ack, context}) => {
                  -  ack();
                  -  // ここで処理を行う
                  -})
                  -
                  -
                  -
                  - -

                  これから:

                  - -
                  -
                  -
                  1
                  -2
                  -3
                  -4
                  -
                  app.shortcut('message-action-callback', async ({shortcut, ack, context}) => {
                  -  await ack();
                  -  // ここで処理を行う
                  -})
                  -
                  -
                  -
                  - -

                  ミドルウェアに関する変更

                  - -

                  もしカスタムのミドルウェアを書いている場合は、その関数を async に変更し、さらに next() の呼び出しを await next() に変更してください。もし後続の処理がある場合は、関数を next() に渡す代わりに、その後続の処理を await next() の後に実行してください。

                  - -

                  これまで:

                  - -
                  -
                  -
                  1
                  -2
                  -3
                  -4
                  -5
                  -6
                  -7
                  -8
                  -
                  function noBotMessages({message, next }) {
                  -  function doAfter() {
                  -    // 後続の処理をここでやる
                  -  }
                  -if (!message.subtype || message.subtype !== 'bot_message') {
                  -    next(doAfter);
                  -  }
                  -}
                  -
                  -
                  -
                  - -

                  これから:

                  - -
                  -
                  -
                  1
                  -2
                  -3
                  -4
                  -5
                  -6
                  -
                  async function noBotMessages({message, next }) {
                  -  if (!message.subtype || message.subtype !== 'bot_message') {
                  -    await next();
                  -    // 後続の処理をここでやる
                  -  }
                  -}
                  -
                  -
                  -
                  - -

                  Bolt 1.x のサポートスケジュール

                  - -

                  @slack/bolt@1.x2020 年 6 月 30 日 より非推奨となります。それまでの期間はケースバイケースでバグ修正や新機能のバックポートを対応を継続します。@slack/bolt@1.x が非推奨となった後は、End of life(正式サポートの終了日)まで クリティカルなバグ修正のみ を実装し、クリティカルではない issue や pull request はクローズします。End of life は 2021 年 4 月 30 日 の予定です。この日からは @slack/bolt@1.x の開発は完全に終了となり、残っている open issue や pull request もクローズされます。

                  - -

                  TypeScript の最低必須バージョン

                  - -

                  TypeScript 利用ガイド でも説明していますが、@slack/bolt@2.x は TypeScirpt 3.7 以上が必須バージョンです。

                  - -
                  -
                  - -
                  - - - - - - - - \ No newline at end of file diff --git a/docs/_site/ja-jp/tutorial/migration-v3.html b/docs/_site/ja-jp/tutorial/migration-v3.html deleted file mode 100644 index 324c46670..000000000 --- a/docs/_site/ja-jp/tutorial/migration-v3.html +++ /dev/null @@ -1,507 +0,0 @@ - - - - - - - Slack | Bolt for JavaScript - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                  -
                  - - -
                  - - -
                  - - -
                  -
                    -
                    - -
                    -

                    3.x マイグレーションガイド

                    - -
                    -

                    このガイドは Bolt 2.x を利用しているアプリを 3.x にアップグレードするための手順について説明します。いくつかの変更が必要とはなりますが、ほとんどのアプリの場合で、おそらく対応に必要な時間は 5 〜 15 分程度です。

                    - -

                    注: もしすぐにアップグレードをしない場合は、Bolt 2.x に関するサポートスケジュールをご確認ください

                    -
                    - -
                    - -

                    InstallationStore と orgAuthorize での OrG レベルでのインストール対応に関する変更

                    - -

                    Bolt for JavaScript 2.5.0 で、私たちは OrG レベルでのインストールのサポートを追加しました。このサポートをあなたのアプリケーションに追加するには、OAuth フローの中で使用される fetchOrgInstallationstoreOrgInstallation という二つの新しいメソッドを導入する必要がありました。 3.x では、よりシンプルなインタフェースの実現と Bolt for Python、Bolt for Java との互換性を考慮して、これらの二つの新しいメソッドのサポートを廃止しました。マイグレーションに必要となる変更については以下のコード例を参考にしてください。

                    - -

                    これまで:

                    - -
                    -
                    -
                    1
                    -2
                    -3
                    -4
                    -5
                    -6
                    -7
                    -8
                    -9
                    -10
                    -11
                    -12
                    -13
                    -14
                    -15
                    -16
                    -17
                    -18
                    -19
                    -20
                    -
                    installationStore: {
                    -    storeInstallation: async (installation) => {
                    -      // change the line below so it saves to your database
                    -      return await database.set(installation.team.id, installation);
                    -    },
                    -    fetchInstallation: async (installQuery) => {
                    -      // change the line below so it fetches from your database
                    -      return await database.get(installQuery.teamId);
                    -    },
                    -    storeOrgInstallation: async (installation) => {
                    -      // include this method if you want your app to support org wide installations
                    -      // change the line below so it saves to your database
                    -      return await database.set(installation.enterprise.id, installation);
                    -    },
                    -    fetchOrgInstallation: async (installQuery) => {
                    -      // include this method if you want your app to support org wide installations
                    -      // change the line below so it fetches from your database
                    -      return await database.get(installQuery.enterpriseId);
                    -    },
                    -  },
                    -
                    -
                    -
                    - -

                    これから:

                    - -
                    -
                    -
                    1
                    -2
                    -3
                    -4
                    -5
                    -6
                    -7
                    -8
                    -9
                    -10
                    -11
                    -12
                    -13
                    -14
                    -15
                    -16
                    -17
                    -18
                    -19
                    -20
                    -21
                    -22
                    -23
                    -24
                    -25
                    -
                    installationStore: {
                    -    storeInstallation: async (installation) => {
                    -      if (installation.isEnterpriseInstall && installation.enterprise !== undefined) {
                    -        // support for org wide app installation
                    -        return await database.set(installation.enterprise.id, installation);
                    -      }
                    -      if (installation.team !== undefined) {
                    -        // single team app installation
                    -        return await database.set(installation.team.id, installation);
                    -      }
                    -      throw new Error('Failed saving installation data to installationStore');
                    -    },
                    -    fetchInstallation: async (installQuery) => {
                    -      // replace database.get so it fetches from your database
                    -      if (installQuery.isEnterpriseInstall && installQuery.enterpriseId !== undefined) {
                    -        // org wide app installation lookup
                    -        return await database.get(installQuery.enterpriseId);
                    -      }
                    -      if (installQuery.teamId !== undefined) {
                    -        // single team app installation lookup
                    -        return await database.get(installQuery.teamId);
                    -      }
                    -      throw new Error('Failed fetching installation');
                    -    },
                    -  },
                    -
                    -
                    -
                    - -

                    この変更に合わせて orgAuthorize 関数のサポートも廃止しました。もし、組み込みの OAuth 機能を利用されていない場合は、代わりに authorize だけを単一のワークスペースへのインストールでも OrG レベルでのインストールでも使うように変更することを推奨します。マイグレーションの手順については、以下のコード例を参考にしてください。

                    - -

                    これまで:

                    - -
                    -
                    -
                    1
                    -2
                    -3
                    -4
                    -5
                    -6
                    -7
                    -
                    const app = new App({ authorize: authorizeFn, orgAuthorize: orgAuthorizeFn, signingSecret: process.env.SLACK_SIGNING_SECRET });
                    -const authorizeFn = async ({ teamId, enterpriseId}) => { 
                    -  // Use teamId to fetch installation details from database
                    -}
                    -const orgAuthorizeFn = async ({ teamId, enterpriseId }) => { 
                    -  // Use enterpriseId to fetch installation details from database
                    -}
                    -
                    -
                    -
                    - -

                    これから:

                    -
                    -
                    -
                    1
                    -2
                    -3
                    -4
                    -5
                    -
                    const app = new App({ authorize: authorizeFn, signingSecret: process.env.SLACK_SIGNING_SECRET });
                    -const authorizeFn = async ({ teamId, enterpriseId, isEnterpriseInstall}) => { 
                    -  // if isEnterpriseInstall is true, use enterpriseId to fetch installation details from database
                    -  // else, use teamId to fetch installation details from database
                    -}
                    -
                    -
                    -
                    - -

                    デフォルトのレシーバーを HTTPReceiver に変更

                    - -

                    3.x から新しい HTTPReceiver というレシーバーを導入し、デフォルトのレシーバー実装を、これまでの ExpressReceiver からこのレシーバーに変更します。この変更は、Bolt for JavaScript を Express.js 以外の人気のある Web フレームワーク(Hapi.js や Koa など)とともに動作させることを容易にします。ExpressReceiver は引き続き Bolt for JavaScript のリリースに含まれます。また、HTTPReceiverExpressReceiver が提供する全ての機能を提供するわけではありません。例えば、一つのユースケースとしては、HTTPReceiver ではカスタムの HTTP ルート(例: ヘルスチェックのための URL を追加する)を追加する機能はサポートされていません。このようなユースケースに対応するためには、引き続き ExpressReceiver を利用することを推奨します。その場合はクラスを import して、インスタンス化したものを App のコンストラクタに渡してください。詳細はカスタム HTTP ルートの追加を参考にしてください。

                    - -

                    Bolt 2.x のサポートスケジュール

                    - -

                    @slack/bolt@2.x2021 年 1 月 12 日 より非推奨となります。それまでの期間はケースバイケースでバグ修正や新機能のバックポートを対応を継続します。@slack/bolt@2.x が非推奨となった後は、End of life(正式サポートの終了日)まで クリティカルなバグ修正のみ を実装し、クリティカルではない issue や pull request はクローズします。End of life は 2021 年 5 月 31 日 の予定です。この日からは @slack/bolt@2.x の開発は完全に終了となり、残っている open issue や pull request もクローズされます。

                    - -

                    Node の最低必須バージョン

                    - -

                    @slack/bolt@3.x は Node は 12.13.0 以上、npm は 6.12.0 以上が必須バージョンです。

                    - -

                    TypeScript の最低必須バージョン

                    - -

                    TypeScript 利用ガイド でも説明していますが、@slack/bolt@3.x は TypeScirpt 4.1 以上が必須バージョンです。

                    - -
                    -
                    - -
                    - - - - - - - - \ No newline at end of file diff --git a/docs/_site/ja-jp/tutorial/using-typescript.html b/docs/_site/ja-jp/tutorial/using-typescript.html deleted file mode 100644 index a697563ec..000000000 --- a/docs/_site/ja-jp/tutorial/using-typescript.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - Slack | Bolt for JavaScript - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                    -
                    - - -
                    - - -
                    - - -
                    -
                      -
                      - -
                      -

                      TypeScript での利用ガイド

                      - -

                      このプロジェクトは TypeScript を利用して開発されているため、多くの API が型に関するメタ情報を持っています🎉 Visual Studio Code や Atom をはじめとする型のメタ情報を読み込んでくれるエディタを利用していたり、あなたも TypeScript を使って開発している場合には、このより優れたドキュメンテーションによって、よりスムーズにコードを書いたり、エラーの早期発見、より楽なリファクタリングなどの恩恵を受けることができるでしょう。

                      - -

                      このページでは、TypeScript を利用しているプロジェクトからこの npm パッケージを利用する方法について解説します。

                      - -

                      最低必須バージョン

                      - -

                      @slack/bolt の最新のメジャーバージョンは TypeScript 4.1 以上での利用をサポートしています。

                      - -
                      -
                      - -
                      - - - - - - - - \ No newline at end of file diff --git a/docs/_site/jp.html b/docs/_site/jp.html deleted file mode 100644 index 0a9803a13..000000000 --- a/docs/_site/jp.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - Redirecting… - - - - -

                      Redirecting…

                      - Click here if you are not redirected. - diff --git a/docs/_site/redirects.json b/docs/_site/redirects.json deleted file mode 100644 index 7c112b572..000000000 --- a/docs/_site/redirects.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "/getting-started": "http://localhost:4000/bolt-js/tutorial/getting-started", - "/getting-started-http": "http://localhost:4000/bolt-js/tutorial/getting-started-http", - "/hubot-migration": "http://localhost:4000/bolt-js/tutorial/hubot-migration", - "/ja-jp/getting-started": "http://localhost:4000/bolt-js/ja-jp/tutorial/getting-started", - "/getting-started/ja-jp": "http://localhost:4000/bolt-js/ja-jp/tutorial/getting-started", - "/ja-jp/getting-started-http": "http://localhost:4000/bolt-js/ja-jp/tutorial/getting-started-http", - "/ja-jp/hubot-migration": "http://localhost:4000/bolt-js/ja-jp/tutorial/hubot-migration", - "/": "http://localhost:4000/bolt-js/concepts", - "/jp": "http://localhost:4000/bolt-js/ja-jp/concepts", - "/ja-jp": "http://localhost:4000/bolt-js/ja-jp/concepts" -} diff --git a/docs/_site/reference.html b/docs/_site/reference.html deleted file mode 100644 index 4402f52a9..000000000 --- a/docs/_site/reference.html +++ /dev/null @@ -1,815 +0,0 @@ - - - - - - - Slack | Bolt for JavaScript - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                      -
                      - - -
                      - -
                      - - -
                      -

                      App interface and configuration

                      - -
                      -

                      This guide is intended to detail the Bolt interface–including listeners and their arguments, initialization options, and errors. It may be helpful to first go through the ⚡️Getting Started guide to learn the basics of building Bolt for JavaScript apps.

                      -
                      - - - -
                      - -

                      Listener functions

                      -

                      Slack apps typically receive and/or respond to one to many incoming events from Slack. This can be something like listening to an Events API event (like when a link associated with your app is shared) or a user invoking one of your app’s shortcuts. For each type of incoming request from Slack, there are distinct methods that you can pass listener functions to handle and respond to the event.

                      - -

                      Methods

                      -

                      Below is the current list of methods that accept listener functions. These methods handle specific event types coming from Slack, and typically include an identifying parameter before the listener function. The identifying parameter (included below) narrows the events to specific interactions that your listener function is intended to handle, such as a specific callback_id, or a certain substring within a message.

                      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                      MethodDescription
                      app.event(eventType, fn);Listens for Events API events. The eventType is a string to identify a specific event to handle (which must be subscribed to in your app’s configuration).
                      app.message([pattern ,] fn);Convenience method to listen specifically to the message event. The pattern parameter can be any substring (string) or RegExp expression, which will be used to identify the incoming message.
                      app.action(actionId, fn);Listens for an action event from a Block Kit element, such as a user interaction with a button, select menu, or datepicker. The actionId identifier is a string that should match the unique action_id included when your app sends the element to a view. Note that a view can be a message, modal, or app home. Note that action elements included in an input block do not trigger any events.
                      app.shortcut(callbackId, fn);Listens for global or message shortcut invocation. The callbackId is a string or RegExp that must match a shortcut callback_id specified within your app’s configuration.
                      app.view(callbackId, fn);Listens for view_submission and view_closed events. view_submission events are sent when a user submits a modal that your app opened. view_closed events are sent when a user closes the modal rather than submits it.
                      app.step(workflowStep)Listen and responds to workflow step events using the callbacks passed in an instance of WorkflowStep. Callbacks include three callbacks: edit, save, and execute. More information on workflow steps can be found in the documentation.
                      app.command(commandName, fn);Listens for slash command invocations. The commandName is a string that must match a slash command specified in your app’s configuration. Slash command names should be prefaced with a / (ex: /helpdesk).
                      app.options(actionId, fn);Listens for options requests (from select menus with an external data source). This isn’t often used, and shouldn’t be mistaken with app.action. The actionId identifier is a string that matches the unique action_id included when you app sends a select with an external data source.
                      - -

                      Constraint objects

                      -

                      There are a collection of constraint objects that some methods have access to. These can be used to narrow the event your listener function handles, or to handle special cases. Constraint objects can be passed in lieu of the identifiers outlined above. Below is a collection of constraint objects and the methods they can be passed to.

                      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                      MethodOptionsDetails
                      app.action(constraints, fn)block_id, action_id, callback_id, (,type)Listens for more than just the action_id. block_id is the ID for the element’s parent block. callback_id is the ID of the view that is passed when instantiating it (only used when action elements are in modals). To specifically handle an action element in blocks or in legacy attachments, you can use type with the value of block_actions or interactive_message respectively.
                      app.shortcut(constraints, fn)type, callback_idAllows specification of the type of shortcut. type must either be shortcut for global shortcuts or message_action for message_shortcuts. callbackId can be a string or RegExp.
                      app.view(constraints, fn)type, callback_idtype must either be view_closed or view_submission, which determines what specific event your listener function is sent. callback_id is the callback_id of the view that is sent when your app opens the modal.
                      app.options(constraints, fn)block_id, action_id, callback_idOptionally listens for block_id and callback_id in addition to action_id. callback_id can only be passed when handling options elements within modals.
                      - -

                      Listener function arguments

                      -

                      Listener functions have access to a set of arguments that may change based on the method which the function is passed to. Below is an explanation of the different arguments. The below table details the different arguments and the methods they’ll be accessible in.

                      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                      ArgumentDescription 
                      payloadAll listenersThe unwrapped contents of the incoming event, which varies based on event. This is a subset of the information included in body which is detailed below. payload is also accessible via the alias corresponding to the method name that the listener is passed to (message, event, action, shortcut, view, command, options) An easy way to understand what’s in a payload is to log it, or use TypeScript.
                      saymessage, event, action commandFunction to send a message to the channel associated with the incoming event. This argument is only available when the listener is triggered for events that contain a channel ID (the most common being message events). say accepts simple strings (for plain-text messages) and objects (for messages containing blocks). say returns a promise that will resolve with a chat.postMessage response. If you’re using an the action method, or an event other than message, you should ensure that the event payload contains a channel ID.
                      ackaction, shortcut, view, command, optionsFunction that must be called to acknowledge that an incoming event was received by your app. ack returns a promise that resolves when complete. Read more in Acknowledging events
                      clientAll listenersWeb API client that uses the token associated with that event. For single-workspace installations, the token is provided to the constructor. For multi-workspace installations, the token is returned by the authorize function.
                      respondaction, shortcut, view, commandFunction that responds to an incoming event if it contains a response_url. respond returns a promise that resolves with the results of responding using the response_url. For shortcuts, respond will only work for message shortcuts (not global shortcuts). For views, respond will only work when using response_url_enabled: true for conversations list and channels list select menus in input blocks in modals.
                      contextAll listenersEvent context. This object contains data about the event and the app, such as the botId. Middleware can add additional context before the event is passed to listeners.
                      bodyAll listenersObject that contains the entire body of the request (superset of payload). Some accessory data is only available outside of the payload (such as trigger_id and authorizations).
                      - -

                      Body and payload references

                      -

                      The structure of the payload and body is detailed on the API site:

                      - - -

                      Difference from listener middleware

                      -

                      Listener middleware is used to implement logic across many listener functions (though usually not all of them). Listener middleware has the same arguments as the above listener functions, with one distinction: they also have a next() function that must be called in order to pass the chain of execution. Learn more about listener middleware in the documentation.

                      - -

                      Built-in middleware functions

                      - -

                      Bolt offers a variety of built-in middleware functions to help simplify development of your Slack applications. These middleware functions implement common patterns to help filter out or focus your own listener function implementations.

                      - -

                      These middleware functions are exported from the main @slack/bolt package for you to easily import in your applications:

                      - -
                      -
                      -
                      1
                      -2
                      -3
                      -4
                      -
                      import { matchMessage } from '@slack/bolt';
                      -app.message(matchMessage('hello'), async ({ message, logger }) => {
                      -  // this function will now only execute if "hello" is present in the message
                      -});
                      -
                      -
                      -
                      - -

                      These middleware functions are divided into two groups: global middleware functions and listener middleware functions.

                      - -

                      Built-in global middleware functions

                      - -
                        -
                      • ignoreSelf(): Filters out any event that originates from the app. Note that this middleware is enabled by default via the ignoreSelf App initialization options.
                      • -
                      • onlyActions: Filters out any event that isn’t an action.
                      • -
                      • onlyCommands: Filters out any event that isn’t a command.
                      • -
                      • onlyEvents: Allows for only events to propagate down the middleware chain.
                      • -
                      • onlyOptions: Filters out any event that isn’t a drop-down-options event.
                      • -
                      • onlyShortcuts: Filters out any event that isn’t a shortcut.
                      • -
                      • onlyViewActions: Filters out any event that isn’t a view_submission or view_closed event.
                      • -
                      - -

                      Built-in listener middleware functions

                      - -
                        -
                      • directMention(): Filters out any message event whose text does not start with an @-mention of the handling app.
                      • -
                      • matchCommandName(pattern): Filters out any shortcut command whose name does not match the provided pattern; pattern can be a string or regular expression.
                      • -
                      • matchConstraints(constraint): Filters out any block_action, View or Options event that does not match the properties of the provided constraint object. Supported constraint object properties include: -
                          -
                        • block_id and action_id: for filtering out block_action events that do not match the provided IDs.
                        • -
                        • callback_id: for filtering out view_* events not matching the provided callback_id.
                        • -
                        • type: for filtering out any event types not matching the provided type.
                        • -
                        -
                      • -
                      • matchEventType(pattern): filters out any event whose type does not match the provided pattern. pattern can be a string or regular expression.
                      • -
                      • matchMessage(pattern): filters out any message or app_mention events whose message contents do not match the provided pattern. pattern can be a string or regular expression.
                      • -
                      • subtype(type): Filters out any message event whose subtype does not exactly equal the provided type.
                      • -
                      - -

                      Initialization options

                      -

                      Bolt includes a collection of initialization options to customize apps. There are two primary kinds of options: Bolt app options and receiver options. The receiver options may change based on the receiver your app uses. The following receiver options are for the default HTTPReceiver (so they’ll work as long as you aren’t using a custom receiver).

                      - -

                      Receiver options

                      -

                      HTTPReceiver options can be passed into the App constructor, just like the Bolt app options. They’ll be passed to the HTTPReceiver instance upon initialization.

                      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                      OptionDescription
                      signingSecretA string from your app’s configuration (under “Basic Information”) which verifies that incoming events are coming from Slack
                      endpointsA string or object that specifies the endpoint(s) that the receiver will listen for incoming requests from Slack. Currently, the only key for the object is key, the value of which is the customizable endpoint (ex: /myapp/events). By default, all events are sent to the /slack/events endpoint
                      processBeforeResponseboolean that determines whether events should be immediately acknowledged. This is primarily useful when running apps on FaaS since listeners will terminate immediately once the request has completed. When set to true it will defer sending the acknowledgement until after your handlers run to prevent early termination. Defaults to false.
                      clientIdThe client ID string from your app’s configuration which is required to configure OAuth.
                      clientSecretThe client secret string from your app’s configuration which is required to configure OAuth.
                      stateSecretRecommended parameter (string) that’s passed when configuring OAuth to prevent CSRF attacks
                      installationStoreDefines how to save, fetch and delete installation data when configuring OAuth. Contains three methods: fetchInstallation, storeInstallation and deleteInstallation. The default installationStore is an in-memory store.
                      scopesArray of scopes that your app will request within the OAuth process.
                      installerOptionsOptional object that can be used to customize the default OAuth support. Read more in the OAuth documentation.
                      dispatchErrorHandlerError handler triggered if an incoming request is to an unexpected path. More details available in the Error Handling documentation.
                      processEventErrorHandlerError handler triggered if event processing threw an exception. More details available in the Error Handling documentation.
                      unhandledRequestHandlerError handler triggered when a request from Slack goes unacknowledged. More details available in the Error Handling documentation.
                      unhandledRequestTimeoutMillisHow long to wait, in milliseconds, from the time a request is received to when the unhandledRequestHandler should be triggered. Default is 3001. More details available in the Error Handling documentation.
                      signatureVerificationboolean that determines whether Bolt should verify Slack’s signature on incoming requests. Defaults to true.
                      customPropertiesExtractorOptional function that can extract custom properties from an incoming receiver event – for example, extracting custom headers to propagate to other services. The function receives one argument that will have the type of the event received by your receiver (e.g. an HTTP request or websocket message) and should return an object with string keys containing your custom properties. More details available in the Customizing a receiver documentation.
                      - -

                      App options

                      -

                      App options are passed into the App constructor. When the receiver argument is undefined the App constructor also accepts the above Receiver options to initialize either a HttpReceiver or a SocketModeReceiver depending on the value of the socketMode argument.

                      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                      OptionDescription
                      receiverAn instance of Receiver that parses and handles incoming events. Must conform to the Receiver interface, which includes init(app), start(), and stop(). More information about receivers is in the documentation.
                      agentOptional HTTP Agent used to set up proxy support. Read more about custom agents in the Node Slack SDK documentation.
                      clientTlsOptional string to set a custom TLS configuration for HTTP client requests. Must be one of: "pfx", "key", "passphrase", "cert", or "ca".
                      convoStoreA store to set and retrieve state-related conversation information. set() sets conversation state and get() fetches it. By default, apps have access to an in-memory store. More information and an example can be found in the documentation.
                      tokenA string from your app’s configuration (under “Settings” > “Install App”) required for calling the Web API. May not be passed when using authorize, orgAuthorize, or OAuth.
                      botIdCan only be used when authorize is not defined. The optional botId is the ID for your bot token (ex: B12345) which can be used to ignore messages sent by your app. If a xoxb- token is passed to your app, this value will automatically be retrieved by your app calling the auth.test method.
                      botUserIdCan only be used when authorize is not defined. The optional botUserId is distinct from the botId, as it’s the user ID associated with your bot user used to identify direct mentions. If a xoxb- token is passed to your app, this value will automatically be retrieved by your app calling the auth.test method.
                      authorizeFunction for multi-team installations that determines which token is associated with the incoming event. The authorize function is passed source data that sometimes contains a userId, conversationId, enterpriseId, teamId and isEnterpriseInstall (depending which information the incoming event contains). An authorize function should either return a botToken, botId, and botUserId, or could return a userToken. If using built-in OAuth support, an authorize function will automatically be created so you do not need to pass one in. More information about authorization functions can be found on
                      loggerOption that allows you to pass a custom logger rather than using the built-in one. Loggers must implement specific methods (the Logger interface), which includes setLevel(level: LogLevel), getLevel(), setName(name: string), debug(...msgs: any[]), info(...msgs: any[]), warn(...msgs: any[]), and error(...msgs: any[]). More information about logging are in the documentation
                      logLevelOption to control how much or what kind of information is logged. The LogLevel export contains the possible levels–in order of most to least information: DEBUG, INFO, WARN, and ERROR. By default, logLevel is set to INFO. More information on logging can be found in the documentation.
                      extendedErrorHandlerOption that accepts a boolean value. When set to true, the global error handler is passed an object with additional request context. Available from version 3.8.0, defaults to false. More information on advanced error handling can be found in the documentation.
                      ignoreSelfboolean to enable a middleware function that ignores any messages coming from your app. Requires a botId. Defaults to true.
                      clientOptions.slackApiUrlAllows setting a custom endpoint for the Slack API. Used most often for testing.
                      socketModeOption that accepts a boolean value. When set to true the app is started in Socket Mode, i.e. it allows your app to connect and receive data from Slack via a WebSocket connection. Defaults to false.
                      developerModeboolean to activate the developer mode. When set to true the logLevel is automatically set to DEBUG and socketMode is set to true. However, explicitly setting these two properties takes precedence over implicitly setting them via developerMode. Furthermore, a custom OAuth failure handler is provided to help debugging. Finally, the body of all incoming requests are logged and thus sensitive information like tokens might be contained in the logs. Defaults to false.
                      deferInitializationboolean to defer initialization of the app and places responsibility for manually calling the async App#init() method on the developer. init() must be called before App#start(). Defaults to false.
                      signatureVerificationboolean that determines whether Bolt should verify Slack’s signature on incoming requests. Defaults to true.
                      - -
                      -

                      Bolt’s client is an instance of WebClient from the Node Slack SDK, so some of that documentation may be helpful as you’re developing.

                      -
                      - -

                      Framework error types

                      -

                      Bolt includes a set of error types to make errors easier to handle, with more specific contextual information. Below is a non-exhaustive list of error codes you may run into during development:

                      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                      Error codeDetails
                      AppInitializationErrorInvalid initialization options were passed. This could include not passing a signing secret, or passing in conflicting options (for example, you can’t pass in both token and authorize). Includes an original property with more details. This error is only thrown during initialization (within the App’s constructor).
                      AuthorizationErrorError exclusively thrown when installation information can’t be fetched or parsed. You may encounter this error when using the built-in OAuth support, or you may want to import and use this error when building your own authorize function.
                      ContextMissingPropertyErrorError thrown when the context object is missing necessary information, such as not including botUserId or botId when ignoreSelf is set to true. The missing property is available in the missingProperty property.
                      ReceiverMultipleAckErrorError thrown within Receiver when your app calls ack() when that request has previously been acknowledged. Currently only used in the default HTTPReceiver.
                      ReceiverAuthenticityErrorError thrown when your app’s request signature could not be verified. The error includes information on why it failed, such as an invalid timestamp, missing headers, or invalid signing secret.
                      MultipleListenerErrorThrown when multiple errors occur when processing multiple listeners for a single event. Includes an originals property with an array of the individual errors.
                      WorkflowStepInitializationErrorError thrown when configuration options are invalid or missing when instantiating a new WorkflowStep instance. This could be scenarios like not including a callback_id, or not including a configuration object. More information on Workflow Steps can be found in the documentation.
                      UnknownErrorAn error that was thrown inside the framework but does not have a specified error code. Contains an original property with more details.
                      - -
                      -

                      You can read the code for error definition and construction in errors.ts.

                      -
                      - -

                      Client errors

                      -

                      Bolt imports a WebClient to call Slack’s APIs. Below is a set of errors you may encounter when making API calls with the client, though you can read more in the web API documentation. When handling client errors, more information can be found in the body within the data property.

                      - - - - - - - - - - - - - - - - - - - - - - - - - - -
                      Error codeDetails
                      PlatformErrorError received when calling a Slack API. Includes a data property.
                      RequestErrorA request could not be sent, perhaps because your network connection is not available. It has an original property with more details.
                      RateLimitedErrorYour app has made too many requests too quickly. Inclues a retryAfter property with the number of seconds you should wait before trying to send again. The WebClient will handle rate limit errors by default–you can read more in the documentation.
                      HTTPErrorThe HTTP response contained an unfamiliar status code. The Web API only responds with 200 (including for errors), or 429 for rate limiting.
                      - -
                      -
                      - -
                      - - - - - - - \ No newline at end of file diff --git a/docs/_site/scripts/tutorial_nav.js b/docs/_site/scripts/tutorial_nav.js deleted file mode 100644 index bfd54b934..000000000 --- a/docs/_site/scripts/tutorial_nav.js +++ /dev/null @@ -1,41 +0,0 @@ -var navTag = 'h3'; - -window.addEventListener('DOMContentLoaded', (event) => { - var sections = document.querySelectorAll(navTag); - var navParent = document.querySelector('.tutorial-nav-list'); - - function createNavElement(title, href) { - var navElement = document.createElement('li'); - - var navCircle = document.createElement('div'); - navCircle.setAttribute('class', 'circle ' + href); - - var navAnchor = document.createElement('a'); - navAnchor.setAttribute('href', '#' + href); - navAnchor.innerText = title; - - navElement.appendChild(navCircle); - navElement.appendChild(navAnchor); - - return navElement; - } - - sections.forEach((navHeader) => { - var newElement = createNavElement(navHeader.innerText, navHeader.id); - navParent.appendChild(newElement); - }); -}); - -window.addEventListener('scroll', (event) => { - var sections = document.querySelectorAll(navTag); - - sections.forEach((navHeader) => { - var navElement = document.querySelector('.' + navHeader.id); - - if (window.scrollY >= navHeader.getBoundingClientRect().top + window.pageYOffset - 5) { - navElement.setAttribute('class', 'circle completed ' + navHeader.id); - } else { - navElement.setAttribute('class', 'circle ' + navHeader.id); - } - }); -}); diff --git a/docs/_site/tutorial/getting-started-http.html b/docs/_site/tutorial/getting-started-http.html deleted file mode 100644 index ae5182cd6..000000000 --- a/docs/_site/tutorial/getting-started-http.html +++ /dev/null @@ -1,808 +0,0 @@ - - - - - - - Slack | Bolt for JavaScript - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                      -
                      - - -
                      - - -
                      - - -
                      -
                        -
                        - -
                        -

                        Getting started with Bolt for JavaScript and HTTP

                        - -
                        -

                        This guide is meant to walk you through getting up and running with a Slack app using Bolt for JavaScript. Along the way, we’ll create a new Slack app, set up your local environment, and develop an app that listens and responds to messages from a Slack workspace.

                        -
                        - -

                        When you’re finished, you’ll have this ⚡️Getting Started app to run, modify, and make your own.

                        - -
                        - -

                        Create an app

                        -

                        First thing’s first: before you start developing with Bolt, you’ll want to create a Slack app.

                        - -
                        -

                        💡 We recommend using a workspace where you won’t disrupt real work getting done — you can create a new one for free.

                        -
                        - -

                        After you fill out an app name (you can change it later) and pick a workspace to install it to, hit the Create App button and you’ll land on your app’s Basic Information page.

                        - -

                        This page contains an overview of your app in addition to important credentials you’ll need later, like the Signing Secret under the App Credentials header.

                        - -

                        Basic Information page

                        - -

                        Look around, add an app icon and description, and then let’s start configuring your app. 🔩

                        - -
                        - -

                        Tokens and installing apps

                        -

                        Slack apps use OAuth to manage access to Slack’s APIs. When an app is installed, you’ll receive a token that the app can use to call API methods.

                        - -

                        There are three main token types available to a Slack app: user (xoxp), bot (xoxb), and app (xapp) tokens.

                        -
                          -
                        • User tokens allow you to call API methods on behalf of users after they install or authenticate the app. There may be several user tokens for a single workspace.
                        • -
                        • Bot tokens are associated with bot users, and are only granted once in a workspace where someone installs the app. The bot token your app uses will be the same no matter which user performed the installation. Bot tokens are the token type that most apps use.
                        • -
                        • App-level tokens represent your app across organizations, including installations by all individual users on all workspaces in a given organization and are commonly used for creating websocket connections to your app.
                        • -
                        - -

                        For brevity, we’re going to use bot tokens for this guide.

                        - -
                          -
                        1. -

                          Navigate to the OAuth & Permissions on the left sidebar and scroll down to the Bot Token Scopes section. Click Add an OAuth Scope.

                          -
                        2. -
                        3. -

                          For now, we’ll just add one scope: chat:write. This grants your app the permission to post messages in channels it’s a member of.

                          -
                        4. -
                        5. -

                          Scroll up to the top of the OAuth & Permissions page and click Install App to Workspace. You’ll be led through Slack’s OAuth UI, where you should allow your app to be installed to your development workspace.

                          -
                        6. -
                        7. -

                          Once you authorize the installation, you’ll land on the OAuth & Permissions page and see a Bot User OAuth Access Token.

                          -
                        8. -
                        - -

                        OAuth Tokens

                        - -
                        -

                        💡 Treat your token like a password and keep it safe. Your app uses it to post and retrieve information from Slack workspaces.

                        -
                        - -
                        - -

                        Setting up your project

                        -

                        With the initial configuration handled, it’s time to set up a new Bolt project. This is where you’ll write the code that handles the logic for your app.

                        - -

                        If you don’t already have a project, let’s create a new one. Create an empty directory and initialize a new project:

                        - -
                        -
                        -
                        1
                        -2
                        -3
                        -
                        mkdir first-bolt-app
                        -cd first-bolt-app
                        -npm init
                        -
                        -
                        -
                        - -

                        You’ll be prompted with a series of questions to describe your new project (you can accept the defaults by hitting Enter on each prompt if you aren’t picky). After you’re done, you’ll have a new package.json file in your directory.

                        - -

                        Before we install the Bolt for JavaScript package to your new project, let’s save the bot token and Signing Secret that were generated when you configured your app.

                        - -
                          -
                        1. Copy your Signing Secret from the Basic Information page and then store it in a new environment variable. The following example works on Linux and macOS; but similar commands are available on Windows. -
                          1
                          -
                          export SLACK_SIGNING_SECRET=<your-signing-secret>
                          -
                          -
                        2. -
                        3. Copy your bot (xoxb) token from the OAuth & Permissions page and store it in another environment variable. -
                          1
                          -
                          export SLACK_BOT_TOKEN=xoxb-<your-bot-token>
                          -
                          -
                        4. -
                        - -
                        -

                        🔒 Remember to keep your token and signing secret secure. At a minimum, you should avoid checking them into public version control, and access them via environment variables as we’ve done above. Checkout the API documentation for more on best practices for app security.

                        -
                        - -

                        Now, let’s create your app. Install the @slack/bolt package and save it to your package.json dependencies using the following command:

                        - -
                        -
                        -
                        1
                        -
                        npm install @slack/bolt
                        -
                        -
                        -
                        - -

                        Create a new entrypoint file called app.js in this directory and add the following code:

                        - -
                        -
                        -
                        1
                        -2
                        -3
                        -4
                        -5
                        -6
                        -7
                        -8
                        -9
                        -10
                        -11
                        -12
                        -13
                        -14
                        -
                        const { App } = require('@slack/bolt');
                        -
                        -// Initializes your app with your bot token and signing secret
                        -const app = new App({
                        -  token: process.env.SLACK_BOT_TOKEN,
                        -  signingSecret: process.env.SLACK_SIGNING_SECRET
                        -});
                        -
                        -(async () => {
                        -  // Start your app
                        -  await app.start(process.env.PORT || 3000);
                        -
                        -  console.log('⚡️ Bolt app is running!');
                        -})();
                        -
                        -
                        -
                        - -

                        Save your app.js file, then on the command line run the following:

                        - -
                        node app.js
                        -
                        - -

                        Your app should let you know that it’s up and running. 🎉

                        - -
                        - -

                        Setting up events with HTTP

                        -

                        Your app behaves similarly to people on your team — it can post messages, add emoji reactions, and listen and respond to events.

                        - -

                        To listen for events happening in a Slack workspace (like when a message is posted or when a reaction is posted to a message) you’ll use the Events API to subscribe to event types.

                        - -

                        Let’s enable events for your app:

                        - -
                          -
                        1. -

                          Go back to your app configuration page (click on the app from your app management page). Click Event Subscriptions on the left sidebar. Toggle the switch labeled Enable Events.

                          -
                        2. -
                        3. -

                          Add your Request URL. Slack will send HTTP POST requests corresponding to events to this Request URL endpoint. Bolt uses the /slack/events path to listen to all incoming requests (whether shortcuts, events, or interactivity payloads). When configuring your Request URL within your app configuration, you’ll append /slack/events, e.g. https://<your-domain>/slack/events. 💡

                          -
                        4. -
                        - -
                        -

                        💡 For local development, you can use a proxy service like ngrok to create a public URL and tunnel requests to your development environment. Refer to ngrok’s getting started guide on how to create this tunnel.

                        -
                        - -

                        Finally, it’s time to tell Slack what events we’d like to listen for. Under Event Subscriptions, toggle the switch labeled Enable Events.

                        - -

                        When an event occurs, Slack will send your app information about the event, like the user that triggered it and the channel it occurred in. Your app will process the details and can respond accordingly.

                        - -

                        Scroll down to Subscribe to Bot Events. There are four events related to messages:

                        -
                          -
                        • message.channels listens for messages in public channels that your app is added to
                        • -
                        • message.groups listens for messages in 🔒 private channels that your app is added to
                        • -
                        • message.im listens for messages in your app’s DMs with users
                        • -
                        • message.mpim listens for messages in multi-person DMs that your app is added to
                        • -
                        - -

                        If you want your bot to listen to messages from everywhere it is added to, choose all four message events. After you’ve selected the events you want your bot to listen to, click the green Save Changes button.

                        - -
                        - -

                        Listening and responding to a message

                        -

                        Your app is now ready for some logic. Let’s start by using the message() method to attach a listener for messages.

                        - -

                        The following example listens and responds to all messages in channels/DMs where your app has been added that contain the word “hello”:

                        - -
                        -
                        -
                        1
                        -2
                        -3
                        -4
                        -5
                        -6
                        -7
                        -8
                        -9
                        -10
                        -11
                        -12
                        -13
                        -14
                        -15
                        -16
                        -17
                        -18
                        -19
                        -
                        const { App } = require('@slack/bolt');
                        -
                        -const app = new App({
                        -  token: process.env.SLACK_BOT_TOKEN,
                        -  signingSecret: process.env.SLACK_SIGNING_SECRET,
                        -});
                        -
                        -// Listens to incoming messages that contain "hello"
                        -app.message('hello', async ({ message, say }) => {
                        -  // say() sends a message to the channel where the event was triggered
                        -  await say(`Hey there <@${message.user}>!`);
                        -});
                        -
                        -(async () => {
                        -  // Start your app
                        -  await app.start(process.env.PORT || 3000);
                        -
                        -  console.log('⚡️ Bolt app is running!');
                        -})();
                        -
                        -
                        -
                        - -

                        If you restart your app, so long as your bot user has been added to the channel/DM, when you send any message that contains “hello”, it will respond.

                        - -

                        This is a basic example, but it gives you a place to start customizing your app based on your own goals. Let’s try something a little more interactive by sending a button rather than plain text.

                        - -
                        - -

                        Sending and responding to actions

                        - -

                        To use features like buttons, select menus, datepickers, modals, and shortcuts, you’ll need to enable interactivity. Similar to events, you’ll need to specify a Request URL for Slack to send the action (such as user clicked a button). Head over to Interactivity & Shortcuts in your app configuration.

                        - -
                        -

                        💡 By default, Bolt uses the same endpoint for interactive components that it uses for events, so use the same request URL as above (in the example, it was https://8e8ec2d7.ngrok.io/slack/events). Press the Save Changes button in the lower right hand corner, and that’s it. Your app is set up to handle interactivity!

                        -
                        - -

                        When interactivity is enabled, interactions with shortcuts, modals, or interactive components (such as buttons, select menus, and datepickers) will be sent to your app as events.

                        - -

                        Now, let’s go back to your app’s code and add logic to handle those events:

                        -
                          -
                        • First, we’ll send a message that contains an interactive component (in this case a button).
                        • -
                        • Next, we’ll listen for the action of a user clicking the button before responding
                        • -
                        - -

                        Below, the code from the last section is modified to send a message containing a button rather than just a string:

                        - -
                        -
                        -
                        1
                        -2
                        -3
                        -4
                        -5
                        -6
                        -7
                        -8
                        -9
                        -10
                        -11
                        -12
                        -13
                        -14
                        -15
                        -16
                        -17
                        -18
                        -19
                        -20
                        -21
                        -22
                        -23
                        -24
                        -25
                        -26
                        -27
                        -28
                        -29
                        -30
                        -31
                        -32
                        -33
                        -34
                        -35
                        -36
                        -37
                        -38
                        -
                        const { App } = require('@slack/bolt');
                        -
                        -const app = new App({
                        -  token: process.env.SLACK_BOT_TOKEN,
                        -  signingSecret: process.env.SLACK_SIGNING_SECRET
                        -});
                        -
                        -// Listens to incoming messages that contain "hello"
                        -app.message('hello', async ({ message, say }) => {
                        -  // say() sends a message to the channel where the event was triggered
                        -  await say({
                        -    blocks: [
                        -      {
                        -        "type": "section",
                        -        "text": {
                        -          "type": "mrkdwn",
                        -          "text": `Hey there <@${message.user}>!`
                        -        },
                        -        "accessory": {
                        -          "type": "button",
                        -          "text": {
                        -            "type": "plain_text",
                        -            "text": "Click Me"
                        -          },
                        -          "action_id": "button_click"
                        -        }
                        -      }
                        -    ],
                        -    text: `Hey there <@${message.user}>!`
                        -  });
                        -});
                        -
                        -(async () => {
                        -  // Start your app
                        -  await app.start(process.env.PORT || 3000);
                        -
                        -  console.log('⚡️ Bolt app is running!');
                        -})();
                        -
                        -
                        -
                        - -

                        The value inside of say() is now an object that contains an array of blocks. Blocks are the building components of a Slack message and can range from text to images to datepickers. In this case, your app will respond with a section block that includes a button as an accessory. Since we’re using blocks, the text is a fallback for notifications and accessibility.

                        - -

                        You’ll notice in the button accessory object, there is an action_id. This will act as a unique identifier for the button so your app can specify what action it wants to respond to.

                        - -
                        -

                        💡 The Block Kit Builder is a simple way to prototype your interactive messages. The builder lets you (or anyone on your team) mockup messages and generates the corresponding JSON that you can paste directly in your app.

                        -
                        - -

                        Now, if you restart your app and say “hello” in a channel your app is in, you’ll see a message with a button. But if you click the button, nothing happens (yet!).

                        - -

                        Let’s add a handler to send a followup message when someone clicks the button:

                        - -
                        -
                        -
                        1
                        -2
                        -3
                        -4
                        -5
                        -6
                        -7
                        -8
                        -9
                        -10
                        -11
                        -12
                        -13
                        -14
                        -15
                        -16
                        -17
                        -18
                        -19
                        -20
                        -21
                        -22
                        -23
                        -24
                        -25
                        -26
                        -27
                        -28
                        -29
                        -30
                        -31
                        -32
                        -33
                        -34
                        -35
                        -36
                        -37
                        -38
                        -39
                        -40
                        -41
                        -42
                        -43
                        -44
                        -
                        const { App } = require('@slack/bolt');
                        -
                        -const app = new App({
                        -  token: process.env.SLACK_BOT_TOKEN,
                        -  signingSecret: process.env.SLACK_SIGNING_SECRET
                        -});
                        -
                        -// Listens to incoming messages that contain "hello"
                        -app.message('hello', async ({ message, say }) => {
                        -  // say() sends a message to the channel where the event was triggered
                        -  await say({
                        -    blocks: [
                        -      {
                        -        "type": "section",
                        -        "text": {
                        -          "type": "mrkdwn",
                        -          "text": `Hey there <@${message.user}>!`
                        -        },
                        -        "accessory": {
                        -          "type": "button",
                        -          "text": {
                        -            "type": "plain_text",
                        -            "text": "Click Me"
                        -          },
                        -          "action_id": "button_click"
                        -        }
                        -      }
                        -    ],
                        -    text: `Hey there <@${message.user}>!`
                        -  });
                        -});
                        -
                        -app.action('button_click', async ({ body, ack, say }) => {
                        -  // Acknowledge the action
                        -  await ack();
                        -  await say(`<@${body.user.id}> clicked the button`);
                        -});
                        -
                        -(async () => {
                        -  // Start your app
                        -  await app.start(process.env.PORT || 3000);
                        -
                        -  console.log('⚡️ Bolt app is running!');
                        -})();
                        -
                        -
                        -
                        - -

                        You can see that we used app.action() to listen for the action_id that we named button_click. If you restart your app and click the button, you’ll see a new message from your app that says you clicked the button.

                        - -
                        - -

                        Next steps

                        -

                        You just built your first Bolt for JavaScript app! 🎉

                        - -

                        Now that you have a basic app up and running, you can start exploring how to make your Bolt app stand out. Here are some ideas about what to explore next:

                        - -
                          -
                        • -

                          Read through the Basic concepts to learn about the different methods and features your Bolt app has access to.

                          -
                        • -
                        • -

                          Explore the different events your bot can listen to with the events() method. All of the events are listed on the API site.

                          -
                        • -
                        • -

                          Bolt allows you to call Web API methods with the client attached to your app. There are over 220 methods on our API site.

                          -
                        • -
                        • -

                          Learn more about the different token types on our API site. Your app may need different tokens depending on the actions you want it to perform. If you are using Socket Mode instead of HTTP, an additional (xapp) token with connections:write scopes is required.

                          -
                        • -
                        - -
                        -
                        - -
                        - - - - - - - - \ No newline at end of file diff --git a/docs/_site/tutorial/getting-started.html b/docs/_site/tutorial/getting-started.html deleted file mode 100644 index 1f5ee4f28..000000000 --- a/docs/_site/tutorial/getting-started.html +++ /dev/null @@ -1,870 +0,0 @@ - - - - - - - Slack | Bolt for JavaScript - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                        -
                        - - -
                        - - -
                        - - -
                        -
                          -
                          - -
                          -

                          Getting started with Bolt for JavaScript

                          - -
                          -

                          This guide is meant to walk you through getting up and running with a Slack app using Bolt for JavaScript. Along the way, we’ll create a new Slack app, set up your local environment, and develop an app that listens and responds to messages from a Slack workspace.

                          -
                          - -

                          When you’re finished, you’ll have this ⚡️Getting Started app to run, modify, and make your own.

                          - -
                          - -

                          Create an app

                          -

                          First thing’s first: before you start developing with Bolt, you’ll want to create a Slack app.

                          - -
                          -

                          💡 We recommend using a workspace where you won’t disrupt real work getting done — you can create a new one for free.

                          -
                          - -

                          After you fill out an app name (you can change it later) and pick a workspace to install it to, hit the Create App button and you’ll land on your app’s Basic Information page.

                          - -

                          This page contains an overview of your app in addition to important credentials you’ll need later, like the Signing Secret under the App Credentials header.

                          - -

                          Basic Information page

                          - -

                          Look around, add an app icon and description, and then let’s start configuring your app. 🔩

                          - -
                          - -

                          Tokens and installing apps

                          -

                          Slack apps use OAuth to manage access to Slack’s APIs. When an app is installed, you’ll receive a token that the app can use to call API methods.

                          - -

                          There are three main token types available to a Slack app: user (xoxp), bot (xoxb), and app (xapp) tokens.

                          -
                            -
                          • User tokens allow you to call API methods on behalf of users after they install or authenticate the app. There may be several user tokens for a single workspace.
                          • -
                          • Bot tokens are associated with bot users, and are only granted once in a workspace where someone installs the app. The bot token your app uses will be the same no matter which user performed the installation. Bot tokens are the token type that most apps use.
                          • -
                          • App-level tokens represent your app across organizations, including installations by all individual users on all workspaces in a given organization and are commonly used for creating websocket connections to your app.
                          • -
                          - -

                          We’re going to use bot and app tokens for this guide.

                          - -
                            -
                          1. -

                            Navigate to the OAuth & Permissions on the left sidebar and scroll down to the Bot Token Scopes section. Click Add an OAuth Scope.

                            -
                          2. -
                          3. -

                            For now, we’ll just add one scope: chat:write. This grants your app the permission to post messages in channels it’s a member of.

                            -
                          4. -
                          5. -

                            Scroll up to the top of the OAuth & Permissions page and click Install App to Workspace. You’ll be led through Slack’s OAuth UI, where you should allow your app to be installed to your development workspace.

                            -
                          6. -
                          7. -

                            Once you authorize the installation, you’ll land on the OAuth & Permissions page and see a Bot User OAuth Access Token.

                            -
                          8. -
                          - -

                          OAuth Tokens

                          - -
                          -

                          💡 Treat your token like a password and keep it safe. Your app uses it to post and retrieve information from Slack workspaces.

                          -
                          - -
                          - -

                          Setting up your project

                          -

                          With the initial configuration handled, it’s time to set up a new Bolt project. This is where you’ll write the code that handles the logic for your app.

                          - -

                          If you don’t already have a project, let’s create a new one. Create an empty directory and initialize a new project:

                          - -
                          -
                          -
                          1
                          -2
                          -3
                          -
                          mkdir first-bolt-app
                          -cd first-bolt-app
                          -npm init
                          -
                          -
                          -
                          - -

                          You’ll be prompted with a series of questions to describe your new project (you can accept the defaults by hitting Enter on each prompt if you aren’t picky). After you’re done, you’ll have a new package.json file in your directory.

                          - -

                          Before we install the Bolt for JavaScript package to your new project, let’s save the bot token and Signing Secret that were generated when you configured your app.

                          - -
                            -
                          1. Copy your Signing Secret from the Basic Information page and then store it in a new environment variable. The following example works on Linux and macOS; but similar commands are available on Windows. -
                            1
                            -
                            export SLACK_SIGNING_SECRET=<your-signing-secret>
                            -
                            -
                          2. -
                          3. Copy your bot (xoxb) token from the OAuth & Permissions page and store it in another environment variable. -
                            1
                            -
                            export SLACK_BOT_TOKEN=xoxb-<your-bot-token>
                            -
                            -
                          4. -
                          - -
                          -

                          🔒 Remember to keep your tokens and signing secret secure. At a minimum, you should avoid checking them into public version control, and access them via environment variables as we’ve done above. Checkout the API documentation for more on best practices for app security.

                          -
                          - -

                          Now, let’s create your app. Install the @slack/bolt package and save it to your package.json dependencies using the following command:

                          - -
                          -
                          -
                          1
                          -
                          npm install @slack/bolt
                          -
                          -
                          -
                          - -

                          Create a new entrypoint file called app.js in this directory and add the following code:

                          - -
                          -
                          -
                          1
                          -2
                          -3
                          -4
                          -5
                          -6
                          -7
                          -8
                          -9
                          -10
                          -11
                          -12
                          -13
                          -14
                          -
                          const { App } = require('@slack/bolt');
                          -
                          -// Initializes your app with your bot token and signing secret
                          -const app = new App({
                          -  token: process.env.SLACK_BOT_TOKEN,
                          -  signingSecret: process.env.SLACK_SIGNING_SECRET
                          -});
                          -
                          -(async () => {
                          -  // Start your app
                          -  await app.start(process.env.PORT || 3000);
                          -
                          -  console.log('⚡️ Bolt app is running!');
                          -})();
                          -
                          -
                          -
                          - -

                          Save your app.js file, then on the command line run the following:

                          - -
                          node app.js
                          -
                          - -

                          Your app should let you know that it’s up and running. 🎉

                          - -
                          - -

                          Setting up events

                          -

                          Your app behaves similarly to people on your team — it can post messages, add emoji reactions, and listen and respond to events.

                          - -

                          To listen for events happening in a Slack workspace (like when a message is posted or when a reaction is posted to a message) you’ll use the Events API to subscribe to event types. For this guide, we are going to be using Socket Mode, our recommended option for those just getting started and building something for their team.

                          - -
                          -

                          💡 Socket Mode lets apps use the Events API and interactive components without exposing a public HTTP endpoint. This can be helpful during development, or if you’re receiving requests from behind a firewall. HTTP is more useful for apps being deployed to hosting environments (like AWS or Heroku), or apps intended for distribution via the Slack App Directory. To continue this setting up guide with HTTP, head over here.

                          -
                          - -

                          Okay, let’s enable Socket Mode:

                          - -
                            -
                          1. -

                            Head to your app’s configuration page (click on the app from your app management page). Navigate to Socket Mode on the left side menu and toggle to enable.

                            -
                          2. -
                          3. -

                            Go to Basic Information and scroll down under the App Token section and click Generate Token and Scopes to generate an app token. Add the connections:write scope to this token and save the generated xapp token, we’ll use that in just a moment.

                            -
                          4. -
                          - -

                          Finally, it’s time to tell Slack what events we’d like to listen for. Under Event Subscriptions, toggle the switch labeled Enable Events.

                          - -

                          When an event occurs, Slack will send your app information about the event, like the user that triggered it and the channel it occurred in. Your app will process the details and can respond accordingly.

                          - -

                          Scroll down to Subscribe to Bot Events. There are four events related to messages:

                          -
                            -
                          • message.channels listens for messages in public channels that your app is added to
                          • -
                          • message.groups listens for messages in 🔒 private channels that your app is added to
                          • -
                          • message.im listens for messages in your app’s DMs with users
                          • -
                          • message.mpim listens for messages in multi-person DMs that your app is added to
                          • -
                          - -

                          If you want your bot to listen to messages from everywhere it is added to, choose all four message events. After you’ve selected the events you want your bot to listen to, click the green Save Changes button.

                          - -

                          Back in your project, make sure to store the xapp token you saved earlier in your environment.

                          - -
                          -
                          -
                          1
                          -
                          export SLACK_APP_TOKEN=xapp-<your-app-token>
                          -
                          -
                          -
                          - -

                          Make a simple change to your Bolt initialization code and restart the app.

                          - -
                          -
                          -
                          1
                          -2
                          -3
                          -4
                          -5
                          -6
                          -7
                          -
                          // Initializes your app in socket mode with your app token and signing secret
                          -const app = new App({
                          -  token: process.env.SLACK_BOT_TOKEN,
                          -  signingSecret: process.env.SLACK_SIGNING_SECRET,
                          -  socketMode: true, // add this
                          -  appToken: process.env.SLACK_APP_TOKEN // add this
                          -});
                          -
                          -
                          -
                          - -
                          - -

                          Listening and responding to a message

                          -

                          Your app is now ready for some logic. Let’s start by using the message() method to attach a listener for messages.

                          - -

                          The following example listens and responds to all messages in channels/DMs where your app has been added that contain the word “hello”:

                          - -
                          -
                          -
                          1
                          -2
                          -3
                          -4
                          -5
                          -6
                          -7
                          -8
                          -9
                          -10
                          -11
                          -12
                          -13
                          -14
                          -15
                          -16
                          -17
                          -18
                          -19
                          -20
                          -21
                          -22
                          -23
                          -24
                          -
                          const { App } = require('@slack/bolt');
                          -
                          -const app = new App({
                          -  token: process.env.SLACK_BOT_TOKEN,
                          -  signingSecret: process.env.SLACK_SIGNING_SECRET,
                          -  socketMode: true,
                          -  appToken: process.env.SLACK_APP_TOKEN,
                          -  // Socket Mode doesn't listen on a port, but in case you want your app to respond to OAuth,
                          -  // you still need to listen on some port!
                          -  port: process.env.PORT || 3000
                          -});
                          -
                          -// Listens to incoming messages that contain "hello"
                          -app.message('hello', async ({ message, say }) => {
                          -  // say() sends a message to the channel where the event was triggered
                          -  await say(`Hey there <@${message.user}>!`);
                          -});
                          -
                          -(async () => {
                          -  // Start your app
                          -  await app.start();
                          -
                          -  console.log('⚡️ Bolt app is running!');
                          -})();
                          -
                          -
                          -
                          - -

                          If you restart your app, so long as your bot user has been added to the channel/DM, when you send any message that contains “hello”, it will respond.

                          - -

                          This is a basic example, but it gives you a place to start customizing your app based on your own goals. Let’s try something a little more interactive by sending a button rather than plain text.

                          - -
                          - -

                          Sending and responding to actions

                          - -

                          To use features like buttons, select menus, datepickers, modals, and shortcuts, you’ll need to enable interactivity. Head over to Interactivity & Shortcuts in your app configuration.

                          - -
                          -

                          💡 You’ll notice that with Socket Mode on, basic interactivity is enabled for us by default, so no further action here is needed. If you’re using HTTP, you’ll need to supply a Request URL for Slack to send events to.

                          -
                          - -

                          When interactivity is enabled, interactions with shortcuts, modals, or interactive components (such as buttons, select menus, and datepickers) will be sent to your app as events.

                          - -

                          Now, let’s go back to your app’s code and add logic to handle those events:

                          -
                            -
                          • First, we’ll send a message that contains an interactive component (in this case a button).
                          • -
                          • Next, we’ll listen for the action of a user clicking the button before responding
                          • -
                          - -

                          Below, the code from the last section is modified to send a message containing a button rather than just a string:

                          - -
                          -
                          -
                          1
                          -2
                          -3
                          -4
                          -5
                          -6
                          -7
                          -8
                          -9
                          -10
                          -11
                          -12
                          -13
                          -14
                          -15
                          -16
                          -17
                          -18
                          -19
                          -20
                          -21
                          -22
                          -23
                          -24
                          -25
                          -26
                          -27
                          -28
                          -29
                          -30
                          -31
                          -32
                          -33
                          -34
                          -35
                          -36
                          -37
                          -38
                          -39
                          -40
                          -41
                          -42
                          -43
                          -
                          const { App } = require('@slack/bolt');
                          -
                          -const app = new App({
                          -  token: process.env.SLACK_BOT_TOKEN,
                          -  signingSecret: process.env.SLACK_SIGNING_SECRET,
                          -  socketMode: true,
                          -  appToken: process.env.SLACK_APP_TOKEN,
                          -  // Socket Mode doesn't listen on a port, but in case you want your app to respond to OAuth,
                          -  // you still need to listen on some port!
                          -  port: process.env.PORT || 3000
                          -});
                          -
                          -// Listens to incoming messages that contain "hello"
                          -app.message('hello', async ({ message, say }) => {
                          -  // say() sends a message to the channel where the event was triggered
                          -  await say({
                          -    blocks: [
                          -      {
                          -        "type": "section",
                          -        "text": {
                          -          "type": "mrkdwn",
                          -          "text": `Hey there <@${message.user}>!`
                          -        },
                          -        "accessory": {
                          -          "type": "button",
                          -          "text": {
                          -            "type": "plain_text",
                          -            "text": "Click Me"
                          -          },
                          -          "action_id": "button_click"
                          -        }
                          -      }
                          -    ],
                          -    text: `Hey there <@${message.user}>!`
                          -  });
                          -});
                          -
                          -(async () => {
                          -  // Start your app
                          -  await app.start();
                          -
                          -  console.log('⚡️ Bolt app is running!');
                          -})();
                          -
                          -
                          -
                          - -

                          The value inside of say() is now an object that contains an array of blocks. Blocks are the building components of a Slack message and can range from text to images to datepickers. In this case, your app will respond with a section block that includes a button as an accessory. Since we’re using blocks, the text is a fallback for notifications and accessibility.

                          - -

                          You’ll notice in the button accessory object, there is an action_id. This will act as a unique identifier for the button so your app can specify what action it wants to respond to.

                          - -
                          -

                          💡 The Block Kit Builder is a simple way to prototype your interactive messages. The builder lets you (or anyone on your team) mockup messages and generates the corresponding JSON that you can paste directly in your app.

                          -
                          - -

                          Now, if you restart your app and say “hello” in a channel your app is in, you’ll see a message with a button. But if you click the button, nothing happens (yet!).

                          - -

                          Let’s add a handler to send a followup message when someone clicks the button:

                          - -
                          -
                          -
                          1
                          -2
                          -3
                          -4
                          -5
                          -6
                          -7
                          -8
                          -9
                          -10
                          -11
                          -12
                          -13
                          -14
                          -15
                          -16
                          -17
                          -18
                          -19
                          -20
                          -21
                          -22
                          -23
                          -24
                          -25
                          -26
                          -27
                          -28
                          -29
                          -30
                          -31
                          -32
                          -33
                          -34
                          -35
                          -36
                          -37
                          -38
                          -39
                          -40
                          -41
                          -42
                          -43
                          -44
                          -45
                          -46
                          -47
                          -48
                          -49
                          -
                          const { App } = require('@slack/bolt');
                          -
                          -const app = new App({
                          -  token: process.env.SLACK_BOT_TOKEN,
                          -  signingSecret: process.env.SLACK_SIGNING_SECRET,
                          -  socketMode: true,
                          -  appToken: process.env.SLACK_APP_TOKEN,
                          -  // Socket Mode doesn't listen on a port, but in case you want your app to respond to OAuth,
                          -  // you still need to listen on some port!
                          -  port: process.env.PORT || 3000
                          -});
                          -
                          -// Listens to incoming messages that contain "hello"
                          -app.message('hello', async ({ message, say }) => {
                          -  // say() sends a message to the channel where the event was triggered
                          -  await say({
                          -    blocks: [
                          -      {
                          -        "type": "section",
                          -        "text": {
                          -          "type": "mrkdwn",
                          -          "text": `Hey there <@${message.user}>!`
                          -        },
                          -        "accessory": {
                          -          "type": "button",
                          -          "text": {
                          -            "type": "plain_text",
                          -            "text": "Click Me"
                          -          },
                          -          "action_id": "button_click"
                          -        }
                          -      }
                          -    ],
                          -    text: `Hey there <@${message.user}>!`
                          -  });
                          -});
                          -
                          -app.action('button_click', async ({ body, ack, say }) => {
                          -  // Acknowledge the action
                          -  await ack();
                          -  await say(`<@${body.user.id}> clicked the button`);
                          -});
                          -
                          -(async () => {
                          -  // Start your app
                          -  await app.start();
                          -
                          -  console.log('⚡️ Bolt app is running!');
                          -})();
                          -
                          -
                          -
                          - -

                          You can see that we used app.action() to listen for the action_id that we named button_click. If you restart your app and click the button, you’ll see a new message from your app that says you clicked the button.

                          - -
                          - -

                          Next steps

                          -

                          You just built your first Bolt for JavaScript app with Socket Mode! 🎉

                          - -

                          Now that you have a basic app up and running, you can start exploring how to make your Bolt app stand out. Here are some ideas about what to explore next:

                          - -
                            -
                          • -

                            Read through the Basic concepts to learn about the different methods and features your Bolt app has access to.

                            -
                          • -
                          • -

                            Explore the different events your bot can listen to with the events() method. All of the events are listed on the API site.

                            -
                          • -
                          • -

                            Bolt allows you to call Web API methods with the client attached to your app. There are over 220 methods on our API site.

                            -
                          • -
                          • -

                            Learn more about the different token types on our API site. Your app may need different tokens depending on the actions you want it to perform. For apps that do not use Socket Mode, typically only a bot (xoxb) token is required. For example of this, see Getting Started with HTTP.

                            -
                          • -
                          - -
                          -
                          - -
                          - - - - - - - - \ No newline at end of file diff --git a/docs/_site/tutorial/hubot-migration.html b/docs/_site/tutorial/hubot-migration.html deleted file mode 100644 index cf67570b4..000000000 --- a/docs/_site/tutorial/hubot-migration.html +++ /dev/null @@ -1,534 +0,0 @@ - - - - - - - Slack | Bolt for JavaScript - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                          -
                          - - -
                          - - -
                          - - -
                          -
                            -
                            - -
                            -

                            Migrating apps from Hubot to Bolt for JavaScript

                            - -
                            -

                            Bolt was created to reduce the time and complexity it takes to build Slack apps. It provides Slack developers a single interface to build using modern features and best practices. This guide is meant to step you through the process of migrating your app from using Hubot to Bolt for JavaScript.

                            - -

                            If you already have an app with a bot user or if you’re looking for code samples that translate Hubot code to Bolt for JavaScript code, you may find it valuable to start by reading through the example script in the Bolt for JavaScript repository.

                            -
                            - -
                            - -

                            Setting the stage

                            -

                            When translating a Hubot app to Bolt for JavaScript, it’s good to know how each are working behind the scenes. Slack’s Hubot adapter is built to interface with the RTM API, which uses a WebSocket-based connection that sends a stream of workspace events to your Hubot app. The RTM API is not recommended for most use cases since it doesn’t include support for newer platform features and it can become very resource-intensive, particularly if the app is installed on multiple or large Slack teams.

                            - -

                            The default Bolt for JavaScript receiver is built to support the Events API, which uses HTTP-based event subscriptions to send JSON payloads to your Bolt app. The Events API includes newer events that aren’t on RTM and is more granular and scalable. It’s recommended for most use cases, though one reason your app may be stuck using the RTM API could be that the server you’re hosting your app from has a firewall that only allows outgoing requests and not incoming ones.

                            - -

                            There are a few other differences you may want to consider before creating a Bolt for JavaScript app:

                            -
                              -
                            • The minimum version of Node for Bolt for JavaScript is v10.0.0. If the server you’re hosting your app from cannot support v10, it’s not possible to migrate your app to Bolt for JavaScript at the moment.
                            • -
                            • Bolt for JavaScript doesn’t have support for external scripts. If your Hubot app uses external scripts that are necessary to your app’s functionality or deployment, you probably want to stay with Hubot for now. If you aren’t sure whether your app has any external scripts, you can check the external-scripts.json file. As we continue to invest in Bolt for JavaScript, we are thinking about the future and how to make development and deployment of Slack apps easier. If there’s a valuable external script that your app uses, we’d love to hear what it is in the dedicated GitHub issue.
                            • -
                            • Hubot apps are written in Coffeescript, which transpiles into JavaScript. We decided to write Bolt in Typescript to give access to rich type information. Bolt apps can be developed using Typescript or JavaScript. The example script shows you how your Coffeescript may translate to JavaScript. If your app is more than a few simple scripts, it may be worth looking into projects like Decaffeinate to convert your CoffeeScript to JavaScript.
                            • -
                            - -
                            - -

                            Configuring your bot

                            -

                            If you have access to an existing Slack app with a bot user, you can jump ahead to the next section. If you aren’t sure, go to your App Management page and check whether your Hubot app is there. If it is, you can use the credentials from that app (go ahead and skip to the next section). Otherwise, we’ll walk you through creating a Slack app.

                            - -

                            Create a Slack app

                            - -

                            The first thing you’ll want to do is create a Slack app.

                            - -
                            -

                            💡We recommend using a workspace where you won’t disrupt real work getting done — you can create a new one for free.

                            -
                            - -

                            After you fill out your app’s name and pick a workspace to install it to, hit the Create App button and you’ll land on your app’s Basic Information page.

                            - -

                            This page contains an overview of your app in addition to important credentials you’ll need later, like the Signing Secret under the App Credentials header.

                            - -

                            Look around, add an app icon and description, and then let’s start configuring your app 🔩

                            - -

                            Add a bot user

                            -

                            On Slack, Hubot apps employ bot users which are designed to interact with users in conversation.

                            - -

                            To add a bot user to your new app, click Bot Users on the left sidebar and then Add A Bot User. Give it a display name and username, then click Add Bot User. There’s more information about what the different fields are on our API site.

                            - -

                            Configure what your bot will hear

                            -

                            The Events API is a bot’s equivalent of eyes and ears. It gives a bot a way to react to posted messages, changes to channels, and other activities that happen in Slack.

                            - -
                            -

                            ⚠️Before you configure your bot’s events, you’ll need a public URL. If you’ve never created a Bolt for JavaScript app or never used the Events API, it’d be helpful to go through setting up your local Bolt project and setting up events in the Getting Started guide.

                            -
                            - -

                            Listening for messages

                            -

                            All Hubot apps can listen to messages by default, so we need to configure your bot user to do the same.

                            - -

                            After walking through setting up events, your Request URL should be verified. Scroll down to Subscribe to Bot Events. There are four events related to messages: message.channels (listens for messages in public channels), message.groups (listens for messages in private channels), message.im (listens for messages in the App Home/DM space), and message.mpim (listens for messages in multi-person DMs).

                            - -

                            If you only want your bot to listen to messages in channels, you can listen to message.channels and message.groups. Or if you want your bot to listen to messages from everywhere it is, choose all four message events.

                            - -

                            After you’ve added the kinds of message events you want your bot to listen to, click Save Changes.

                            - -

                            Listening for other events

                            -

                            Your Hubot app may have responded to other events depending on what functionality you used. Look through your script and identify any places where your script uses react, respond, or presenceChange:

                            -
                              -
                            • If your app uses respond, subscribe to the app_mention event. This listens for any time your bot user is mentioned.
                            • -
                            • If your app uses react, subscribe to the reaction_added event. This listens for any time a reaction is added to a message in channels your bot user is in.
                            • -
                            • If your app uses presenceChange, there is no corresponding event. If this event is important to your bot’s functionality, you may have to continue using Hubot or modify the app’s logic.
                            • -
                            - -
                            -

                            💡An added benefit to Bolt is you can listen to any Events API event. So after you’re done migrating, you can listen to more events like when a user joins the workspace or when a user opens a DM with your app.

                            -
                            - -

                            After you added events that correspond to your app’s functionality, click Save Changes.

                            - -

                            Changes to script interfaces

                            -

                            Bolt’s interface was designed to conform to the Slack API language as much as possible, while Hubot was designed with more generalized language to abstract multiple services. While the interfaces are similar, converting a Hubot script to a Bolt for JavaScript one still requires some code changes.

                            - -

                            Bolt for JavaScript doesn’t use res or expose the raw request from Slack. Instead, you can use the payload body from payload, or common functionality like sending a message using say().

                            - -
                            -

                            ⚙️To make it easier, we’ve created a sample script on GitHub that showcases Hubot’s core functionality using equivalent functionality written with Bolt for JavaScript.

                            -
                            - -

                            Listening to patterns using message() -

                            -

                            Hubot scripts use hear() listen to messages with a matching pattern. Bolt for JavaScript instead uses message() and accepts a string or RegExp for the pattern.

                            - -
                            -

                            👨‍💻👩‍💻Anywhere where you use hear() in your code, change it to use message()

                            -
                            - -

                            Read more about listening to messages.

                            - -

                            Responding with a message using say() and respond() -

                            -

                            Hubot scripts use send() to send a message to the same conversation and reply() to send a message to the same conversation with an @-mention to the user that sent the original message.

                            - -

                            Bolt for JavaScript uses await say() in place of send(), or await respond() to use the response_url to send a reply. To add an @-mention to the beginning of your reply, you can use the user ID found in the context object. For example, for a message event you could use await say('<@${message.user}> Hello :wave:')

                            - -

                            The arguments for Hubot’s send() and Bolt for JavaScript’s say() are mostly the same, although say() allows you to send messages with interactive components like buttons, select menus, and datepickers.

                            - -
                            -

                            👨‍💻👩‍💻Anywhere where you use send() in your code, change it to use await say()

                            -
                            - -

                            Read more about responding to messages.

                            - -

                            -respond and react -

                            - -

                            In the previous section, you should have subscribed your app to the app_mention event if your Hubot script uses respond(), and reaction_added if you uses react().

                            - -

                            Bolt for JavaScript uses a method called event() that allows you to listen to any Events API event. To change your code, you’ll just change any respond() to app.event(‘app_mention’) and any react() to app.event(‘reaction_added’). This is detailed more in the example script.

                            - -
                            -

                            👨‍💻👩‍💻Anywhere where you use respond() in your code, change it to use app.event(‘app_mention’). Anywhere you use react, change it to app.event(‘reaction_added’).

                            -
                            - -

                            Read more about listening to events.

                            - -

                            Using Web API methods with Bolt for JavaScript

                            -

                            In Hubot, you needed to import the WebClient package from @slack/client. Bolt for JavaScript imports a WebClient instance for you by default, and exposes it as the client argument available on all listeners.

                            - -

                            To use the built-in WebClient, you’ll need to pass the token used to instantiate your app or the token associated with the team your request is coming from. This is found on the context object passed in to your listener functions. For example, to add a reaction to a message, you’d use:

                            - -
                            -
                            -
                            
                            -
                            -
                            -
                            1
                            -2
                            -3
                            -4
                            -5
                            -6
                            -7
                            -8
                            -9
                            -10
                            -11
                            -12
                            -13
                            -
                            app.message('react', async ({ message, context, client, logger }) => {
                            -  try {
                            -    const result = await client.reactions.add({
                            -      token: context.botToken,
                            -      name: 'star',
                            -      channel: message.channel,
                            -      timestamp: message.ts,
                            -    });
                            -  }
                            -  catch (error) {
                            -    logger.error(error);
                            -  }
                            -});
                            -
                            -
                            -
                            - -
                            -

                            👨‍💻👩‍💻Change your Web API calls to use one the client argument.

                            -
                            - -

                            Read more about using the Web API with Bolt.

                            - -

                            Using middleware with Bolt for JavaScript

                            -

                            Hubot has three kinds of middleware: receive (runs before any listeners are called), listener (runs for every matching listener), and response (runs for every response sent).

                            - -

                            Bolt for JavaScript only has two kinds of middleware — global and listener:

                            - - -

                            In Bolt for JavaScript, both kinds of middleware must call await next() to pass control of execution from one middleware to the next. If your middleware encounters an error during execution, you can throw it and the error will be bubbled up through the previously-executed middleware chain.

                            - -

                            To migrate your existing middleware functions, it’s evident that Hubot’s receive middleware aligns with the use case for global middleware in Bolt for JavaScript. And Hubot and Bolt’s listener middleware are nearly the same. To migrate Hubot’s response middleware, wrap Bolt for JavaScript’s say() or respond() in your own function, and then call it.

                            - -

                            If your middleware needs to perform post-processing of an event, you can call await next() and any code after will be processed after the downstream middleware has been called.

                            - -

                            Migrating the brain to the conversation store

                            -

                            Hubot has an in-memory store called the brain. This enables a Hubot script to get and set basic pieces of data. Bolt for JavaScript uses a conversation store, which is a global middleware with a get()/set() interface.

                            - -

                            The default, built-in conversation store uses an in-memory store similar to Hubot, with the ability to set an expiration time in milliseconds. There are two ways to get and set conversation state:

                            -
                              -
                            • Call app.convoStore.get() with a conversation ID to retrieve the state of a conversation, and call app.convoStore.set() with a conversation ID, conversation state (key-value pair), and an optional expiresAt time in milliseconds.
                            • -
                            • In listener middleware, call context.updateConversation() with the updated conversation state, or use context.conversation to access the current state of the conversation.
                            • -
                            - -

                            If there is more than one instance of your app running, the built-in conversation store will not be shared among the processes so you’ll want to implement a conversation store that fetches conversation state from a database.

                            - -

                            Read more about conversation stores.

                            - -

                            Next steps

                            -

                            If you’ve made it this far, it means you’ve likely converted your Hubot app into a Bolt for JavaScript app! ✨⚡

                            - -

                            Now that you have your flashy new Bolt for JavaScript app, you can explore how to power it up:

                            -
                              -
                            • Consider adding interactivity like buttons and select menus. These weren’t supported by Hubot and will allow your app to include contextual actions when sending messages to Slack.
                            • -
                            • Read the documentation to explore what else is possible with Bolt for JavaScript.
                            • -
                            • Check out our sample app that shows you how to use events and interactive components.
                            • -
                            - -

                            And if you have difficulties while developing, reach out to our developer support team to at developers@slack.com, and if you run into a problem with the framework open an issue on GitHub.

                            - -
                            -
                            - -
                            - - - - - - - - \ No newline at end of file diff --git a/docs/_site/tutorial/migration-v2.html b/docs/_site/tutorial/migration-v2.html deleted file mode 100644 index 644351270..000000000 --- a/docs/_site/tutorial/migration-v2.html +++ /dev/null @@ -1,525 +0,0 @@ - - - - - - - Slack | Bolt for JavaScript - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                            -
                            - - -
                            - - -
                            - - -
                            -
                              -
                              - -
                              -

                              Migrating to v2.x

                              - -
                              -

                              This guide will walk you through the process of updating your app from using @slack/bolt@1.x to @slack/bolt@2.x. There are a few changes you’ll need to make but for most apps, these changes can be applied in 5 - 15 minutes.

                              - -

                              Note: Make sure to checkout our support schedule for @slack/bolt@1.x if you don’t plan on upgrading right away

                              -
                              - -
                              - -

                              Upgrading your listeners to async

                              - -

                              Listeners in your app should updated to async functions and say(), respond(), and ack() should be prefaced with await.

                              - -

                              Before:

                              - -
                              -
                              -
                              1
                              -2
                              -3
                              -4
                              -
                              app.action('some-action-id', ({action, ack, say}) => { 
                              -  ack();
                              -  say('hello world');
                              -})
                              -
                              -
                              -
                              - -

                              After:

                              - -
                              -
                              -
                              1
                              -2
                              -3
                              -4
                              -
                              app.action('some-action-id', async ({action, ack, say}) => { 
                              -  await ack();
                              -  await say('hello world');
                              -})
                              -
                              -
                              -
                              - -

                              Error Handling

                              - -

                              The recent changes in Bolt for JavaScript V2 have improved our ability to catch errors and filter them to the global error handler. It is still recommended to manage errors in the listeners themselves instead of letting them propagate to the global handler when possible.

                              - -

                              Handling Errors in Listeners with try/catch

                              - -
                              -
                              -
                              1
                              -2
                              -3
                              -4
                              -5
                              -6
                              -7
                              -8
                              -9
                              -
                              app.action('some-action-id', async ({action, ack, say, logger}) => { 
                              -  try {
                              -    await ack();
                              -    await say('hello world');
                              -  } catch (error) {
                              -    logger.error(error);
                              -    // handle error
                              -  }
                              -})
                              -
                              -
                              -
                              - -

                              Handling Errors with the Global Error Handler

                              - -
                              -
                              -
                              1
                              -2
                              -3
                              -4
                              -
                              app.error(async (error) => {
                              -  // Check the details of the error to handle cases where you should retry sending a message or stop the app
                              -  console.error(error);
                              -});
                              -
                              -
                              -
                              - -

                              Other error related changes include:

                              - -
                                -
                              • When your listener doesn’t call ack within the 3 second time limit, we log the failure instead of throwing an error.
                              • -
                              • If multiple errors occur when processing multiple listeners for a single event, Bolt for JavaScript will return a wrapper error with a code property of ErrorCode.MultipleListenerError and an originals property that contains an array of the individual errors.
                              • -
                              - -

                              Message Shortcuts

                              - -

                              Message shortcuts (previously referred to as message actions) now use the shortcut() method instead of the action() method.

                              - -

                              Before:

                              - -
                              -
                              -
                              1
                              -2
                              -3
                              -4
                              -
                              app.action({ callback_id: 'message-action-callback' }, ({action, ack, context}) => {
                              -  ack();
                              -  // Do stuff
                              -})
                              -
                              -
                              -
                              - -

                              After:

                              - -
                              -
                              -
                              1
                              -2
                              -3
                              -4
                              -
                              app.shortcut('message-action-callback', async ({shortcut, ack, context}) => {
                              -  await ack();
                              -  // Do stuff
                              -})
                              -
                              -
                              -
                              - -

                              Upgrading Middleware

                              - -

                              If you wrote a custom middleware, adjust your function to async and update next() to await next(). If your middleware does some post processing, instead of passing a function to next(), you can now run it after await next().

                              - -

                              Before:

                              - -
                              -
                              -
                              1
                              -2
                              -3
                              -4
                              -5
                              -6
                              -7
                              -8
                              -9
                              -
                              function noBotMessages({ message, next }) {
                              -  function doAfter() { 
                              -    // Post processing goes here
                              -  }
                              -
                              -if (!message.subtype || message.subtype !== 'bot_message') {
                              -    next(doAfter);
                              -  }
                              -}
                              -
                              -
                              -
                              - -

                              After:

                              - -
                              -
                              -
                              1
                              -2
                              -3
                              -4
                              -5
                              -6
                              -
                              async function noBotMessages({ message, next }) {
                              -  if (!message.subtype || message.subtype !== 'bot_message') {
                              -    await next();
                              -    // Post processing goes here
                              -  }
                              -}
                              -
                              -
                              -
                              - -

                              @slack/bolt@1.x support schedule

                              - -

                              @slack/bolt@1.x will be deprecated on June 30th, 2020. We plan on continuing to implement bug fixes and will also consider back-porting new features on a case by case basis up until then. Once @slack/bolt@1.x has been deprecated, we will only implement critical bug fixes until the official end of life date and close non critical issues and pull requests. End of life is slated for April 30th, 2021. At this time, development will fully stop for @slack/bolt@1.x and all remaining open issues and pull requests will be closed.

                              - -

                              Minimum TypeScript Version

                              - -

                              As outlined in our using TypeScript guide, @slack/bolt@2.x requires a minimum TypeScript version of 3.7.

                              - -
                              -
                              - -
                              - - - - - - - - \ No newline at end of file diff --git a/docs/_site/tutorial/migration-v3.html b/docs/_site/tutorial/migration-v3.html deleted file mode 100644 index 54e6c7dd3..000000000 --- a/docs/_site/tutorial/migration-v3.html +++ /dev/null @@ -1,515 +0,0 @@ - - - - - - - Slack | Bolt for JavaScript - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                              -
                              - - -
                              - - -
                              - - -
                              -
                                -
                                - -
                                -

                                Migrating to v3.x

                                - -
                                -

                                This guide will walk you through the process of updating your app from using @slack/bolt@2.x to @slack/bolt@3.x. There are a few changes you’ll need to make but for most apps, these changes can be applied in 5 - 15 minutes.

                                - -

                                Note: Make sure to checkout our support schedule for @slack/bolt@2.x if you don’t plan on upgrading right away

                                -
                                - -
                                - -

                                Org Wide App Installation Changes to InstallationStore & orgAuthorize

                                - -

                                In Bolt for JavaScript 2.5.0, we introduced support for org wide app installations. To add support to your applications, two new methods were introduced to the Installation Store used during OAuth, fetchOrgInstallation & storeOrgInstallation. With @slack/bolt@3.x, we have dropped support for these two new methods for a simpler interface and to be better aligned with Bolt for Python and Bolt for Java. See the code samples below for the recommended changes to migrate.

                                - -

                                Before:

                                - -
                                -
                                -
                                1
                                -2
                                -3
                                -4
                                -5
                                -6
                                -7
                                -8
                                -9
                                -10
                                -11
                                -12
                                -13
                                -14
                                -15
                                -16
                                -17
                                -18
                                -19
                                -20
                                -
                                installationStore: {
                                -    storeInstallation: async (installation) => {
                                -      // change the line below so it saves to your database
                                -      return await database.set(installation.team.id, installation);
                                -    },
                                -    fetchInstallation: async (installQuery) => {
                                -      // change the line below so it fetches from your database
                                -      return await database.get(installQuery.teamId);
                                -    },
                                -    storeOrgInstallation: async (installation) => {
                                -      // include this method if you want your app to support org wide installations
                                -      // change the line below so it saves to your database
                                -      return await database.set(installation.enterprise.id, installation);
                                -    },
                                -    fetchOrgInstallation: async (installQuery) => {
                                -      // include this method if you want your app to support org wide installations
                                -      // change the line below so it fetches from your database
                                -      return await database.get(installQuery.enterpriseId);
                                -    },
                                -  },
                                -
                                -
                                -
                                - -

                                After:

                                - -
                                -
                                -
                                1
                                -2
                                -3
                                -4
                                -5
                                -6
                                -7
                                -8
                                -9
                                -10
                                -11
                                -12
                                -13
                                -14
                                -15
                                -16
                                -17
                                -18
                                -19
                                -20
                                -21
                                -22
                                -23
                                -24
                                -25
                                -
                                installationStore: {
                                -    storeInstallation: async (installation) => {
                                -      if (installation.isEnterpriseInstall && installation.enterprise !== undefined) {
                                -        // support for org wide app installation
                                -        return await database.set(installation.enterprise.id, installation);
                                -      }
                                -      if (installation.team !== undefined) {
                                -        // single team app installation
                                -        return await database.set(installation.team.id, installation);
                                -      }
                                -      throw new Error('Failed saving installation data to installationStore');
                                -    },
                                -    fetchInstallation: async (installQuery) => {
                                -      // replace database.get so it fetches from your database
                                -      if (installQuery.isEnterpriseInstall && installQuery.enterpriseId !== undefined) {
                                -        // org wide app installation lookup
                                -        return await database.get(installQuery.enterpriseId);
                                -      }
                                -      if (installQuery.teamId !== undefined) {
                                -        // single team app installation lookup
                                -        return await database.get(installQuery.teamId);
                                -      }
                                -      throw new Error('Failed fetching installation');
                                -    },
                                -  },
                                -
                                -
                                -
                                - -

                                Along with this change, we have also dropped support for orgAuthorize, and instead recommend developers to use authorize for both the single workspace installs and org wide app installs (if you are not using the built-in OAuth or providing a token when initializing App). See the code sample below for migration steps:

                                - -

                                Before:

                                - -
                                -
                                -
                                1
                                -2
                                -3
                                -4
                                -5
                                -6
                                -7
                                -8
                                -9
                                -
                                const app = new App({ authorize: authorizeFn, orgAuthorize: orgAuthorizeFn, signingSecret: process.env.SLACK_SIGNING_SECRET });
                                -
                                -const authorizeFn = async ({ teamId, enterpriseId}) => { 
                                -  // Use teamId to fetch installation details from database
                                -}
                                -
                                -const orgAuthorizeFn = async ({ teamId, enterpriseId }) => { 
                                -  // Use enterpriseId to fetch installation details from database
                                -}
                                -
                                -
                                -
                                - -

                                After:

                                -
                                -
                                -
                                1
                                -2
                                -3
                                -4
                                -5
                                -6
                                -
                                const app = new App({ authorize: authorizeFn, signingSecret: process.env.SLACK_SIGNING_SECRET });
                                -
                                -const authorizeFn = async ({ teamId, enterpriseId, isEnterpriseInstall}) => { 
                                -  // if isEnterpriseInstall is true, use enterpriseId to fetch installation details from database
                                -  // else, use teamId to fetch installation details from database
                                -}
                                -
                                -
                                -
                                - -

                                HTTP Receiver as default

                                - -

                                In @slack/bolt@3.x, we have introduced a new default HTTPReceiver which replaces the previous default ExpressReceiver. This will allow Bolt for JavaScript apps to easily work with other popular web frameworks (Hapi.js, Koa, etc). ExpressReceiver is still being shipped with Bolt for JavaScript and HTTPReceiver will not provide all the same functionality. One use case that isn’t supported by HTTPReceiver is creating custom routes (ex: create a route to do a health check). For these use cases, we recommend continuing to use ExpressReceiver by importing the class, and creating your own instance of it, and passing this instance into the constructor of App. See our documentation on adding custom http routes for an example.

                                - -

                                @slack/bolt@2.x support schedule

                                - -

                                @slack/bolt@2.x will be deprecated on January 12th, 2021. We will only implement critical bug fixes until the official end of life date and close non critical issues and pull requests, which is slated for May 31st, 2021. At this time, development will fully stop for @slack/bolt@2.x and all remaining open issues and pull requests will be closed.

                                - -

                                Minimum Node Version

                                - -

                                @slack/bolt@3.x requires a minimum Node version of 12.13.0 and minimum npm version of 6.12.0 .

                                - -

                                Minimum TypeScript Version

                                - -

                                As outlined in our using TypeScript guide, @slack/bolt@3.x requires a minimum TypeScript version of 4.1.

                                - -
                                -
                                - -
                                - - - - - - - - \ No newline at end of file diff --git a/docs/_site/tutorial/using-typescript.html b/docs/_site/tutorial/using-typescript.html deleted file mode 100644 index e6dedf84a..000000000 --- a/docs/_site/tutorial/using-typescript.html +++ /dev/null @@ -1,348 +0,0 @@ - - - - - - - Slack | Bolt for JavaScript - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                                -
                                - - -
                                - - -
                                - - -
                                -
                                  -
                                  - -
                                  -

                                  Using TypeScript

                                  -
                                  -

                                  ⚠️ This guide is a work-in-progress.

                                  -
                                  - -

                                  See the sample TypeScript project to see a TypeScript equivalent of the Getting Started app (and a few other basic examples).

                                  - -

                                  This project is written and built using TypeScript, which means many of the APIs have type information metadata 🎉. If you’re using a code editor like VSCode, Atom, or many others that know how to read that metadata, or if you’re using TypeScript in your own project, you’ll benefit from improved documentation as your write code, early detection of errors, easier refactoring, and more.

                                  - -

                                  This page helps describe how to use this package from a project that also uses TypeScript.

                                  - -

                                  Minimum version

                                  - -

                                  The latest major version of @slack/bolt is supported to build against a minimum TypeScript version of v4.1.

                                  - - -
                                  -
                                  - -
                                  - - - - - - - - \ No newline at end of file