koishi-plugin-vv-bot
Version:
基于 https://github.com/Cicada000/VV 的VV表情包机器人
200 lines (198 loc) • 7.07 kB
JavaScript
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
});