rocket.chat.picsearcher/PicSearcherApp.js
Nanako 7371f2acdb 修改:将颜色插值范围改到70%到90%
新增:当请求失败时发送日志
修改:将原图链接和作者链接移动到按钮中
修改:添加图片角色信息
2024-12-09 23:44:05 +08:00

269 lines
10 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PicSearcherApp = void 0;
exports.processMessageImages = processMessageImages;
exports.getFileBlob = getFileBlob;
const App_1 = require("@rocket.chat/apps-engine/definition/App");
const messages_1 = require("@rocket.chat/apps-engine/definition/messages");
const defaultSauceNAOResponse = {
header: {
user_id: '',
account_type: '',
short_limit: '',
long_limit: '',
long_remaining: 0,
short_remaining: 0,
status: 0,
results_requested: 0,
index: {},
search_depth: '',
minimum_similarity: 0,
query_image_display: '',
query_image: '',
results_returned: 0,
},
results: [],
};
let myLogger;
let SauceNAOErrStr;
class PicSearcherApp extends App_1.App {
constructor(info, logger, accessors) {
super(info, logger, accessors);
this.rootUrl = '';
myLogger = logger;
}
async initialize(configurationExtend, environmentRead) {
this.rootUrl = await environmentRead.getEnvironmentVariables().getValueByName('ROOT_URL');
myLogger.log('rootUrl:', this.rootUrl);
return super.initialize(configurationExtend, environmentRead);
}
checkPostMessageSent(message, read, http) {
return Promise.resolve(true);
}
async executePostMessageSent(message, read, http, persistence, modify) {
const author = await read.getUserReader().getAppUser();
if (!message.attachments || message.attachments.length <= 0) {
return Promise.resolve();
}
myLogger.info('message.attachments:', message.attachments);
const imageBlobs = await processMessageImages(message, http, read);
const result = await searchImageOnSauceNAO(imageBlobs[0].blob, imageBlobs[0].suffix);
await sendSearchResults(modify, message.room, result, author);
}
async sendMessage(textMessage, room, author, modify) {
const messageBuilder = modify.getCreator().startMessage({
text: textMessage,
});
messageBuilder.setSender(author);
messageBuilder.setRoom(room);
return modify.getCreator().finish(messageBuilder);
}
}
exports.PicSearcherApp = PicSearcherApp;
async function processMessageImages(message, http, context) {
const blobs = [];
if (!message.attachments || message.attachments.length === 0) {
throw new Error('No attachments found in the message.');
}
for (const attachment of message.attachments) {
if (attachment.imageUrl) {
try {
const fileId = attachment.imageUrl.split('/')[2];
const r = {
blob: await getFileBlob(fileId, context),
suffix: attachment.imageUrl.split('.').pop() || '',
};
blobs.push(r);
}
catch (error) {
throw new Error(`Error fetching image content: ${error.message}`);
}
}
}
return blobs;
}
async function getFileBlob(fileId, read) {
try {
const buffer = await read.getUploadReader().getBufferById(fileId);
return new Blob([buffer]);
}
catch (error) {
myLogger.error(`Error fetching file content: ${error.message}`);
}
return new Blob();
}
async function searchImageOnSauceNAO(image, suffix) {
const formData = new FormData();
formData.append('file', image, 'image.' + suffix);
try {
const response = await fetch('https://saucenao.com/search.php', {
method: 'POST',
body: formData,
});
if (response.ok) {
const results = [];
const html = await response.text();
const resultBlocks = html.match(/<div class="result">\s*<table class="resulttable">([\s\S]*?)<\/table>\s*<\/div>/g) || [];
resultBlocks.forEach((block) => {
const result = {
similarity: '',
title: '',
thumbnailUrl: '',
sourceUrl: '',
creator: '',
creatorUrl: '',
source: '',
id: '',
};
const similarityMatch = block.match(/<div class="resultsimilarityinfo">(\d+\.\d+)%<\/div>/);
if (similarityMatch) {
result.similarity = similarityMatch[1];
}
const titleMatch = block.match(/<div class="resulttitle"><strong>(.*?)<\/strong>/);
if (titleMatch) {
result.title = titleMatch[1];
}
const thumbnailMatch = block.match(/src="(https:\/\/img\d\.saucenao\.com\/[^"]+)"/);
if (thumbnailMatch) {
result.thumbnailUrl = thumbnailMatch[1];
}
const sourceMatch = block.match(/<strong>(?:pixiv ID|dA ID|Tweet ID):\s*<\/strong><a href="([^"]+)"[^>]*>(\d+)<\/a>/);
if (sourceMatch) {
result.sourceUrl = sourceMatch[1];
result.id = sourceMatch[2];
}
const creatorMatch = block.match(/<strong>(?:Member|Author|Twitter):\s*<\/strong><a href="([^"]+)"[^>]*>(.*?)<\/a>/);
if (creatorMatch) {
result.creatorUrl = creatorMatch[1];
result.creator = creatorMatch[2];
}
const characterSection = block.match(/<strong>Characters:\s*<\/strong>(.*?)<(?:br|\/div)/);
if (characterSection && characterSection[1]) {
const chars = characterSection[1].match(/"([^"]+)"/g);
if (chars) {
result.characters = chars
.map((char) => char.replace(/"/g, ''))
.join(', ');
}
}
if (block.includes('pixiv ID:')) {
result.source = 'Pixiv';
}
else if (block.includes('dA ID:')) {
result.source = 'DeviantArt';
}
else if (block.includes('E-Hentai')) {
result.source = 'E-Hentai';
}
else if (block.includes('Twitter')) {
result.source = 'Twitter';
}
results.push(result);
});
if (results.length === 0) {
SauceNAOErrStr = html;
}
SauceNAOErrStr = '';
return results;
}
else {
console.error('请求失败:', response.status, response.statusText);
SauceNAOErrStr = '请求失败: ' + response.status + ' ' + response.statusText;
}
}
catch (error) {
console.error('搜索请求出错:', error);
SauceNAOErrStr = '搜索请求出错: ' + error;
}
return [];
}
function getInterpolatedColor(similarity) {
const clampedSimilarity = Math.max(70, Math.min(90, similarity));
const t = (clampedSimilarity - 70) / 20;
const red = Math.round(255 * (1 - t));
const green = Math.round(255 * t);
const redHex = red.toString(16).padStart(2, '0');
const greenHex = green.toString(16).padStart(2, '0');
return `#${redHex}${greenHex}00`;
}
async function sendSearchResults(modify, room, results, appUser) {
const attachments = results.map((result, index) => {
var _a, _b;
const similarityValue = parseFloat(result.similarity.replace('%', ''));
const color = getInterpolatedColor(similarityValue);
let textStr = result.source + ': ' + result.id;
if (result.sourceUrl) {
textStr += '\n' + result.sourceUrl;
}
if (result.characters) {
textStr += '\n' + 'Characters: ' + result.characters;
}
const attachmentObject = {
title: {
value: `${result.title} (${result.similarity}%)`,
},
author: {
name: result.creator,
},
text: textStr,
thumbnailUrl: result.thumbnailUrl,
collapsed: index !== 0,
color,
};
if (!result.thumbnailUrl) {
delete attachmentObject.thumbnailUrl;
}
if (textStr === ': ') {
delete attachmentObject.text;
delete attachmentObject.collapsed;
}
if (result.sourceUrl) {
(_a = attachmentObject.actions) === null || _a === void 0 ? void 0 : _a.push({
type: messages_1.MessageActionType.BUTTON,
text: '查看原图',
url: result.sourceUrl,
});
}
if (result.creatorUrl) {
(_b = attachmentObject.actions) === null || _b === void 0 ? void 0 : _b.push({
type: messages_1.MessageActionType.BUTTON,
text: '查看作者',
url: result.creatorUrl,
});
}
if (result.characters) {
attachmentObject.fields = [{
short: false,
title: 'Characters',
value: result.characters,
}];
}
return attachmentObject;
});
const text = results.length > 0 ? '以下是查找到的结果:' : SauceNAOErrStr.length > 0 ? SauceNAOErrStr : '没有找到相关结果';
const messageBuilder = modify.getCreator().startMessage()
.setRoom(room)
.setUsernameAlias(appUser.username)
.setText(text)
.setAttachments(attachments);
await modify.getCreator().finish(messageBuilder);
}
async function urlToBlob(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.blob();
}
catch (error) {
throw new Error(`Failed to fetch image: ${error}`);
}
}
async function test() {
const blob = await urlToBlob('https://www.z4a.net/images/2024/12/09/69054578_p0.jpg');
const result = await searchImageOnSauceNAO(blob, 'jpg');
console.log(result);
}
exports.default = PicSearcherApp;