如何在 iMessage 上做一个 ChatGPT 聊天机器人
- Authors
- @SLIPPERTOPIA
随着 OpenAI 的 API 支持模型越多越丰富,价格也越来越便宜。很多人应该都结合这些 API 做了一些有趣的项目,比如聊天机器人、摘要提取等。聊天机器人可以跟 IM 平台对接,比如 Telegram、Discord、Slack 或者微信等。
但今天我们做点不一样的,我们在 iMessage 平台上做一个聊天机器人。
两个问题
要在 iMessage 上做一个聊天机器人,我们需要解决两个问题:
- 如何接入 iMessage 平台,也就是如何发送和接收 iMessage
- 如何实现聊天机器人,即如何处理消息
第二个问题比较简单,像 GPT4、Gemini 这样的模型已经可以很好地处理对话。强力的模型加上合适的 prompt,实现一个通用聊天机器人几乎没有难度。
难点在第一个,iMessage 是一个封闭的生态,没有公开的 API 或 SDK。那我们如何接入 iMessage 平台呢?
简单的来说,我们用 AppleScript 来发送消息,通过读取 iMessage 数据库来获取消息。
困难的地方在于,要做到这两点,首先需要有一台 Mac,因为
- 需要用到 AppleScript;
- 需要为 bot 配置一个 iMessage 账号,然后使用这个账号是用来接收和发送消息。
有了这两个条件,我们就可以开搞了。
问题一:接入 iMessage 平台
发送消息
iMessage 没有公开的 API,我们无法直接通过代码来发送消息。但有一个间接的方法,那就是通过 AppleScript 来实现。
AppleScript 是苹果自带的一个脚本语言,旨在让用户能够通过脚本来控制 Mac 上的应用程序。我们可以通过 AppleScript 来操控 iMessage 给特定用户发送消息。
例如我们想给用户 [email protected]
发送消息 Hello, Alice!
,我们可以通过以下脚本来实现。
osascript -e 'tell application "Messages" to send "Hello, Alice!" to buddy "[email protected]"'
这里的 osascript
是 macOS 中的一个命令行工具,用于运行 AppleScript 和其他 OSA(Open Scripting Architecture)脚本。有了这个办法,我们可以通过 Python 来封装这个命令,把它当作一个接口来使用。
def send_message(user, message):
os.system(f"osascript -e 'tell application "Messages" to send "{message}" to buddy "{user}"'")
体验如何?
实际使用上,这个接口执行速度还是非常快的,基本上发出去的消息几乎感觉不到延迟,跟直接在 iMessage 上聊天感觉差不多。
当然也不是完全没有问题,并行发送多条消息时,即使是通过(真)多线程调用接口,实际上仍然是一个一个发送的,每条消息之前有一定的延迟。那如果这个机器人要处理大量消息,可能就会有明显的延迟了。
接收消息
按道理来说,我们也可以通过 AppleScript 来实现接收消息,但实际上并不是很好用。笔者测试下来发现总是报错,后来几经波折,总算找到了一个更好的方法。
这个方法是通过直接读取 iMessage 的数据库文件 ~/Library/Messages/chat.db
来获取消息。chat.db
数据库中包含了多个表,分别用于存储消息、联系人、聊天会话等信息。比如:
attachment
: 存储消息中的附件(如图片、视频、文件等)的信息。chat
: 存储聊天会话的信息。chat_handle_join
: 一个连接表,用于将聊天会话与联系人(handle)关联起来。chat_message_join
: 一个连接表,用于将聊天会话与消息(message)关联起来。deleted_messages
: 存储已删除的消息。handle
: 存储联系人信息,如电话号码或电子邮件地址。message
: 存储所有的消息内容。
对实现一个聊天机器人来说,比较重要的两个表是 message
及 handle
。
Message 表存储了所有的消息,包括发送者、接收者、消息内容等一共 81 个字段(部分截取字段如下表)。
# sqlite3 ~/Library/Messages/chat.db "PRAGMA table_info(message);
0|ROWID|INTEGER|0||1
1|guid|TEXT|1||0
2|text|TEXT|0||0
3|replace|INTEGER|0|0|0
4|service_center|TEXT|0||0
5|handle_id|INTEGER|0|0|0
6|subject|TEXT|0||0
7|country|TEXT|0||0
...
78|part_count|INTEGER|0||0
79|is_stewie|INTEGER|0|0|0
80|is_kt_verified|INTEGER|0|0|0
Handle 表存储了联系人信息,包括联系人的 id、电话号码、邮箱等信息。如果要获取某个特定联系人的消息,比如 [email protected]
,可以通过联合表 message
和 handle
来查询。
SELECT
datetime(message.date / 1000000000 + strftime('%s','2001-01-01 00:00:00'), 'unixepoch', 'localtime') as date,
message.is_from_me,
message.text
FROM message
JOIN handle ON message.handle_id = handle.ROWID
WHERE handle.id = [email protected]
ORDER BY message.date;
其中 handle.id
就是用户的 iMessage 账号,可以是邮箱、电话号码等。从这里拿到消息后,我们可以通过一些逻辑来处理消息,比如把消息传给模型来生成回复。
问题二:接入模型
这一块就比较容易处理了,Gemini、OpenAI 的 API 也都比较好用,只要把消息传给模型,然后把模型生成的回复发送出去就可以了。
from openai import OpenAI
client = OpenAI(api_key="your-api-key")
def generate_reply(message:str)->str:
response = client.create(
model="gpt-4.0-turbo",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": message},
]
)
return response.choices[0].message.content
更多功能
如果只是做一个简单的聊天机器人,上面的功能就够了。
但如果想支持更多功能,比如多个模型之间进行切换,system prompt 设置等,就需要从代码实现层面来支持。毕竟 iMessage 不像 Telegram 那样有丰富的 API,我们需要通过代码来实现这些功能。
参考 Telegram bot 的实现,我们可以指定以 /
开头的命令来执行特定的功能。比如:
Available commands:
1 /models, list available models
2 /setmodel <model_id>, set model
3 /help, show this help
4 <text>, generate response for text
5 /start, start the bot
6 /stop, stop the bot
7 /role, show all roles
8 /setrole <role_id>, set role
9 /info, show current model and role
命令识别通过规则匹配,然后执行相应的功能。
小结
本文介绍了如何在 iMessage 平台上实现一个聊天机器人。实现方式是通过 AppleScript 来发送信息,通过读取 iMessage 数据库来接收消息。聊天部分则通过调用 OpenAI 的 API 来实现。
在 iMessage 上做一个聊天机器人的好处是,无需要安装额外的应用,随时随地都可以在 iPhone, iPad 上进行聊天。当然也有一些局限性,比如需要有 Mac 设备,需要配置 iMessage 账号等。