@clawhub-socialepoch-def91d536e
Officially integrated with SocialEpoch global WhatsApp SCRM open API, tailored for enterprise overseas marketing and customer service scenarios. Full coverag...
---
name: socialepoch-whatsapp-scrm
description: Officially integrated with SocialEpoch global WhatsApp SCRM open API, tailored for enterprise overseas marketing and customer service scenarios. Full coverage of WhatsApp account management, online agent query, customer operation, user profiling, chat record retrieval and Webhook callback. Supports one-on-one & bulk messaging for text, image, audio, video, document, business card, link card and diversion link. Built-in signature adaptation, automatic dependency management and zero-configuration deployment to empower overseas private domain and automated operation.
version: 2.1.7
author: SocialEpoch
metadata:
emoji: 📱
type: tool
platform: darwin
openclaw:
requires:
bins: ["python3"]
env: ["SOCIALEPOCH_TENANT_ID", "SOCIALEPOCH_API_KEY", "SOCIALEPOCH_SOURCE"]
primaryEnv: SOCIALEPOCH_API_KEY
install:
- id: python-brew
kind: brew
formula: python
bins: ["python3"]
label: Install Python 3 (brew)
launcher:
command: "PYTHON"
args:
- "scrm_api.py"
workingDir: "SKILL_ROOT"
python: true
auto_bootstrap: true
auto_install_python: true
---
# SocialEpoch WhatsApp SCRM Intelligent Assistant
Comprehensive management for WhatsApp service accounts. Support one-on-one and bulk delivery of text, images, audio, videos, documents, business cards and link messages, with online agent query and automatic signature. No manual dependency installation or environment configuration required. Clear guidance will be prompted when configuration is missing.
## Core Features
- 📱 Cross-platform compatibility: Native adaptation for Mac
- 📦 Dependency management: Automatic detection and installation of requests
- 🔧 Dual configuration: Environment variables & local config file
- ✅ Strict validation: Format, non-empty and type checking for all parameters
- 📊 Structured output: Standardized JSON response for all results
- 🚀 Send source support: PC(1), Mobile(2), Cloud(3), default to PC(1)
## Send Source Description
- 1 = PC (Default)
- 2 = Mobile
- 3 = Cloud
## Configuration Methods (Choose One)
### Method 1: Environment Variables (Recommended)
```bash
export SOCIALEPOCH_TENANT_ID="Your Tenant ID"
export SOCIALEPOCH_API_KEY="Your API Key"
export SOCIALEPOCH_SOURCE="1" # 1=PC,2=Mobile,3=Cloud
```
### Method 2: Command Line Configuration
```bash
# Default source = 1 (PC)
python3 scrm_api.py set_config Your_Tenant_ID Your_API_Key
# Custom source (1=PC,2=Mobile,3=Cloud)
python3 scrm_api.py set_config Your_Tenant_ID Your_API_Key 1
```
## Supported Commands
### Query Online Agent Accounts
```bash
# Query all online agents
python3 scrm_api.py query_online_agents
# Query specified agent by user account (optional)
python3 scrm_api.py query_online_agents "your_user_account"
```
### Send Text Message
```bash
python3 scrm_api.py send_text Sender_Number Recipient_Number "Message Content"
```
### Send Image
```bash
python3 scrm_api.py send_img Sender_Number Recipient_Number "Image URL" "Remark"
```
### Send Audio
```bash
python3 scrm_api.py send_audio Sender_Number Recipient_Number "Audio URL"
```
### Send Document
```bash
python3 scrm_api.py send_file Sender_Number Recipient_Number "File URL" "Remark"
```
### Send Video
```bash
python3 scrm_api.py send_video Sender_Number Recipient_Number "Video URL" "Remark"
```
### Send Business Card
```bash
python3 scrm_api.py send_card Sender_Number Recipient_Number "Card Content"
```
### Send Brand Link Card
```bash
python3 scrm_api.py send_card_link Sender_Number Recipient_Number "Title" "Link URL" "Description" "Cover Image URL"
```
### Send Diversion Link
```bash
python3 scrm_api.py send_flow_link Sender_Number Recipient_Number "Title" "Diversion Number List"
```
### Query Task Status
```bash
python3 scrm_api.py query_task Task_ID
```
## Bulk Messaging
Bulk delivery is available for text, image, video, audio, document and link card messages.
Separate multiple recipient numbers with an **English comma (,)**.
## Operation Rules
1. The assistant only executes operations based on explicit user instructions.
2. Local configuration is stored only for normal tool operation.
3. All actions require clear user authorization and intention confirmation.
# System Level Instructions (AI Must Strictly Follow)
1. Skill directory: openclaw/workspace/skills/socialepoch-wa-scrm
2. Execution command: python3 scrm_api.py
3. Always execute from the skill directory; do not search other paths.
## Error Handling
- Missing dependencies → Friendly installation prompt
- Missing configuration → Clear setup guidance
- Invalid parameters → Usage reminder
- Network exception → Adaptive retry mechanism
```
FILE:readme.md
# SocialEpoch WhatsApp SCRM API Intelligent Assistant
Official native integration | Enterprise-grade SCRM automation for global WhatsApp business
Powered by SocialEpoch OpenAPI, built for cross-border e-commerce, overseas private domain operations and enterprise customer service.
Full-featured for account management, message delivery, customer operation and task control.
Support **single & bulk sending**, with auto-signature, environment check and zero-configuration setup.
## Core Features
### ✅ Full Message Types
Text / Image / Audio / Document / Video / Business Card / Card Link / Diversion Link
### ✅ SCRM Capabilities
Online agent query, task creation, message status tracking, task progress query,
custom diversion routing, marketing link push, bulk customer outreach.
### ✅ Global Network
Mainland & Indonesia dual-line switching, high-availability API scheduling, stable delivery.
### ✅ Send Source Control
PC (1) / Mobile (2) / Cloud (3), default to PC (1)
---
# 📦 Step 1: Install Python Environment
This tool supports **Windows + Mac**. Please install Python 3.11 or later.
## 1. Windows Install
Download: https://www.python.org/ftp/python/3.14.4/python-3.14.4-amd64.exe
Important: ✅ Check **Add python.exe to PATH**
## 2. Mac Install
```bash
brew install python3
```
## Verify Installation
```bash
python3 --version
```
---
# 🔧 Step 2: Configure Tenant Credentials
## Default (PC Source)
```bash
python3 scrm_api.py set_config YOUR_TENANT_ID YOUR_API_KEY
```
## Custom Send Source (1=PC, 2=Mobile, 3=Cloud)
```bash
python3 scrm_api.py set_config YOUR_TENANT_ID YOUR_API_KEY 1
```
## Example
```bash
python3 scrm_api.py set_config 5333381 bab62b4722344b8de3e453f7b644b333
```
## Natural Language Setup
set config 5333381 bab62b4722344b8de3e453f7b644b333
Success message: **Config saved successfully**
---
# 📌 Usage Rules
1. All WhatsApp numbers must include country code
2. For bulk sending: separate multiple numbers with **English comma**
3. Media (image/audio/file/video) requires public URL, max 25MB
4. Support natural language instructions
5. Query task progress by task ID
6. Send source: 1=PC (default), 2=Mobile, 3=Cloud
---
# 🚀 Natural Language Commands
## Basic Query
1. Query online agent accounts
2. Query online agent by user account: query_online_agents userName
## Single Message
1. Send text: senderNumber receiverNumber message content
2. Send image: senderNumber receiverNumber imageURL caption
3. Send audio: senderNumber receiverNumber audioURL
4. Send file: senderNumber receiverNumber fileURL caption
5. Send video: senderNumber receiverNumber videoURL caption
6. Send card link: senderNumber receiverNumber title link description imageURL
7. Send diversion link: senderNumber receiverNumber title routeList
## Bulk Sending (comma-separated numbers)
1. Bulk send text: senderNumber receiver1,receiver2 message
2. Bulk send image: senderNumber receiver1,receiver2 imageURL caption
3. Bulk send audio: senderNumber receiver1,receiver2 audioURL
4. Bulk send file: senderNumber receiver1,receiver2 fileURL caption
5. Bulk send video: senderNumber receiver1,receiver2 videoURL caption
6. Bulk send card link: senderNumber receiver1,receiver2 title link description imageURL
## Task Management
1. Query task status: query task taskId
---
# Technical Advantages
- Zero-code natural language interaction
- Auto dependency installation
- Full task lifecycle tracking
- Stable single & bulk delivery
- Compatible with OpenClaw
- Local configuration persistence
- Secure & reliable execution
- Customizable send source (PC/Mobile/Cloud)
---
# Official Resources
API Documentation: https://doc.socialepoch.com/wa-scrm-open-api-doc/
```
FILE:scrm_api.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
import json
import time
import hashlib
import subprocess
# Windows encoding compatibility
try:
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
except Exception:
pass
import warnings
# Only suppress trivial warnings, reserve SSL security check
warnings.filterwarnings("ignore", category=DeprecationWarning)
API_BASE = "https://api.socialepoch.com"
TIMEOUT = 10
RETRY_TIMES = 2
# ==========================
# Cross-platform config path: Windows / Mac / Linux
# ==========================
if os.name == "nt":
# Windows
CONFIG_DIR = os.path.join(os.environ.get("USERPROFILE", ""), ".openclaw")
else:
# Mac / Linux
CONFIG_DIR = os.path.expanduser("~/.openclaw")
CONFIG_FILE = os.path.join(CONFIG_DIR, "scrm_config.json")
SUPPORTED_COMMANDS = {
"set_config", "help", "query_online_agents", "query_task",
"send_text", "send_img", "send_audio", "send_file", "send_video",
"send_card", "send_card_link", "send_flow_link",
"bulk_send", "bulk_send_img", "bulk_send_audio",
"bulk_send_file", "bulk_send_video", "bulk_send_card_link"
}
# ==========================
# Skip SIGINT handler for Windows compatibility
# ==========================
try:
import signal
signal.signal(signal.SIGINT, lambda *_: sys.exit(130))
except Exception:
pass
# ==========================
# Task status mapping (English)
# ==========================
STATUS_TEXT = {
0: "Pending Dispatch",
1: "Pending Send",
2: "Sending",
3: "Sent",
4: "Delivered",
5: "Read",
6: "Read & Replied",
7: "Read & No Reply",
-1: "Send Failed"
}
TASK_STATUS_TEXT = {
1: "Not Started",
2: "Pending",
3: "Bulk Processing",
4: "Stopped",
5: "Completed",
6: "Paused"
}
# ==========================
# Unified text cleaning to fix \n escaped to \\n
# ==========================
def clean_text(raw: str) -> str:
if not raw:
return ""
s = raw.replace("\\n", "\n").replace("\\r", "\r").replace("\\t", "\t")
return s.rstrip()
def output(code=200, message="", data=None):
print(json.dumps({"code": code, "message": message, "data": data}, ensure_ascii=False, indent=2))
sys.exit(0)
# ==========================
# Auto install dependencies (Cross-platform)
# ==========================
def install_deps():
try:
import requests
return
except ImportError:
pass
pip_args = [
sys.executable, "-m", "pip",
"install", "requests<2.32.0",
"--no-warn-script-location"
]
if os.name != "nt":
pip_args.extend(["--user", "--break-system-packages"])
try:
subprocess.check_call(
pip_args,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
except Exception:
output(-1, "Dependency installation failed, please check your Python environment")
try:
import requests
except ImportError:
output(-1, "Load requests failed, please install manually")
install_deps()
import requests
requests.packages.urllib3.disable_warnings()
# ==========================
# Config management
# ==========================
def load_config():
tid = os.environ.get("SOCIALEPOCH_TENANT_ID", "").strip()
key = os.environ.get("SOCIALEPOCH_API_KEY", "").strip()
source = os.environ.get("SOCIALEPOCH_SOURCE", "").strip()
env_cfg = {}
if tid and key:
env_cfg = {"TENANT_ID": tid, "API_KEY": key, "API_BASE": API_BASE}
if source:
env_cfg["SOURCE"] = source
if env_cfg:
final_cfg = env_cfg
else:
if not os.path.exists(CONFIG_FILE):
output(-1, "Config not found. Please run: python3 scrm_api.py set_config [TENANT_ID] [API_KEY] [SOURCE(optional,1=PC,2=Mobile,3=Cloud)]")
try:
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
cfg = json.load(f)
tid = cfg.get("TENANT_ID", "").strip()
key = cfg.get("API_KEY", "").strip()
if not tid or not key:
output(-1, "Config file incomplete")
final_cfg = {
"TENANT_ID": tid,
"API_KEY": key,
"API_BASE": API_BASE,
"SOURCE": cfg.get("SOURCE", "1")
}
except Exception:
output(-1, "Failed to read config file")
try:
final_cfg["SOURCE"] = str(final_cfg.get("SOURCE", "1"))[0]
except:
final_cfg["SOURCE"] = "1"
return final_cfg
def save_config(tid, key, source="1"):
# Load existing config first to keep old values
old_cfg = {}
if os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
old_cfg = json.load(f)
# Use new value if provided, otherwise keep old one
final_tid = tid.strip() if tid.strip() else old_cfg.get("TENANT_ID", "").strip()
final_key = key.strip() if key.strip() else old_cfg.get("API_KEY", "").strip()
final_source = source.strip() if source.strip() else old_cfg.get("SOURCE", "1").strip()
# Validation
if not final_tid or not final_key:
output(-1, "Usage: scrm_api.py set_config TENANT_ID API_KEY [SOURCE(optional,1=PC,2=Mobile,3=Cloud)]")
os.makedirs(CONFIG_DIR, exist_ok=True)
cfg = {
"TENANT_ID": final_tid,
"API_KEY": final_key,
"SOURCE": final_source
}
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
json.dump(cfg, f, indent=2)
output(200, "Config saved successfully")
# ==========================
# Signature generation
# ==========================
def make_sign(tenant_id, api_key):
ts = str(int(time.time() * 1000))
s = f"{tenant_id}{ts}{api_key}"
return ts, hashlib.md5(s.encode()).hexdigest()
# ==========================
# API request core
# ==========================
def request_api(path, body, method="POST"):
cfg = load_config()
ts, token = make_sign(cfg["TENANT_ID"], cfg["API_KEY"])
headers = {
"Content-Type": "application/json",
"tenant_id": cfg["TENANT_ID"],
"timestamp": ts,
"token": token
}
for _ in range(RETRY_TIMES + 1):
try:
if method == "POST":
r = requests.post(
cfg["API_BASE"] + path,
json=body,
headers=headers,
timeout=TIMEOUT,
verify=True
)
else:
r = requests.get(
cfg["API_BASE"] + path,
params=body,
headers=headers,
timeout=TIMEOUT,
verify=True
)
if r.status_code == 200:
return r.json()
except Exception:
time.sleep(1)
output(-1, "API request failed")
# ==========================
# Business functions
# ==========================
def query_online_agents(userName=""):
cfg = load_config()
return request_api("/group-dispatch-api/user/queryUserStatus", {
"userId": "",
"source": cfg.get("SOURCE", "1"),
"userName": userName.strip() if userName else ""
})
def send_text(send, to, text):
cfg = load_config()
safe_text = clean_text(text)
return request_api("/group-dispatch-api/gsTask/assign/soCreate", {
"name": "wa-text", "sendType": 1, "targetType": 1,
"sendWhatsApp": send, "friendWhatsApp": to,
"source": cfg.get("SOURCE", "1"),
"content": [{"type": 1, "text": safe_text, "sort": 0}]
})
def send_img(send, to, url, caption=""):
cfg = load_config()
safe_caption = clean_text(caption)
return request_api("/group-dispatch-api/gsTask/assign/soCreate", {
"name": "wa-img", "sendType": 1, "targetType": 1,
"sendWhatsApp": send, "friendWhatsApp": to,
"source": cfg.get("SOURCE", "1"),
"content": [{"type": 2, "url": url, "text": safe_caption, "sort": 0}]
})
def send_audio(send, to, url):
cfg = load_config()
return request_api("/group-dispatch-api/gsTask/assign/soCreate", {
"name": "wa-audio", "sendType": 1, "targetType": 1,
"sendWhatsApp": send, "friendWhatsApp": to,
"source": cfg.get("SOURCE", "1"),
"content": [{"type": 3, "url": url, "sort": 0}]
})
def send_file(send, to, url, caption=""):
cfg = load_config()
safe_caption = clean_text(caption)
return request_api("/group-dispatch-api/gsTask/assign/soCreate", {
"name": "wa-file", "sendType": 1, "targetType": 1,
"sendWhatsApp": send, "friendWhatsApp": to,
"source": cfg.get("SOURCE", "1"),
"content": [{"type": 4, "url": url, "text": safe_caption, "sort": 0}]
})
def send_video(send, to, url, caption=""):
cfg = load_config()
safe_caption = clean_text(caption)
return request_api("/group-dispatch-api/gsTask/assign/soCreate", {
"name": "wa-video", "sendType": 1, "targetType": 1,
"sendWhatsApp": send, "friendWhatsApp": to,
"source": cfg.get("SOURCE", "1"),
"content": [{"type": 5, "url": url, "text": safe_caption, "sort": 0}]
})
def send_card(send, to, card):
cfg = load_config()
safe_card = clean_text(card)
return request_api("/group-dispatch-api/gsTask/assign/soCreate", {
"name": "wa-card", "sendType": 1, "targetType": 1,
"sendWhatsApp": send, "friendWhatsApp": to,
"source": cfg.get("SOURCE", "1"),
"content": [{"type": 6, "text": safe_card, "sort": 0}]
})
def send_card_link(send, to, title, link, text="", img=""):
cfg = load_config()
safe_text = clean_text(text)
return request_api("/group-dispatch-api/gsTask/assign/soCreate", {
"name": "wa-clink", "sendType": 1, "targetType": 1,
"sendWhatsApp": send, "friendWhatsApp": to,
"source": cfg.get("SOURCE", "1"),
"content": [{"type": 10, "title": title, "text": safe_text, "link": link, "url": img, "sort": 0}]
})
def send_flow_link(send, to, title, route_list):
cfg = load_config()
return request_api("/group-dispatch-api/gsTask/assign/soCreate", {
"name": "wa-flink", "sendType": 1, "targetType": 1,
"sendWhatsApp": send, "friendWhatsApp": to,
"source": cfg.get("SOURCE", "1"),
"content": [{"type": 11, "title": title, "text": title, "routeType": 3, "routeList": route_list, "sort": 0}]
})
def query_task(task_id):
res = request_api("/group-dispatch-api/gsTask/queryExecuteStatus", {"taskId": task_id}, "GET")
data = res.get("data", {})
task_status = data.get("status")
if task_status in TASK_STATUS_TEXT:
data["status_text"] = TASK_STATUS_TEXT[task_status]
for item in data.get("info", []):
s = item.get("status")
if s in STATUS_TEXT:
item["status_text"] = STATUS_TEXT[s]
return res
# ==========================
# Bulk text message
# ==========================
def bulk_send(sendWhatsapp, friendList, text):
cfg = load_config()
sendInfos = []
for friend in friendList:
sendInfos.append({
"sendWhatsApp": sendWhatsapp,
"friendWhatsApp": friend.strip()
})
safe_text = clean_text(text)
content = [{
"type": 1,
"text": safe_text,
"sort": 0
}]
return request_api("/group-dispatch-api/gsTask/assign/moscCreate", {
"name": "bulk_send",
"sendType": 1,
"targetType": 1,
"source": cfg.get("SOURCE", "1"),
"sendInfos": sendInfos,
"content": content
})
# ==========================
# Bulk image message
# ==========================
def bulk_send_img(sendWhatsapp, friendList, url, caption=""):
cfg = load_config()
sendInfos = [{"sendWhatsApp": sendWhatsapp, "friendWhatsApp": f.strip()} for f in friendList]
safe_caption = clean_text(caption)
content = [{
"type": 2,
"url": url,
"text": safe_caption,
"sort": 0
}]
return request_api("/group-dispatch-api/gsTask/assign/moscCreate", {
"name": "bulk_img",
"sendType": 1,
"targetType": 1,
"source": cfg.get("SOURCE", "1"),
"sendInfos": sendInfos,
"content": content
})
# ==========================
# Bulk audio message
# ==========================
def bulk_send_audio(sendWhatsapp, friendList, url):
cfg = load_config()
sendInfos = [{"sendWhatsApp": sendWhatsapp, "friendWhatsApp": f.strip()} for f in friendList]
content = [{
"type": 3,
"url": url,
"sort": 0
}]
return request_api("/group-dispatch-api/gsTask/assign/moscCreate", {
"name": "bulk_audio",
"sendType": 1,
"targetType": 1,
"source": cfg.get("SOURCE", "1"),
"sendInfos": sendInfos,
"content": content
})
# ==========================
# Bulk file message
# ==========================
def bulk_send_file(sendWhatsapp, friendList, url, caption=""):
cfg = load_config()
sendInfos = [{"sendWhatsApp": sendWhatsapp, "friendWhatsApp": f.strip()} for f in friendList]
safe_caption = clean_text(caption)
content = [{
"type": 4,
"url": url,
"text": safe_caption,
"sort": 0
}]
return request_api("/group-dispatch-api/gsTask/assign/moscCreate", {
"name": "bulk_file",
"sendType": 1,
"targetType": 1,
"source": cfg.get("SOURCE", "1"),
"sendInfos": sendInfos,
"content": content
})
# ==========================
# Bulk video message
# ==========================
def bulk_send_video(sendWhatsapp, friendList, url, caption=""):
cfg = load_config()
sendInfos = [{"sendWhatsApp": sendWhatsapp, "friendWhatsApp": f.strip()} for f in friendList]
safe_caption = clean_text(caption)
content = [{
"type": 5,
"url": url,
"text": safe_caption,
"sort": 0
}]
return request_api("/group-dispatch-api/gsTask/assign/moscCreate", {
"name": "bulk_video",
"sendType": 1,
"targetType": 1,
"source": cfg.get("SOURCE", "1"),
"sendInfos": sendInfos,
"content": content
})
# ==========================
# Bulk link card
# ==========================
def bulk_send_card_link(sendWhatsapp, friendList, title, link, text="", img=""):
cfg = load_config()
sendInfos = [{"sendWhatsApp": sendWhatsapp, "friendWhatsApp": f.strip()} for f in friendList]
safe_text = clean_text(text)
content = [{
"type": 10,
"title": title,
"text": safe_text,
"link": link,
"url": img,
"sort": 0
}]
return request_api("/group-dispatch-api/gsTask/assign/moscCreate", {
"name": "bulk_card_link",
"sendType": 1,
"targetType": 1,
"source": cfg.get("SOURCE", "1"),
"sendInfos": sendInfos,
"content": content
})
# ==========================
# Entry
# ==========================
def main():
if len(sys.argv) < 2:
output(200, "Commands: help set_config query_online_agents query_task send_text send_img send_audio send_file send_video send_card send_card_link send_flow_link bulk series")
cmd = sys.argv[1]
args = sys.argv[2:]
if cmd not in SUPPORTED_COMMANDS:
output(-1, f"Unsupported command: {cmd}")
try:
res = {}
if cmd == "help":
output(200, "Available commands: set_config query_online_agents query_task send_text send_img send_audio send_file send_video send_card send_card_link send_flow_link bulk series")
elif cmd == "set_config":
tid = args[0] if len(args) >= 1 else ""
ak = args[1] if len(args) >= 2 else ""
source = args[2] if len(args) >= 3 else "1"
save_config(tid, ak, source)
elif cmd == "query_online_agents":
res = query_online_agents(args[0] if len(args) >= 1 else "")
elif cmd == "query_task":
res = query_task(args[0] if args else "")
elif cmd == "send_text":
res = send_text(args[0], args[1], " ".join(args[2:]))
elif cmd == "send_img":
res = send_img(args[0], args[1], args[2], " ".join(args[3:]))
elif cmd == "send_audio":
res = send_audio(args[0], args[1], args[2])
elif cmd == "send_file":
res = send_file(args[0], args[1], args[2], " ".join(args[3:]))
elif cmd == "send_video":
res = send_video(args[0], args[1], args[2], " ".join(args[3:]))
elif cmd == "send_card":
res = send_card(args[0], args[1], args[2])
elif cmd == "send_card_link":
text = " ".join(args[4:]) if len(args) >= 5 else ""
img = args[5] if len(args) >= 6 else ""
res = send_card_link(args[0], args[1], args[2], args[3], text, img)
elif cmd == "send_flow_link":
route_list = args[3] if len(args) >= 4 else [1]
res = send_flow_link(args[0], args[1], args[2], route_list)
elif cmd == "bulk_send":
send = args[0]
friendList = args[1].split(",")
text = " ".join(args[2:])
res = bulk_send(send, friendList, text)
elif cmd == "bulk_send_img":
send = args[0]
friendList = args[1].split(",")
url = args[2]
caption = " ".join(args[3:])
res = bulk_send_img(send, friendList, url, caption)
elif cmd == "bulk_send_audio":
send = args[0]
friendList = args[1].split(",")
url = args[2]
res = bulk_send_audio(send, friendList, url)
elif cmd == "bulk_send_file":
send = args[0]
friendList = args[1].split(",")
url = args[2]
caption = " ".join(args[3:])
res = bulk_send_file(send, friendList, url, caption)
elif cmd == "bulk_send_video":
send = args[0]
friendList = args[1].split(",")
url = args[2]
caption = " ".join(args[3:])
res = bulk_send_video(send, friendList, url, caption)
elif cmd == "bulk_send_card_link":
send = args[0]
friendList = args[1].split(",")
title = args[2]
link = args[3]
text = args[4] if len(args) >= 5 else ""
img = args[5] if len(args) >= 6 else ""
res = bulk_send_card_link(send, friendList, title, link, text, img)
else:
res = {"code": 200, "message": "Success", "data": None}
output(res.get("code", 200), res.get("message", "Success"), res.get("data"))
except Exception as e:
output(-1, f"Execution failed: {str(e)}")
if __name__ == "__main__":
main()
FILE:skill.json
{
"slug": "socialepoch-whatsapp-scrm",
"name": "SocialEpoch - WhatsApp SCRM API",
"version": "2.1.7",
"author": "SocialEpoch",
"description": "Officially integrated with SocialEpoch WhatsApp SCRM OpenAPI. Support one-on-one & bulk messaging, multi-account operation, customer auto-management and overseas automation. Includes text, image, audio, document, video, business card, link card and diversion link for global customer service, cross-border business & overseas marketing scenarios.",
"category": "Marketing/Customer Service/Overseas/AI Tools",
"tags": [
"SocialEpoch",
"socialepoch",
"SOCIALEPOCH",
"WhatsApp",
"whatsapp",
"WHATSAPP",
"wa",
"ws",
"scrm",
"customer-management",
"bulk-sending",
"mass-messaging",
"multi-account",
"message-push",
"marketing",
"customer-service",
"overseas",
"cross-border",
"global",
"automation",
"link-card",
"media-message"
],
"icon": "📱",
"permissions": ["network", "config"],
"configSchema": {
"SOCIALEPOCH_TENANT_ID": {
"type": "string",
"label": "Tenant ID",
"required": true,
"placeholder": "Enter your SocialEpoch Tenant ID"
},
"SOCIALEPOCH_API_KEY": {
"type": "string",
"label": "API Key",
"required": true,
"secret": true,
"placeholder": "Enter your SocialEpoch API Key"
},
"SOCIALEPOCH_SOURCE": {
"type": "string",
"label": "Send Source (1=PC,2=Mobile,3=Cloud)",
"required": false,
"default": "1",
"placeholder": "1 (default)"
},
"mediaTips": {
"type": "string",
"label": "Media Usage Instructions",
"required": false,
"default": "All media files require public accessible URLs; Use English comma to separate multiple phone numbers for bulk sending.",
"readonly": true
}
},
"triggers": [
"SocialEpoch",
"socialepoch",
"WhatsApp",
"whatsapp",
"wa message",
"ws service",
"Query online accounts",
"Query online agent",
"Send text message",
"Send image",
"Send audio",
"Send document",
"Send video",
"Send business card",
"Send link card",
"Send diversion link",
"Query task status",
"Bulk text sending",
"Bulk image sending",
"Bulk audio sending",
"Bulk document sending",
"Bulk video sending",
"Bulk link card sending",
"WhatsApp customer service",
"WhatsApp bulk message",
"WhatsApp mass sending",
"Overseas customer marketing",
"Multi account message delivery"
],
"metadata": {
"requires": {
"config": ["SOCIALEPOCH_TENANT_ID", "SOCIALEPOCH_API_KEY", "SOCIALEPOCH_SOURCE"]
}
},
"autoLoad": true,
"docAutoImport": true,
"always": false,
"privacy": "none"
}官方原生对接 SocialEpoch 全域 WhatsApp SCRM 开放API,深度适配企业级海外营销与客服场景。全量覆盖WhatsApp账号管理、查询在线客服号(支持按账号精准查询)、客户运营、用户画像、聊天记录查询、WebHook回调推送,支持文字/图片/音频/视频/文件/名片/名片超链/分流超链全类型消...
---
name: socialepoch-wa-scrm
description: 官方原生对接 SocialEpoch 全域 WhatsApp SCRM 开放API,深度适配企业级海外营销与客服场景。全量覆盖WhatsApp账号管理、查询在线客服号(支持按账号精准查询)、客户运营、用户画像、聊天记录查询、WebHook回调推送,支持文字/图片/音频/视频/文件/名片/名片超链/分流超链全类型消息单发与批量群发,内置自动签名、智能依赖适配、免环境配置,支持PC/手机/云端三端发送标识,一键快速调用,稳定高效赋能海外私域与自动化运营。
version: 2.1.8
author: SocialEpoch
metadata:
emoji: 📱
type: tool
platform: darwin
openclaw:
requires:
bins: ["python3"]
env: ["SOCIALEPOCH_TENANT_ID", "SOCIALEPOCH_API_KEY", "SOCIALEPOCH_SOURCE"]
primaryEnv: SOCIALEPOCH_API_KEY
install:
- id: python-brew
kind: brew
formula: python
bins: ["python3"]
label: Install Python 3 (brew)
launcher:
command: "PYTHON"
args:
- "scrm_api.py"
workingDir: "SKILL_ROOT"
python: true
auto_bootstrap: true
auto_install_python: true
---
# SocialEpoch WhatsApp SCRM 智能助手
全面管理 WhatsApp 客服账号,支持单发和批量发送发送文字、图片、音频、视频、文件、名片、超链等消息,支持查询全部在线客服或指定坐席账号,自动签名。无需手动安装依赖、无需提前配置环境,首次调用/无配置会自动提示并引导设置。
## 核心特性
- 🚀 跨平台兼容:Mac 自适应
- 📦 自动依赖:智能检测并安装 requests
- 🔧 自动配置:环境变量 + 配置文件双模式
- ✅ 严格校验:参数格式/非空/类型全校验
- 📊 结构化输出:所有返回值标准化 JSON
- 🎯 发送端标识:1=PC(默认) 2=手机 3=云端
- 🔍 精准查询:支持按 userName 查询指定坐席
## 发送端说明
- 1 = PC 端(默认)
- 2 = 手机端
- 3 = 云端
## 环境配置(二选一)
### 方式 1:环境变量(推荐)
```bash
export SOCIALEPOCH_TENANT_ID="你的tenant_id"
export SOCIALEPOCH_API_KEY="你的api_key"
export SOCIALEPOCH_SOURCE="1"
```
### 方式 2:命令行配置
```bash
# 默认使用PC端发送
python3 scrm_api.py set_config 你的tenant_id 你的API_KEY
# 指定发送端 1/2/3
python3 scrm_api.py set_config 你的tenant_id 你的API_KEY 1
```
## 支持命令
### 查询在线客服号
```bash
# 查询全部在线客服
python3 scrm_api.py query_online_agents
# 查询指定坐席账号
python3 scrm_api.py query_online_agents "user001"
```
### 发送文字消息
```bash
python3 scrm_api.py send_text 发送号 接收号 "消息内容"
```
### 发送图片
```bash
python3 scrm_api.py send_img 发送号 接收号 "图片链接" "备注"
```
### 发送音频
```bash
python3 scrm_api.py send_audio 发送号 接收号 "音频链接"
```
### 发送文件
```bash
python3 scrm_api.py send_file 发送号 接收号 "文件链接" "备注"
```
### 发送视频
```bash
python3 scrm_api.py send_video 发送号 接收号 "视频链接" "备注"
```
### 发送名片
```bash
python3 scrm_api.py send_card 发送号 接收号 "名片内容"
```
### 发送名片超链
```bash
python3 scrm_api.py send_card_link 发送号 接收号 "标题" "链接" "描述" "封面图URL"
```
### 发送分流超链
```bash
python3 scrm_api.py send_flow_link 发送号 接收号 "标题" "分流列表号码"
```
### 查询任务状态
```bash
python3 scrm_api.py query_task 任务ID
```
---
## 批量发送
支持文字、图片、文件、视频、音频、名片超链批量发送,多个接收号码使用英文逗号「,」分隔。
## 运行规则
This skill provides secure API calling for WhatsApp SCRM.
Please confirm before sending messages.
# 系统级指令(AI强制遵守)
1. 此技能目录:openclaw/workspace/skills/socialepoch-wa-scrm
2. 执行命令:python3 scrm_api.py
3. 永远从技能目录执行,不寻找其他路径
## 错误处理
- 依赖缺失 → 自动安装
- 配置缺失 → 清晰提示
- 参数错误 → 自动提示正确用法
- 网络异常 → 自动重试
```
FILE:readme.md
---
# SocialEpoch WhatsApp SCRM 智能助手
🔥 官方原生 · 全域WhatsApp · 企业级SCRM自动化解决方案
依托 SocialEpoch 开放API,深度适配海外私域、跨境电商、企业客服场景,
全覆盖 WhatsApp 账号管理、消息推送、客户运营、任务管控,
支持**单发 + 批量群发**双模式,内置自动签名、依赖自检、免配置开箱即用。
支持 **PC/手机/云端三端发送标识**,可**按坐席账号精准查询在线状态**。
## 核心能力
### ✅ 全类型消息覆盖
文字 / 图片 / 音频 / 文件 / 视频 / 名片 / 名片超链 / 分流超链
### ✅ 配套SCRM能力
客服账号在线查询(支持全部/指定账号)、任务创建、消息状态追踪、任务进度查询、
自定义分流链路、营销短链推送、批量触达运营
### ✅ 发送端类型
1 = PC 端(默认)
2 = 手机端
3 = 云端
### ✅ 环境适配
国内/印尼双线路无缝切换,高可用API调度,稳定群发不限制
---
# 📦 第一步:安装 Python 运行环境(必做)
本工具支持 **Windows + Mac 全平台**,请先安装 Python 3.14.4 或以上版本。
## 1. Windows 安装(官方稳定版)
**下载地址:**
https://www.python.org/ftp/python/3.14.4/python-3.14.4-amd64.exe
**安装关键:**
✅ 必须勾选 **Add python.exe to PATH**
安装完成后,关闭所有命令行,重新打开即可生效。
## 2. Mac 安装(推荐方式)
### 方式 A:Homebrew 安装(最简单)
```bash
brew install python3
```
### 方式 B:官方安装包
https://www.python.org/ftp/python/3.14.4/python-3.14.4-macos11.pkg
## 验证环境是否正常
打开终端执行:
```bash
python3 --version
```
出现版本号即表示安装成功 ✅
---
# 🔧 第二步:配置租户信息(必做)
## 全平台统一配置命令
```bash
# 默认使用PC端发送
python3 scrm_api.py set_config 你的租户ID 你的API密钥
# 自定义发送端(1=PC,2=手机,3=云端)
python3 scrm_api.py set_config 你的租户ID 你的API密钥 1
```
## 示例
```bash
python3 scrm_api.py set_config 5333381 bab62b4722344b8de3e453f7b644b333
```
## 自然语言配置示例
设置配置 5333381 bab62b4722344b8de3e453f7b644b333
出现 **配置保存成功** 即完成 ✅
---
# 📌 统一使用规范
1. 所有 WhatsApp 账号必须携带**国家码**
2. 批量发送:多个号码使用**英文逗号**分割
3. 图片/音频/文件/视频仅支持**公网 URL**,最大 25MB
4. 支持纯口语化指令操作,无需记忆复杂命令
5. 任务提交后可通过**任务ID**查询实时进度(自动中文释义)
6. 发送端类型:1=PC(默认)、2=手机、3=云端
---
# 🚀 口语化实操指令(AI 强制记忆)
## 基础查询
1. 查询在线客服号
2. 查询指定客服账号:user001
## 单发消息示例
1. 发送文字:用【发送账号】发给【接收账号】自定义内容
2. 发送图片:用【发送账号】发给【接收账号】图片链接 备注文案
3. 发送音频:用【发送账号】发给【接收账号】音频链接
4. 发送文件:用【发送账号】发给【接收账号】文件链接 备注文案
5. 发送视频:用【发送账号】发给【接收账号】视频链接 备注文案
6. 发送名片超链:用【发送账号】发给【接收账号】标题 跳转链接 描述 封面图链接
7. 发送分流超链:用【发送账号】发给【接收账号】展示标题 分流列表
## 批量群发示例(逗号分割多账号)
1. 群发文字:用【发送账号】发给【账号1,账号2,账号3】群发内容
2. 群发图片:用【发送账号】给这几个人发图片 账号1,账号2 图片链接 备注
3. 群发音频:用【发送账号】发给【账号1,账号2】音频链接
4. 群发文件:用【发送账号】发给【账号1,账号2】文件链接 备注
5. 群发视频:用【发送账号】发给【账号1,账号2】视频链接 备注
6. 群发名片超链:用【发送账号】发给【账号1,账号2】标题 链接 描述 封面图
## 任务管理
1. 查询任务状态:查询任务 对应任务ID
---
# 技术优势
- 零代码调用,纯口语交互
- 自动依赖安装、环境自检、双重配置模式
- 任务全链路追踪,状态自动转中文展示
- 区分单发/群发逻辑,多账号自动隔离处理
- 适配 OpenClaw 架构,本地固化、永久记忆、无幻觉
- 支持三端发送标识 + 坐席精准查询
---
# 官方资源
API 文档:https://doc.socialepoch.com/wa-scrm-open-api-doc/
```
---
FILE:scrm_api.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
import json
import time
import hashlib
import subprocess
# Windows 编码崩溃防护
try:
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
except:
pass
# 禁用所有警告(解决SSL报错)
import warnings
warnings.filterwarnings("ignore")
API_BASE = "https://api.socialepoch.com"
TIMEOUT = 10
RETRY_TIMES = 2
# ==========================
# 【跨平台兼容】配置目录自动适配 Windows/Mac
# ==========================
if os.name == "nt": # Windows
CONFIG_DIR = os.path.join(os.environ.get("USERPROFILE", ""), ".openclaw")
else: # Mac/Linux
CONFIG_DIR = os.path.expanduser("~/.openclaw")
CONFIG_FILE = os.path.join(CONFIG_DIR, "scrm_config.json")
SUPPORTED_COMMANDS = {"set_config", "help", "query_online_agents", "query_task", "send_text", "send_img", "send_audio",
"send_file", "send_video", "send_card", "send_card_link", "send_flow_link", "bulk_send",
"bulk_send_img", "bulk_send_audio", "bulk_send_file", "bulk_send_video", "bulk_send_card_link"}
# ==========================
# 【跨平台兼容】Windows 不支持 SIGINT 优雅退出
# ==========================
try:
import signal
signal.signal(signal.SIGINT, lambda *_: sys.exit(130))
except:
pass
# ==========================
# 状态中文映射
# ==========================
STATUS_TEXT = {
0: "待下发",
1: "待发送",
2: "发送中",
3: "已发送",
4: "已到达",
5: "已读",
6: "已读已回",
7: "已读未回",
-1: "发送失败"
}
TASK_STATUS_TEXT = {
1: "待开始",
2: "待发送",
3: "群发中",
4: "已停止",
5: "已完成",
6: "已暂停"
}
def output(code=200, message="", data=None):
print(json.dumps({"code": code, "message": message, "data": data}, ensure_ascii=False, indent=2))
sys.exit(0)
# ==========================
# 全自动安装依赖(Windows + Mac 双兼容)
# ==========================
def install_deps():
try:
import requests
return
except ImportError:
pass
pip_args = [sys.executable, "-m", "pip", "install", "requests<2.32.0", "--no-warn-script-location"]
# Windows 不需要加 --user --break-system-packages
if os.name != "nt":
pip_args.extend(["--user", "--break-system-packages"])
try:
subprocess.check_call(
pip_args,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
except:
pass
try:
import requests
except:
output(-1, "依赖安装失败,请检查Python环境")
install_deps()
import requests
requests.packages.urllib3.disable_warnings()
# ==========================
# 配置管理
# ==========================
def load_config():
tid = os.environ.get("SOCIALEPOCH_TENANT_ID", "").strip()
key = os.environ.get("SOCIALEPOCH_API_KEY", "").strip()
source = os.environ.get("SOCIALEPOCH_SOURCE", "").strip()
env_cfg = {}
if tid and key:
env_cfg = {"TENANT_ID": tid, "API_KEY": key, "API_BASE": API_BASE}
if source:
env_cfg["SOURCE"] = source
if env_cfg:
final_cfg = env_cfg
else:
if not os.path.exists(CONFIG_FILE):
output(-1, "请先配置密钥:python3 scrm_api.py set_config tenant_id api_key [source]")
try:
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
cfg = json.load(f)
tid = cfg.get("TENANT_ID", "").strip()
key = cfg.get("API_KEY", "").strip()
if not tid or not key:
output(-1, "配置文件不完整")
final_cfg = {
"TENANT_ID": tid,
"API_KEY": key,
"API_BASE": API_BASE,
"SOURCE": cfg.get("SOURCE", "1")
}
except:
output(-1, "配置文件读取失败")
try:
final_cfg["SOURCE"] = str(final_cfg.get("SOURCE", "1"))[0]
except:
final_cfg["SOURCE"] = "1"
return final_cfg
def save_config(tid, key, source="1"):
# 先读取旧配置,不传的参数就用旧的
old_cfg = {}
if os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
old_cfg = json.load(f)
# 不传 = 保留原来的值
final_tid = tid.strip() if tid.strip() else old_cfg.get("TENANT_ID", "").strip()
final_key = key.strip() if key.strip() else old_cfg.get("API_KEY", "").strip()
final_source = source.strip() if source.strip() else old_cfg.get("SOURCE", "1").strip()
# 必须保证最终有值
if not final_tid or not final_key:
output(-1, "用法:scrm_api.py set_config 租户ID API密钥 [发送端类型(1/2/3)]")
os.makedirs(CONFIG_DIR, exist_ok=True)
cfg = {
"TENANT_ID": final_tid,
"API_KEY": final_key,
"SOURCE": final_source
}
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
json.dump(cfg, f, indent=2)
output(200, "配置保存成功")
# ==========================
# 签名
# ==========================
def make_sign(tenant_id, api_key):
ts = str(int(time.time() * 1000))
s = f"{tenant_id}{ts}{api_key}"
return ts, hashlib.md5(s.encode()).hexdigest()
# ==========================
# API 请求
# ==========================
def request_api(path, body, method="POST"):
cfg = load_config()
ts, token = make_sign(cfg["TENANT_ID"], cfg["API_KEY"])
headers = {
"Content-Type": "application/json",
"tenant_id": cfg["TENANT_ID"],
"timestamp": ts,
"token": token
}
for _ in range(RETRY_TIMES + 1):
try:
if method == "POST":
r = requests.post(
cfg["API_BASE"] + path,
json=body,
headers=headers,
timeout=TIMEOUT,
verify=True
)
else:
r = requests.get(
cfg["API_BASE"] + path,
params=body,
headers=headers,
timeout=TIMEOUT,
verify=True
)
if r.status_code == 200:
return r.json()
except:
time.sleep(1)
output(-1, "API请求失败")
# ==========================
# 业务功能
# ==========================
def query_online_agents(userName=""):
cfg = load_config()
return request_api("/group-dispatch-api/user/queryUserStatus", {
"userId": "",
"source": cfg.get("SOURCE", "1"),
"userName": userName.strip() if userName else ""
})
def send_text(send, to, text):
cfg = load_config()
return request_api("/group-dispatch-api/gsTask/assign/soCreate", {
"name": "wa-text", "sendType": 1, "targetType": 1,
"sendWhatsApp": send, "friendWhatsApp": to,
"source": cfg.get("SOURCE", "1"),
"content": [{"type": 1, "text": text, "sort": 0}]
})
def send_img(send, to, url, caption=""):
cfg = load_config()
return request_api("/group-dispatch-api/gsTask/assign/soCreate", {
"name": "wa-img", "sendType": 1, "targetType": 1,
"sendWhatsApp": send, "friendWhatsApp": to,
"source": cfg.get("SOURCE", "1"),
"content": [{"type": 2, "url": url, "text": caption, "sort": 0}]
})
def send_audio(send, to, url):
cfg = load_config()
return request_api("/group-dispatch-api/gsTask/assign/soCreate", {
"name": "wa-audio", "sendType": 1, "targetType": 1,
"sendWhatsApp": send, "friendWhatsApp": to,
"source": cfg.get("SOURCE", "1"),
"content": [{"type": 3, "url": url, "sort": 0}]
})
def send_file(send, to, url, caption=""):
cfg = load_config()
return request_api("/group-dispatch-api/gsTask/assign/soCreate", {
"name": "wa-file", "sendType": 1, "targetType": 1,
"sendWhatsApp": send, "friendWhatsApp": to,
"source": cfg.get("SOURCE", "1"),
"content": [{"type": 4, "url": url, "text": caption, "sort": 0}]
})
def send_video(send, to, url, caption=""):
cfg = load_config()
return request_api("/group-dispatch-api/gsTask/assign/soCreate", {
"name": "wa-video", "sendType": 1, "targetType": 1,
"sendWhatsApp": send, "friendWhatsApp": to,
"source": cfg.get("SOURCE", "1"),
"content": [{"type": 5, "url": url, "text": caption, "sort": 0}]
})
def send_card(send, to, card):
cfg = load_config()
return request_api("/group-dispatch-api/gsTask/assign/soCreate", {
"name": "wa-card", "sendType": 1, "targetType": 1,
"sendWhatsApp": send, "friendWhatsApp": to,
"source": cfg.get("SOURCE", "1"),
"content": [{"type": 6, "text": card, "sort": 0}]
})
def send_card_link(send, to, title, link, text="", img=""):
cfg = load_config()
return request_api("/group-dispatch-api/gsTask/assign/soCreate", {
"name": "wa-clink", "sendType": 1, "targetType": 1,
"sendWhatsApp": send, "friendWhatsApp": to,
"source": cfg.get("SOURCE", "1"),
"content": [{"type": 10, "title": title, "text": text, "link": link, "url": img, "sort": 0}]
})
def send_flow_link(send, to, title, route_list):
cfg = load_config()
return request_api("/group-dispatch-api/gsTask/assign/soCreate", {
"name": "wa-flink", "sendType": 1, "targetType": 1,
"sendWhatsApp": send, "friendWhatsApp": to,
"source": cfg.get("SOURCE", "1"),
"content": [{"type": 11, "title": title, "text": title, "routeType": 3, "routeList": route_list, "sort": 0}]
})
def query_task(task_id):
res = request_api("/group-dispatch-api/gsTask/queryExecuteStatus", {"taskId": task_id}, "GET")
# === 自动把数字状态转中文 ===
data = res.get("data", {})
task_status = data.get("status")
if task_status in TASK_STATUS_TEXT:
data["status_text"] = TASK_STATUS_TEXT[task_status]
for item in data.get("info", []):
s = item.get("status")
if s in STATUS_TEXT:
item["status_text"] = STATUS_TEXT[s]
# ======================================
return res
# ==========================
# 【真正群发】文字消息(多目标)
# ==========================
def bulk_send(sendWhatsapp, friendList, text):
cfg = load_config()
# 构建发送列表
sendInfos = []
for friend in friendList:
sendInfos.append({
"sendWhatsApp": sendWhatsapp,
"friendWhatsApp": friend.strip()
})
# 构建内容
content = [{
"type": 1,
"text": text,
"sort": 0
}]
# 群发接口(官方)
return request_api("/group-dispatch-api/gsTask/assign/moscCreate", {
"name": "bulk_send",
"sendType": 1,
"targetType": 1,
"source": cfg.get("SOURCE", "1"),
"sendInfos": sendInfos,
"content": content
})
# ==========================
# 【群发】图片消息
# ==========================
def bulk_send_img(sendWhatsapp, friendList, url, caption=""):
cfg = load_config()
sendInfos = [{"sendWhatsApp": sendWhatsapp, "friendWhatsApp": f.strip()} for f in friendList]
content = [{
"type": 2,
"url": url,
"text": caption,
"sort": 0
}]
return request_api("/group-dispatch-api/gsTask/assign/moscCreate", {
"name": "bulk_img",
"sendType": 1,
"targetType": 1,
"source": cfg.get("SOURCE", "1"),
"sendInfos": sendInfos,
"content": content
})
# ==========================
# 【群发】音频消息
# ==========================
def bulk_send_audio(sendWhatsapp, friendList, url):
cfg = load_config()
sendInfos = [{"sendWhatsApp": sendWhatsapp, "friendWhatsApp": f.strip()} for f in friendList]
content = [{
"type": 3,
"url": url,
"sort": 0
}]
return request_api("/group-dispatch-api/gsTask/assign/moscCreate", {
"name": "bulk_audio",
"sendType": 1,
"targetType": 1,
"source": cfg.get("SOURCE", "1"),
"sendInfos": sendInfos,
"content": content
})
# ==========================
# 【群发】文件消息
# ==========================
def bulk_send_file(sendWhatsapp, friendList, url, caption=""):
cfg = load_config()
sendInfos = [{"sendWhatsApp": sendWhatsapp, "friendWhatsApp": f.strip()} for f in friendList]
content = [{
"type": 4,
"url": url,
"text": caption,
"sort": 0
}]
return request_api("/group-dispatch-api/gsTask/assign/moscCreate", {
"name": "bulk_file",
"sendType": 1,
"targetType": 1,
"source": cfg.get("SOURCE", "1"),
"sendInfos": sendInfos,
"content": content
})
# ==========================
# 【群发】视频消息
# ==========================
def bulk_send_video(sendWhatsapp, friendList, url, caption=""):
cfg = load_config()
sendInfos = [{"sendWhatsApp": sendWhatsapp, "friendWhatsApp": f.strip()} for f in friendList]
content = [{
"type": 5,
"url": url,
"text": caption,
"sort": 0
}]
return request_api("/group-dispatch-api/gsTask/assign/moscCreate", {
"name": "bulk_video",
"sendType": 1,
"targetType": 1,
"source": cfg.get("SOURCE", "1"),
"sendInfos": sendInfos,
"content": content
})
# ==========================
# 【群发】名片超链
# ==========================
def bulk_send_card_link(sendWhatsapp, friendList, title, link, text="", img=""):
cfg = load_config()
sendInfos = [{"sendWhatsApp": sendWhatsapp, "friendWhatsApp": f.strip()} for f in friendList]
content = [{
"type": 10,
"title": title,
"text": text,
"link": link,
"url": img,
"sort": 0
}]
return request_api("/group-dispatch-api/gsTask/assign/moscCreate", {
"name": "bulk_clink",
"sendType": 1,
"targetType": 1,
"source": cfg.get("SOURCE", "1"),
"sendInfos": sendInfos,
"content": content
})
# ==========================
# 命令入口
# ==========================
def main():
if len(sys.argv) < 2:
output(200,
"支持命令:help set_config query_online_agents query_task send_text send_img send_audio send_file send_video send_card send_card_link send_flow_link bulk_send bulk_send_img bulk_send_audio bulk_send_file bulk_send_video bulk_send_card_link")
cmd = sys.argv[1]
args = sys.argv[2:]
if cmd not in SUPPORTED_COMMANDS:
output(-1, f"不支持命令:{cmd}")
try:
if cmd == "help":
output(200,
"命令:set_config query_online_agents query_task send_text send_img send_audio send_file send_video send_card send_card_link send_flow_link bulk_send bulk_send_img bulk_send_audio bulk_send_file bulk_send_video bulk_send_card_link")
elif cmd == "set_config":
tid = args[0] if len(args) >= 1 else ""
ak = args[1] if len(args) >= 2 else ""
source = args[2] if len(args) >= 3 else "1"
save_config(tid, ak, source)
elif cmd == "query_online_agents":
res = query_online_agents(args[0] if len(args) >= 1 else "")
elif cmd == "query_task":
res = query_task(args[0] if args else "")
elif cmd == "send_text":
res = send_text(args[0], args[1], " ".join(args[2:]))
elif cmd == "send_img":
res = send_img(args[0], args[1], args[2], " ".join(args[3:]))
elif cmd == "send_audio":
res = send_audio(args[0], args[1], args[2])
elif cmd == "send_file":
res = send_file(args[0], args[1], args[2], " ".join(args[3:]))
elif cmd == "send_video":
res = send_video(args[0], args[1], args[2], " ".join(args[3:]))
elif cmd == "send_card":
res = send_card(args[0], args[1], args[2])
elif cmd == "send_card_link":
# 参数:发送号 接收号 标题 链接 描述(可选) 封面图(可选)
text = " ".join(args[4:5]) if len(args) >= 5 else ""
img = args[5] if len(args) >= 6 else ""
res = send_card_link(args[0], args[1], args[2], args[3], text, img)
elif cmd == "send_flow_link":
# 格式:发送号 接收号 标题 分流号码列表
route_list = args[3] if len(args) >= 4 else [1]
res = send_flow_link(args[0], args[1], args[2], route_list)
elif cmd == "bulk_send":
# 格式:发送号 接收号1,接收号2 文字内容
send = args[0]
friendList = args[1].split(",") # 多个号码用逗号分隔
text = " ".join(args[2:])
res = bulk_send(send, friendList, text)
elif cmd == "bulk_send_img":
send = args[0]
friendList = args[1].split(",")
url = args[2]
caption = " ".join(args[3:])
res = bulk_send_img(send, friendList, url, caption)
elif cmd == "bulk_send_audio":
send = args[0]
friendList = args[1].split(",")
url = args[2]
res = bulk_send_audio(send, friendList, url)
elif cmd == "bulk_send_file":
send = args[0]
friendList = args[1].split(",")
url = args[2]
caption = " ".join(args[3:])
res = bulk_send_file(send, friendList, url, caption)
elif cmd == "bulk_send_video":
send = args[0]
friendList = args[1].split(",")
url = args[2]
caption = " ".join(args[3:])
res = bulk_send_video(send, friendList, url, caption)
elif cmd == "bulk_send_card_link":
send = args[0]
friendList = args[1].split(",")
title = args[2]
link = args[3]
text = args[4] if len(args) >= 5 else ""
img = args[5] if len(args) >= 6 else ""
res = bulk_send_card_link(send, friendList, title, link, text, img)
else:
res = {"code": 200, "message": "成功", "data": None}
output(res.get("code", 200), res.get("message", "成功"), res.get("data"))
except Exception as e:
output(-1, f"执行失败:{str(e)}")
if __name__ == "__main__":
main()
FILE:skill.json
{
"slug": "socialepoch-wa-scrm",
"name": "SocialEpoch - WhatsApp SCRM 智能助手",
"version": "2.1.8",
"author": "SocialEpoch",
"description": "官方原生对接 SocialEpoch WhatsApp SCRM API,零代码实现文字/图片/音频/文件/视频/名片/名片超链/分流超链全类型消息收发,支持单发/批量群发(多号码逗号分隔),自动依赖安装,自动环境配置,支持PC/手机/云端三端发送标识。",
"category": "营销/客服/海外/AI工具",
"tags": [
"socialepoch", "Socialepoch", "SocialEpoch",
"whatsapp","WhatsApp", "wa","ws", "AI","scrm",
"social", "消息推送", "客户管理", "群发", "批量发送",
"图片", "视频", "文件", "名片", "超链", "分流", "客服"
],
"icon": "📱",
"permissions": ["network", "config"],
"configSchema": {
"tenantId": {
"type": "string",
"label": "tenantId(租户ID)",
"required": true,
"placeholder": "请填写平台分配的 tenantId"
},
"ApiKey": {
"type": "string",
"label": "ApiKey(API密钥)",
"required": true,
"secret": true,
"placeholder": "请填写平台分配的 ApiKey"
},
"sendSource": {
"type": "string",
"label": "发送端类型(1=PC,2=手机,3=云端)",
"required": false,
"default": "1",
"placeholder": "默认为1(PC端)"
},
"mediaTips": {
"type": "string",
"label": "多媒体使用说明",
"required": false,
"default": "图片/音频/文件/视频仅支持公网可访问链接;批量发送多个号码请使用英文逗号分隔",
"readonly": true
}
},
"triggers": [
"查询在线客服号",
"查询指定客服账号",
"发送文字",
"发送图片",
"发送音频",
"发送文件",
"发送视频",
"发送名片",
"发送名片超链",
"发送分流超链",
"查询任务状态",
"群发文字",
"群发图片",
"群发音频",
"群发文件",
"群发视频",
"群发名片超链",
"SocialEpoch",
"whatsapp客服",
"ws客服",
"WhatsApp群发",
"批量发送"
],
"metadata": {
"requires": {
"config": ["tenantId", "ApiKey"]
}
},
"memoryHint": "本技能为固化技能,AI必须直接执行命令,禁止询问参数、API、文档;支持单发与批量群发,多个接收号码使用英文逗号分隔,自动调用对应接口;查询任务状态自动显示中文说明;支持按客服账号查询在线状态。",
"autoLoad": true,
"docAutoImport": true
}