Slack: команда /horoscope
Ta treść nie jest jeszcze dostępna w Twoim języku.
Хтось у каналі пише /horoscope leo, і в канал приходить гороскоп Лева на сьогодні. Slack надсилає команду POST-запитом на ваш сервер і чекає відповіді не довше за 3 секунди, тож серверу потрібна публічна адреса з HTTPS. Бібліотека Bolt не потрібна: увесь обмін це один запит і одна відповідь.
1. Застосунок Slack
Section titled “1. Застосунок Slack”- На api.slack.com/apps натисніть Create New App → From scratch і виберіть робочий простір.
- Slash Commands → Create New Command: команда
/horoscope, Request URLhttps://bot.example.com/slack/horoscope, Usage Hintleo. - Basic Information → App Credentials: скопіюйте Signing Secret.
- Install App → Install to Workspace.
- Ключ AstroWay Тільки сервер з областю
public/horoscope/*, як описано в розділі Ключ для автоматизації.
export ASTROWAY_API_KEY="aw_live_..."export SLACK_SIGNING_SECRET="..."2. Код
Section titled “2. Код”Збережіть як slack.mjs і запустіть node slack.mjs:
// Node 22 or newer, no dependencies.import { createServer } from 'node:http';import { createHmac, timingSafeEqual } from 'node:crypto';
const ASTROWAY = 'https://api.astroway.info/v1';const LANG = 'uk';// One text per sign per day: at most 12 calls a day, whatever the traffic.const cache = new Map();const SIGNS = ['aries', 'taurus', 'gemini', 'cancer', 'leo', 'virgo', 'libra', 'scorpio', 'sagittarius', 'capricorn', 'aquarius', 'pisces'];
function signedBySlack(raw, timestamp, signature) { // Older than five minutes reads as a replay. if (!timestamp || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; const expected = 'v0=' + createHmac('sha256', process.env.SLACK_SIGNING_SECRET) .update(`v0:${timestamp}:${raw}`).digest('hex'); return signature?.length === expected.length && timingSafeEqual(Buffer.from(signature), Buffer.from(expected));}
async function horoscope(sign) { const key = `${new Date().toISOString().slice(0, 10)} ${sign}`; if (!cache.has(key)) { const res = await fetch(`${ASTROWAY}/public/horoscope/daily?sign=${sign}&lang=${LANG}`, { headers: { 'X-Api-Key': process.env.ASTROWAY_API_KEY }, }); const json = await res.json(); if (!json.ok) return { response_type: 'ephemeral', text: `AstroWay: ${json.error.message}` }; cache.set(key, `*${sign}, ${json.data.date}*\n${json.data.horoscope}`); } return { response_type: 'in_channel', text: cache.get(key) };}
createServer(async (req, res) => { const chunks = []; for await (const chunk of req) chunks.push(chunk); const raw = Buffer.concat(chunks).toString(); if (!signedBySlack(raw, req.headers['x-slack-request-timestamp'], req.headers['x-slack-signature'])) { res.writeHead(401).end(); return; }
const sign = new URLSearchParams(raw).get('text')?.trim().toLowerCase() ?? ''; const reply = SIGNS.includes(sign) ? await horoscope(sign) : { response_type: 'ephemeral', text: `Usage: /horoscope leo\n${SIGNS.join(', ')}` }; res.writeHead(200, { 'Content-Type': 'application/json' }).end(JSON.stringify(reply));}).listen(process.env.PORT ?? 3000);Як це працює
Section titled “Як це працює”- Підпис. Slack підписує кожен запит: HMAC-SHA256 від
v0:<timestamp>:<тіло>вашим Signing Secret. Код перевіряє підпис і відкидає запит, старший за п’ять хвилин, тож чужий або повторений запит отримує401. in_channelчиephemeral. Гороскоп бачать усі в каналі, а підказку чи помилку бачить лише той, хто набрав команду.- 3 секунди. Перший запит знака за день іде в API, наступні беруться з кешу за кілька мілісекунд. Якщо замінити гороскоп на щось повільніше, наприклад PDF-звіт, відповідайте одразу, а результат надсилайте пізніше на
response_urlіз тіла запиту. - Ліміт. Ключ Free на
/v1/public/*рахується як запит без ключа, 30 на годину з IP сервера; кеш тримає бот у межах 12 запитів на добу. Неправильний ключ тут не дає401, а мовчки отримує той самий анонімний ліміт.