Slack 导出转文字 — 免费、私密

把 Slack 工作区导出转成可读文稿。将用户 ID 还原成姓名,展开链接和提及,并去掉加入与退出提示。

看懂一份 Slack 工作区导出

Slack 的导出不是一个文件,而是一棵目录树:一个频道一个文件夹,里面一天一个 JSON 文件,根目录下再放几份工作区元数据。随便打开一个按天的文件,你看到的是一个消息对象数组,作者是 U04J8K2L9 这样的代号,正文里穿插着尖括号标记,时间戳是带微秒、以字符串形式存放的 Unix 纪元时间。

这个页面把它重新拼成可读的文字稿:一个频道一份,按日期排列,用真名。把整个 .zip 拖进来——它在这个浏览器标签页里读取,从不上传。

名字在另一个文件里

这是让 Slack 导出没有工具就读不了的最关键的一点。消息标识作者只用用户 id,别无其他。从 id 到人名的映射住在压缩包根目录的 users.json 里,和每一条需要它的消息分开。

所以 users.json 会被最先读取,在任何频道文件之前,得到的映射贯穿整个解析过程。每个用户有好几个可选名字——真实姓名、显示名、账号 handle——按这个顺序尝试,因为真实姓名是读者认得出的,而显示名是设置过之后 Slack 会展示的。有些导出还会在消息上直接嵌一个 user_profile 对象;它存在时会被优先采用,因为它记录的是当时的名字,而不是现在的。

机器人消息根本没有用户 id,带的是一个 bot 标识,有时还有一个 username。这些会被单独处理,好让一个集成的输出有归属,而不是显示成空白。

Slack 自己的标记语法

消息文本不是纯文本。Slack 会把几类引用包在尖括号里,放着不管,文字稿就很难读:

  • 用户提及写成 <@U04J8K2L9>。它们会对着同一份用户映射解析,变成可读的 @名字
  • 频道引用写成 <#C01234ABC|general>,竖线后面有时有名字,有时没有。
  • 链接的形式是 <https://example.com|显示文字>。文字会保留,URL 跟在后面的括号里,措辞和目标地址都不会丢。
  • 广播提及比如 <!here><!channel>,会变成它们的朴素等价写法。
  • HTML 实体。与号、尖括号和引号在源文件里是转义的,会被还原回来。

值得丢掉的噪声

Slack 把事件也记成带 subtype 的消息。入频道、退频道、用途和话题变更、归档通知——全都和真正的对话躺在同一个数组里。在稍有规模的工作区里,这些可能比真实消息还多,尤其是在所有人被自动加入的公共频道里。它们会被归为系统消息,默认丢弃。

还有一种情况:一条消息完全没有文本——一次文件上传,或者一个内容全在 attachment 块里的机器人帖子。丢弃一条消息之前会先检查 attachment 里有没有文本,这样一个把输出发在附件里的集成仍然会贡献它的内容,而不是凭空消失。

把按天分的文件重新拼起来

因为每个频道被拆成一天一个文件,一个频道的历史必须重新汇总。文件按排序后的顺序处理,对以日期命名的文件来说这就是时间顺序,消息被追加进该频道的单一文字稿。结果是一段连续的对话,顶上一个标明频道名的标题,而不是几百个碎片。

不是频道数据的文件——频道清单、集成日志、各种私信索引——会被跳过。任何解析失败的内容也是跳过而不是让整个压缩包失败,所以一天的数据损坏不会让你丢掉整个工作区。

人们用它来做什么

在免费工作区触到消息条数上限、旧对话不再可达之前先把历史保存下来。迁移到别的工具时,把一份能读的东西留在身后。用一份文字稿而不是一堆 JSON 目录去回应合规或法务请求。把一个从没被写下来的决定的讨论过程找回来。把一个项目频道喂给模型,还原半年里究竟发生了什么。

怎么拿到导出

工作区的所有者或管理员可以在工作区设置里生成,路径是导入/导出数据,然后导出。标准导出覆盖公共频道。私有频道和私信需要另一档导出权限,并且在多数司法辖区还需要一个书面理由。

在本地读取

工作区导出是一个组织能产出的最敏感的文件之一。里面有内部讨论、客户姓名、有人粘进不该粘的频道里的凭据,以及所有当事人毫不修饰的看法。把它上传给第三方转换器,对大多数公司来说是一起需要上报的事故。

这里什么都不会被传出去。压缩包由这个浏览器标签页里的 JavaScript 打开并解析,只有可读的输出会被显示。没有服务器收到这个文件,所以没有留存问题要回答,也没有东西需要请求删除。

其他平台在通用聊天记录导出页处理。