mirror of
https://github.com/titanwings/colleague-skill.git
synced 2026-04-04 22:59:06 +08:00
Compare commits
7 Commits
b8305a38f3
...
ce30311a72
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce30311a72 | ||
|
|
3e9cc07b62 | ||
|
|
be54d25f03 | ||
|
|
3db7e8d83e | ||
|
|
c276624214 | ||
|
|
804025553a | ||
|
|
7aff711793 |
29
README.md
29
README.md
@@ -31,6 +31,22 @@
|
||||
|
||||
---
|
||||
|
||||
### 🌟 同系列项目:[前任.skill](https://github.com/titanwings/ex-skill)
|
||||
|
||||
> 根据大家的 issue 反馈,更新了一版 **前任.skill**!现已支持:
|
||||
>
|
||||
> - **微信聊天记录全自动导入**(Windows / macOS,桌面端登录即可,无需任何配置)
|
||||
> - **iMessage 全自动提取**(macOS 用户)
|
||||
> - **完整星盘解读**(太阳/月亮/上升/金星/火星/水星 × 12 星座)
|
||||
> - **MBTI 16 型 + 认知功能**、九型人格、依恋风格全支持
|
||||
> - 支持所有性别认同与关系类型
|
||||
>
|
||||
> 同事跑了用 **同事.skill**,前任跑了用 **[前任.skill](https://github.com/titanwings/ex-skill)**,赛博永生一条龙 🌟🌟🌟
|
||||
>
|
||||
> 觉得有意思的话,给两个项目都点个 Star 吧!
|
||||
|
||||
---
|
||||
|
||||
## 支持的数据来源
|
||||
|
||||
> 目前还是同事.skill 的 beta 测试版本,后续会有更多来源支持,请多多关注!
|
||||
@@ -40,6 +56,7 @@
|
||||
| 飞书(自动采集) | ✅ API | ✅ | ✅ | 输入姓名即可,全自动 |
|
||||
| 钉钉(自动采集) | ⚠️ 浏览器 | ✅ | ✅ | 钉钉 API 不支持历史消息 |
|
||||
| Slack(自动采集) | ✅ API | — | — | 需管理员安装 Bot;免费版限 90 天 |
|
||||
| 微信聊天记录 | ✅ SQLite | — | — | 目前测试下来不太稳定,推荐先用下方开源工具代替 |
|
||||
| PDF | — | ✅ | — | 手动上传 |
|
||||
| 图片 / 截图 | ✅ | — | — | 手动上传 |
|
||||
| 飞书 JSON 导出 | ✅ | ✅ | — | 手动上传 |
|
||||
@@ -47,6 +64,18 @@
|
||||
| Markdown | ✅ | ✅ | — | 手动上传 |
|
||||
| 直接粘贴文字 | ✅ | — | — | 手动输入 |
|
||||
|
||||
### 推荐的微信聊天记录导出工具
|
||||
|
||||
以下工具为独立的开源项目,本项目不包含它们的代码,仅在解析器中适配了它们的导出格式。目前微信自动解密测试下来不太稳定,可以先用这些开源工具导出聊天记录,再粘贴或导入到本项目中使用:
|
||||
|
||||
| 工具 | 平台 | 说明 |
|
||||
|------|------|------|
|
||||
| [WeChatMsg](https://github.com/LC044/WeChatMsg) | Windows | 微信聊天记录导出,支持多种格式 |
|
||||
| [PyWxDump](https://github.com/xaoyaoo/PyWxDump) | Windows | 微信数据库解密导出 |
|
||||
| [留痕](https://github.com/greyovo/留痕) | macOS | 微信聊天记录导出(Mac 用户推荐) |
|
||||
|
||||
> 工具信息来自 [@therealXiaomanChu](https://github.com/therealXiaomanChu),感谢各位开源作者,一起助力赛博永生!
|
||||
|
||||
---
|
||||
|
||||
## 安装
|
||||
|
||||
35
README_EN.md
35
README_EN.md
@@ -21,7 +21,7 @@ Your predecessor handed over, trying to condense three years into three pages?<b
|
||||
|
||||
<br>
|
||||
|
||||
Provide source materials (Feishu messages, DingTalk docs, emails, screenshots)<br>
|
||||
Provide source materials (Feishu messages, DingTalk docs, Slack messages, emails, screenshots)<br>
|
||||
plus your subjective description of the person<br>
|
||||
and get an **AI Skill that actually works like them**
|
||||
|
||||
@@ -31,6 +31,22 @@ and get an **AI Skill that actually works like them**
|
||||
|
||||
---
|
||||
|
||||
### 🌟 Related Project: [ex.skill](https://github.com/titanwings/ex-skill)
|
||||
|
||||
> Based on community feedback, we've updated **ex.skill** (a.k.a. 前任.skill)! Now supports:
|
||||
>
|
||||
> - **WeChat chat history auto-import** (Windows / macOS, just keep WeChat desktop logged in, zero config)
|
||||
> - **iMessage auto-extraction** (macOS users)
|
||||
> - **Full astrology chart interpretation** (Sun/Moon/Rising/Venus/Mars/Mercury × 12 signs)
|
||||
> - **MBTI 16 types + cognitive functions**, Enneagram, attachment styles
|
||||
> - All gender identities and relationship types supported
|
||||
>
|
||||
> Colleague left? Use **colleague.skill**. Ex left? Use **[ex.skill](https://github.com/titanwings/ex-skill)**. Cyber-immortality, end to end 🌟🌟🌟
|
||||
>
|
||||
> If you find this interesting, give both projects a Star!
|
||||
|
||||
---
|
||||
|
||||
## Supported Data Sources
|
||||
|
||||
> This is still a beta version of colleague.skill — more sources coming soon, stay tuned!
|
||||
@@ -39,6 +55,8 @@ and get an **AI Skill that actually works like them**
|
||||
|--------|:--------:|:-----------:|:------------:|-------|
|
||||
| Feishu (auto) | ✅ API | ✅ | ✅ | Just enter a name, fully automatic |
|
||||
| DingTalk (auto) | ⚠️ Browser | ✅ | ✅ | DingTalk API doesn't support message history |
|
||||
| Slack (auto) | ✅ API | — | — | Requires admin to install Bot; free plan limited to 90 days |
|
||||
| WeChat chat history | ✅ SQLite | — | — | Currently unstable, recommend using open-source tools below |
|
||||
| PDF | — | ✅ | — | Manual upload |
|
||||
| Images / Screenshots | ✅ | — | — | Manual upload |
|
||||
| Feishu JSON export | ✅ | ✅ | — | Manual upload |
|
||||
@@ -46,6 +64,18 @@ and get an **AI Skill that actually works like them**
|
||||
| Markdown | ✅ | ✅ | — | Manual upload |
|
||||
| Paste text directly | ✅ | — | — | Manual input |
|
||||
|
||||
### Recommended WeChat Chat Export Tools
|
||||
|
||||
These are independent open-source projects — this project does not include their code, but our parsers are compatible with their export formats. WeChat auto-decryption is currently unstable, so we recommend using these open-source tools to export chat history, then paste or import into this project:
|
||||
|
||||
| Tool | Platform | Description |
|
||||
|------|----------|-------------|
|
||||
| [WeChatMsg](https://github.com/LC044/WeChatMsg) | Windows | WeChat chat history export, supports multiple formats |
|
||||
| [PyWxDump](https://github.com/xaoyaoo/PyWxDump) | Windows | WeChat database decryption & export |
|
||||
| [留痕 (Liuhen)](https://github.com/greyovo/留痕) | macOS | WeChat chat history export (recommended for Mac users) |
|
||||
|
||||
> Tool recommendations from [@therealXiaomanChu](https://github.com/therealXiaomanChu). Thanks to all the open-source authors — together for cyber-immortality!
|
||||
|
||||
---
|
||||
|
||||
## Install
|
||||
@@ -75,7 +105,7 @@ git clone https://github.com/titanwings/colleague-skill ~/.openclaw/workspace/sk
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
|
||||
> Feishu/DingTalk auto-collection requires App credentials. See [INSTALL.md](INSTALL.md) for details.
|
||||
> Feishu/DingTalk/Slack auto-collection requires App credentials. See [INSTALL.md](INSTALL.md) for details.
|
||||
|
||||
---
|
||||
|
||||
@@ -179,6 +209,7 @@ create-colleague/
|
||||
│ ├── feishu_browser.py # Feishu browser method
|
||||
│ ├── feishu_mcp_client.py # Feishu MCP method
|
||||
│ ├── dingtalk_auto_collector.py # DingTalk auto-collector
|
||||
│ ├── slack_auto_collector.py # Slack auto-collector
|
||||
│ ├── email_parser.py # Email parser
|
||||
│ ├── skill_writer.py # Skill file management
|
||||
│ └── version_manager.py # Version archive & rollback
|
||||
|
||||
@@ -63,7 +63,8 @@ def setup_config() -> None:
|
||||
print(" im:chat.members:readonly 读取群成员")
|
||||
print()
|
||||
print(" 用户类:")
|
||||
print(" contact:user.base:readonly 搜索用户")
|
||||
print(" contact:user.base:readonly 读取用户基本信息")
|
||||
print(" contact:department.base:readonly 遍历部门查找用户(按姓名搜索必需)")
|
||||
print()
|
||||
print(" 文档类:")
|
||||
print(" docs:doc:readonly 读取文档")
|
||||
@@ -133,36 +134,134 @@ def api_post(path: str, body: dict, config: dict) -> dict:
|
||||
|
||||
# ─── 用户搜索 ─────────────────────────────────────────────────────────────────
|
||||
|
||||
def find_user(name: str, config: dict) -> Optional[dict]:
|
||||
"""通过姓名搜索飞书用户"""
|
||||
print(f" 搜索用户:{name} ...", file=sys.stderr)
|
||||
def _find_user_by_contact(name: str, config: dict) -> Optional[dict]:
|
||||
"""通过邮箱或手机号查找用户(使用 tenant_access_token)"""
|
||||
# 判断输入类型
|
||||
emails, mobiles = [], []
|
||||
if "@" in name:
|
||||
emails = [name]
|
||||
elif name.replace("+", "").replace("-", "").isdigit():
|
||||
mobiles = [name]
|
||||
else:
|
||||
return None # 不是邮箱或手机号,跳过
|
||||
|
||||
data = api_get(
|
||||
"/search/v1/user",
|
||||
{"query": name, "page_size": 10},
|
||||
config,
|
||||
)
|
||||
body = {}
|
||||
if emails:
|
||||
body["emails"] = emails
|
||||
if mobiles:
|
||||
body["mobiles"] = mobiles
|
||||
|
||||
data = api_post("/contact/v3/users/batch_get_id", body, config)
|
||||
if data.get("code") != 0:
|
||||
print(f" 搜索用户失败(code={data.get('code')}):{data.get('msg')}", file=sys.stderr)
|
||||
print(f" 邮箱/手机号查找失败(code={data.get('code')}):{data.get('msg')}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
users = data.get("data", {}).get("results", [])
|
||||
user_list = data.get("data", {}).get("user_list", [])
|
||||
for item in user_list:
|
||||
user_id = item.get("user_id")
|
||||
if user_id:
|
||||
# 获取用户详情
|
||||
detail = api_get(f"/contact/v3/users/{user_id}", {"user_id_type": "user_id"}, config)
|
||||
if detail.get("code") == 0:
|
||||
user_data = detail.get("data", {}).get("user", {})
|
||||
print(f" 找到用户:{user_data.get('name', user_id)}", file=sys.stderr)
|
||||
return user_data
|
||||
# 如果详情拉不到,返回基本信息
|
||||
return {"user_id": user_id, "open_id": item.get("open_id", ""), "name": name}
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _find_user_by_department(name: str, config: dict) -> Optional[dict]:
|
||||
"""遍历部门查找用户(使用 tenant_access_token,需要 contact:department.base:readonly)"""
|
||||
print(f" 通过部门遍历查找 {name} ...", file=sys.stderr)
|
||||
|
||||
# 递归获取所有部门 ID
|
||||
dept_ids = ["0"] # 0 = 根部门
|
||||
queue = ["0"]
|
||||
while queue:
|
||||
parent_id = queue.pop(0)
|
||||
data = api_get(
|
||||
f"/contact/v3/departments/{parent_id}/children",
|
||||
{"page_size": 50, "fetch_child": False},
|
||||
config,
|
||||
)
|
||||
if data.get("code") != 0:
|
||||
if parent_id == "0":
|
||||
print(f" 部门遍历失败(code={data.get('code')}):{data.get('msg')}", file=sys.stderr)
|
||||
print(f" 请确认已开通 contact:department.base:readonly 权限", file=sys.stderr)
|
||||
return None
|
||||
continue
|
||||
|
||||
children = data.get("data", {}).get("items", [])
|
||||
for child in children:
|
||||
child_id = child.get("department_id", "")
|
||||
if child_id:
|
||||
dept_ids.append(child_id)
|
||||
queue.append(child_id)
|
||||
|
||||
print(f" 共 {len(dept_ids)} 个部门,搜索用户 ...", file=sys.stderr)
|
||||
|
||||
# 在每个部门中查找用户
|
||||
matches = []
|
||||
for dept_id in dept_ids:
|
||||
page_token = None
|
||||
while True:
|
||||
params = {"department_id": dept_id, "page_size": 50}
|
||||
if page_token:
|
||||
params["page_token"] = page_token
|
||||
|
||||
data = api_get("/contact/v3/users/find_by_department", params, config)
|
||||
if data.get("code") != 0:
|
||||
break
|
||||
|
||||
users = data.get("data", {}).get("items", [])
|
||||
for u in users:
|
||||
uname = u.get("name", "")
|
||||
en_name = u.get("en_name", "")
|
||||
if name in uname or name in en_name or uname == name or en_name == name:
|
||||
matches.append(u)
|
||||
|
||||
if not data.get("data", {}).get("has_more"):
|
||||
break
|
||||
page_token = data.get("data", {}).get("page_token")
|
||||
|
||||
if len(matches) >= 10:
|
||||
break # 够了
|
||||
|
||||
return _select_user(matches, name)
|
||||
|
||||
|
||||
def _select_user(users: list, name: str) -> Optional[dict]:
|
||||
"""从候选列表中选择用户"""
|
||||
if not users:
|
||||
print(f" 未找到用户:{name}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
# 去重(按 user_id)
|
||||
seen = set()
|
||||
deduped = []
|
||||
for u in users:
|
||||
uid = u.get("user_id", u.get("open_id", id(u)))
|
||||
if uid not in seen:
|
||||
seen.add(uid)
|
||||
deduped.append(u)
|
||||
users = deduped
|
||||
|
||||
if len(users) == 1:
|
||||
u = users[0]
|
||||
print(f" 找到用户:{u.get('name')}({u.get('department_path', [''])[0]})", file=sys.stderr)
|
||||
dept_ids = u.get("department_ids", [])
|
||||
print(f" 找到用户:{u.get('name')}(部门:{dept_ids[0] if dept_ids else ''})", file=sys.stderr)
|
||||
return u
|
||||
|
||||
# 多个结果,让用户选择
|
||||
print(f"\n 找到 {len(users)} 个结果,请选择:")
|
||||
for i, u in enumerate(users):
|
||||
dept = u.get("department_path", [""])
|
||||
dept_str = dept[0] if dept else ""
|
||||
print(f" [{i+1}] {u.get('name')} {dept_str} {u.get('user_id', '')}")
|
||||
dept_ids = u.get("department_ids", [])
|
||||
dept_str = dept_ids[0] if dept_ids else ""
|
||||
en = u.get("en_name", "")
|
||||
label = f"{u.get('name', '')} ({en})" if en else u.get("name", "")
|
||||
print(f" [{i+1}] {label} dept={dept_str} uid={u.get('user_id', '')}")
|
||||
|
||||
choice = input("\n 选择编号(默认 1):").strip() or "1"
|
||||
try:
|
||||
@@ -172,6 +271,35 @@ def find_user(name: str, config: dict) -> Optional[dict]:
|
||||
return users[0]
|
||||
|
||||
|
||||
def find_user(name: str, config: dict) -> Optional[dict]:
|
||||
"""搜索飞书用户
|
||||
|
||||
策略:
|
||||
1. 如果输入是邮箱/手机号 → 直接用 batch_get_id(最快)
|
||||
2. 否则 → 遍历部门查找(需要 contact:department.base:readonly)
|
||||
3. 如果部门遍历也失败 → 提示用户改用邮箱/手机号
|
||||
"""
|
||||
print(f" 搜索用户:{name} ...", file=sys.stderr)
|
||||
|
||||
# 方法 1:邮箱/手机号直接查找
|
||||
user = _find_user_by_contact(name, config)
|
||||
if user:
|
||||
return user
|
||||
|
||||
# 方法 2:部门遍历
|
||||
user = _find_user_by_department(name, config)
|
||||
if user:
|
||||
return user
|
||||
|
||||
# 都失败
|
||||
print(f"\n ❌ 未能找到用户 {name}", file=sys.stderr)
|
||||
print(f" 建议:", file=sys.stderr)
|
||||
print(f" 1. 确认已开通 contact:department.base:readonly 权限", file=sys.stderr)
|
||||
print(f" 2. 改用邮箱搜索:--name user@company.com", file=sys.stderr)
|
||||
print(f" 3. 改用手机号搜索:--name +8613800138000", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
# ─── 消息记录 ─────────────────────────────────────────────────────────────────
|
||||
|
||||
def get_chats_with_user(user_open_id: str, config: dict) -> list:
|
||||
|
||||
Reference in New Issue
Block a user