UNPKG

koishi-plugin-vv-bot

Version:

基于 https://github.com/Cicada000/VV 的VV表情包机器人

200 lines (198 loc) 7.07 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name2 in all) __defProp(target, name2, { get: all[name2], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { Config: () => Config, apply: () => apply, name: () => name }); module.exports = __toCommonJS(src_exports); var import_koishi = require("koishi"); var name = "vv-bot"; var Config = import_koishi.Schema.object({}); async function fetchData(url) { const response = await fetch(url); if (!response.ok) { throw new Error("Network response was not ok"); } const data = await response.text(); return data; } __name(fetchData, "fetchData"); function parseApiResults(apiText) { const lines = apiText.split("\n").filter((line) => line.trim() !== ""); return lines.map((line) => { try { return JSON.parse(line); } catch (e) { console.error("解析 JSON 行失败:", line, e); return null; } }).filter((item) => item !== null); } __name(parseApiResults, "parseApiResults"); async function fetchIndex(groupIndex, baseUrl) { const indexUrl = `${baseUrl}/${groupIndex}.index`; const response = await fetch(indexUrl); if (!response.ok) { throw new Error(`获取索引失败: ${response.status} ${response.statusText}`); } return await response.arrayBuffer(); } __name(fetchIndex, "fetchIndex"); function parseIndex(indexData, folderId, frameNum) { const dataView = new DataView(indexData); let offset = 0; const gridW = dataView.getUint32(offset, true); offset += 4; const gridH = dataView.getUint32(offset, true); offset += 4; const folderCount = dataView.getUint32(offset, true); offset += 4; offset += folderCount * 4; const fileCount = dataView.getUint32(offset, true); offset += 4; let left = 0, right = fileCount - 1; while (left <= right) { const mid = Math.floor((left + right) / 2); const recordOffset = offset + mid * 16; const currFolder = dataView.getUint32(recordOffset, true); const currFrame = dataView.getUint32(recordOffset + 4, true); const currOffset = Number(dataView.getBigUint64(recordOffset + 8, true)); if (currFolder === folderId && currFrame === frameNum) { let endOffset = void 0; if (mid < fileCount - 1) { const nextRecordOffset = offset + (mid + 1) * 16; endOffset = Number(dataView.getBigUint64(nextRecordOffset + 8, true)); } return { startOffset: currOffset, endOffset }; } else if (currFolder < folderId || currFolder === folderId && currFrame < frameNum) { left = mid + 1; } else { right = mid - 1; } } return null; } __name(parseIndex, "parseIndex"); async function blobToDataURL(blob) { const arrayBuffer = await blob.arrayBuffer(); const base64 = Buffer.from(arrayBuffer).toString("base64"); return `data:image/webp;base64,${base64}`; } __name(blobToDataURL, "blobToDataURL"); async function extractFrame(folderId, frameNum, baseUrl) { const groupIndex = Math.floor((folderId - 1) / 10); try { const indexData = await fetchIndex(groupIndex, baseUrl); const offsetInfo = parseIndex(indexData, folderId, frameNum); if (!offsetInfo) { console.error(`未找到 folder ${folderId} 中 frame ${frameNum}`); return null; } const { startOffset, endOffset } = offsetInfo; const imageUrl = `${baseUrl}/${groupIndex}.webp`; const headers = {}; if (endOffset) { headers["Range"] = `bytes=${startOffset}-${endOffset - 1}`; } else { headers["Range"] = `bytes=${startOffset}-`; } let response = await fetch(imageUrl, { method: "GET", headers }); if (response.status === 416 || !response.ok) { response = await fetch(imageUrl, { method: "GET" }); } if (!response.ok) { throw new Error(`HTTP error: ${response.status} ${response.statusText}`); } const blob = await response.blob(); if (!blob || blob.size === 0) { throw new Error("空响应"); } return new Blob([blob], { type: "image/webp" }); } catch (error) { console.error(`提取 folder ${folderId} 中 frame ${frameNum} 时出错:`, error); return null; } } __name(extractFrame, "extractFrame"); async function getThumbnailDataURL(folderId, frameNum, baseUrl) { const frameBlob = await extractFrame(folderId, frameNum, baseUrl); if (frameBlob) { return await blobToDataURL(frameBlob); } return null; } __name(getThumbnailDataURL, "getThumbnailDataURL"); async function getPreviewImageDataUrlsFromText(apiText, baseUrl) { const results = parseApiResults(apiText); const urls = []; for (const result of results) { const filename = result.filename; const timestamp = result.timestamp; if (!filename || !timestamp) continue; const folderMatch = filename.match(/\[P(\d+)\]/); const timeMatch = timestamp.match(/^(\d+)m(\d+)s$/); if (!folderMatch || !timeMatch) continue; const folderId = parseInt(folderMatch[1], 10); const minutes = parseInt(timeMatch[1], 10); const seconds = parseInt(timeMatch[2], 10); const totalSeconds = minutes * 60 + seconds; const dataUrl = await getThumbnailDataURL(folderId, totalSeconds, baseUrl); if (dataUrl) { urls.push(dataUrl); } } return urls; } __name(getPreviewImageDataUrlsFromText, "getPreviewImageDataUrlsFromText"); function apply(ctx) { ctx.command("vv <message> [count:number]", "搜索 VV 表情包").action(async (_, parameter, count) => { if (!parameter) return "请输入搜索关键词"; if (!count) count = 1; if (count > 5) count = 5; try { const url = `https://vvapi.cicada000.work/search?query=${parameter}&min_ratio=50&min_similarity=0.5&max_results=${count}`; const apiText = await fetchData(url); const baseUrl = "https://vv.noxylva.org"; const urls = await getPreviewImageDataUrlsFromText(apiText, baseUrl); let res = []; for (const dataUrl of urls) { res.push(import_koishi.h.image(dataUrl)); } return res; } catch (error) { console.error("搜索 VV 表情包时出错:", error); return "搜索 VV 表情包时出错, 请再试一次"; } }); } __name(apply, "apply"); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Config, apply, name });