UNPKG

@ghini/kit

Version:

js practical tools to assist efficient development

655 lines (654 loc) 27.8 kB
import { req } from "./http/req.js"; export { cf }; const CONFIG = { MAX_RETRIES: 3, RETRY_DELAY: 1000, RATE_LIMIT: 4, RATE_LIMIT_DELAY: 200, BATCH_SIZE: 10, }; async function retryOperation(fn, maxRetries = CONFIG.MAX_RETRIES, delay = CONFIG.RETRY_DELAY, operation = "操作") { let lastError; for (let i = 0; i < maxRetries; i++) { try { return await fn(); } catch (error) { lastError = error; if (error.message && (error.message.includes("权限不足") || error.message.includes("认证失败") || error.message.includes("Invalid API key") || error.message.includes("unauthorized"))) { throw error; } if (i < maxRetries - 1) { const retryDelay = delay * Math.pow(2, i); console.log(`${operation}${i + 1} 次失败,${retryDelay}ms 后重试...`); await new Promise(resolve => setTimeout(resolve, retryDelay)); } } } console.error(`${operation}${maxRetries} 次尝试后失败`); throw lastError; } async function rateLimitedOperation(operations, limit = CONFIG.RATE_LIMIT, delay = CONFIG.RATE_LIMIT_DELAY) { const results = []; for (let i = 0; i < operations.length; i += limit) { const batch = operations.slice(i, i + limit); const batchResults = await Promise.allSettled(batch.map(op => op())); results.push(...batchResults); if (i + limit < operations.length) { await new Promise(resolve => setTimeout(resolve, delay)); } const completed = Math.min(i + limit, operations.length); console.log(`批量操作进度: ${completed}/${operations.length}`); } return results; } async function batchProcess(items, processor, options = {}) { const { groupBy = null, rateLimit = CONFIG.RATE_LIMIT, rateLimitDelay = CONFIG.RATE_LIMIT_DELAY, operationName = "批量操作" } = options; let results; if (groupBy) { const grouped = {}; items.forEach((item, index) => { const key = groupBy(item); if (!grouped[key]) grouped[key] = []; grouped[key].push({ item, index }); }); results = new Array(items.length); await Promise.all(Object.values(grouped).map(async (group) => { for (const { item, index } of group) { try { results[index] = await processor(item); } catch (error) { results[index] = { success: false, error: error.message }; } } })); } else if (items.length > CONFIG.BATCH_SIZE) { const operations = items.map(item => () => processor(item)); const settledResults = await rateLimitedOperation(operations, rateLimit, rateLimitDelay); results = settledResults.map(result => result.status === 'fulfilled' ? result.value : { success: false, error: result.reason?.message || '未知错误' }); } else { results = await Promise.all(items.map(item => processor(item).catch(error => ({ success: false, error: error.message })))); } const successCount = results.filter(r => r.success !== false).length; const failCount = results.length - successCount; console.log(`\n📊 ${operationName}执行完成:`); console.log(` ✅ 成功: ${successCount} 条`); if (failCount > 0) { console.log(` ❌ 失败: ${failCount} 条`); } console.log(` 📋 总计: ${results.length} 条\n`); return results; } async function cf(obj) { const key = obj.key; const domain = obj.domain; const email = obj.email; let auth, headers = {}; if (email) { headers = { "X-Auth-Email": email, "X-Auth-Key": key, }; console.dev("使用Global API Key认证"); } else { auth = "Bearer " + key; console.dev("使用API Token认证"); } const zid = await getZoneId.bind({ domain, auth, headers })(); return { auth, headers, domain, zid, getZoneId, add, madd, set, mset, del, mdel, setSecurity, setByContent, msetByContent, setByContentForce, msetByContentForce, }; } async function setByContent(pre, oldContent, newContent, type = "A", ttl = 60) { const host = pre + "." + this.domain; try { if (!this.zid) { throw new Error(`无法获取Zone ID,请检查域名: ${this.domain}`); } console.log(`查找记录: ${host} ${type} ${oldContent}`); let res = await retryOperation(async () => { if (this.headers && Object.keys(this.headers).length > 0) { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/dns_records?type=${type}&name=${host}`, {}, this.headers); } else { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/dns_records?type=${type}&name=${host}`, { auth: this.auth }); } }, CONFIG.MAX_RETRIES, CONFIG.RETRY_DELAY, `查询记录 ${host}`); if (!res.data.success) { throw new Error(`查询记录失败: ${JSON.stringify(res.data.errors)}`); } const targetRecord = res.data.result.find(record => record.content === oldContent); if (!targetRecord) { console.log(`未找到内容为 ${oldContent} 的记录`); return { success: false, message: `未找到内容为 ${oldContent} 的记录`, action: 'not_found' }; } console.log(`找到目标记录ID: ${targetRecord.id}`); const updateData = { type: type, name: host, content: newContent, proxied: targetRecord.proxied || false, priority: targetRecord.priority || 10, ttl: ttl }; let updateRes = await retryOperation(async () => { if (this.headers && Object.keys(this.headers).length > 0) { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/dns_records/${targetRecord.id} put`, { json: updateData }, this.headers); } else { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/dns_records/${targetRecord.id} put`, { auth: this.auth, json: updateData }); } }, CONFIG.MAX_RETRIES, CONFIG.RETRY_DELAY, `更新记录 ${host}`); if (updateRes.data.success) { console.log(`✅ 成功更新: ${host} ${oldContent}${newContent}`); return { success: true, message: `已将 ${host}${oldContent} 更新为 ${newContent}`, record: updateRes.data.result, action: 'updated' }; } else { throw new Error(`更新记录失败: ${JSON.stringify(updateRes.data.errors)}`); } } catch (error) { console.error(`更新记录时出错:`, error.message); return { success: false, error: error.message }; } } async function msetByContent(updates) { return batchProcess(updates, async (update) => { const [pre, oldContent, newContent, type, ttl] = update; return this.setByContent(pre, oldContent, newContent, type, ttl); }, { groupBy: update => update[0], operationName: "批量内容更新" }); } async function setByContentForce(pre, oldContent, newContent, type = "A", ttl = 60) { const result = await this.setByContent(pre, oldContent, newContent, type, ttl); if (result.success) { return result; } if (result.action === 'not_found') { console.log(`未找到旧记录,强制添加新记录: ${pre}.${this.domain} ${newContent}`); try { const addResult = await add.bind({ auth: this.auth, headers: this.headers, zid: this.zid, })({ type: type, name: pre + "." + this.domain, content: newContent, proxied: false, priority: 10, ttl: ttl }); if (addResult.success) { console.log(`✅ 强制添加成功: ${pre}.${this.domain} ${newContent}`); return { success: true, message: `已为 ${pre}.${this.domain} 强制添加新记录 ${newContent}`, record: addResult.result, action: 'added' }; } else { throw new Error(`强制添加记录失败: ${JSON.stringify(addResult.errors)}`); } } catch (error) { console.error(`强制添加记录时出错:`, error.message); return { success: false, error: error.message }; } } return result; } async function msetByContentForce(updates) { return batchProcess(updates, async (update) => { const [pre, oldContent, newContent, type, ttl] = update; return this.setByContentForce(pre, oldContent, newContent, type, ttl); }, { groupBy: update => update[0], operationName: "批量强制更新" }); } async function getZoneId() { try { console.dev("获取Zone ID,域名:", this.domain); let res = await retryOperation(async () => { if (this.headers && Object.keys(this.headers).length > 0) { return await req(`https://api.cloudflare.com/client/v4/zones?name=${this.domain}`, {}, this.headers); } else { return await req(`https://api.cloudflare.com/client/v4/zones?name=${this.domain}`, { auth: this.auth }); } }, CONFIG.MAX_RETRIES, CONFIG.RETRY_DELAY, `获取Zone ID for ${this.domain}`); if (res.data.success && res.data.result.length > 0) { return res.data.result[0].id; } else { throw new Error("记录未找到或权限不足"); } } catch (error) { console.error("获取 Zone ID 失败:", error.message); return null; } } async function mset(arr) { return batchProcess(arr, async (item) => this.set(item), { groupBy: (item) => { if (Array.isArray(item)) { return item[0]; } else { const parts = item.split(' '); return parts[0]; } }, operationName: "批量设置记录" }); } async function set(str) { let pre, content, type, priority, ttl; if (Array.isArray(str)) { [pre, content, type, priority, ttl] = str; } else { let processedStr = ""; let inQuotes = false; for (let i = 0; i < str.length; i++) { const char = str.charAt(i); if (char === '"') { inQuotes = !inQuotes; processedStr += char; } else if (char === " ") { processedStr += inQuotes ? "{+}" : char; } else { processedStr += char; } } processedStr = processedStr.replace(/ +/g, " ").trim(); const parts = processedStr.split(" "); pre = parts[0]; if (parts[1] && parts[1].startsWith('"')) { let quoteContent = parts[1]; let contentEndIndex = 1; if (!parts[1].endsWith('"') || parts[1].length <= 1) { for (let i = 2; i < parts.length; i++) { quoteContent += " " + parts[i]; contentEndIndex = i; if (parts[i].endsWith('"')) break; } } content = '"' + quoteContent .substring(1, quoteContent.length - 1) .replace(/\{\+\}/g, " ") + '"'; type = parts[contentEndIndex + 1] || "A"; priority = parts[contentEndIndex + 2] || 10; ttl = parts[contentEndIndex + 3] || 60; } else { content = parts[1] || ""; type = parts[2] || "A"; priority = parts[3] || 10; ttl = parts[4] || 60; } } const host = pre + "." + this.domain; const recordTtl = ttl === "auto" || isNaN(parseInt(ttl)) ? 60 : parseInt(ttl) || 60; try { if (!this.zid) { throw new Error(`无法获取Zone ID,请检查域名: ${this.domain}`); } const recordsToAdd = []; if (type === "A" && content.includes(",")) { const ipList = [ ...new Set(content .split(",") .map((ip) => ip.trim()) .filter((ip) => ip !== "")), ]; ipList.forEach(ip => { recordsToAdd.push({ type: type, name: host, content: ip, proxied: false, priority: parseInt(priority) || 10, ttl: recordTtl }); }); } else { recordsToAdd.push({ type: type || "A", name: host, content, proxied: false, priority: parseInt(priority) || 10, ttl: recordTtl }); } let res = await retryOperation(async () => { if (this.headers && Object.keys(this.headers).length > 0) { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/dns_records?type=${type}&name=${host}`, {}, this.headers); } else { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/dns_records?type=${type}&name=${host}`, { auth: this.auth }); } }, CONFIG.MAX_RETRIES, CONFIG.RETRY_DELAY, `查询 ${host} 的现有记录`); const existingRecords = res.data.result || []; console.log(`准备添加 ${recordsToAdd.length} 条记录到 ${host}`); let addResults = []; if (recordsToAdd.length <= 5) { const addPromises = recordsToAdd.map(record => retryOperation(() => add.bind({ auth: this.auth, headers: this.headers, zid: this.zid, })(record), CONFIG.MAX_RETRIES, CONFIG.RETRY_DELAY, `添加记录 ${record.content}`)); const results = await Promise.allSettled(addPromises); let allSuccess = true; for (const result of results) { if (result.status === 'rejected' || !result.value?.success) { allSuccess = false; console.error(`添加记录失败:`, result.reason || result.value?.errors); } else { addResults.push(result.value); } } if (!allSuccess) { throw new Error(`部分记录添加失败,保留原有记录`); } } else { const addOperations = recordsToAdd.map(record => () => retryOperation(() => add.bind({ auth: this.auth, headers: this.headers, zid: this.zid, })(record), CONFIG.MAX_RETRIES, CONFIG.RETRY_DELAY, `添加记录 ${record.content}`)); const results = await rateLimitedOperation(addOperations, CONFIG.RATE_LIMIT, CONFIG.RATE_LIMIT_DELAY); let allSuccess = true; for (const result of results) { if (result.status === 'rejected' || !result.value?.success) { allSuccess = false; console.error(`添加记录失败:`, result.reason || result.value?.errors); } else { addResults.push(result.value); } } if (!allSuccess) { throw new Error(`部分记录添加失败,保留原有记录`); } } if (existingRecords.length > 0) { console.log(`新记录添加成功,开始删除 ${existingRecords.length} 条旧记录`); if (existingRecords.length <= 5) { const deletePromises = existingRecords.map((record) => retryOperation(async () => { if (this.headers && Object.keys(this.headers).length > 0) { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/dns_records/${record.id} delete`, {}, this.headers); } else { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/dns_records/${record.id} delete`, { auth: this.auth }); } }, CONFIG.MAX_RETRIES, CONFIG.RETRY_DELAY, `删除记录 ${record.id}`)); const deleteResults = await Promise.allSettled(deletePromises); deleteResults.forEach((result, index) => { if (result.status === 'rejected') { console.warn(`删除旧记录失败 (ID: ${existingRecords[index].id}):`, result.reason); } }); } else { const deleteOperations = existingRecords.map((record) => () => retryOperation(async () => { if (this.headers && Object.keys(this.headers).length > 0) { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/dns_records/${record.id} delete`, {}, this.headers); } else { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/dns_records/${record.id} delete`, { auth: this.auth }); } }, CONFIG.MAX_RETRIES, CONFIG.RETRY_DELAY, `删除记录 ${record.id}`)); const deleteResults = await rateLimitedOperation(deleteOperations, CONFIG.RATE_LIMIT, CONFIG.RATE_LIMIT_DELAY); deleteResults.forEach((result, index) => { if (result.status === 'rejected') { console.warn(`删除旧记录失败 (ID: ${existingRecords[index].id}):`, result.reason); } }); } } const message = recordsToAdd.length > 1 ? `已为 ${host} 添加 ${recordsToAdd.length}${type}记录` : `已更新 ${host} 的记录`; if (recordsToAdd.length > 1) { const ips = recordsToAdd.map(r => r.content).join(', '); console.log(`✅ 成功设置: ${host} ${type} → [${ips}]`); } else { const oldContents = existingRecords.map(r => r.content).join(', '); if (existingRecords.length > 0) { console.log(`✅ 成功更新: ${host} ${type} ${oldContents ? `[${oldContents}] → ` : ''}${content}`); } else { console.log(`✅ 成功添加: ${host} ${type} ${content}`); } } return { success: true, message }; } catch (error) { console.error(`操作 ${host} 时出错:`, error.message); return { success: false, error: error.message }; } } async function madd(arr) { return batchProcess(arr, async (item) => this.add(item), { groupBy: (item) => item.name, operationName: "批量添加记录" }); } async function add(json) { try { let res = await retryOperation(async () => { if (this.headers && Object.keys(this.headers).length > 0) { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/dns_records post`, { json }, this.headers); } else { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/dns_records post`, { auth: this.auth, json }); } }, CONFIG.MAX_RETRIES, CONFIG.RETRY_DELAY, `添加记录 ${json.name}`); if (res.data.success) { console.log(`✅ 成功添加: ${json.name} ${json.type} ${json.content}`); } return res.data; } catch (error) { console.error(`添加记录 ${json.name} 失败:`, error.message); throw error; } } async function mdel(arr) { return batchProcess(arr, async (pre) => this.del(pre), { groupBy: (pre) => pre, operationName: "批量删除记录" }); } async function del(pre) { try { const host = pre + "." + this.domain; let res = await retryOperation(async () => { if (this.headers && Object.keys(this.headers).length > 0) { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/dns_records?type=A&name=${host}`, {}, this.headers); } else { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/dns_records?type=A&name=${host}`, { auth: this.auth }); } }, CONFIG.MAX_RETRIES, CONFIG.RETRY_DELAY, `查询记录 ${host}`); const recordId = res.data.result[0]?.id; if (!recordId) { console.log(`记录 ${host} 不存在,跳过删除`); return { success: true, message: `记录 ${host} 不存在` }; } res = await retryOperation(async () => { if (this.headers && Object.keys(this.headers).length > 0) { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/dns_records/${recordId} delete`, {}, this.headers); } else { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/dns_records/${recordId} delete`, { auth: this.auth }); } }, CONFIG.MAX_RETRIES, CONFIG.RETRY_DELAY, `删除记录 ${host}`); console.log(`删除${host}: ${res.data.success ? "成功" : "失败"}`); return res.data; } catch (error) { console.error(`删除记录失败:`, error.message); throw error; } } async function setSecurity(options = {}) { try { const { description = "安全规则", expression = "", action = "managed_challenge", priority = 999, } = options; let existingRule = null; let listResponse = await retryOperation(async () => { if (this.headers && Object.keys(this.headers).length > 0) { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/firewall/rules`, {}, this.headers); } else { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/firewall/rules`, { auth: this.auth }); } }, CONFIG.MAX_RETRIES, CONFIG.RETRY_DELAY, "查询安全规则"); if (listResponse.data.success && listResponse.data.result.length > 0) { existingRule = listResponse.data.result.find((rule) => rule.description === description); } let response; if (existingRule) { console.log(`找到现有规则 "${description}",准备更新...`); const filterId = existingRule.filter.id; let filterUpdateResponse = await retryOperation(async () => { if (this.headers && Object.keys(this.headers).length > 0) { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/filters/${filterId} put`, { json: { expression: expression, paused: false } }, this.headers); } else { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/filters/${filterId} put`, { auth: this.auth, json: { expression: expression, paused: false } }); } }, CONFIG.MAX_RETRIES, CONFIG.RETRY_DELAY, "更新过滤器"); if (!filterUpdateResponse.data.success) { throw new Error(`更新过滤器失败: ${JSON.stringify(filterUpdateResponse.data.errors)}`); } response = await retryOperation(async () => { if (this.headers && Object.keys(this.headers).length > 0) { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/firewall/rules/${existingRule.id} put`, { json: { action: action, priority: priority, paused: false, description: description, filter: { id: filterId, }, }, }, this.headers); } else { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/firewall/rules/${existingRule.id} put`, { auth: this.auth, json: { action: action, priority: priority, paused: false, description: description, filter: { id: filterId, }, }, }); } }, CONFIG.MAX_RETRIES, CONFIG.RETRY_DELAY, "更新安全规则"); if (response.data.success) { console.log(`安全规则 "${description}" 更新成功!`); return response.data.result; } else { console.error("更新安全规则失败:", response.data.errors); throw new Error(JSON.stringify(response.data.errors)); } } else { console.log(`未找到安全规则 "${description}",准备创建...`); const requestBody = [ { filter: { expression: expression, paused: false, }, action: action, priority: priority, paused: false, description: description, }, ]; response = await retryOperation(async () => { if (this.headers && Object.keys(this.headers).length > 0) { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/firewall/rules post`, { json: requestBody }, this.headers); } else { return await req(`https://api.cloudflare.com/client/v4/zones/${this.zid}/firewall/rules post`, { auth: this.auth, json: requestBody }); } }, CONFIG.MAX_RETRIES, CONFIG.RETRY_DELAY, "创建安全规则"); if (response.data.success) { console.log(`安全规则 "${description}" 创建成功!`); return response.data.result[0]; } else { console.error("创建安全规则失败:", response.data.errors); throw new Error(JSON.stringify(response.data.errors)); } } } catch (error) { console.error("设置安全规则时出错:", error.message); throw error; } }