This repository has been archived on 2026-08-07. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
openclaw/cron/scripts/clear-calendar.mjs
T

139 lines
3.6 KiB
JavaScript

#!/usr/bin/env node
/**
* 清空 CalDAV 日历事件
*/
import https from 'https';
const CONFIG = {
url: 'https://micecola.top:5001/caldav/zhangqing/home/',
user: 'zhangqing',
password: 'MiceCola1265'
};
function log(level, message) {
const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19);
console.log(`${timestamp} - ${level} - ${message}`);
}
async function request(method, path = '', body = null) {
return new Promise((resolve, reject) => {
const url = new URL(CONFIG.url + path);
const options = {
hostname: url.hostname,
port: url.port || 443,
path: url.pathname,
method: method,
headers: {
'Authorization': 'Basic ' + Buffer.from(`${CONFIG.user}:${CONFIG.password}`).toString('base64'),
'Content-Type': 'application/xml; charset=utf-8',
'Depth': 'infinity'
},
rejectUnauthorized: false
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve({ status: res.statusCode, data, headers: res.headers });
} else {
resolve({ status: res.statusCode, data, headers: res.headers });
}
});
});
req.on('error', reject);
if (body) req.write(body);
req.end();
});
}
async function getAllEvents() {
// 使用 PROPFIND 获取日历内容
const response = await request('PROPFIND', '', `<?xml version="1.0" encoding="utf-8"?>
<d:propfind xmlns:d="DAV:">
<d:prop>
<d:resourcetype />
<d:getcontenttype />
</d:prop>
</d:propfind>`);
// 提取所有 .ics 文件的 href(可能在 <href> 或 <propstat><prop><href> 中)
const hrefs = [];
// 匹配所有 href,排除目录
const allHrefs = response.data.match(/<href>([^<]+)<\/href>/gi) || [];
for (const match of allHrefs) {
const href = match.replace(/<\/?href>/gi, '');
// 只保留 .ics 文件,排除目录
if (href.endsWith('.ics')) {
// 解码 URL 编码
const decodedHref = decodeURIComponent(href);
if (!hrefs.includes(decodedHref)) {
hrefs.push(decodedHref);
}
}
}
return hrefs;
}
async function deleteEvent(href) {
return new Promise((resolve, reject) => {
// href 是相对路径,需要用完整 URL
const baseUrl = new URL(CONFIG.url);
const fullPath = href.startsWith('/') ? href : '/' + href;
const options = {
hostname: baseUrl.hostname,
port: baseUrl.port || 443,
path: fullPath,
method: 'DELETE',
headers: {
'Authorization': 'Basic ' + Buffer.from(`${CONFIG.user}:${CONFIG.password}`).toString('base64')
},
rejectUnauthorized: false
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => resolve(res.statusCode));
});
req.on('error', reject);
req.end();
});
}
async function main() {
log('INFO', '获取日历事件列表...');
const hrefs = await getAllEvents();
log('INFO', `找到 ${hrefs.length} 个事件`);
let deleted = 0;
let failed = 0;
for (const href of hrefs) {
const status = await deleteEvent(href);
if (status >= 200 && status < 300) {
deleted++;
log('INFO', `已删除: ${href.split('/').pop()}`);
} else {
failed++;
log('ERROR', `删除失败 (${status}): ${href}`);
}
}
log('INFO', `完成: 删除 ${deleted} 个,失败 ${failed} 个`);
}
main().catch(e => {
log('ERROR', `程序异常: ${e.message}`);
process.exit(1);
});