基于InnoGames框架构建高效Slack机器人:游戏开发团队自动化协作实践
1. 项目概述:一个为游戏开发团队量身定制的Slack机器人
如果你在一个快节奏的游戏开发团队工作,每天在Slack里被各种通知、部署状态、错误报告和任务提醒刷屏,那你一定懂我的痛。信息过载,关键消息被淹没,不同工具间的数据孤岛,这些都是效率杀手。几年前,我们团队就面临这样的困境,直到我们决定自己动手,基于 innogames/slack-bot 这个开源框架,打造了一个深度集成我们内部工作流的“超级助理”。
innogames/slack-bot 不是一个开箱即用的成品机器人,而是一个由德国知名游戏公司InnoGames开源的高质量Python框架。它的核心价值在于,为开发者提供了一个坚实、灵活且易于扩展的基座,让你可以快速构建出能够理解复杂指令、与多种第三方服务(如Jira、GitLab、Jenkins、监控系统)对话,并能以富文本、按钮、菜单等交互形式呈现信息的智能Slack机器人。简单说,它把与Slack API通信的繁琐细节都封装好了,你只需要专注于编写实现具体业务逻辑的“插件”(Plugin)。对于任何需要将Slack作为统一协作中枢,并希望自动化工作流的中大型技术团队,尤其是游戏开发、互联网产品团队,这个项目都是一个绝佳的起点。
2. 核心架构与设计哲学拆解
2.1 为什么选择这个框架而非其他方案?
在决定采用 innogames/slack-bot 之前,我们评估过几种主流方案。像 slackclient (官方SDK) 和 bolt-python 提供了最基础的API绑定,但我们需要从零开始搭建消息路由、插件管理、状态维护等基础设施,初期投入巨大。一些更上层的框架(如 errbot )功能全面,但可能过于臃肿,定制化路径不够清晰。
innogames/slack-bot 的吸引力在于它的“折中”哲学。它不是一个试图解决所有问题的庞然大物,而是一个精心设计的“微内核”架构。框架本身只解决三个核心问题:1) 与Slack RTM API和Events API的稳定连接与事件分发;2) 一套清晰的插件加载、生命周期管理机制;3) 丰富的消息构建和交互组件封装。剩下的业务逻辑,全部由插件实现。这种设计带来了几个显著优势:
模块化与高内聚 :每个功能(如查询构建状态、创建Jira单子)都是一个独立的插件,代码隔离性好,便于单独开发、测试和部署。新人接手也能快速理解,因为每个插件的职责都非常单一。 易于扩展 :框架定义好了插件接口( BasePlugin ),你只需要继承并实现几个关键方法(如 init_commands 注册命令, handle_message 处理消息),就能快速接入新功能。框架内部的事件总线会自动将匹配的消息路由到对应的插件。 生产环境就绪 :它内置了连接重试、错误处理、日志集成等生产级特性,这些都是我们在使用原始SDK时需要反复踩坑才能完善的。
2.2 核心组件交互流程
理解框架如何工作,是高效使用它的前提。其核心数据流可以概括为以下几步:
- 连接与监听 :机器人启动后,会根据配置,通过Slack的Socket Mode或Events API建立持久化连接,开始监听指定频道(Channel)或直接消息(DM)中的事件。
- 事件解析与路由 :当用户在Slack中发送消息或进行交互(如点击按钮)时,Slack会将一个事件(Event)推送给机器人。框架的核心路由器(
MessageDispatcher)会解析这个事件,提取出文本、用户、频道等信息。 - 命令匹配与插件调用 :路由器根据预定义的命令前缀(如
!或@bot_name)和正则表达式模式,在所有已加载的插件中查找匹配项。一旦找到,就会将事件和控制权交给该插件的处理函数。 - 业务逻辑执行 :插件处理函数执行具体的业务逻辑,例如调用Jenkins API查询构建状态、解析Git提交信息、或查询数据库。
- 响应构建与发送 :插件使用框架提供的
SlackResponse或BlockKit构建器,创建格式丰富的响应消息(可以包含文本、附件、按钮、下拉菜单、图片等),然后通过框架发送回Slack。
提示:框架支持“阻塞式”和“非阻塞式”响应。对于需要长时间运行的操作(如触发一个长达10分钟的构建任务),务必使用异步或延迟响应模式,先发送一个“已收到”的即时反馈,避免Slack连接超时。
3. 从零开始:环境搭建与第一个插件
3.1 基础环境与项目初始化
假设我们使用Python 3.9+的环境。首先从GitHub克隆项目并安装依赖。
# 克隆仓库
git clone https://github.com/innogames/slack-bot.git
cd slack-bot
# 强烈建议使用虚拟环境
python -m venv venv
source venv/bin/activate # Linux/macOS
# venv\Scripts\activate # Windows
# 安装依赖,推荐使用pip安装
pip install -e .
接下来是最关键的一步:创建Slack App并获取凭证。你需要前往 api.slack.com/apps 创建一个新的App。
- Basic Information :记下
App ID和Signing Secret。 - Socket Mode :如果你希望机器人能主动推送消息而不依赖公网回调地址(推荐用于开发和生产),需要启用Socket Mode,并生成一个
App-Level Token(权限需要connections:write)。同时,在“Event Subscriptions”中,你需要订阅message.channels,message.im等事件,但回调地址可以留空或填一个占位符,因为Socket Mode下不使用HTTPS回调。 - OAuth & Permissions :安装应用到你的工作区,并获取
Bot User OAuth Token(以xoxb-开头)。根据你的插件需求,为Bot授权相应的权限(Scopes),例如channels:history,chat:write,commands等。 - 安装与分发 :将App安装到你的开发工作区。
现在,在项目根目录创建你的配置文件 config.yaml 。框架支持YAML格式的配置,清晰易读。
slack:
# 使用 Socket Mode 配置
socket_mode: true
app_token: "xapp-xxxxxxxx" # 你的 App-Level Token
bot_token: "xoxb-xxxxxxxx" # 你的 Bot User OAuth Token
signing_secret: "xxxxxxxx" # 你的 Signing Secret
# 如果不用Socket Mode,则需配置以下两项
# socket_mode: false
# verification_token: "xxxxxxxx" # 旧版Verification Token
bot:
# 机器人响应的命令前缀,例如 !help
prefix: "!"
# 插件自动加载的路径
plugin_paths:
- "plugins"
# 是否在未知命令时显示帮助信息
show_unknown_command: true
logging:
level: "INFO"
format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
3.2 编写你的第一个“Hello World”插件
框架的核心是插件。让我们在 plugins/ 目录下创建一个最简单的问候插件 greeter.py 。
# plugins/greeter.py
from slack_bot.plugin_base import BasePlugin
class GreeterPlugin(BasePlugin):
"""一个简单的问候插件,演示基本命令注册和响应。"""
def init_commands(self):
# 注册命令:当用户输入 `!hello` 时,触发 `cmd_hello` 函数
self.register_command(
trigger="hello", # 命令关键字,结合config中的prefix,最终命令是 `!hello`
func=self.cmd_hello,
description="打个招呼", # 帮助信息中会显示
args_count=0, # 期望的参数个数,0表示不需要额外参数
)
# 注册另一个带参数的命令
self.register_command(
trigger="greet",
func=self.cmd_greet,
description="向某人问好,用法: !greet <名字>",
args_count=1, # 期望1个参数
)
def cmd_hello(self, msg, args):
"""处理 !hello 命令"""
# `msg` 对象包含了完整的消息事件信息
user = msg.user.name
# 使用 `self.slack.send_message` 回复消息
# `msg.channel` 是消息来源的频道ID
self.slack.send_message(msg.channel, f"你好啊,{user}!欢迎使用Slack机器人。")
def cmd_greet(self, msg, args):
"""处理 !greet <名字> 命令"""
# `args` 是一个列表,包含了用户输入命令后的所有以空格分隔的参数
name = args[0] if args else "朋友"
response = f"今天天气不错,{name}!"
# 你也可以返回一个字符串,框架会自动帮你发送到原频道
return response
# 可选:插件加载和卸载时的生命周期钩子
def on_load(self):
self.logger.info("GreeterPlugin 加载成功!")
def on_unload(self):
self.logger.info("GreeterPlugin 即将卸载。")
这个插件展示了几个关键点:
- 继承
BasePlugin:这是所有插件的基类。 -
init_commands方法 :在这里注册你的命令。register_command方法将命令关键字、处理函数和元数据绑定。 - 命令处理函数 :第一个参数总是
msg(消息事件对象),第二个参数是args(命令参数列表)。函数可以直接调用self.slack.send_message发送消息,也可以直接返回一个字符串作为响应。 - 生命周期钩子 :
on_load和on_unload允许你在插件加载和卸载时执行初始化或清理操作。
3.3 运行与测试
创建一个主程序入口文件 run_bot.py :
# run_bot.py
from slack_bot.bot import SlackBot
import yaml
import logging
def main():
# 加载配置
with open('config.yaml', 'r') as f:
config = yaml.safe_load(f)
# 创建机器人实例
bot = SlackBot(config)
# 启动机器人(这是一个阻塞调用,会一直运行直到收到停止信号)
try:
bot.run()
except KeyboardInterrupt:
logging.info("收到中断信号,正在关闭机器人...")
finally:
bot.close()
if __name__ == "__main__":
main()
在终端运行 python run_bot.py 。如果一切配置正确,你会看到日志输出,显示插件加载成功,并建立了Slack连接。现在,转到你的Slack工作区,在任意机器人所在的频道或直接消息中,输入 !hello 和 !greet InnoGames ,看看机器人的回应吧!
注意:在开发初期,强烈建议将机器人添加到一个单独的私有频道进行测试,避免打扰其他同事。同时,Slack API有速率限制,频繁发送消息可能会触发限制,在插件逻辑中应考虑加入适当的延迟或缓存。
4. 进阶实战:构建一个与CI/CD集成的部署状态查询插件
一个“玩具”插件不足以体现价值。让我们构建一个实战中极其有用的插件:与Jenkins(或其他CI/CD工具)集成,查询指定项目的构建状态。
4.1 插件设计与依赖安装
这个插件需要:
- 解析用户命令,如
!build status <项目名>或!build trigger <项目名> <分支>。 - 调用Jenkins的REST API获取信息或触发构建。
- 将结果格式化为美观的Slack消息,用颜色和字段清晰展示。
首先,安装必要的Python库。 innogames/slack-bot 框架本身不限制你的依赖。
pip install python-jenkins requests
在 config.yaml 中增加Jenkins的配置项:
jenkins:
base_url: "https://jenkins.your-company.com"
username: "your-bot-user" # 或使用API Token
password: "your-api-token" # 推荐使用API Token而非真实密码
# 或者使用 `token` 字段,取决于你的jenkins库支持
4.2 插件实现:状态查询与交互按钮
我们创建一个 jenkins_plugin.py 。
# plugins/jenkins_plugin.py
import jenkins
from slack_bot.plugin_base import BasePlugin
from slack_bot.messages import SlackResponse, MessageAttachment, MessageButton
class JenkinsPlugin(BasePlugin):
"""Jenkins构建状态查询与操作插件。"""
def init_commands(self):
self.register_command(
trigger="build status",
func=self.cmd_build_status,
description="查询Jenkins项目构建状态,用法: !build status <项目名> [构建号]",
args_count_min=1, # 至少需要1个参数(项目名)
args_count_max=2, # 最多2个参数(项目名和可选的构建号)
)
self.register_command(
trigger="build trigger",
func=self.cmd_build_trigger,
description="触发Jenkins项目构建,用法: !build trigger <项目名> <分支>",
args_count=2,
)
def _get_jenkins_client(self):
"""获取Jenkins客户端连接(带简单缓存或配置读取)。"""
# 从bot的全局配置中读取Jenkins凭证
jenkins_config = self.bot.config.get('jenkins', {})
server = jenkins.Jenkins(
jenkins_config['base_url'],
username=jenkins_config.get('username'),
password=jenkins_config.get('password') or jenkins_config.get('token')
)
return server
def cmd_build_status(self, msg, args):
"""查询构建状态"""
project_name = args[0]
build_number = args[1] if len(args) > 1 else 'lastBuild' # 默认为最后一次构建
try:
client = self._get_jenkins_client()
# 获取构建信息
build_info = client.get_build_info(project_name, build_number)
# 解析关键信息
result = build_info.get('result') # SUCCESS, FAILURE, UNSTABLE, None(构建中)
building = build_info.get('building', False)
number = build_info.get('number')
url = build_info.get('url')
duration = build_info.get('duration', 0) / 1000 # 转换为秒
timestamp = build_info.get('timestamp', 0) / 1000 # 转换为秒
# 根据状态决定颜色和文本
if building:
color = '#FFFF00' # 黄色,构建中
status_text = f"构建 #{number} 正在进行中..."
elif result == 'SUCCESS':
color = '#36A64F' # 绿色,成功
status_text = f"构建 #{number} 成功!"
elif result == 'FAILURE':
color = '#FF0000' # 红色,失败
status_text = f"构建 #{number} 失败。"
else:
color = '#CCCCCC' # 灰色,其他状态
status_text = f"构建 #{number} 状态: {result}"
# 创建富文本附件(Attachment)
attachment = MessageAttachment(
fallback=f"{project_name} 构建状态: {status_text}", # 纯文本回退
color=color,
title=f"{project_name} - 构建 #{number}",
title_link=url,
text=status_text,
fields=[
{"title": "项目", "value": project_name, "short": True},
{"title": "构建号", "value": str(number), "short": True},
{"title": "持续时间", "value": f"{duration:.1f} 秒", "short": True},
{"title": "触发者", "value": build_info.get('actions', [{}])[0].get('causes', [{}])[0].get('userId', 'N/A'), "short": True},
],
footer="Jenkins",
ts=timestamp # 显示构建时间戳
)
# 创建交互按钮
buttons = []
buttons.append(MessageButton(
name="open_in_jenkins",
text="在Jenkins中打开",
value=url,
style="primary" if result == 'SUCCESS' else "default"
))
# 如果是失败构建,可以添加一个“重新构建”按钮(需要额外逻辑处理按钮点击事件)
if result == 'FAILURE':
buttons.append(MessageButton(
name="rebuild",
text="重新构建",
value=f"{project_name}|{branch}", # 可以传递项目名和分支信息
style="danger",
confirm={
"title": "确认重新构建",
"text": f"确定要触发 {project_name} 的新构建吗?",
"ok_text": "确定",
"dismiss_text": "取消"
}
))
# 构建完整的Slack响应
response = SlackResponse()
response.add_attachment(attachment)
# 将按钮添加到响应中(Slack Block Kit格式更现代,但附件+按钮兼容性好)
# 这里演示使用传统的附件动作。对于更复杂的交互,建议迁移到Block Kit。
response.set_actions(buttons)
return response
except jenkins.NotFoundException:
return f"错误:未找到项目 '{project_name}' 或构建号 '{build_number}'。"
except Exception as e:
self.logger.exception(f"查询Jenkins状态时出错: {e}")
return f"查询构建状态时发生内部错误: {str(e)}"
def cmd_build_trigger(self, msg, args):
"""触发新的构建"""
project_name, branch = args[0], args[1]
# 注意:实际触发构建可能需要参数,这里简化处理
try:
client = self._get_jenkins_client()
# 假设我们通过参数化构建,参数名为 'BRANCH'
parameters = {'BRANCH': branch}
queue_id = client.build_job(project_name, parameters=parameters)
return f"已成功触发项目 *{project_name}* 的分支 *{branch}* 构建。队列ID: {queue_id}\n我会在构建完成后通知你。"
# 在实际场景中,你可能会在这里启动一个后台任务来轮询构建状态,完成后通过 `self.slack.send_message` 主动推送通知。
except Exception as e:
self.logger.exception(f"触发Jenkins构建时出错: {e}")
return f"触发构建失败: {str(e)}"
# 处理按钮交互事件(需要配置Slack Interactivity)
def register_listeners(self):
"""注册交互事件监听器。"""
# 框架提供了装饰器或方法来处理交互负载(Interactivity Payload)
# 这里是一个概念示例,具体实现取决于框架版本和Slack API设置
@self.bot.on('interactive')
def handle_interaction(payload):
if payload.get('type') == 'block_actions':
for action in payload['actions']:
if action['action_id'] == 'rebuild':
# 解析之前存储在value里的数据
project_name, branch = action['value'].split('|')
# 调用Jenkins API触发构建
# ... 触发逻辑 ...
# 更新原消息或发送新消息
self.slack.update_message(
channel=payload['channel']['id'],
ts=payload['message_ts'],
text=f"已接受重新构建请求,正在触发 {project_name}@{branch} ..."
)
这个插件展示了更高级的特性:
- 复杂的命令解析 :支持可选参数和参数数量范围。
- 外部API集成 :安全地使用配置中的凭证连接Jenkins。
- 富媒体响应 :使用
MessageAttachment创建带颜色、字段、脚注的丰富消息卡片。 - 交互元素 :添加按钮,并初步探讨了如何处理按钮点击事件(这需要额外配置Slack App的Interactivity Request URL)。
- 错误处理 :对网络异常、API错误进行了捕获,并向用户返回友好的错误信息。
5. 插件生态与高级特性探索
5.1 插件间的通信与数据共享
在复杂的机器人中,插件可能需要协作。例如,一个“数据统计”插件可能需要收集所有命令的使用情况。框架提供了几种方式:
- 通过Bot核心存储共享 :
BasePlugin的self.bot.storage属性可以作为一个简单的键值存储,用于在插件间共享数据。但要注意线程安全。 - 使用事件总线(Event Bus) :更优雅的方式是使用内置的事件系统。插件可以发布(publish)自定义事件,其他插件可以订阅(subscribe)这些事件。
# 在插件A中发布事件
self.bot.event_bus.publish('deployment_finished', {
'project': 'game-backend',
'version': 'v1.2.3',
'status': 'success',
'user': 'alice'
})
# 在插件B中订阅事件
@self.bot.event_bus.subscribe('deployment_finished')
def handle_deployment(event_data):
if event_data['status'] == 'success':
# 发送通知到庆祝频道
self.slack.send_message('#celebrations', f"🎉 {event_data['project']} v{event_data['version']} 部署成功!")
- 依赖注入 :在插件的
__init__或on_load中,可以通过self.bot.get_plugin('other_plugin_name')获取其他已加载插件的实例,直接调用其方法(需谨慎设计,避免循环依赖)。
5.2 定时任务与后台作业
机器人除了响应消息,还需要主动执行任务,如定时发送日报、监控报警等。框架通常不直接内置定时任务调度器,但可以轻松地与 apscheduler 或 celery 等库集成。
一个简单的模式是在插件加载时启动调度器:
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
class DailyReportPlugin(BasePlugin):
def on_load(self):
self.scheduler = BackgroundScheduler()
# 每天上午9点发送报告
trigger = CronTrigger(hour=9, minute=0, timezone='Asia/Shanghai')
self.scheduler.add_job(self.send_daily_report, trigger)
self.scheduler.start()
def on_unload(self):
if self.scheduler:
self.scheduler.shutdown()
def send_daily_report(self):
# 生成报告内容
report = self._generate_report()
# 发送到指定频道
self.slack.send_message('#team-daily', report)
5.3 状态管理与持久化
对于需要记住上下文的对话(例如,一个多步骤的工单创建流程),插件需要管理用户状态。框架本身不提供开箱即用的状态机,但你可以利用 self.bot.storage 或外部数据库(如Redis)来实现。
一个简单的基于内存的会话管理示例:
class TicketPlugin(BasePlugin):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 用于存储用户当前会话状态 {user_id: {'step': 1, 'data': {...}}}
self.user_sessions = {}
def cmd_createticket(self, msg, args):
user_id = msg.user.id
if user_id not in self.user_sessions:
# 开始新会话
self.user_sessions[user_id] = {'step': 1, 'data': {'creator': user_id}}
return "请输入问题标题:"
else:
session = self.user_sessions[user_id]
current_step = session['step']
# 根据步骤处理用户输入
if current_step == 1:
session['data']['title'] = ' '.join(args)
session['step'] = 2
return "请详细描述问题:"
elif current_step == 2:
session['data']['description'] = ' '.join(args)
# 所有信息收集完毕,创建工单
ticket_id = self._create_ticket_in_db(session['data'])
del self.user_sessions[user_id] # 清除会话
return f"工单 #{ticket_id} 创建成功!"
注意:上述内存存储方式在机器人重启后会丢失所有状态。对于生产环境,必须使用Redis、数据库或文件进行持久化。
6. 生产环境部署与运维要点
6.1 配置管理与安全
永远不要将敏感信息(如Token、密码)硬编码在代码或明文的 config.yaml 中。推荐做法:
- 环境变量 :使用
os.environ读取。
在启动前设置环境变量:slack: bot_token: ${SLACK_BOT_TOKEN}export SLACK_BOT_TOKEN=xoxb-...。 - 密钥管理服务 :如AWS Secrets Manager、HashiCorp Vault,在应用启动时动态获取。
- 配置文件分层 :区分
config.default.yaml(默认值)、config.production.yaml(生产覆盖值),通过环境变量APP_ENV决定加载哪个。
6.2 进程管理与高可用
对于7x24小时运行的机器人,需要可靠的进程管理。
- 使用系统服务 :在Linux上,创建
systemd服务文件是标准做法。# /etc/systemd/system/slack-bot.service [Unit] Description=InnoGames Slack Bot After=network.target [Service] Type=simple User=slackbot WorkingDirectory=/opt/slack-bot Environment=PYTHONPATH=/opt/slack-bot Environment=APP_ENV=production ExecStart=/opt/slack-bot/venv/bin/python run_bot.py Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target - 容器化部署 :使用Docker可以更好地隔离环境。编写
Dockerfile,构建镜像,通过Kubernetes或Docker Compose编排。 - 负载均衡与多实例 :通常一个机器人实例足够。但如果消息量极大,可以考虑运行多个实例,并通过外部的消息队列(如RabbitMQ)来分发Slack事件(这需要更复杂的架构设计)。
6.3 监控、日志与告警
没有监控的系统就是在黑暗中飞行。
- 日志 :框架集成了Python标准
logging。确保配置合理的日志级别(生产环境用INFO或WARNING),并设置日志轮转(如使用TimedRotatingFileHandler)。 - 健康检查 :可以编写一个简单的插件,响应
!health命令,返回机器人的基本状态(如运行时间、插件数量、内存使用率)。 - 集成监控系统 :在插件中,可以使用
statsd或prometheus客户端库上报指标(如命令调用次数、API调用延迟、错误计数)。这些指标可以接入Grafana等看板。 - 错误告警 :将
ERROR及以上级别的日志接入你的告警系统(如Sentry, PagerDuty),确保问题能被及时发现。
7. 避坑指南与最佳实践
在近两年的开发和运维中,我们积累了不少经验教训,这里分享几条最重要的:
- 速率限制是头号敌人 :Slack API对消息发送、调用频率有严格的限制。插件中任何循环发送消息的逻辑都必须加入延迟(如
time.sleep(1))。批量操作时,优先考虑使用chat.scheduleMessageAPI预约发送,或先汇总信息再发送一条合并消息。 - 处理好“提及”(@)和“线程” :当机器人被
@提及或在某个线程中回复时,msg对象中的channel和thread_ts字段至关重要。回复时如果不指定thread_ts,消息会出现在主频道,可能破坏对话上下文。始终检查msg.thread_ts,如果存在,则使用self.slack.send_message(msg.channel, text, thread_ts=msg.thread_ts)进行线程内回复。 - 插件加载顺序 :如果插件间有依赖关系(如A插件需要B插件提供的服务),可以在主配置或插件元数据中定义加载顺序。简单的做法是在依赖插件的
on_load方法中,检查所需插件是否已加载。 - 超时与重试 :所有对外部服务(Jenkins, Jira, 数据库)的调用都必须设置超时,并实现重试逻辑(可以使用
tenacity库)。一个缓慢的API调用会阻塞整个机器人的事件循环。 - 权限最小化原则 :在Slack App配置中,只授予Bot完成功能所必需的最少OAuth权限。定期审查权限列表。
- 测试策略 :为插件编写单元测试(模拟Slack事件)和集成测试(在测试工作区运行真实机器人)。框架的设计使得插件很容易被独立测试。
- 文档与维护 :为每个插件编写清晰的
README,说明其命令、参数、配置项和用途。随着插件数量增长,一个清晰的目录结构和命名规范(如ci_jenkins.py,ticketing_jira.py)会极大提升可维护性。
最后, innogames/slack-bot 框架是一个强大的起点,但它不是银弹。随着业务复杂度的增长,你可能会遇到框架的边界。这时,不要害怕去阅读其源码,甚至提交PR。它的代码结构清晰,是学习如何构建一个稳健的聊天机器人框架的绝佳材料。我们的机器人从最初的几个简单命令,发展到如今集成了几十个内部系统、承担着团队日常协作核心枢纽的角色,这个框架功不可没。希望你的Slack机器人之旅也能同样高效和充满乐趣。
更多推荐


所有评论(0)