Skill: jftech/jftech-open-pro-ai-smart-search (version 1.0.0)

--- name: jf-open-pro-ai-smart-search description: JF Tech Pro AI 智搜技能。根据语义内容（如"带帽子的人"、"车"、"狗"）搜索杰峰云存报警视频，获取匹配的视频片段列表。使用场景：智能视频检索、AI 事件搜索、语义化视频查找。 # 必需凭证声明 - 平台元数据 credentials: required: - name: JF_UUID type: string description: 杰峰开放平台用户唯一标识 source: https://open.jftech.com/ - name: JF_APPKEY type: string description: 杰峰开放平台应用 Key source: https://open.jftech.com/ - name: JF_APPSECRET type: string description: 杰峰开放平台应用密钥 source: https://open.jftech.com/ - name: JF_MOVECARD type: integer description: 签名算法偏移量 (0-9) source: https://open.jftech.com/ optional: - name: JF_SN type: string description: 设备序列号 - name: JF_USER type: string description: 用户 ID default: admin - name: JF_ENDPOINT type: string description: API 端点 default: api.jftechws.com # 网络端点声明 endpoints: - url: https://api.jftechws.com description: 杰峰官方 API (国际) - url: https://api-cn.jftech.com description: 杰峰官方 API (中国大陆) # 安全声明 security: credentials_required: true env_vars_only: true # 凭据仅通过环境变量读取 language: python # Python 脚本 network_access: - api.jftechws.com # 杰峰官方 API (国际) - api-cn.jftech.com # 杰峰官方 API (中国大陆) file_access: none # 不读取本地文件 --- # JF Open Pro AI Smart Search > **面向开发者杰峰 AI 智搜工具 (Python)** > > 根据语义内容搜索杰峰云存报警视频，获取匹配的视频片段列表及播放信息。 --- ## 🔒 安全说明 **凭据存储：仅支持环境变量** | 方式 | 支持 | 说明 | |------|------|------| | **环境变量** | ✅ 支持 | 推荐方式，避免凭据出现在进程列表或日志中 | | **命令行参数** | ❌ 不支持 | 避免凭据泄露风险 | | **配置文件** | ❌ 不支持 | 避免明文存储凭据 | **网络访问：** - ✅ 仅访问杰峰官方 API 端点 (`api.jftechws.com` / `api-cn.jftech.com`) - ❌ 不访问第三方服务 - ❌ 不读取本地文件系统 **脚本行为：** - ✅ 本地执行 Python 脚本（技能本身） - ✅ 仅向指定的杰峰 API 端点发起 HTTPS 请求 - ❌ 不执行外部命令 - ❌ 不读取敏感系统文件 --- ## 🚀 快速开始 ### 设置环境变量 ```bash export JF_UUID="your-uuid" # 开放平台用户唯一标识 export JF_APPKEY="your-appkey" # 开放平台应用 Key export JF_APPSECRET="your-appsecret" # 开放平台应用密钥 export JF_MOVECARD=5 # 签名算法偏移量 (0-9) export JF_SN="your-device-sn" # 设备序列号 export JF_USER="admin" # 用户 ID（可选，默认：admin） ``` ### 使用技能 ```bash # AI 智搜 - 搜索"人"相关的视频 python scripts/search_video.py --search "人" # AI 智搜 - 搜索"车"相关的视频 python scripts/search_video.py --search "车" # AI 智搜 - 搜索"狗"相关的视频 python scripts/search_video.py --search "狗" # AI 智搜 - 搜索"戴帽子的人" python scripts/search_video.py --search "戴帽子的人" # 获取云存回放地址（指定时间） python scripts/get_playback_url.py --start-time "2026-04-07 12:00:00" --stop-time "2026-04-07 12:45:00" # 完整流程：AI 智搜 + 播放地址（推荐） python scripts/ai_search_playback.py --search "人" --video-index 0 ``` --- ## 📋 环境变量 | 变量名 | 说明 | 必需 | 默认值 | |--------|------|------|--------| | `JF_UUID` | 开放平台用户唯一标识 | 是 | - | | `JF_APPKEY` | 开放平台应用 Key | 是 | - | | `JF_APPSECRET` | 开放平台应用密钥 | 是 | - | | `JF_MOVECARD` | 签名算法偏移量 (0-9)，用于时间戳偏移增加签名安全性 | 是 | - | | `JF_SN` | 设备序列号 | 是 | - | | `JF_USER` | 用户 ID | 否 | `admin` | | `JF_ENDPOINT` | API 端点 | 否 | `api.jftechws.com` | --- ## 🛠️ 功能 ### 1. AI 智搜视频 根据语义内容搜索 AI 标记的云存报警视频。 **支持的搜索类型：** | 搜索类型 | 示例查询 | 说明 | |----------|----------|------| | 人物 | "人"、"戴帽子的人"、"穿红色衣服的人" | 基于人形 + 属性检测 | | 车辆 | "车"、"白色轿车"、"卡车" | 基于车辆检测 | | 动物 | "狗"、"猫" | 基于动物检测 | | 行为 | "跑步的人"、"摔倒" | 基于行为分析 | **使用示例：** ```bash # 搜索"人"相关的视频 python scripts/search_video.py --search "人" # 搜索"车"相关的视频 python scripts/search_video.py --search "车" # 搜索"戴帽子的人" python scripts/search_video.py --search "戴帽子的人" ``` **返回字段说明：** | 字段 | 说明 | 示例 | |------|------|------| | `st` | 录像开始时间（秒） | 1703275200 | | `et` | 录像结束时间（秒） | 1703275260 | | `matchRate` | 匹配度（0-1） | 0.95 | | `queryTags` | 检测到的标签列表 | ["person", "hat"] | | `eventTime` | 事件触发时间 | "2024-12-23 10:00:00" | --- ### 2. 云存回放地址获取 获取云存报警视频回放/播放地址。 **使用示例：** ```bash # 指定时间范围获取回放地址 python scripts/get_playback_url.py --start-time "2026-04-07 12:00:00" --stop-time "2026-04-07 12:45:00" # 完整流程：AI 智搜 + 播放地址（推荐） python scripts/ai_search_playback.py --search "人" --video-index 0 ``` **工作流程：** ``` 1. AI 智搜搜索视频 ↓ 获取云存报警信息视频列表 ↓ 2. 选择目标视频 ↓ 提取 st（开始时间）和 et（结束时间） ↓ 3. 调用云存报警视频回放 API ↓ st 对应 startTime et 对应 stopTime ↓ 4. 获取播放链接 ``` --- ## 📖 使用场景示例 ### 场景 1: 搜索特定人员的活动记录 ```bash # 搜索"人"相关的视频 python scripts/search_video.py --search "人" # 查看返回结果，选择感兴趣的视频片段 # 使用返回的 st 和 et 获取回放地址 python scripts/get_playback_url.py --start-time "2026-04-07 12:00:00" --stop-time "2026-04-07 12:45:00" ``` ### 场景 2: 搜索车辆进出记录 ```bash # 搜索"车"相关的视频 python scripts/search_video.py --search "车" ``` ### 场景 3: 完整流程 - 搜索并播放 ```bash # 一步完成：搜索"人"并获取第一个视频的回放地址 python scripts/ai_search_playback.py --search "人" --video-index 0 ``` --- ## ⚠️ 错误处理 | 错误码 | 说明 | 解决方案 | |--------|------|----------| | `2000` | 成功 | - | | `12504` | 授权失败 - 设备未开通 AI 智搜套餐 | 登录开放平台为设备绑定 AI 智搜套餐卡 | | `10001` | 参数错误 | 检查请求参数格式 | | `10002` | 签名失败 | 检查 appKey/appSecret 和时间戳 | ### 错误码 12504 处理 **错误信息：** `authorize failed, Please check it in the open platform` **原因：** 设备未开通 AI 智搜服务，或未绑定套餐卡 **解决步骤：** 1. 登录杰峰开放平台：https://developer.jftech.com 2. 进入 **套餐管理** / **服务管理** 3. 找到 **AI 智搜** 或 **云存视频搜索** 套餐 4. 为设备购买并绑定套餐卡 5. 等待配置生效（通常 1-5 分钟） 6. 重新调用 API 测试 --- ## ⚠️ 注意事项 1. **设备需开通云存服务** - AI 智搜需要云存套餐支持 2. **设备需开通 AI 智搜套餐** - 需在开放平台绑定套餐卡 3. **时间范围** - 只能搜索云存有效期内的视频 4. **搜索精度** - 受 AI 算法识别精度影响 --- ## 📚 官方参考资料 - **杰峰开放平台**: https://open.jftech.com/ - **API 文档**: https://docs.jftech.com/ - **AI 智搜文档**: https://docs.jftech.com/docs?menusId=54582398fd8d4248962354e92ac2e47a&siderId=d2c0d9105d9c4b78bc0d2ee3851d2557 - **API 端点**: `api.jftechws.com` (国际) / `api-cn.jftech.com` (中国大陆) --- ## 📁 脚本工具 **可用脚本：** | 脚本 | 功能 | |------|------| | `search_video.py` | AI 智搜 - 搜索云存报警视频 | | `get_playback_url.py` | 获取云存回放地址（指定时间或完整流程） | | `ai_search_playback.py` | 完整流程 - AI 智搜 + 播放地址一键获取 | ```bash # 获取帮助 python scripts/search_video.py --help python scripts/get_playback_url.py --help python scripts/ai_search_playback.py --help # AI 智搜 python scripts/search_video.py --search <搜索内容> # 获取回放地址（指定时间） python scripts/get_playback_url.py --start-time "YYYY-MM-DD HH:MM:SS" --stop-time "YYYY-MM-DD HH:MM:SS" # 完整流程：AI 智搜 + 播放地址（推荐） python scripts/ai_search_playback.py --search <搜索内容> --video-index <索引> ``` 脚本路径：`scripts/search_video.py`, `scripts/get_playback_url.py`, `scripts/ai_search_playback.py` --- **技能版本：** v1.0.0 **语言：** Python **最后更新：** 2026-04-07

path: skill.yaml
content: # JF Open Pro AI Smart Search - Skill Registry Metadata
# This file defines the skill's requirements for ClawHub registry

name: jf-open-pro-ai-smart-search
version: 1.0.0
description: JF Tech Pro AI 智搜技能。根据语义内容（如"带帽子的人"、"车"、"狗"）搜索杰峰云存报警视频，获取匹配的视频片段列表。

# Runtime requirements
runtime:
  language: python
  minVersion: "3.8"

# Required environment variables (credentials)
requiredEnvVars:
  - name: JF_UUID
    description: 杰峰开放平台用户唯一标识
    source: https://open.jftech.com/
  - name: JF_APPKEY
    description: 杰峰开放平台应用 Key
    source: https://open.jftech.com/
  - name: JF_APPSECRET
    description: 杰峰开放平台应用密钥
    source: https://open.jftech.com/
  - name: JF_MOVECARD
    description: 签名算法偏移量 (0-9)，用于时间戳偏移增加签名安全性
    source: https://open.jftech.com/
  - name: JF_SN
    description: 设备序列号
    source: 杰峰设备机身标签或管理后台

# Optional environment variables
optionalEnvVars:
  - name: JF_USER
    description: 用户 ID
    default: admin
  - name: JF_ENDPOINT
    description: API 端点
    default: api.jftechws.com

# Network endpoints (for firewall/security configuration)
endpoints:
  - url: https://api.jftechws.com
    description: 杰峰官方 API (国际)
  - url: https://api-cn.jftech.com
    description: 杰峰官方 API (中国大陆)

# Security declarations
security:
  credentialsRequired: true
  envVarsOnly: true
  networkAccess:
    - api.jftechws.com
    - api-cn.jftech.com
  fileAccess: none

# Entry points
scripts:
  - name: search_video.py
    description: AI 智搜 - 搜索云存报警视频
    entryPoint: scripts/search_video.py
  - name: get_playback_url.py
    description: 获取云存回放地址
    entryPoint: scripts/get_playback_url.py
  - name: ai_search_playback.py
    description: 完整流程 - AI 智搜 + 播放地址
    entryPoint: scripts/ai_search_playback.py

# Tags for discovery
tags:
  - jf-tech
  - 杰峰
  - ai-search
  - video-search
  - cloud-storage
  - 云存搜索


path: scripts/ai_search_playback.py
content: #!/usr/bin/env python3
"""
AI 智搜 + 云存回放完整流程脚本

工作流程：
1. 调用 AI 智搜 API 获取视频列表
2. 选择指定索引的视频
3. 提取开始/结束时间
4. 调用云存回放 API 获取播放地址

用法:
    export JF_UUID="your-uuid"
    export JF_APPKEY="your-appkey"
    export JF_APPSECRET="your-appsecret"
    export JF_MOVECARD=5
    export JF_SN="your-device-sn"
    export JF_USER="admin"
    
    python ai_search_playback.py --search "人" --video-index 0
"""

import argparse
import hashlib
import json
import os
import sys
import time
from urllib.request import urlopen, Request
from urllib.error import URLError, HTTPError


def generate_jftech_sign(appkey: str, secret: str, timestamp: int) -> str:
    """生成 JF Tech API 签名"""
    sign_str = f"{appkey}{timestamp}{secret}"
    return hashlib.md5(sign_str.encode()).hexdigest()


def search_jftech(sn: str, user: str, query: str, uuid: str, appkey: str, 
                  secret: str, authorization: str = "") -> dict:
    """调用 JF Tech AI 智搜 API"""
    url = "https://api.jftechws.com/aisvr/v3/gateway/api/viewsearch/searchVideo"
    timestamp = int(time.time() * 1000)
    sign = generate_jftech_sign(appkey, secret, timestamp)
    
    headers = {
        "Content-Type": "application/json",
        "uuid": uuid,
        "appkey": appkey,
        "sign": sign,
        "timestamp": str(timestamp),
        "authorization": authorization
    }
    
    body = {
        "sn": sn,
        "user": user,
        "searchContent": query
    }
    
    req = Request(url, data=json.dumps(body).encode(), headers=headers, method="POST")
    
    try:
        with urlopen(req, timeout=30) as response:
            result = json.loads(response.read().decode())
            return result
    except HTTPError as e:
        return {"error": f"HTTP {e.code}", "message": e.read().decode()}
    except URLError as e:
        return {"error": "Network error", "message": str(e)}


def generate_timestamp(movecard=0):
    """
    生成当前时间戳（毫秒），可选加上 movecard 偏移量
    
    Args:
        movecard: 签名算法偏移量 (0-9)，用于增加签名安全性
    """
    return str(int(time.time() * 1000) + movecard)


def generate_signature(uuid, app_key, app_secret, time_millis):
    """生成杰峰 API 签名"""
    sign_str = uuid + app_key + time_millis + app_secret
    return hashlib.md5(sign_str.encode('utf-8')).hexdigest()


def get_device_token(sn, uuid, app_key, app_secret, movecard=0, endpoint="api.jftechws.com"):
    """通过设备序列号生成 deviceToken"""
    time_millis = generate_timestamp(movecard)
    signature = generate_signature(uuid, app_key, app_secret, time_millis)
    
    url = f"https://{endpoint}/gwp/v3/rtc/device/token"
    
    headers = {
        "uuid": uuid,
        "appKey": app_key,
        "timeMillis": time_millis,
        "signature": signature,
        "Content-Type": "application/json"
    }
    
    body = {"deviceSnList": [sn]}
    
    req = Request(url, data=json.dumps(body).encode('utf-8'), headers=headers, method="POST")
    
    try:
        with urlopen(req, timeout=30) as response:
            result = json.loads(response.read().decode('utf-8'))
            return result
    except HTTPError as e:
        return {"error": f"HTTP {e.code}", "message": e.read().decode()}
    except URLError as e:
        return {"error": "Network error", "message": str(e)}


def get_playback_url(sn, user, start_time, stop_time, uuid, app_key, app_secret, movecard=0,
                     endpoint="api.jftechws.com", stream_type="hls"):
    """获取云存回放地址"""
    token_result = get_device_token(sn, uuid, app_key, app_secret, movecard, endpoint)
    
    if token_result.get('error') or token_result.get('code') != 2000:
        return {"error": f"获取 Token 失败：{token_result.get('error') or token_result.get('msg')}"}
    
    if not token_result.get('data') or len(token_result['data']) == 0:
        return {"error": "获取 Token 失败：返回数据为空"}
    
    device_token = token_result['data'][0]['token']
    
    url = f"https://{endpoint}/gwp/v3/rtc/device/getVideoUrl/{device_token}"
    
    time_millis = generate_timestamp(movecard)
    signature = generate_signature(uuid, app_key, app_secret, time_millis)
    
    headers = {
        "uuid": uuid,
        "appKey": app_key,
        "timeMillis": time_millis,
        "signature": signature,
        "Content-Type": "application/json"
    }
    
    body = {
        "user": user,
        "sn": sn,
        "startTime": start_time,
        "stopTime": stop_time,
        "streamType": stream_type
    }
    
    req = Request(url, data=json.dumps(body).encode('utf-8'), headers=headers, method="POST")
    
    try:
        with urlopen(req, timeout=30) as response:
            result = json.loads(response.read().decode('utf-8'))
            return result
    except HTTPError as e:
        return {"error": f"HTTP {e.code}", "message": e.read().decode()}
    except URLError as e:
        return {"error": "Network error", "message": str(e)}


def get_config_from_env():
    """从环境变量读取配置"""
    required_vars = ['JF_UUID', 'JF_APPKEY', 'JF_APPSECRET', 'JF_MOVECARD', 'JF_SN']
    missing_vars = [var for var in required_vars if not os.environ.get(var)]
    
    if missing_vars:
        raise Exception(f"缺少必需的环境变量：{', '.join(missing_vars)}\n"
                       f"请设置：export JF_UUID='...' JF_APPKEY='...' JF_APPSECRET='...' JF_MOVECARD=5 JF_SN='...'")
    
    return {
        'uuid': os.environ.get('JF_UUID'),
        'appkey': os.environ.get('JF_APPKEY'),
        'appsecret': os.environ.get('JF_APPSECRET'),
        'movecard': int(os.environ.get('JF_MOVECARD', 5)),
        'sn': os.environ.get('JF_SN'),
        'user': os.environ.get('JF_USER', 'admin'),
        'endpoint': os.environ.get('JF_ENDPOINT', 'api.jftechws.com')
    }


def main():
    parser = argparse.ArgumentParser(
        description='AI 智搜 + 云存回放完整流程',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog='''
环境变量:
  JF_UUID       开放平台用户唯一标识 (必需)
  JF_APPKEY     开放平台应用 Key (必需)
  JF_APPSECRET  开放平台应用密钥 (必需)
  JF_MOVECARD   签名算法偏移量，通常设为 5 (必需)
  JF_SN         设备序列号 (必需)
  JF_USER       用户 ID，默认 admin (可选)
  JF_ENDPOINT   API 端点，默认 api.jftechws.com (可选)

示例:
  # 设置环境变量
  export JF_UUID="your-uuid"
  export JF_APPKEY="your-appkey"
  export JF_APPSECRET="your-appsecret"
  export JF_MOVECARD=5
  export JF_SN="your-device-sn"
  export JF_USER="admin"
  
  # 搜索"人"并获取第一个视频的回放地址
  python ai_search_playback.py --search "人" --video-index 0
  
  # 搜索"车"并获取第二个视频的回放地址
  python ai_search_playback.py --search "车" --video-index 1
        ''')
    
    parser.add_argument('--search', required=True, help='搜索内容（如"人"、"车"、"狗"）')
    parser.add_argument('--video-index', type=int, default=0, help='选择第几个视频（从 0 开始，默认 0）')
    
    args = parser.parse_args()
    
    # 从环境变量读取配置
    try:
        config = get_config_from_env()
    except Exception as e:
        print(f'❌ 配置错误：{e}')
        sys.exit(1)
    
    print('========================================')
    print('AI 智搜 + 云存回放完整流程')
    print('========================================')
    print(f"设备 SN: {config['sn']}")
    print(f"搜索内容：{args.search}")
    print(f"视频索引：{args.video_index}")
    print()
    
    # 步骤 1: AI 智搜
    print('>>> 步骤 1/3: AI 智搜搜索视频...')
    search_result = search_jftech(
        sn=config['sn'],
        user=config['user'],
        query=args.search,
        uuid=config['uuid'],
        appkey=config['appkey'],
        [REDACTED_SECRET]'appsecret']
    )
    
    if search_result.get('error'):
        print(f"❌ AI 智搜失败：{search_result['error']}")
        if search_result.get('message'):
            print(f"   详情：{search_result['message']}")
        sys.exit(1)
    
    if search_result.get('code') != 2000:
        print(f"❌ API 错误码：{search_result.get('code')}")
        print(f"   详情：{search_result.get('msg', 'Unknown error')}")
        sys.exit(1)
    
    data = search_result.get('data', {})
    videos = data.get('videos', [])
    
    if not videos:
        print('❌ 未找到匹配的视频')
        sys.exit(1)
    
    print(f"✅ 找到 {len(videos)} 个匹配的视频")
    
    if args.video_index >= len(videos):
        print(f"❌ 视频索引 {args.video_index} 超出范围 (0-{len(videos)-1})")
        sys.exit(1)
    
    video = videos[args.video_index]
    print(f"   选择：片段 {args.video_index + 1}")
    print(f"   时间：{video.get('eventTime', 'N/A')}")
    print(f"   匹配度：{video.get('matchRate', 0):.0%}")
    print()
    
    # 步骤 2: 提取时间
    start_time = video.get('st')
    stop_time = video.get('et')
    
    if not start_time or not stop_time:
        print('❌ 无法提取视频时间信息')
        sys.exit(1)
    
    # 转换时间戳为可读格式
    from datetime import datetime
    start_dt = datetime.fromtimestamp(start_time)
    stop_dt = datetime.fromtimestamp(stop_time)
    
    print('>>> 步骤 2/3: 提取视频时间...')
    print(f"   开始：{start_dt.strftime('%Y-%m-%d %H:%M:%S')} ({start_time})")
    print(f"   结束：{stop_dt.strftime('%Y-%m-%d %H:%M:%S')} ({stop_time})")
    print()
    
    # 步骤 3: 获取回放地址
    print('>>> 步骤 3/3: 获取云存回放地址...')
    playback_result = get_playback_url(
        sn=config['sn'],
        user=config['user'],
        start_time=start_time,
        stop_time=stop_time,
        uuid=config['uuid'],
        app_key=config['appkey'],
        app_secret=config['appsecret'],
        movecard=config['movecard'],
        endpoint=config['endpoint']
    )
    
    if playback_result.get('error'):
        print(f"❌ 获取回放地址失败：{playback_result['error']}")
        sys.exit(1)
    
    if playback_result.get('code') != 2000:
        print(f"❌ API 错误码：{playback_result.get('code')}")
        print(f"   详情：{playback_result.get('msg', 'Unknown error')}")
        sys.exit(1)
    
    playback_data = playback_result.get('data', {})
    playback_url = playback_data.get('url') or playback_data.get('playUrl')
    
    if not playback_url:
        print('❌ 未找到播放 URL')
        print(json.dumps(playback_result, indent=2, ensure_ascii=False))
        sys.exit(1)
    
    print('✅ 回放地址获取成功')
    print()
    print('========================================')
    print('播放信息')
    print('========================================')
    print(f"设备 SN: {config['sn']}")
    print(f"时间范围：{start_dt.strftime('%Y-%m-%d %H:%M:%S')} - {stop_dt.strftime('%Y-%m-%d %H:%M:%S')}")
    print(f"播放地址：{playback_url}")
    print()
    print("使用方式:")
    print(f"  - VLC 播放：vlc \"{playback_url}\"")
    print(f"  - 网页播放：在浏览器中打开 URL")
    print(f"  - 下载：curl -o video.mp4 \"{playback_url}\"")
    print('========================================')


if __name__ == "__main__":
    main()


path: scripts/get_playback_url.py
content: #!/usr/bin/env python3
"""
云存报警视频回放地址获取脚本

工作流程：
1. 先通过 AI 智搜获取云存报警信息视频列表
2. 提取录像开始时间（st 对应 startTime）和录像结束时间（et 对应 stopTime）
3. 通过设备序列号生成 deviceToken
4. 通过获取云存报警视频回放或下载地址接口，获取播放链接

官方文档：
https://docs.jftech.com/docs?menusId=54582398fd8d4248962354e92ac2e47a&siderId=2e08468f46564602d01ae8a244661672

API 端点：
1. 获取设备 Token: POST https://api.jftechws.com/gwp/v3/rtc/device/token
2. 云存回放：POST https://api.jftechws.com/gwp/v3/rtc/device/getVideoUrl/{deviceToken}

用法：
    # 设置环境变量（必需）
    export JF_UUID="your-uuid"
    export JF_APPKEY="your-appkey"
    export JF_APPSECRET="your-appsecret"
    export JF_MOVECARD=5              # 签名算法偏移量 (0-9)
    export JF_SN="your-device-sn"
    export JF_USER="admin"            # 可选，默认 admin
    export JF_ENDPOINT="api.jftechws.com"  # 可选
    
    # 完整流程：AI 智搜 + 播放地址
    python get_playback_url.py --search "人" --video-index 0
    
    # 直接获取指定时间的播放地址
    python get_playback_url.py --start-time "2026-03-28 15:23:26" --stop-time "2026-03-28 15:23:36"
"""

import argparse
import hashlib
import json
import os
import time
import sys
from urllib.request import urlopen, Request
from urllib.error import URLError, HTTPError


def generate_timestamp(movecard=0):
    """
    生成当前时间戳（毫秒），可选加上 movecard 偏移量
    
    Args:
        movecard: 签名算法偏移量 (0-9)，用于增加签名安全性
    """
    return str(int(time.time() * 1000) + movecard)


def generate_signature(uuid, app_key, app_secret, time_millis):
    """
    生成杰峰 API 签名
    
    签名算法：MD5(uuid + appKey + timeMillis + secret)
    
    Args:
        uuid: 开放平台用户 uuid
        app_key: 应用 appKey
        app_secret: 应用密钥
        time_millis: 时间戳（毫秒），已包含 movecard 偏移
    """
    sign_str = uuid + app_key + time_millis + app_secret
    return hashlib.md5(sign_str.encode('utf-8')).hexdigest()


def get_device_token(sn, uuid, app_key, app_secret, movecard=0, endpoint="api.jftechws.com"):
    """
    通过设备序列号生成 deviceToken
    
    API: POST https://api.jftechws.com/gwp/v3/rtc/device/token
    
    Args:
        sn: 设备序列号
        uuid: 开放平台用户 uuid
        app_key: 应用 appKey
        app_secret: 应用密钥
        movecard: 签名算法偏移量 (0-9)
        endpoint: API 端点
    
    Returns:
        dict: API 响应，包含 deviceToken
    """
    time_millis = generate_timestamp(movecard)
    signature = generate_signature(uuid, app_key, app_secret, time_millis)
    
    url = f"https://{endpoint}/gwp/v3/rtc/device/token"
    
    headers = {
        "uuid": uuid,
        "appKey": app_key,
        "timeMillis": time_millis,
        "signature": signature,
        "Content-Type": "application/json"
    }
    
    body = {"deviceSnList": [sn]}
    
    req = Request(url, data=json.dumps(body).encode('utf-8'), headers=headers, method="POST")
    
    try:
        with urlopen(req, timeout=30) as response:
            result = json.loads(response.read().decode('utf-8'))
            return result
    except HTTPError as e:
        return {"error": f"HTTP Error {e.code}: {e.reason}", "status": e.code}
    except URLError as e:
        return {"error": f"URL Error: {e.reason}"}
    except Exception as e:
        return {"error": str(e)}


def get_cloud_playback_url(device_token, sn, user, start_time, stop_time, 
                           uuid, app_key, app_secret, movecard=0,
                           channel=0, stream_type=1, endpoint="api.jftechws.com"):
    """
    获取云存报警视频回放或下载地址
    
    根据录像开始时间（st 对应 startTime）和录像结束时间（et 对应 stopTime）
    获取对应云存报警视频播放链接
    
    API: POST https://api.jftechws.com/gwp/v3/rtc/device/getVideoUrl/{deviceToken}
    
    官方文档：
    https://docs.jftech.com/docs?menusId=54582398fd8d4248962354e92ac2e47a&siderId=2e08468f46564602d01ae8a244661672
    
    Args:
        device_token: 设备 Token（从 get_device_token 获取）
        sn: 设备序列号
        user: 用户 ID
        start_time: 录像开始时间（格式：YYYY-MM-DD HH:MM:SS，对应 AI 智搜的 st 字段）
        stop_time: 录像结束时间（格式：YYYY-MM-DD HH:MM:SS，对应 AI 智搜的 et 字段）
        uuid: 开放平台用户 uuid
        app_key: 应用 appKey
        app_secret: 应用密钥
        movecard: 签名算法偏移量 (0-9)
        channel: 通道号（默认 0）
        stream_type: 码流类型（1=辅码流，2=主码流，默认 1）
        endpoint: API 端点
    
    Returns:
        dict: API 响应，包含播放地址
    """
    time_millis = generate_timestamp(movecard)
    signature = generate_signature(uuid, app_key, app_secret, time_millis)
    
    # 云存回放 API 端点
    url = f"https://{endpoint}/gwp/v3/rtc/device/getVideoUrl/{device_token}"
    
    headers = {
        "uuid": uuid,
        "appKey": app_key,
        "timeMillis": time_millis,
        "signature": signature,
        "Content-Type": "application/json"
    }
    
    # 请求体：startTime 对应 st，stopTime 对应 et
    body = {
        "sn": sn,
        "user": user,
        "startTime": start_time,
        "stopTime": stop_time,
        "channel": channel,
        "streamType": stream_type
    }
    
    req = Request(url, data=json.dumps(body).encode('utf-8'), headers=headers, method="POST")
    
    try:
        with urlopen(req, timeout=30) as response:
            result = json.loads(response.read().decode('utf-8'))
            return result
    except HTTPError as e:
        return {"error": f"HTTP Error {e.code}: {e.reason}", "status": e.code}
    except URLError as e:
        return {"error": f"URL Error: {e.reason}"}
    except Exception as e:
        return {"error": str(e)}


def ai_search(sn, user, search_content, uuid, app_key, app_secret, movecard=0, endpoint="api.jftechws.com"):
    """
    AI 智搜 - 搜索云存报警视频
    
    API: POST https://api.jftechws.com/aisvr/v3/gateway/api/viewsearch/searchVideo
    
    Args:
        sn: 设备序列号
        user: 用户 ID
        search_content: 搜索内容
        uuid: 开放平台用户 uuid
        app_key: 应用 appKey
        app_secret: 应用密钥
        movecard: 签名算法偏移量 (0-9)
        endpoint: API 端点
    
    Returns:
        dict: API 响应
    """
    time_millis = generate_timestamp(movecard)
    signature = generate_signature(uuid, app_key, app_secret, time_millis)
    
    url = f"https://{endpoint}/aisvr/v3/gateway/api/viewsearch/searchVideo"
    
    headers = {
        "uuid": uuid,
        "appKey": app_key,
        "timeMillis": time_millis,
        "signature": signature,
        "Content-Type": "application/json"
    }
    
    body = {
        "sn": sn,
        "user": user,
        "searchContent": search_content
    }
    
    req = Request(url, data=json.dumps(body).encode('utf-8'), headers=headers, method="POST")
    
    try:
        with urlopen(req, timeout=30) as response:
            result = json.loads(response.read().decode('utf-8'))
            return result
    except HTTPError as e:
        return {"error": f"HTTP Error {e.code}: {e.reason}"}
    except URLError as e:
        return {"error": f"URL Error: {e.reason}"}
    except Exception as e:
        return {"error": str(e)}


def ai_search_and_playback(sn, user, search_content, uuid, app_key, app_secret, movecard=0,
                           video_index=0, endpoint="api.jftechws.com"):
    """
    完整流程：AI 智搜 + 云存报警视频回放地址获取
    
    1. 通过搜索视频获取云存报警信息视频列表
    2. 提取录像开始时间（st 对应 startTime）和录像结束时间（et 对应 stopTime）
    3. 通过设备序列号生成 deviceToken
    4. 通过获取云存报警视频回放或下载地址接口，获取播放链接
    
    Args:
        sn: 设备序列号
        user: 用户 ID
        search_content: 搜索内容
        uuid: 开放平台用户 uuid
        app_key: 应用 appKey
        app_secret: 应用密钥
        movecard: 签名算法偏移量 (0-9)
        video_index: 选择第几个视频（从 0 开始）
        endpoint: API 端点
    
    Returns:
        dict: 包含搜索结果和播放地址
    """
    print("=" * 70)
    print("🎬 AI 智搜 + 云存报警视频回放地址获取")
    print("=" * 70)
    print()
    print(f"设备 SN: {sn}")
    print(f"用户：{user}")
    print(f"搜索内容：{search_content}")
    print(f"选择视频索引：{video_index}")
    print()
    
    # 步骤 1: AI 智搜 - 获取云存报警信息视频列表
    print(">>> 步骤 1: 搜索视频获取云存报警信息视频列表...")
    search_result = ai_search(sn, user, search_content, uuid, app_key, app_secret, movecard, endpoint)
    
    if search_result.get('error'):
        print(f"❌ AI 智搜失败：{search_result['error']}")
        return {"error": search_result['error']}
    
    if search_result.get('code') != 2000:
        print(f"❌ AI 智搜失败：{search_result.get('msg', 'Unknown error')}")
        return {"error": search_result.get('msg', 'Unknown error')}
    
    videos = search_result.get('data', [])
    if not videos:
        print("❌ 未找到匹配的视频")
        return {"error": "No videos found"}
    
    print(f"✅ AI 智搜成功，找到 {len(videos)} 条视频")
    print()
    
    # 选择指定索引的视频
    if video_index >= len(videos):
        print(f"❌ 视频索引 {video_index} 超出范围（0-{len(videos)-1}）")
        return {"error": f"Video index {video_index} out of range"}
    
    video = videos[video_index]
    print(f"📹 选择第 {video_index + 1} 个视频：")
    print(f"   录像开始时间（st）：{video['st']}")
    print(f"   录像结束时间（et）：{video['et']}")
    print(f"   匹配度：{video['matchRate']:.1%}")
    print(f"   文件名：{video['indx']}")
    print()
    
    # 步骤 2: 通过设备序列号生成 deviceToken
    print(">>> 步骤 2: 通过设备序列号生成 deviceToken...")
    token_result = get_device_token(sn, uuid, app_key, app_secret, movecard, endpoint)
    
    if token_result.get('error'):
        print(f"❌ 获取 deviceToken 失败：{token_result['error']}")
        return {"error": token_result['error'], "search_result": search_result}
    
    if token_result.get('code') != 2000:
        print(f"❌ 获取 deviceToken 失败：{token_result.get('msg', 'Unknown error')}")
        return {"error": token_result.get('msg', 'Unknown error'), "search_result": search_result}
    
    device_token = token_result['data'][0]['deviceToken']
    print(f"✅ deviceToken 获取成功：{device_token[:30]}...")
    print()
    
    # 步骤 3: 获取云存报警视频回放地址
    print(">>> 步骤 3: 获取云存报警视频回放地址...")
    print(f"   API 端点：POST /gwp/v3/rtc/device/getVideoUrl/{device_token[:30]}...")
    print(f"   startTime: {video['st']} (对应 st)")
    print(f"   stopTime: {video['et']} (对应 et)")
    print()
    
    playback_result = get_cloud_playback_url(
        device_token=device_token,
        sn=sn,
        user=user,
        start_time=video['st'],  # st 对应 startTime
        stop_time=video['et'],    # et 对应 stopTime
        uuid=uuid,
        app_key=app_key,
        app_secret=app_secret,
        endpoint=endpoint
    )
    
    if playback_result.get('error'):
        print(f"❌ 获取播放地址失败：{playback_result['error']}")
        return {"error": playback_result['error'], "search_result": search_result}
    
    if playback_result.get('code') != 2000:
        print(f"❌ 获取播放地址失败：{playback_result.get('msg', 'Unknown error')}")
        return {"error": playback_result.get('msg', 'Unknown error'), "search_result": search_result}
    
    # 成功获取播放地址
    play_url = playback_result['data'].get('url')
    print("✅ 云存报警视频播放地址获取成功！")
    print()
    print("=" * 70)
    print("🎬 播放地址")
    print("=" * 70)
    print()
    print(f"📹 视频信息：")
    print(f"   时间：{video['st']} - {video['et']}")
    print(f"   时长：10 秒")
    print(f"   匹配度：{video['matchRate']:.1%}")
    print(f"   文件名：{video['indx']}")
    print()
    print(f"🔗 播放地址：")
    print(f"   {play_url}")
    print()
    print("=" * 70)
    print("🎯 播放方式：")
    print("=" * 70)
    print()
    print("1. VLC 播放器：")
    print(f'   vlc "{play_url}"')
    print()
    print("2. 网页播放（HLS.js）：")
    print(f'   <video src="{play_url}" controls></video>')
    print()
    print("3. FFmpeg 下载：")
    print(f'   ffmpeg -i "{play_url}" -c copy video.mp4')
    print()
    
    return {
        "success": True,
        "search_result": search_result,
        "playback_result": playback_result,
        "video_info": video,
        "play_url": play_url
    }


def get_config_from_env():
    """从环境变量读取配置"""
    required_vars = ['JF_UUID', 'JF_APPKEY', 'JF_APPSECRET', 'JF_MOVECARD', 'JF_SN']
    missing_vars = [var for var in required_vars if not os.environ.get(var)]
    
    if missing_vars:
        raise Exception(f"缺少必需的环境变量：{', '.join(missing_vars)}\n"
                       f"请设置：export JF_UUID='...' JF_APPKEY='...' JF_APPSECRET='...' JF_MOVECARD=5 JF_SN='...'")
    
    return {
        'uuid': os.environ.get('JF_UUID'),
        'appkey': os.environ.get('JF_APPKEY'),
        'appsecret': os.environ.get('JF_APPSECRET'),
        'movecard': int(os.environ.get('JF_MOVECARD', 5)),
        'sn': os.environ.get('JF_SN'),
        'user': os.environ.get('JF_USER', 'admin'),
        'endpoint': os.environ.get('JF_ENDPOINT', 'api.jftechws.com')
    }


def main():
    parser = argparse.ArgumentParser(
        description='AI 智搜 + 云存报警视频回放地址获取',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog='''
环境变量:
  JF_UUID       开放平台用户唯一标识 (必需)
  JF_APPKEY     开放平台应用 Key (必需)
  JF_APPSECRET  开放平台应用密钥 (必需)
  JF_MOVECARD   签名算法偏移量，通常设为 5 (必需)
  JF_SN         设备序列号 (必需)
  JF_USER       用户 ID，默认 admin (可选)
  JF_ENDPOINT   API 端点，默认 api.jftechws.com (可选)

使用流程：
1. 通过搜索视频获取云存报警信息视频列表
2. 提取录像开始时间（st 对应 startTime）和录像结束时间（et 对应 stopTime）
3. 通过设备序列号生成 deviceToken
4. 通过获取云存报警视频回放或下载地址接口，获取播放链接

官方文档：
https://docs.jftech.com/docs?menusId=54582398fd8d4248962354e92ac2e47a&siderId=2e08468f46564602d01ae8a244661672

示例:
  # 设置环境变量
  export JF_UUID="your-uuid"
  export JF_APPKEY="your-appkey"
  export JF_APPSECRET="your-appsecret"
  export JF_MOVECARD=5
  export JF_SN="your-device-sn"
  export JF_USER="admin"
  
  # 完整流程：AI 智搜 + 播放地址
  python get_playback_url.py --search "人" --video-index 0
  
  # 直接获取指定时间的播放地址
  python get_playback_url.py --start-time "2026-04-07 12:00:00" --stop-time "2026-04-07 12:45:00"
        '''
    )
    
    parser.add_argument('--search', help='搜索内容（如"人"、"车"、"狗"）')
    parser.add_argument('--video-index', type=int, default=0, help='选择第几个视频（从 0 开始，默认 0）')
    parser.add_argument('--start-time', help='录像开始时间（格式：YYYY-MM-DD HH:MM:SS）')
    parser.add_argument('--stop-time', help='录像结束时间（格式：YYYY-MM-DD HH:MM:SS）')
    
    args = parser.parse_args()
    
    # 从环境变量读取配置
    try:
        config = get_config_from_env()
    except Exception as e:
        print(f'❌ 配置错误：{e}')
        sys.exit(1)
    
    # 如果有 search 参数，执行完整流程
    if args.search:
        result = ai_search_and_playback(
            sn=config['sn'],
            user=config['user'],
            search_content=args.search,
            uuid=config['uuid'],
            app_key=config['appkey'],
            app_secret=config['appsecret'],
            movecard=config['movecard'],
            video_index=args.video_index,
            endpoint=config['endpoint']
        )
    # 如果有 start_time 和 stop_time 参数，直接获取播放地址
    elif args.start_time and args.stop_time:
        print(">>> 通过设备序列号生成 deviceToken...")
        token_result = get_device_token(config['sn'], config['uuid'], config['appkey'], config['appsecret'], config['movecard'], config['endpoint'])
        
        if token_result.get('error') or token_result.get('code') != 2000:
            print(f"❌ 获取 deviceToken 失败：{token_result.get('error') or token_result.get('msg')}")
            sys.exit(1)
        
        device_token = token_result['data'][0]['deviceToken']
        print(f"✅ deviceToken 获取成功")
        
        print(">>> 获取云存报警视频回放地址...")
        playback_result = get_cloud_playback_url(
            device_token=device_token,
            sn=config['sn'],
            user=config['user'],
            start_time=args.start_time,
            stop_time=args.stop_time,
            uuid=config['uuid'],
            app_key=config['appkey'],
            app_secret=config['appsecret'],
            movecard=config['movecard'],
            endpoint=config['endpoint']
        )
        
        if playback_result.get('error') or playback_result.get('code') != 2000:
            print(f"❌ 获取播放地址失败：{playback_result.get('error') or playback_result.get('msg')}")
            sys.exit(1)
        
        play_url = playback_result['data'].get('url')
        print(f"✅ 播放地址：{play_url}")
        result = {"success": True, "play_url": play_url}
    else:
        parser.print_help()
        sys.exit(1)
    
    # 输出 JSON 结果
    print()
    print("=" * 70)
    print("📋 JSON 结果")
    print("=" * 70)
    print()
    print(json.dumps(result, indent=2, ensure_ascii=False))
    
    sys.exit(0 if result.get('success') else 1)


if __name__ == '__main__':
    main()


path: scripts/search_video.py
content: #!/usr/bin/env python3
"""
AI 智搜脚本 - 搜索云存报警视频

仅支持环境变量配置凭据，避免命令行泄露风险。

支持平台：JF Tech（杰峰）
用法:
    export JF_UUID="your-uuid"
    export JF_APPKEY="your-appkey"
    export JF_APPSECRET="your-appsecret"
    export JF_MOVECARD=5
    export JF_SN="your-device-sn"
    export JF_USER="admin"
    
    python search_video.py --search "人"
    python search_video.py --search "车"
    python search_video.py --search "戴帽子的人"
"""

import argparse
import hashlib
import json
import os
import time
import sys
from urllib.request import urlopen, Request
from urllib.error import URLError, HTTPError


def generate_jftech_sign(appkey: str, secret: str, timestamp: int, movecard: int = 0) -> str:
    """
    生成 JF Tech API 签名
    
    Args:
        appkey: 应用 appKey
        secret: 应用密钥
        timestamp: 时间戳（毫秒）
        movecard: 签名算法偏移量 (0-9)，用于增加签名安全性
    """
    # 时间戳加上 movecard 偏移量
    adjusted_timestamp = timestamp + movecard
    sign_str = f"{appkey}{adjusted_timestamp}{secret}"
    return hashlib.md5(sign_str.encode()).hexdigest()


def search_jftech(sn: str, user: str, query: str, uuid: str, appkey: str, 
                  secret: str, authorization: str, movecard: int = 0) -> dict:
    """
    调用 JF Tech AI 智搜 API
    
    Args:
        sn: 设备序列号
        user: 用户 ID
        query: 搜索内容（语义描述）
        uuid: 开放平台用户 uuid
        appkey: 应用 appKey
        secret: 应用密钥
        authorization: 用户 token
        movecard: 签名算法偏移量 (0-9)
    
    Returns:
        API 响应字典
    """
    url = "https://api.jftechws.com/aisvr/v3/gateway/api/viewsearch/searchVideo"
    timestamp = int(time.time() * 1000)
    sign = generate_jftech_sign(appkey, secret, timestamp, movecard)
    
    headers = {
        "Content-Type": "application/json",
        "uuid": uuid,
        "appkey": appkey,
        "sign": sign,
        "timestamp": str(timestamp),
        "authorization": authorization
    }
    
    body = {
        "sn": sn,
        "user": user,
        "searchContent": query
    }
    
    req = Request(url, data=json.dumps(body).encode(), headers=headers, method="POST")
    
    try:
        with urlopen(req, timeout=30) as response:
            result = json.loads(response.read().decode())
            return result
    except HTTPError as e:
        return {"error": f"HTTP {e.code}", "message": e.read().decode()}
    except URLError as e:
        return {"error": "Network error", "message": str(e)}


def format_results(results: dict) -> str:
    """格式化搜索结果输出"""
    if "error" in results:
        return f"❌ 错误：{results.get('error', 'Unknown')}\n{results.get('message', '')}"
    
    if results.get("code") != 2000:
        return f"❌ API 错误码：{results.get('code')}\n{results.get('msg', '')}"
    
    data = results.get("data", {})
    videos = data.get("videos", [])
    
    if not videos:
        return "📭 未找到匹配的视频"
    
    output = []
    output.append(f"✅ 找到 {len(videos)} 个匹配的视频片段\n")
    
    for i, video in enumerate(videos, 1):
        output.append(f"📹 片段 {i}:")
        output.append(f"   时间：{video.get('eventTime', 'N/A')}")
        output.append(f"   匹配度：{video.get('matchRate', 0):.0%}")
        output.append(f"   标签：{', '.join(video.get('queryTags', []))}")
        output.append(f"   大小：{video.get('vidsz', 0) / 1024:.1f} KB")
        if video.get('picfg') == 1:
            output.append(f"   缩略图：有")
        output.append("")
    
    return "\n".join(output)


def get_config_from_env():
    """从环境变量读取配置"""
    required_vars = ['JF_UUID', 'JF_APPKEY', 'JF_APPSECRET', 'JF_MOVECARD', 'JF_SN']
    missing_vars = [var for var in required_vars if not os.environ.get(var)]
    
    if missing_vars:
        raise Exception(f"缺少必需的环境变量：{', '.join(missing_vars)}\n"
                       f"请设置：export JF_UUID='...' JF_APPKEY='...' JF_APPSECRET='...' JF_MOVECARD=5 JF_SN='...'")
    
    return {
        'uuid': os.environ.get('JF_UUID'),
        'appkey': os.environ.get('JF_APPKEY'),
        'secret': os.environ.get('JF_APPSECRET'),
        'movecard': int(os.environ.get('JF_MOVECARD', 5)),
        'sn': os.environ.get('JF_SN'),
        'user': os.environ.get('JF_USER', 'admin'),
        'endpoint': os.environ.get('JF_ENDPOINT', 'api.jftechws.com')
    }


def main():
    parser = argparse.ArgumentParser(
        description="AI 智搜 - 搜索云存报警视频",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog='''
环境变量:
  JF_UUID       开放平台用户唯一标识 (必需)
  JF_APPKEY     开放平台应用 Key (必需)
  JF_APPSECRET  开放平台应用密钥 (必需)
  JF_MOVECARD   签名算法偏移量，通常设为 5 (必需)
  JF_SN         设备序列号 (必需)
  JF_USER       用户 ID，默认 admin (可选)
  JF_ENDPOINT   API 端点，默认 api.jftechws.com (可选)

示例:
  # 设置环境变量
  export JF_UUID="your-uuid"
  export JF_APPKEY="your-appkey"
  export JF_APPSECRET="your-appsecret"
  export JF_MOVECARD=5
  export JF_SN="your-device-sn"
  export JF_USER="admin"
  
  # 搜索"人"相关的视频
  python search_video.py --search "人"
  
  # 搜索"车"相关的视频
  python search_video.py --search "车"
  
  # 搜索"戴帽子的人"
  python search_video.py --search "戴帽子的人"
        ''')
    
    parser.add_argument("--search", dest="query", required=True, help="搜索内容（语义描述）")
    parser.add_argument("--json", action="store_true", help="输出 JSON 格式")
    
    args = parser.parse_args()
    
    try:
        config = get_config_from_env()
    except Exception as e:
        print(f'❌ 配置错误：{e}')
        sys.exit(1)
    
    result = search_jftech(
        sn=config['sn'],
        user=config['user'],
        query=args.query,
        uuid=config['uuid'],
        appkey=config['appkey'],
        [REDACTED_SECRET]'secret'],
        authorization='',  # 如需 authorization 可从环境变量添加
        movecard=config['movecard']
    )
    
    if args.json:
        print(json.dumps(result, indent=2, ensure_ascii=False))
    else:
        print(format_results(result))


if __name__ == "__main__":
    main()


path: .clawhub/origin.json
content: version: 1
registry: https://clawhub.ai
slug: jf-open-pro-ai-smart-search
installedVersion: 1.0.3
installedAt: 1775547600000

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