Skill: huguiqi/fk-boss-greeting (version 1.0.0)

--- name: fk-boss-greeting description: Boss直聘打招呼自动化工具。用于搜索职位并向HR主动打招呼。当用户要求在Boss直聘(老板直聘)上发打招呼、投递简历、搜索职位并沟通时使用。支持：搜索Java/架构师/技术经理等职位，自动过滤黑名单公司(阿里、百度、腾讯、字节、京东等)和无关岗位(算法、AI、测试、产品经理等)，逐个排队打招呼(间隔1秒)，记录已打招呼避免重复。 --- # Boss 直聘打招呼自动化 ## 前置条件 本工具依赖 `boss` CLI，默认路径 `~/.local/bin/boss`。 ### 登录 从 Chrome 浏览器读取登录态（用户需先在 Chrome 登录 zhipin.com）： ```bash ~/.local/bin/boss login --cookie-source chrome ``` **禁止使用二维码登录**。如凭证过期或异常，先 `boss logout` 再重新登录。 ### 工具版本 安装路径：`~/.local/bin/boss`（通过 uv tool 安装） 升级命令： ```bash cd /Users/huguiqi/Public/javaWorkspace/github/boss-cli && git pull && uv tool install --reinstall . ``` ## 打招呼流程 ### 1. 读取过滤规则 过滤规则文件：[references/filter-rules.json](references/filter-rules.json) 规则包括： - **公司黑名单**：阿里、百度、腾讯、字节、京东、拼多多、滴滴、快手、小米、华为、蚂蚁等 - **职位黑名单**：算法、AI、测试、产品经理、初级、实习、大模型等 - **搜索关键词**：Java架构师、技术经理、软件工程师、研发经理、微服务架构师等（共10个） - **城市优先级**：上海 > 北京 > 苏州 > 杭州 > 无锡 - **每次上限**：30 个 - **打招呼间隔**：1 秒 ### 2. 搜索职位 对每个关键词 × 每个城市依次搜索，间隔 1.5 秒： ```bash ~/.local/bin/boss search "Java架构师" --city 上海 ``` 过滤逻辑： 1. 排除公司黑名单中的公司 2. 排除职位黑名单中的关键词（算法、AI、测试、产品经理等） 3. 只保留 Java 相关职位（包含 java、架构师、技术经理、微服务、spring、技术负责人、研发经理、服务端） 4. 去重（按 securityId） ### 3. 逐个排队打招呼 **禁止使用 `batch-greet`**（会并行发送，触发风控）。必须逐个执行： ```bash ~/.local/bin/boss greet <securityId> ``` 每次间隔 **1 秒**，每轮最多 **30 个**。 搜索和打招呼均可使用脚本：[scripts/boss_auto_greet.sh](scripts/boss_auto_greet.sh) ### 4. 限速处理 如遇 "频繁" 或 "限速" 错误： - 等待 30 秒后重试 - 如遇 code=32（账户封禁）或 code=36（异常行为），停止操作并告知用户 ## 去重机制 - 每次打招呼的 securityId 记录到日志 - 同一会话内不重复打招呼 - 识别"开聊提醒"（已沟通过的）避免重复 ## 注意事项 1. **不并行**：所有打招呼必须排队，逐个执行 2. **控制频率**：每次最多 30 个，避免账户被封 3. **只打 Java 相关**：过滤非 Java 的无关职位 4. **登录态**：Boss 直聘 Web 登录态有效期短，每次操作前重新执行 `boss login --cookie-source chrome` 5. **停用定时任务时**：需明确告知用户当前状态

path: README.md
content: # 🦞 小龙虾 Boss 直聘自动打招呼工具

> 2026 年，工作不好找，程序员更不好找。  
> 手动刷 Boss 太慢了？让小龙虾帮你。

这是一个基于 [kabi-boss-cli](https://github.com/jackwener/boss-cli) 封装的 **Boss 直聘自动打招呼 Skill**，专为找工作的程序员打造。

它能帮你自动搜索职位、过滤掉不想去的公司、排除不相关的岗位，然后逐个跟 HR 打招呼——让你把时间花在真正重要的面试准备上。

---

## ✨ 功能

- 🔍 **自动搜索**：Java架构师、技术经理、研发经理、软件工程师等 10 个关键词，覆盖上海/北京/苏州/杭州/无锡
- 🚫 **智能过滤**：
  - 公司黑名单：阿里、百度、腾讯、字节、京东、拼多多、滴滴、快手、小米、华为、蚂蚁...
  - 职位黑名单：算法、AI、大模型、测试、产品经理、初级/实习...
  - 只保留 Java 相关的技术岗
- 🐢 **安全第一**：逐个排队打招呼，间隔 1 秒，每轮最多 30 个，避免被风控封号
- 📝 **去重记录**：不会重复打扰同一个 HR
- ⏰ **定时任务**：支持周一/周三上午 9 点自动运行

---

## 📦 安装

### 1. 安装 kabi-boss-cli

```bash
pip install kabi-boss-cli
# 或通过 uv
uv tool install kabi-boss-cli
```

### 2. 登录 Boss 直聘

先在 **Chrome 浏览器**打开 [zhipin.com](https://www.zhipin.com) 并登录，然后：

```bash
boss login --cookie-source chrome
```

> 💡 不需要扫码，直接从 Chrome 读取登录态即可。

### 3. 导入 Skill

在 OpenClaw（或支持 Skill 的 AI 助手）中导入 `fk-boss-greeting.skill`。

---

## 🚀 使用

### 手动打招呼

告诉 AI 助手：

> "帮我找匹配的职位打招呼"  
> "搜索上海 Java 架构师职位并打招呼"

AI 会自动：登录 → 搜索 → 过滤 → 逐个打招呼。

### 定时任务

> "设置每周一、周三上午 9 点自动打招呼"

---

## ⚙️ 规则配置

编辑 `references/filter-rules.json` 可自定义：

```json
{
  "company_blacklist": ["阿里", "百度", "腾讯", ...],
  "job_blacklist": ["算法", "测试", "产品经理", ...],
  "search_keywords": ["Java架构师", "技术经理", ...],
  "city_priority": ["上海", "北京", "苏州", "杭州", "无锡"],
  "max_greets_per_day": 30
}
```

---

## ⚠️ 安全须知

1. **控制频率**：每轮最多 30 个，间隔 1 秒，别贪多
2. **不要并行**：逐个打招呼，别同时发
3. **账号风险**：高频操作可能触发 Boss 直聘风控（code=32/36），如被封需在 APP 手动解封
4. **合理使用**：这工具是帮你省时间的，不是滥用平台的手段

---

## 🛠 技术栈

- [kabi-boss-cli](https://github.com/jackwener/boss-cli) - Boss 直聘命令行工具
- Python 3 - 搜索过滤脚本
- OpenClaw Skill - AI Agent 封装

---

## 📄 License

Apache-2.0

---

> 🦞 小龙虾出品，祝早日上岸！  
> 如果有帮助，给个 ⭐ Star 呗～


path: references/filter-rules.json
content: {
  "version": "1.0",
  "description": "Boss直聘打招呼过滤规则，所有打招呼操作默认必须应用此规则",
  "last_updated": "2026-05-07",
  "company_blacklist": [
    "阿里", "阿里巴巴", "百度", "腾讯", "字节", "字节跳动",
    "京东", "美团", "拼多多", "网易", "滴滴", "快手",
    "小米", "华为", "蚂蚁", "蚂蚁集团",
    "BOSS直聘", "boss直聘"
  ],
  "job_blacklist": [
    "算法", "机器学习", "深度学习", "大模型", "LLM", "NLP", "CV",
    "视觉", "语音", "推荐", "广告", "AIGC", "AGI", "ChatGPT",
    "生成式", "智能驾驶", "智能座舱", "AI工程师", "AI架构师",
    "测试", "自动化测试", "测试工程师", "测试开发", "QA", "QC",
    "产品经理", "产品助理", "产品主管", "产品总监", "产品负责人",
    "初级", "校园招聘", "25届", "26届", "应届", "实习",
    "中层管理"
  ],
  "search_keywords": [
    "Java架构师", "Java技术经理", "Java软件工程师",
    "微服务架构师", "Java技术专家",
    "架构师", "技术经理", "软件工程师",
    "技术负责人", "研发经理"
  ],
  "city_priority": ["上海", "北京", "苏州", "杭州", "无锡"],
  "salary_preferred": "25-35K",
  "experience": "5-10年",
  "max_greets_per_day": 30,
  "greet_interval_seconds": 3,
  "anti_duplicate": {
    "enabled": true,
    "track_file": "logs/greeted_ids.txt",
    "retention_days": 7
  }
}


path: scripts/boss_auto_greet.sh
content: #!/usr/bin/env python3
"""
Boss Auto Greet Script
从 config/filter-rules.json 读取过滤规则，搜索 Java 相关职位，过滤后打招呼
"""

import json
import subprocess
import time
import os
from datetime import date

# ========== 路径配置 ==========
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
WORKSPACE = os.path.dirname(SCRIPT_DIR)
CONFIG_FILE = os.path.join(WORKSPACE, "config", "filter-rules.json")
LOG_FILE = os.path.join(WORKSPACE, "logs", "boss_greet.log")
TRACK_FILE = os.path.join(WORKSPACE, "logs", "greeted_ids.txt")
TODAY_MARKER = os.path.join(WORKSPACE, "logs", f"greeted_{date.today().isoformat()}.txt")

BOSS_CMD = os.path.expanduser("~/.local/bin/boss")
BOSS_LOGIN_CMD = BOSS_CMD + " login --cookie-source chrome"

# ========== 加载配置 ==========
def load_config():
    with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
        cfg = json.load(f)
    return cfg

cfg = load_config()

COMPANY_BLACK = set(cfg["company_blacklist"])
JOB_BLACK = set(cfg["job_blacklist"])
KEYWORDS = cfg["search_keywords"]
CITY_PRIORITY = cfg["city_priority"]
MAX_GREET = cfg.get("max_greets_per_day", 50)
SLEEP_BETWEEN_GREET = 1  # 逐个排队，间隔1秒
SLEEP_BETWEEN_SEARCH = 2

# ========== 工具函数 ==========

def log(msg):
    ts = time.strftime('%Y-%m-%d %H:%M:%S')
    line = f"[{ts}] {msg}"
    print(line)
    os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
    with open(LOG_FILE, 'a', encoding='utf-8') as f:
        f.write(line + '\n')

def run_boss(args, _retry_login=True):
    """调用 boss 命令，返回 JSON；认证失败时自动尝试 chrome cookie 登录"""
    cmd = [BOSS_CMD] + args
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
    try:
        data = json.loads(result.stdout)
    except json.JSONDecodeError:
        return {"ok": False, "error": result.stdout or result.stderr}

    if not data.get("ok") and ("环境异常" in json.dumps(data) or "登录" in json.dumps(data)):
        log(f"认证过期，尝试 chrome cookie 登录...")
        subprocess.run(BOSS_LOGIN_CMD.split(), capture_output=True, timeout=60)
        if _retry_login:
            return run_boss(args, _retry_login=False)
    return data

def is_already_greeted(sid):
    """检查是否已打过招呼"""
    if os.path.exists(TODAY_MARKER):
        with open(TODAY_MARKER, 'r', encoding='utf-8') as f:
            if sid in f.read().splitlines():
                return True
    if os.path.exists(TRACK_FILE):
        with open(TRACK_FILE, 'r', encoding='utf-8') as f:
            if sid in f.read().splitlines():
                return True
    return False

def mark_greeted(sid):
    """标记为已打招呼"""
    for f in [TRACK_FILE, TODAY_MARKER]:
        os.makedirs(os.path.dirname(f), exist_ok=True)
        with open(f, 'a', encoding='utf-8') as fh:
            fh.write(sid + '\n')

def greet(sid, name, brand):
    """单次打招呼"""
    result = run_boss(["greet", sid])
    if result.get("ok"):
        log(f"✅ {name} @ {brand}")
        return True
    else:
        err = json.dumps(result, ensure_ascii=False)
        if "频繁" in err or "限速" in err:
            log(f"⚠️ 限速，等待35秒...")
            time.sleep(35)
            result = run_boss(["greet", sid])
            if result.get("ok"):
                log(f"✅ {name} @ {brand} (重试成功)")
                return True
        log(f"❌ {name} @ {brand}: {str(err)[:100]}")
        return False

def filter_job(brand, job_name, skills_text):
    """过滤黑名单职位"""
    text = (brand + job_name + skills_text).lower()
    for kw in COMPANY_BLACK:
        if kw.lower() in text:
            return False, f"公司黑名单: {kw}"
    for kw in JOB_BLACK:
        if kw.lower() in text:
            return False, f"职位黑名单: {kw}"
    return True, "ok"

def search_and_collect(keyword, city):
    """搜索并收集符合条件的职位"""
    log(f"搜索: {keyword} @ {city}")
    result = run_boss(["search", keyword, "--city", city])
    if not result.get("ok"):
        err_msg = result.get('error', 'unknown')
        if isinstance(err_msg, str):
            err_msg = err_msg[:80]
        else:
            err_msg = str(err_msg)[:80]
        log(f"搜索失败: {err_msg}")
        return []

    jobs = result.get("data", {}).get("jobList", []) or []
    collected = []
    for j in jobs:
        sid = j.get("securityId", "")
        brand = j.get("brandName", "") or ""
        job_name = j.get("jobName", "") or ""
        salary = j.get("salaryDesc", "") or ""
        skills = " ".join(j.get("skills", []) or [])
        job_exp = j.get("jobExperience", "") or ""

        if not sid:
            continue

        ok, reason = filter_job(brand, job_name, skills)
        if not ok:
            continue

        if is_already_greeted(sid):
            continue

        collected.append({
            "sid": sid,
            "name": job_name,
            "brand": brand,
            "salary": salary,
            "exp": job_exp,
            "skills": skills,
            "city": city,
        })
        log(f"  📋 {job_name} @ {brand} ({salary}, {job_exp})")

    return collected

# ========== 主流程 ==========

def main():
    os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
    os.makedirs(os.path.dirname(TRACK_FILE), exist_ok=True)

    log("==== 开始批量打招呼任务 (配置驱动) ====")

    # 先登录
    log("登录...")
    subprocess.run(BOSS_LOGIN_CMD.split(), capture_output=True, timeout=30)

    # 按城市优先级搜索
    all_jobs = {}

    for city in CITY_PRIORITY:
        for kw in KEYWORDS:
            jobs = search_and_collect(kw, city)
            for j in jobs:
                if j["sid"] not in all_jobs:
                    all_jobs[j["sid"]] = j
            time.sleep(SLEEP_BETWEEN_SEARCH)

    count = len(all_jobs)
    log(f"共找到 {count} 个未打招呼的合格职位")

    if count == 0:
        log("无新职位，任务结束")
        return

    # 按城市优先级排序打招呼
    city_rank = {c: i for i, c in enumerate(CITY_PRIORITY)}
    sorted_jobs = sorted(all_jobs.items(), key=lambda x: city_rank.get(x[1]["city"], 99))

    greeted = 0
    for i, (sid, job) in enumerate(sorted_jobs):
        if greeted >= MAX_GREET:
            log(f"已达到每日上限 {MAX_GREET} 个，停止")
            break
        log(f"[{greeted+1}/{MAX_GREET}] 打招呼: {job['name']} @ {job['brand']} [{job['city']}]")
        ok = greet(sid, job["name"], job["brand"])
        if ok:
            mark_greeted(sid)
            greeted += 1
        time.sleep(SLEEP_BETWEEN_GREET)

    log(f"本次打招呼 {greeted} 个")
    log("=== 任务完成 ===")

if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)