koishi-plugin-emojihub-bili
Version:
[<ruby>Emojihub-bili<rp>(</rp><rt>点我查看食用方法</rt><rp>)</rp></ruby>](https://www.npmjs.com/package/koishi-plugin-emojihub-bili)🐱支持QQ官方json按钮!20群即可发按钮!😍可自定义添加或删除任意表情包!基本每个指令都有上千张!支持定时/定量消息触发 • 9.5 kB
text/typescript
import fs from "node:fs";
import path from "node:path";
import { type Context } from "koishi";
import { Config } from "./config";
import { logError, logInfoformat, getAllFiles, getVirtualFilename, logger } from "./utils";
import { isHttpUrl, resolveLocalPath } from "./path";
import { getLocalImageProxyUrl } from "./media-proxy";
export interface ImageResult {
imageUrl: string | null;
isLocal: boolean;
imageName?: string;
imageTime?: Date;
imageSize?: number;
imagePath?: string;
}
function isImageFile(filePath: string) {
const lower = filePath.toLowerCase();
return lower.endsWith(".jpg") || lower.endsWith(".png") || lower.endsWith(".gif") || lower.endsWith(".bmp") || lower.endsWith(".webp");
}
function isAbsoluteLikePath(input: string) {
return path.isAbsolute(input) || path.win32.isAbsolute(input) || path.posix.isAbsolute(input);
}
function toLocalImageUrl(ctx: Context, localPath: string): string | null {
return getLocalImageProxyUrl(ctx, localPath);
}
export async function getImageAsBase64(imagePath: string) {
try {
const filePath = resolveLocalPath(imagePath) ?? imagePath;
const imageBuffer = fs.readFileSync(filePath);
return imageBuffer.toString("base64");
} catch (error) {
logger.error("Error converting image to base64:", error);
return null;
}
}
export async function determineImagePath(
txtPath: string,
config: Config,
channelId: string | null,
command: string,
ctx: Context,
local_picture_name: string[] | null = null,
): Promise<ImageResult> {
const localPath = resolveLocalPath(txtPath);
if (localPath) {
try {
const stats = fs.lstatSync(localPath);
if (stats.isDirectory()) {
return await getRandomImageFromFolder(localPath, config, channelId, command, ctx, local_picture_name);
}
if (stats.isFile()) {
if (isImageFile(localPath)) {
logInfoformat(config, channelId, command, `本地图片的绝对路径: ${txtPath}`);
const imageUrl = toLocalImageUrl(ctx, localPath);
if (!imageUrl) return { imageUrl: null, isLocal: false };
return {
imageUrl,
isLocal: true,
imageName: path.basename(localPath),
imageTime: stats.mtime,
imageSize: stats.size,
imagePath: localPath,
};
}
if (localPath.toLowerCase().endsWith(".txt")) {
return await getRandomImageUrlFromFile(localPath, config, channelId, command, ctx);
}
}
} catch {
return { imageUrl: null, isLocal: false };
}
}
if (isHttpUrl(txtPath)) {
logInfoformat(config, channelId, command, `直接的图片链接 ${txtPath}`);
return { imageUrl: txtPath, isLocal: false };
}
const allValidPaths = getAllValidPaths(config);
if (allValidPaths.length === 0) {
return { imageUrl: null, isLocal: false };
}
const pickedPath = allValidPaths[Math.floor(Math.random() * allValidPaths.length)];
const pickedLocalPath = resolveLocalPath(pickedPath);
if (!pickedLocalPath) return { imageUrl: null, isLocal: false };
try {
const stats = fs.lstatSync(pickedLocalPath);
if (stats.isDirectory()) {
return await getRandomImageFromFolder(pickedLocalPath, config, channelId, command, ctx, local_picture_name);
}
if (stats.isFile()) {
if (isImageFile(pickedLocalPath)) {
logInfoformat(config, channelId, command, `随机选择的本地图片路径: ${pickedPath}`);
const imageUrl = toLocalImageUrl(ctx, pickedLocalPath);
if (!imageUrl) return { imageUrl: null, isLocal: false };
return {
imageUrl,
isLocal: true,
imageName: path.basename(pickedLocalPath),
imageTime: stats.mtime,
imageSize: stats.size,
imagePath: pickedLocalPath,
};
}
if (pickedLocalPath.toLowerCase().endsWith(".txt")) {
return await getRandomImageUrlFromFile(pickedLocalPath, config, channelId, command, ctx);
}
}
} catch {
return { imageUrl: null, isLocal: false };
}
return { imageUrl: null, isLocal: false };
}
export function getRandomEmojiHubCommand(config: Config) {
const commands = config.MoreEmojiHubList.map((emoji) => emoji.command);
if (commands.length > 0) {
return commands[Math.floor(Math.random() * commands.length)];
}
return null;
}
function getAllValidPaths(config: Config): string[] {
return config.MoreEmojiHubList.filter((emoji) => {
const sourceUrl = emoji.source_url;
return isAbsoluteLikePath(sourceUrl) || isHttpUrl(sourceUrl) || sourceUrl.startsWith("file:");
}).map((emoji) => emoji.source_url);
}
async function getRandomImageFromFolder(
folderPath: string,
config: Config,
channelId: string | null,
command: string,
ctx: Context,
local_picture_name: string[] | null,
) {
if (!fs.existsSync(folderPath)) {
logError(`错误:路径不存在: ${folderPath}`);
return { imageUrl: null, isLocal: false };
}
let files = config.searchSubfolders
? getAllFiles(folderPath)
: fs.readdirSync(folderPath).map((file) => path.join(folderPath, file));
files = files.filter((file) => isImageFile(file));
if (files.length === 0) {
logError("文件夹中未找到有效图片文件(jpg,png,gif,webp,bmp)");
return { imageUrl: null, isLocal: false };
}
if (local_picture_name?.length > 0) {
files = files.filter((file) => {
let filenameToMatch;
if (config.searchSubfoldersWithfilename && config.searchSubfolders) {
filenameToMatch = getVirtualFilename(file, folderPath);
} else {
filenameToMatch = path.basename(file);
}
const filenameLower = filenameToMatch.toLowerCase();
return local_picture_name.every((keyword) => filenameLower.includes(keyword.toLowerCase()));
});
if (files.length === 0) {
logError(`未找到匹配关键词 "${local_picture_name.join(" ")}" 的图片文件`);
return { imageUrl: null, isLocal: false };
}
}
const imagePath = files[Math.floor(Math.random() * files.length)];
logInfoformat(config, channelId, command, `使用文件夹 ${folderPath} \n发送本地图片为 ${imagePath}`);
const stats = fs.statSync(imagePath);
const imageUrl = toLocalImageUrl(ctx, imagePath);
if (!imageUrl) return { imageUrl: null, isLocal: false };
return {
imageUrl,
isLocal: true,
imageName: path.basename(imagePath),
imageTime: stats.mtime,
imageSize: stats.size,
imagePath,
};
}
export async function getRandomImageUrlFromFile(
txtPath: string,
config: Config,
channelId: string | null,
command: string,
ctx: Context,
) {
let urls: string[];
let imageUrl: string;
try {
urls = fs.readFileSync(txtPath, "utf8").split("\n").filter((url) => url.trim() !== "");
} catch (error) {
if (error instanceof Error && error.message === "ENOENT") {
return { imageUrl: null, isLocal: false };
}
logError(error);
return { imageUrl: null, isLocal: false };
}
if (urls.length === 0) {
logError(`错误!无有效URL可用:${txtPath}`);
return { imageUrl: null, isLocal: false };
}
const index = Math.floor(Math.random() * urls.length);
let txtUrl = urls[index].trim();
const extraPrefix = "https:";
const bilibiliPrefix = "https://i0.hdslb.com/bfs/";
if (txtUrl.startsWith(extraPrefix + bilibiliPrefix)) {
txtUrl = txtUrl.replace(extraPrefix, "");
}
if (!txtUrl.startsWith("https://") && !txtUrl.startsWith("http://")) {
txtUrl = bilibiliPrefix + txtUrl;
}
imageUrl = txtUrl.trim();
if (config.LocalSendNetworkPicturesList && config.LocalSendNetworkPicturesList.length > 0) {
const normalizedList = config.LocalSendNetworkPicturesList.split(/\n|,|,/).map((item) => item.trim().toLowerCase());
const lowerCaseCommand = command.toLowerCase();
if (normalizedList.includes(lowerCaseCommand)) {
const outputPath = path.join(__dirname, `${Date.now()}.png`);
try {
const localOutputPath = await downloadImage(txtUrl, outputPath, ctx);
ctx.setTimeout(() => {
fs.unlinkSync(localOutputPath);
logInfoformat(config, null, null, `临时文件已删除:${localOutputPath}`);
}, config.deletePictime * 1000);
logInfoformat(config, channelId, command, `下载并发送本地图片: ${localOutputPath}`);
const localImageUrl = toLocalImageUrl(ctx, localOutputPath);
return { imageUrl: localImageUrl, isLocal: true, imagePath: localOutputPath };
} catch (downloadError) {
logError(`图片下载失败:${downloadError.message}`);
return { imageUrl: null, isLocal: false };
}
}
}
logInfoformat(config, channelId, command, `使用文件 ${txtPath} \n发送URL为:${imageUrl}`);
return { imageUrl, isLocal: false };
}
export async function downloadImage(url: string, outputPath: string, ctx: Context) {
try {
const response = await ctx.http.get(url, { responseType: "arraybuffer" });
const buffer = Buffer.from(response);
await fs.promises.writeFile(outputPath, buffer);
return outputPath;
} catch (error) {
logError(`下载图片失败: ${error.message}`);
throw error;
}
}
export function listAllCommands(config: Config): string[] {
const allCommands = config.MoreEmojiHubList.map((emoji) => emoji.command);
if (allCommands.length === 0) {
logError("未找到任何表情包指令。");
}
return allCommands;
}