@insta-dev01/intclaw 1.0.11 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.en.md +424 -0
- package/README.md +365 -164
- package/index.ts +28 -0
- package/openclaw.plugin.json +10 -39
- package/package.json +69 -40
- package/src/channel.ts +557 -0
- package/src/config/accounts.ts +230 -0
- package/src/config/schema.ts +144 -0
- package/src/core/connection.ts +733 -0
- package/src/core/message-handler.ts +1268 -0
- package/src/core/provider.ts +106 -0
- package/src/core/state.ts +54 -0
- package/src/directory.ts +95 -0
- package/src/gateway-methods.ts +237 -0
- package/src/onboarding.ts +387 -0
- package/src/policy.ts +19 -0
- package/src/probe.ts +213 -0
- package/src/reply-dispatcher.ts +674 -0
- package/src/runtime.ts +7 -0
- package/src/sdk/helpers.ts +317 -0
- package/src/sdk/types.ts +515 -0
- package/src/secret-input.ts +19 -0
- package/src/services/media/audio.ts +54 -0
- package/src/services/media/chunk-upload.ts +293 -0
- package/src/services/media/common.ts +154 -0
- package/src/services/media/file.ts +70 -0
- package/src/services/media/image.ts +67 -0
- package/src/services/media/index.ts +10 -0
- package/src/services/media/video.ts +162 -0
- package/src/services/media.ts +1134 -0
- package/src/services/messaging/index.ts +16 -0
- package/src/services/messaging/send.ts +137 -0
- package/src/services/messaging.ts +800 -0
- package/src/targets.ts +45 -0
- package/src/types/index.ts +52 -0
- package/src/utils/agent.ts +63 -0
- package/src/utils/async.ts +51 -0
- package/src/utils/constants.ts +9 -0
- package/src/utils/http-client.ts +84 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/logger.ts +78 -0
- package/src/utils/session.ts +118 -0
- package/src/utils/token.ts +94 -0
- package/src/utils/utils-legacy.ts +506 -0
- package/.env.example +0 -11
- package/skills/intclaw_matrix/SKILL.md +0 -20
- package/src/channel/intclaw_channel.js +0 -155
- package/src/index.js +0 -23
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 视频处理模块
|
|
3
|
+
* 支持视频元数据提取、封面生成、视频消息发送
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { IntclawConfig } from '../../types/index.ts';
|
|
7
|
+
import { VIDEO_MARKER_PATTERN, toLocalPath, uploadMediaToIntClaw } from './common.ts';
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
|
|
10
|
+
/** 视频信息接口 */
|
|
11
|
+
export interface VideoInfo {
|
|
12
|
+
path: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 提取视频元数据(时长、分辨率)
|
|
17
|
+
*/
|
|
18
|
+
export async function extractVideoMetadata(
|
|
19
|
+
filePath: string,
|
|
20
|
+
log?: any,
|
|
21
|
+
): Promise<{ duration: number; width: number; height: number } | null> {
|
|
22
|
+
try {
|
|
23
|
+
const ffmpeg = require('fluent-ffmpeg');
|
|
24
|
+
const ffmpegPath = require('@ffmpeg-installer/ffmpeg').path;
|
|
25
|
+
const ffprobePath = require('@ffprobe-installer/ffprobe').path;
|
|
26
|
+
ffmpeg.setFfmpegPath(ffmpegPath);
|
|
27
|
+
ffmpeg.setFfprobePath(ffprobePath);
|
|
28
|
+
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
ffmpeg.ffprobe(filePath, (err: any, metadata: any) => {
|
|
31
|
+
if (err) {
|
|
32
|
+
log?.warn?.(`[IntClaw][Video] ffprobe 执行失败:${err.message}`);
|
|
33
|
+
resolve(null);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const duration = metadata.format?.duration ? Math.floor(parseFloat(metadata.format.duration)) : 0;
|
|
38
|
+
const videoStream = metadata.streams?.find((s: any) => s.codec_type === 'video');
|
|
39
|
+
const width = videoStream?.width || 0;
|
|
40
|
+
const height = videoStream?.height || 0;
|
|
41
|
+
resolve({ duration, width, height });
|
|
42
|
+
} catch (err) {
|
|
43
|
+
log?.warn?.(`[IntClaw][Video] 解析 ffprobe 输出失败`);
|
|
44
|
+
resolve(null);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
} catch (err: any) {
|
|
49
|
+
log?.warn?.(`[IntClaw][Video] 提取视频元数据失败:${err.message}`);
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 生成视频封面图(第 1 秒截图)
|
|
56
|
+
*/
|
|
57
|
+
export async function extractVideoThumbnail(
|
|
58
|
+
videoPath: string,
|
|
59
|
+
outputPath: string,
|
|
60
|
+
log?: any,
|
|
61
|
+
): Promise<string | null> {
|
|
62
|
+
try {
|
|
63
|
+
const ffmpeg = require('fluent-ffmpeg');
|
|
64
|
+
const ffmpegPath = require('@ffmpeg-installer/ffmpeg').path;
|
|
65
|
+
const path = await import('path');
|
|
66
|
+
ffmpeg.setFfmpegPath(ffmpegPath);
|
|
67
|
+
|
|
68
|
+
return new Promise((resolve) => {
|
|
69
|
+
ffmpeg(videoPath)
|
|
70
|
+
.screenshots({
|
|
71
|
+
count: 1,
|
|
72
|
+
folder: path.dirname(outputPath),
|
|
73
|
+
filename: path.basename(outputPath),
|
|
74
|
+
timemarks: ['1'],
|
|
75
|
+
size: '?x360',
|
|
76
|
+
})
|
|
77
|
+
.on('end', () => {
|
|
78
|
+
log?.info?.(`[IntClaw][Video] 封面生成成功:${outputPath}`);
|
|
79
|
+
resolve(outputPath);
|
|
80
|
+
})
|
|
81
|
+
.on('error', (err: any) => {
|
|
82
|
+
log?.error?.(`[IntClaw][Video] 封面生成失败:${err.message}`);
|
|
83
|
+
resolve(null);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
} catch (err: any) {
|
|
87
|
+
log?.error?.(`[IntClaw][Video] ffmpeg 失败:${err.message}`);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 提取视频标记并发送视频消息
|
|
94
|
+
*/
|
|
95
|
+
export async function processVideoMarkers(
|
|
96
|
+
content: string,
|
|
97
|
+
sessionWebhook: string,
|
|
98
|
+
config: IntclawConfig,
|
|
99
|
+
oapiToken: string | null,
|
|
100
|
+
log?: any,
|
|
101
|
+
useProactiveApi: boolean = false,
|
|
102
|
+
target?: any,
|
|
103
|
+
): Promise<string> {
|
|
104
|
+
const logPrefix = useProactiveApi ? '[IntClaw][Video][Proactive]' : '[IntClaw][Video]';
|
|
105
|
+
|
|
106
|
+
if (!oapiToken) {
|
|
107
|
+
log?.warn?.(`${logPrefix} 无 oapiToken,跳过视频处理`);
|
|
108
|
+
return content;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const matches = [...content.matchAll(VIDEO_MARKER_PATTERN)];
|
|
112
|
+
if (matches.length === 0) {
|
|
113
|
+
log?.info?.(`${logPrefix} 未检测到视频标记,跳过处理`);
|
|
114
|
+
return content;
|
|
115
|
+
}
|
|
116
|
+
const videoInfos: VideoInfo[] = [];
|
|
117
|
+
const invalidVideos: string[] = [];
|
|
118
|
+
|
|
119
|
+
for (const match of matches) {
|
|
120
|
+
try {
|
|
121
|
+
const videoData = JSON.parse(match[1]);
|
|
122
|
+
const rawPath = videoData.path;
|
|
123
|
+
const absPath = toLocalPath(rawPath);
|
|
124
|
+
videoInfos.push({ path: absPath });
|
|
125
|
+
} catch (err) {
|
|
126
|
+
log?.warn?.(`${logPrefix} 解析视频标记失败:${match[1]}`);
|
|
127
|
+
invalidVideos.push(match[1]);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (videoInfos.length === 0) {
|
|
132
|
+
// 只有无效标记时,也要移除标记避免原样输出
|
|
133
|
+
if (invalidVideos.length > 0) {
|
|
134
|
+
log?.warn?.(`${logPrefix} 检测到无效视频标记,已忽略并移除`);
|
|
135
|
+
return content.replaceAll(VIDEO_MARKER_PATTERN, '').trim();
|
|
136
|
+
}
|
|
137
|
+
return content;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
log?.info?.(`${logPrefix} 检测到 ${videoInfos.length} 个视频,开始上传...`);
|
|
141
|
+
|
|
142
|
+
let result = content;
|
|
143
|
+
for (const match of matches) {
|
|
144
|
+
const full = match[0];
|
|
145
|
+
try {
|
|
146
|
+
const videoData = JSON.parse(match[1]);
|
|
147
|
+
const absPath = toLocalPath(videoData.path);
|
|
148
|
+
if (!fs.existsSync(absPath)) {
|
|
149
|
+
log?.warn?.(`${logPrefix} 视频文件不存在:${absPath}`);
|
|
150
|
+
result = result.replace(full, '⚠️ 视频文件不存在');
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const mediaId = await uploadMediaToIntClaw(absPath, 'video', oapiToken, 20 * 1024 * 1024, log);
|
|
154
|
+
result = result.replace(full, mediaId ? `[视频已上传:${mediaId}]` : '⚠️ 视频上传失败');
|
|
155
|
+
} catch {
|
|
156
|
+
log?.warn?.(`${logPrefix} 解析视频标记失败:${match[1]}`);
|
|
157
|
+
result = result.replace(full, '');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return result;
|
|
162
|
+
}
|