UNPKG

macro_api

Version:

A comprehensive, production-ready API toolkit for various services including Stripe, Slack, SendGrid, Vercel, AWS S3, Docker Hub, and more.

371 lines (370 loc) 11.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SlackAPI = void 0; const axios_1 = __importDefault(require("axios")); const crypto_1 = __importDefault(require("crypto")); /** * Production-ready Slack API wrapper for team communication & workflow automation */ class SlackAPI { constructor(config) { this.baseUrl = 'https://slack.com/api'; if (!config.botToken) { throw new Error('Slack bot token is required'); } this.botToken = config.botToken; this.appToken = config.appToken; this.signingSecret = config.signingSecret; } async request(method, endpoint, data, headers) { try { const defaultHeaders = { 'Authorization': `Bearer ${this.botToken}`, 'Content-Type': method === 'POST' ? 'application/json' : 'application/x-www-form-urlencoded' }; const requestHeaders = { ...defaultHeaders, ...headers }; const response = await (0, axios_1.default)({ method, url: `${this.baseUrl}/${endpoint}`, data: method === 'POST' ? data : undefined, params: method === 'GET' ? data : undefined, headers: requestHeaders, timeout: 30000 }); const result = response.data; if (!result.ok) { throw new Error(`Slack API Error: ${result.error || 'Unknown error'}`); } return response.data; } catch (error) { this.handleSlackError(error); throw error; } } handleSlackError(error) { if (error.response?.data?.error) { const slackError = error.response.data.error; throw new Error(`Slack API Error: ${slackError}`); } } /** * Send a message to a channel or user */ async sendMessage(channel, text, options) { const data = { channel, text }; if (options?.blocks) data.blocks = JSON.stringify(options.blocks); if (options?.threadTs) data.thread_ts = options.threadTs; if (options?.unfurlLinks !== undefined) data.unfurl_links = options.unfurlLinks; if (options?.unfurlMedia !== undefined) data.unfurl_media = options.unfurlMedia; if (options?.asUser !== undefined) data.as_user = options.asUser; return this.request('POST', 'chat.postMessage', data); } /** * Upload a file to Slack */ async uploadFile(channels, file, options) { const formData = new FormData(); if (typeof file === 'string') { formData.append('content', file); } else { formData.append('file', new Blob([file]), options?.filename || 'file'); } formData.append('channels', Array.isArray(channels) ? channels.join(',') : channels); if (options?.filename) formData.append('filename', options.filename); if (options?.title) formData.append('title', options.title); if (options?.filetype) formData.append('filetype', options.filetype); if (options?.initialComment) formData.append('initial_comment', options.initialComment); if (options?.threadTs) formData.append('thread_ts', options.threadTs); return this.request('POST', 'files.upload', formData, { 'Content-Type': 'multipart/form-data' }); } /** * Create a new channel */ async createChannel(name, options) { // Remove invalid characters and convert to lowercase const channelName = name.toLowerCase().replace(/[^a-z0-9-_]/g, '-'); const data = { name: channelName }; if (options?.isPrivate) data.is_private = options.isPrivate; const response = await this.request('POST', 'conversations.create', data); // Set topic and purpose if provided if (options?.topic) { await this.setChannelTopic(response.channel.id, options.topic); } if (options?.purpose) { await this.setChannelPurpose(response.channel.id, options.purpose); } return response.channel; } /** * Set channel topic */ async setChannelTopic(channel, topic) { await this.request('POST', 'conversations.setTopic', { channel, topic }); } /** * Set channel purpose */ async setChannelPurpose(channel, purpose) { await this.request('POST', 'conversations.setPurpose', { channel, purpose }); } /** * Get user information */ async getUserInfo(userId) { const response = await this.request('GET', 'users.info', { user: userId }); return response.user; } /** * Set a reminder */ async setReminder(text, time, user) { const data = { text, time }; if (user) data.user = user; const response = await this.request('POST', 'reminders.add', data); return response.reminder; } /** * Search messages */ async searchMessages(query, options) { const data = { query }; if (options?.count) data.count = options.count; if (options?.highlight !== undefined) data.highlight = options.highlight; if (options?.page) data.page = options.page; if (options?.sortDir) data.sort_dir = options.sortDir; return this.request('GET', 'search.messages', data); } /** * Handle slash command with signature verification */ async handleSlashCommand(payload, signature, timestamp, rawBody) { // Verify signature if signing secret is available if (this.signingSecret && signature && timestamp && rawBody) { this.verifySlackSignature(signature, timestamp, rawBody); } // Process the command based on the command type return this.processSlashCommand(payload); } /** * Verify Slack request signature */ verifySlackSignature(signature, timestamp, body) { if (!this.signingSecret) { throw new Error('Signing secret is required for signature verification'); } const time = parseInt(timestamp); const currentTime = Math.floor(Date.now() / 1000); // Check if the timestamp is too old (more than 5 minutes) if (Math.abs(currentTime - time) > 300) { throw new Error('Request timestamp is too old'); } // Create the signature const sigBasestring = `v0:${timestamp}:${body}`; const expectedSignature = 'v0=' + crypto_1.default .createHmac('sha256', this.signingSecret) .update(sigBasestring) .digest('hex'); // Use timingSafeEqual to prevent timing attacks if (!crypto_1.default.timingSafeEqual(Buffer.from(expectedSignature), Buffer.from(signature))) { throw new Error('Invalid signature'); } } /** * Process slash command */ async processSlashCommand(payload) { // This is a basic implementation - extend based on your needs const { command, text, channelId, userId } = payload; switch (command) { case '/help': return { responseType: 'ephemeral', text: 'Available commands: /help, /status', blocks: [ { type: 'section', text: { type: 'mrkdwn', text: '*Available Commands:*\n• `/help` - Show this help message\n• `/status` - Check system status' } } ] }; case '/status': return { responseType: 'in_channel', text: 'System status: All systems operational', blocks: [ { type: 'section', text: { type: 'mrkdwn', text: ':white_check_mark: *System Status:* All systems operational' } } ] }; default: return { responseType: 'ephemeral', text: `Unknown command: ${command}. Type \`/help\` for available commands.` }; } } /** * Get channel list */ async getChannels(excludeArchived = true) { const data = { exclude_archived: excludeArchived, types: 'public_channel,private_channel' }; const response = await this.request('GET', 'conversations.list', data); return response.channels; } /** * Join a channel */ async joinChannel(channel) { await this.request('POST', 'conversations.join', { channel }); } /** * Leave a channel */ async leaveChannel(channel) { await this.request('POST', 'conversations.leave', { channel }); } /** * Invite users to a channel */ async inviteUsersToChannel(channel, users) { await this.request('POST', 'conversations.invite', { channel, users: users.join(',') }); } /** * Get channel members */ async getChannelMembers(channel) { const response = await this.request('GET', 'conversations.members', { channel }); return response.members; } /** * Archive a channel */ async archiveChannel(channel) { await this.request('POST', 'conversations.archive', { channel }); } /** * Unarchive a channel */ async unarchiveChannel(channel) { await this.request('POST', 'conversations.unarchive', { channel }); } /** * Update a message */ async updateMessage(channel, ts, text, blocks) { const data = { channel, ts, text }; if (blocks) data.blocks = JSON.stringify(blocks); return this.request('POST', 'chat.update', data); } /** * Delete a message */ async deleteMessage(channel, ts) { await this.request('POST', 'chat.delete', { channel, ts }); } /** * Add reaction to a message */ async addReaction(channel, timestamp, name) { await this.request('POST', 'reactions.add', { channel, timestamp, name }); } /** * Remove reaction from a message */ async removeReaction(channel, timestamp, name) { await this.request('POST', 'reactions.remove', { channel, timestamp, name }); } /** * Get team information */ async getTeamInfo() { const response = await this.request('GET', 'team.info'); return response.team; } /** * Test API connection */ async testConnection() { try { await this.request('GET', 'api.test'); return true; } catch (error) { return false; } } } exports.SlackAPI = SlackAPI;