添加完整的微信插件测试框架,包含: - 微信插件源码(weixin-plugin/) - TypeScript 类型定义(types.ts) - 插件编译脚本(scripts/build-weixin-plugin.sh) - 简化测试脚本(scripts/test-weixin-simple.js) - 完整测试脚本(scripts/test-weixin-plugin.js) - 项目配置和依赖管理(package.json) - 使用说明文档(README.md) 主要功能: - 支持二维码登录认证 - HTTP 长轮询消息接收 - 文本、图片、视频、文件消息收发 - 自动回复测试 - 运行状态统计 Generated with [Claude Code](https://claude.com/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
141 lines
3.8 KiB
TypeScript
141 lines
3.8 KiB
TypeScript
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import * as https from "node:https";
|
|
import * as http from "node:http";
|
|
|
|
const RUNTIME = "node";
|
|
const RUNTIME_VERSION = process.versions.node;
|
|
const HOSTNAME = os.hostname() || "unknown";
|
|
|
|
function resolveMainLogDir(): string {
|
|
const stateDir = process.env.OPENCLAW_STATE_DIR?.trim() ||
|
|
process.env.CLAWDBOT_STATE_DIR?.trim() ||
|
|
path.join(os.homedir(), ".openclaw");
|
|
return path.join(stateDir, "logs");
|
|
}
|
|
|
|
const MAIN_LOG_DIR = resolveMainLogDir();
|
|
const SUBSYSTEM = "originclawai/weixin";
|
|
|
|
const LEVEL_IDS: Record<string, number> = {
|
|
TRACE: 1,
|
|
DEBUG: 2,
|
|
INFO: 3,
|
|
WARN: 4,
|
|
ERROR: 5,
|
|
FATAL: 6,
|
|
};
|
|
|
|
const DEFAULT_LOG_LEVEL = "INFO";
|
|
|
|
function resolveMinLevel(): number {
|
|
const env = process.env.OPENCLAW_LOG_LEVEL?.toUpperCase() ||
|
|
process.env.LOG_LEVEL?.toUpperCase();
|
|
if (env && env in LEVEL_IDS) return LEVEL_IDS[env];
|
|
return LEVEL_IDS[DEFAULT_LOG_LEVEL];
|
|
}
|
|
|
|
let minLevelId = resolveMinLevel();
|
|
|
|
export function setLogLevel(level: string): void {
|
|
const upper = level.toUpperCase();
|
|
if (!(upper in LEVEL_IDS)) {
|
|
throw new Error(`Invalid log level: ${level}. Valid levels: ${Object.keys(LEVEL_IDS).join(", ")}`);
|
|
}
|
|
minLevelId = LEVEL_IDS[upper];
|
|
}
|
|
|
|
function toLocalISO(now: Date): string {
|
|
const offsetMs = -now.getTimezoneOffset() * 60_000;
|
|
const sign = offsetMs >= 0 ? "+" : "-";
|
|
const abs = Math.abs(now.getTimezoneOffset());
|
|
const offStr = `${sign}${String(Math.floor(abs / 60)).padStart(2, "0")}:${String(abs % 60).padStart(2, "0")}`;
|
|
return new Date(now.getTime() + offsetMs).toISOString().replace("Z", offStr);
|
|
}
|
|
|
|
function localDateKey(now: Date): string {
|
|
return toLocalISO(now).slice(0, 10);
|
|
}
|
|
|
|
function resolveMainLogPath(): string {
|
|
const dateKey = localDateKey(new Date());
|
|
return path.join(MAIN_LOG_DIR, `originclawai-${dateKey}.log`);
|
|
}
|
|
|
|
let logDirEnsured = false;
|
|
|
|
export type Logger = {
|
|
info(message: string): void;
|
|
debug(message: string): void;
|
|
warn(message: string): void;
|
|
error(message: string): void;
|
|
withAccount(accountId: string): Logger;
|
|
getLogFilePath(): string;
|
|
close(): void;
|
|
};
|
|
|
|
function buildLoggerName(accountId?: string): string {
|
|
return accountId ? `${SUBSYSTEM}/${accountId}` : SUBSYSTEM;
|
|
}
|
|
|
|
function writeLog(level: string, message: string, accountId?: string): void {
|
|
const levelId = LEVEL_IDS[level] ?? LEVEL_IDS.INFO;
|
|
if (levelId < minLevelId) return;
|
|
|
|
const now = new Date();
|
|
const loggerName = buildLoggerName(accountId);
|
|
const prefixedMessage = accountId ? `[${accountId}] ${message}` : message;
|
|
const entry = JSON.stringify({
|
|
"0": loggerName,
|
|
"1": prefixedMessage,
|
|
_meta: {
|
|
runtime: RUNTIME,
|
|
runtimeVersion: RUNTIME_VERSION,
|
|
hostname: HOSTNAME,
|
|
name: loggerName,
|
|
parentNames: ["originclawai"],
|
|
date: now.toISOString(),
|
|
logLevelId: LEVEL_IDS[level] ?? LEVEL_IDS.INFO,
|
|
logLevelName: level,
|
|
},
|
|
time: toLocalISO(now),
|
|
});
|
|
try {
|
|
if (!logDirEnsured) {
|
|
fs.mkdirSync(MAIN_LOG_DIR, { recursive: true });
|
|
logDirEnsured = true;
|
|
}
|
|
fs.appendFileSync(resolveMainLogPath(), `${entry}\n`, "utf-8");
|
|
} catch {
|
|
// Best-effort
|
|
}
|
|
}
|
|
|
|
function createLogger(accountId?: string): Logger {
|
|
return {
|
|
info(message: string): void {
|
|
writeLog("INFO", message, accountId);
|
|
},
|
|
debug(message: string): void {
|
|
writeLog("DEBUG", message, accountId);
|
|
},
|
|
warn(message: string): void {
|
|
writeLog("WARN", message, accountId);
|
|
},
|
|
error(message: string): void {
|
|
writeLog("ERROR", message, accountId);
|
|
},
|
|
withAccount(id: string): Logger {
|
|
return createLogger(id);
|
|
},
|
|
getLogFilePath(): string {
|
|
return resolveMainLogPath();
|
|
},
|
|
close(): void {
|
|
// No-op
|
|
},
|
|
};
|
|
}
|
|
|
|
export const logger: Logger = createLogger(); |