From 505985185a838144ff3f06195c53597075d357cd Mon Sep 17 00:00:00 2001 From: MykolaGolubyev Date: Tue, 22 Jul 2025 19:37:37 -0400 Subject: [PATCH] slack: basic bot scaffold slack: basic bot scaffold slack: basic bot scaffold --- znai-slack-bot/.gitignore | 4 + znai-slack-bot/README.md | 130 ++++++++++++++++++++++++++++++++ znai-slack-bot/pyproject.toml | 30 ++++++++ znai-slack-bot/requirements.txt | 3 + znai-slack-bot/slack_bot.py | 106 ++++++++++++++++++++++++++ 5 files changed, 273 insertions(+) create mode 100644 znai-slack-bot/.gitignore create mode 100644 znai-slack-bot/README.md create mode 100644 znai-slack-bot/pyproject.toml create mode 100644 znai-slack-bot/requirements.txt create mode 100644 znai-slack-bot/slack_bot.py diff --git a/znai-slack-bot/.gitignore b/znai-slack-bot/.gitignore new file mode 100644 index 000000000..20d9d3484 --- /dev/null +++ b/znai-slack-bot/.gitignore @@ -0,0 +1,4 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class diff --git a/znai-slack-bot/README.md b/znai-slack-bot/README.md new file mode 100644 index 000000000..4acdab314 --- /dev/null +++ b/znai-slack-bot/README.md @@ -0,0 +1,130 @@ +# Znai Slack Bot - Local Testing Instructions + +## Prerequisites + +1. Python 3.8+ +2. A Slack workspace where you can create apps +3. ngrok (for exposing local server to Slack) + +## Setup + +### 1. Create a Slack App + +1. Go to https://api.slack.com/apps +2. Click "Create New App" > "From scratch" +3. Name your app (e.g., "Znai Bot") and select your workspace +4. Navigate to "OAuth & Permissions" in the sidebar +5. Under "Bot Token Scopes", add: + - `chat:write` + - `users:read` +6. Click "Install to Workspace" and authorize the app +7. Copy the "Bot User OAuth Token" (starts with `xoxb-`) + +### 2. Install Dependencies + +```bash +cd znai-slack-bot +pip install -r requirements.txt +``` + +### 3. Set Environment Variables + +```bash +export SLACK_BOT_TOKEN= +export SLACK_CHANNEL="#your-channel-name" +``` + +### 4. Run the Server + +```bash +python slack_bot.py +``` + +The server will start on http://localhost:5000 + +## Testing + +### Using curl + +```bash +curl -X POST http://localhost:5111/ask-in-slack \ + -H "Content-Type: application/json" \ + -d '{ + "username": "U096JGE7BPY", + "message": "How do I configure the API endpoint?", + "link": "https://example.com/docs/api-config" + }' +``` + +### Testing with Code Snippets + +```bash +curl -X POST http://localhost:5111/ask-in-slack \ + -H "Content-Type: application/json" \ + -d '{ + "username": "golubev.nikolay", + "message": [ + {"type": "text", "content": "I have an issue with this code:"}, + {"type": "code", "content": "const api = new API();\napi.connect();", "language": "javascript"}, + {"type": "text", "content": "It throws an error on line 2."} + ], + "link": "https://example.com/docs/troubleshooting" + }' +``` + +### Testing from Browser (Cross-Domain) + +Create a simple HTML file to test CORS: + +```html + + + + + + +``` + +## Finding Slack User IDs + +To find a user's Slack ID: +1. In Slack, click on the user's profile +2. Click the three dots menu > "Copy member ID" +3. The ID will look like "U1234567890" + +Alternatively, you can use the username directly (without @) and the bot will attempt to tag them. + +## Message Format + +The `message` field can be: +- A simple string: `"How do I configure the API?"` +- An array of content blocks for mixed text and code: + ```json + [ + {"type": "text", "content": "Here's my code:"}, + {"type": "code", "content": "print('Hello')", "language": "python"}, + {"type": "text", "content": "Why doesn't it work?"} + ] + ``` + +## Troubleshooting + +1. **401 Unauthorized**: Check your SLACK_BOT_TOKEN +2. **Channel not found**: Ensure the bot is added to the channel +3. **CORS errors**: The Flask-CORS package should handle this, but check browser console +4. **User not tagged**: Ensure you're using the correct Slack user ID (starts with U) \ No newline at end of file diff --git a/znai-slack-bot/pyproject.toml b/znai-slack-bot/pyproject.toml new file mode 100644 index 000000000..202db0de2 --- /dev/null +++ b/znai-slack-bot/pyproject.toml @@ -0,0 +1,30 @@ +[build-system] +requires = ["setuptools>=45", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "znai-slack-bot" +version = "1.0.0" +description = "Slack bot for Znai documentation questions" +requires-python = ">=3.9" +dependencies = [ + "flask==3.0.0", + "flask-cors==4.0.0", + "slack-sdk==3.26.1" +] + +[project.scripts] +znai-slack-bot = "slack_bot:main" + +[tool.black] +line-length = 88 +target-version = ['py39'] + +[tool.isort] +profile = "black" +line_length = 88 + +[tool.mypy] +python_version = "3.9" +warn_return_any = true +warn_unused_configs = true \ No newline at end of file diff --git a/znai-slack-bot/requirements.txt b/znai-slack-bot/requirements.txt new file mode 100644 index 000000000..53dd990ba --- /dev/null +++ b/znai-slack-bot/requirements.txt @@ -0,0 +1,3 @@ +flask==3.0.0 +flask-cors==4.0.0 +slack-sdk==3.26.1 \ No newline at end of file diff --git a/znai-slack-bot/slack_bot.py b/znai-slack-bot/slack_bot.py new file mode 100644 index 000000000..db165a199 --- /dev/null +++ b/znai-slack-bot/slack_bot.py @@ -0,0 +1,106 @@ +import os +import json +from flask import Flask, request, jsonify +from flask_cors import CORS +from slack_sdk import WebClient +from slack_sdk.errors import SlackApiError + +app = Flask(__name__) +CORS(app) + +slack_token = os.environ.get("SLACK_BOT_TOKEN") +slack_channel = "#help-domain-name" +slack_client = WebClient(token=slack_token) + +@app.route('/ask-in-slack', methods=['POST']) +def ask_in_slack(): + try: + data = request.json + + if not data: + return jsonify({"error": "No data provided"}), 400 + + username = data.get('username') + message = data.get('message') + link = data.get('link') + + if not username or not message: + return jsonify({"error": "Missing required fields: username and message"}), 400 + + slack_message = format_slack_message(username, message, link) + + result = slack_client.chat_postMessage( + channel=slack_channel, + blocks=slack_message, + text=f"Question from {username}" + ) + + return jsonify({"success": True, "ts": result['ts']}), 200 + + except SlackApiError as e: + return jsonify({"error": f"Slack API error: {e.response['error']}"}), 500 + except Exception as e: + return jsonify({"error": str(e)}), 500 + +def format_slack_message(username, message, link=None): + blocks = [{ + "type": "section", + "text": { + "type": "mrkdwn", + "text": f"<@{username}> asked a question:" + } + }, {"type": "divider"}] + + parts = parse_message_with_code(message) + + for part in parts: + if part['type'] == 'text': + blocks.append({ + "type": "section", + "text": { + "type": "mrkdwn", + "text": part['content'] + } + }) + elif part['type'] == 'code': + blocks.append({ + "type": "section", + "text": { + "type": "mrkdwn", + "text": f"```{part.get('language', '')}\n{part['content']}\n```" + } + }) + + if link: + blocks.append({"type": "divider"}) + blocks.append({ + "type": "section", + "text": { + "type": "mrkdwn", + "text": f"<{link}|View more details>" + } + }) + + return blocks + +def parse_message_with_code(message): + parts = [] + + if isinstance(message, str): + parts.append({"type": "text", "content": message}) + elif isinstance(message, list): + for item in message: + if isinstance(item, dict): + parts.append(item) + else: + parts.append({"type": "text", "content": str(item)}) + elif isinstance(message, dict): + if 'type' in message and 'content' in message: + parts.append(message) + else: + parts.append({"type": "text", "content": json.dumps(message)}) + + return parts + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5111, debug=True) \ No newline at end of file