Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions znai-slack-bot/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Python
__pycache__/
*.py[cod]
*$py.class
130 changes: 130 additions & 0 deletions znai-slack-bot/README.md
Original file line number Diff line number Diff line change
@@ -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
<!DOCTYPE html>
<html>
<body>
<script>
fetch('http://localhost:5000/ask-in-slack', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: 'U1234567890',
message: 'Test message from browser',
link: 'https://example.com'
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
</script>
</body>
</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)
30 changes: 30 additions & 0 deletions znai-slack-bot/pyproject.toml
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions znai-slack-bot/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
flask==3.0.0
flask-cors==4.0.0
slack-sdk==3.26.1
106 changes: 106 additions & 0 deletions znai-slack-bot/slack_bot.py
Original file line number Diff line number Diff line change
@@ -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)