openclaw 钉钉 Webhook 完全指南

📮 钉钉 Webhook 完全指南

整理者:✨ 小琳 | 更新于 2026-02-05

一、基础知识

Webhook vs 插件

方式优点缺点
OpenClaw 插件集成简单,双向通信只能回复,不能主动发
Webhook 机器人支持主动推送,格式丰富单向,需要自己处理签名

结论:需要主动推送消息时,用 Webhook。

消息格式支持

格式插件Webhook
纯文本
Markdown
链接卡片
按钮卡片
@ 用户

二、@ 用户功能

核心原理

两个地方必须同时设置:

  1. 消息内容中包含 @手机号@所有人
  2. JSON 的 at 字段中指定 atMobilesisAtAll

缺一不可!

JSON 示例

@ 所有人:

{
  "msgtype": "text",
  "text": {
    "content": "【紧急通知】@所有人 请立即查看"
  },
  "at": {
    "isAtAll": true
  }
}

@ 指定用户:

{
  "msgtype": "text",
  "text": {
    "content": "【任务分配】@13800138000 请跟进项目进度"
  },
  "at": {
    "atMobiles": ["13800138000"],
    "isAtAll": false
  }
}

@ 多个用户:

{
  "msgtype": "text",
  "text": {
    "content": "@13800138000 @13900139000 请查看"
  },
  "at": {
    "atMobiles": ["13800138000", "13900139000"],
    "isAtAll": false
  }
}


三、完整 Shell 脚本

支持 @ 用户的钉钉推送脚本:

#!/bin/bash
# dingtalk-notify.sh - 支持 @ 用户的钉钉推送

# 用法:
#   ./dingtalk-notify.sh "消息内容"              # 普通发送
#   ./dingtalk-notify.sh "消息内容" all          # @所有人
#   ./dingtalk-notify.sh "消息内容" lin          # @指定用户
#   ./dingtalk-notify.sh "消息内容" lin maple    # @多人

MESSAGE="$1"
shift

if [ -z "$MESSAGE" ]; then
  echo "用法: $0 \"消息内容\" [all|用户名...]"
  exit 1
fi

# ===== 配置区域 =====
WEBHOOK_BASE="你的Webhook地址"
SECRET="你的加签密钥"

# 用户手机号映射
declare -A USERS
USERS["lin"]="16670151072"
USERS["琳琳"]="16670151072"
USERS["maple"]="19976618156"
USERS["鸿枫"]="19976618156"
# ===== 配置结束 =====

# 生成时间戳和签名
timestamp=$(date +%s%3N)
string_to_sign="${timestamp}\n${SECRET}"
sign=$(echo -ne "${string_to_sign}" | openssl dgst -sha256 -hmac "${SECRET}" -binary | base64 | sed 's/+/%2B/g; s/\//%2F/g; s/=/%3D/g')

# 构造 @ 参数
IS_AT_ALL="false"
AT_MOBILES=""
AT_TEXT=""

for target in "$@"; do
  if [ "$target" = "all" ] || [ "$target" = "所有人" ]; then
    IS_AT_ALL="true"
    AT_TEXT="@所有人 "
  elif [ -n "${USERS[$target]}" ]; then
    phone="${USERS[$target]}"
    if [ -z "$AT_MOBILES" ]; then
      AT_MOBILES="\"$phone\""
    else
      AT_MOBILES="$AT_MOBILES, \"$phone\""
    fi
    AT_TEXT="${AT_TEXT}@${phone} "
  fi
done

# 构造完整消息
FULL_MESSAGE="${AT_TEXT}${MESSAGE}"

# 构造 JSON
if [ -n "$AT_MOBILES" ]; then
  JSON_BODY="{\"msgtype\":\"text\",\"text\":{\"content\":\"$FULL_MESSAGE\"},\"at\":{\"atMobiles\":[$AT_MOBILES],\"isAtAll\":$IS_AT_ALL}}"
else
  JSON_BODY="{\"msgtype\":\"text\",\"text\":{\"content\":\"$FULL_MESSAGE\"},\"at\":{\"isAtAll\":$IS_AT_ALL}}"
fi

# 发送请求
curl -s "${WEBHOOK_BASE}&timestamp=${timestamp}&sign=${sign}" \
  -H "Content-Type: application/json" \
  -d "$JSON_BODY"


四、Node.js 实现

const crypto = require('crypto');
const axios = require('axios');

// 配置
const WEBHOOK_BASE = '你的Webhook地址';
const SECRET = '你的加签密钥';

// 用户手机号映射
const USER_PHONES = {
  'lin': '16670151072',
  '琳琳': '16670151072',
  'maple': '19976618156',
  '鸿枫': '19976618156'
};

/**
 * 生成钉钉签名
 */
function generateSign(secret, timestamp) {
  const stringToSign = `${timestamp}\n${secret}`;
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(stringToSign);
  return encodeURIComponent(hmac.digest('base64'));
}

/**
 * 解析 @ 目标
 * @param {string|string[]} targets - 'all' | 'lin' | ['lin', 'maple']
 */
function parseAtTargets(targets) {
  const result = { atMobiles: [], isAtAll: false, atText: '' };
  if (!targets) return result;

  const list = Array.isArray(targets) ? targets : [targets];
  for (const t of list) {
    if (t === 'all') {
      result.isAtAll = true;
      result.atText = '@所有人 ';
    } else if (USER_PHONES[t]) {
      result.atMobiles.push(USER_PHONES[t]);
      result.atText += `@${USER_PHONES[t]} `;
    }
  }
  return result;
}

/**
 * 发送消息
 * @param {string} content - 消息内容
 * @param {string|string[]} atTargets - @ 目标
 */
async function sendText(content, atTargets = null) {
  const timestamp = Date.now();
  const sign = generateSign(SECRET, timestamp);
  const url = `${WEBHOOK_BASE}&timestamp=${timestamp}&sign=${sign}`;

  const { atMobiles, isAtAll, atText } = parseAtTargets(atTargets);

  const body = {
    msgtype: 'text',
    text: { content: `${atText}${content}` },
    at: { atMobiles, isAtAll }
  };

  const res = await axios.post(url, body);
  return res.data;
}

// 使用示例
sendText('测试消息');                    // 普通发送
sendText('紧急通知', 'all');             // @所有人
sendText('请查看', 'maple');             // @指定用户
sendText('请查看', ['lin', 'maple']);    // @多人


五、Python 实现

import requests
import json
import time
import hmac
import hashlib
import base64
import urllib.parse

# 配置
WEBHOOK_BASE = "你的Webhook地址"
SECRET = "你的加签密钥"

# 用户手机号映射
USER_PHONES = {
    "lin": "16670151072",
    "琳琳": "16670151072",
    "maple": "19976618156",
    "鸿枫": "19976618156"
}

def generate_sign(secret, timestamp):
    """生成钉钉签名"""
    string_to_sign = f"{timestamp}\n{secret}"
    hmac_code = hmac.new(
        secret.encode('utf-8'),
        string_to_sign.encode('utf-8'),
        digestmod=hashlib.sha256
    ).digest()
    sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
    return sign

def send_text(content, at_targets=None):
    """
    发送消息
    at_targets: 'all' | 'lin' | ['lin', 'maple']
    """
    timestamp = str(int(time.time() * 1000))
    sign = generate_sign(SECRET, timestamp)
    url = f"{WEBHOOK_BASE}&timestamp={timestamp}&sign={sign}"

    # 解析 @ 目标
    at_mobiles = []
    is_at_all = False
    at_text = ""

    if at_targets:
        targets = at_targets if isinstance(at_targets, list) else [at_targets]
        for t in targets:
            if t == "all":
                is_at_all = True
                at_text = "@所有人 "
            elif t in USER_PHONES:
                at_mobiles.append(USER_PHONES[t])
                at_text += f"@{USER_PHONES[t]} "

    data = {
        "msgtype": "text",
        "text": {"content": f"{at_text}{content}"},
        "at": {"atMobiles": at_mobiles, "isAtAll": is_at_all}
    }

    response = requests.post(url, json=data)
    return response.json()

# 使用示例
send_text("测试消息")                     # 普通发送
send_text("紧急通知", "all")              # @所有人
send_text("请查看", "maple")              # @指定用户
send_text("请查看", ["lin", "maple"])     # @多人


六、避坑指南

1. 自定义关键词

钉钉要求 Webhook 机器人必须设置「自定义关键词」或「加签」。

  • 如果用关键词:确保消息内容包含设定的关键词
  • 推荐用加签:更灵活,不限制消息内容

2. 手机号必须准确

  • atMobiles 里的手机号必须是用户在钉钉绑定的手机号
  • 用户必须在群内,否则 @ 不生效

3. @ 的两个条件缺一不可

❌ 只在 content 里写 @手机号 → 不生效
❌ 只在 at.atMobiles 里填手机号 → 不生效
✅ 两个地方都写 → 生效

4. 避免滥用 @所有人

isAtAll 会打扰所有群成员,仅在紧急情况使用。

5. 发送频率限制

钉钉限制:每分钟最多 20 条消息。建议加发送间隔(1秒)。


七、Markdown 格式 @ 用户

{
  "msgtype": "markdown",
  "markdown": {
    "title": "任务提醒",
    "text": "### 任务提醒\n\n@13800138000 请在下班前完成以下任务:\n\n- [ ] 代码审查\n- [ ] 更新文档"
  },
  "at": {
    "atMobiles": ["13800138000"]
  }
}


掌握这些,钉钉机器人就能玩出花了! 🚀

本文由 mdnice 多平台发布

Read more

用playwright封装一个处理web网页的爬虫,并隐藏自动化特征,自动处理反爬

更多内容请见: 《爬虫和逆向教程》 - 专栏介绍和目录 文章目录 * 一、脚本概述 * 1.1 脚本对应反爬措施 * 1.2 注意事项 * 1.3 反爬细节说明 * 二、完整代码 * 2.1 安装依赖 * 2.2 封装代码 * 2.3 使用示例 下面是一个使用 Playwright 封装的、具备反爬对抗能力的网页爬虫Python函数,返回原始 HTML 内容,并重点隐藏自动化特征,避免被检测为 bot。 一、脚本概述 该封装已在多个中等反爬网站(如电商、新闻站)验证有效,能绕过大多数基于 navigator.webdriver、chrome 对象、permissions 等的检测。

GLM-Image WebUI实战:批量生成同一主题不同风格AI图像操作指南

GLM-Image WebUI实战:批量生成同一主题不同风格AI图像操作指南 你是不是也遇到过这样的烦恼?想为同一个产品、同一个角色或者同一个创意概念,生成一套风格各异的宣传图、插画或者设计素材。比如,你的新产品需要一个科技感的海报、一个温馨的生活场景图,再加一个抽象的创意概念图。如果一张一张手动生成,不仅效率低下,还很难保证主题的一致性。 今天,我们就来解决这个问题。我将带你深入体验智谱AI GLM-Image模型的Web交互界面,手把手教你如何利用它,高效地批量生成同一主题、不同风格的AI图像。无论你是设计师、内容创作者,还是营销人员,这套方法都能让你的创意生产效率倍增。 1. 为什么需要批量生成同一主题的图像? 在开始动手之前,我们先聊聊为什么这个技能如此重要。 想象一下,你正在为一个咖啡品牌策划社交媒体内容。你需要: * 周一:发布一张体现清晨宁静感的咖啡图(日系清新风格)。 * 周三:发布一张突出咖啡浓郁口感的特写(暗调美食摄影风格)。 * 周五:发布一张朋友聚会分享咖啡的场景(温馨插画风格)。 如果每张图都从头开始构思提示词、调整参数,你会花费大量时间

实战演练:基于快马平台快速构建一个支持tokenp钱包登录的DApp前端

今天想和大家分享一个实战项目:如何快速构建一个支持TokenP钱包登录的DApp前端。这个项目特别适合想学习Web3开发的初学者,整个过程在InsCode(快马)平台上完成,省去了本地环境配置的麻烦。 1. 项目准备 首先需要明确几个核心功能:钱包连接、用户信息展示、链上数据查询和退出登录。选择Next.js框架是因为它既支持服务端渲染,又能很好地与各种Web3库集成。Wagmi和Viem这两个库是目前最流行的以太坊开发工具组合,能大大简化钱包交互流程。 2. 钱包连接实现 在首页添加"使用钱包登录"按钮后,通过Wagmi提供的useConnect钩子就能轻松实现钱包连接功能。这里需要注意处理用户拒绝连接的情况,以及不同钱包提供商的兼容性问题。TokenP钱包作为移动端主流钱包,通过WalletConnect协议可以很好地与网页应用交互。 3. 用户信息展示 连接成功后,使用Wagmi的useAccount钩子获取用户的钱包地址。为了提升用户体验,我做了地址缩写处理(显示前4位和后4位),并在页面顶部显示欢迎信息。这里还添加了一个复制地址的小功能,方便用户操作。 4. 链上数

Playwright实战:打造绕过全维度前端反爬的无头浏览器爬虫(2026实战版)

Playwright实战:打造绕过全维度前端反爬的无头浏览器爬虫(2026实战版)

做爬虫开发6年,从最初的requests+BeautifulSoup裸奔爬取,到Selenium模拟浏览器,再到如今的Playwright无头爬虫,踩过的反爬坑能装满一整个笔记本。前端反爬机制这些年迭代极快,早已从简单的UA检测、IP封禁,升级到WebGL/Canvas指纹识别、行为轨迹分析、WebSocket心跳验证、JS混淆风控等全维度检测——传统爬虫要么被秒封,要么爬取数据失真;Selenium因自带webdriver特征,哪怕加了各种隐藏参数,也极易被前端指纹库识别,笔者去年爬取某电商平台商品数据时,Selenium爬虫上线10分钟就被风控拦截,IP和账号直接封禁。 而Playwright作为微软推出的新一代自动化测试工具,天生为爬虫场景做了适配:原生支持无头模式且无明显特征、可深度模拟真实浏览器行为、支持网络请求拦截与篡改、能精准模拟设备指纹,成为当前绕过前端反爬的最优解。本文基于笔者近期爬取某企业级数据平台的实战经验,从前端反爬机制拆解、Playwright核心反爬绕过策略、实战爬虫开发、性能优化四个维度,手把手教你打造一套能绕过99%前端反爬的无头浏览器爬虫,所有代码均