234 lines
8.6 KiB
JavaScript
234 lines
8.6 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 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;
|
|
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();
|
|
myLogger.info('HTML:', html);
|
|
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):\s*<\/strong><a href="([^"]+)"[^>]*>(\d+)<\/a>/);
|
|
if (sourceMatch) {
|
|
result.sourceUrl = sourceMatch[1];
|
|
result.id = sourceMatch[2];
|
|
}
|
|
const creatorMatch = block.match(/<strong>(?:Member|Author):\s*<\/strong><a href="([^"]+)"[^>]*>(.*?)<\/a>/);
|
|
if (creatorMatch) {
|
|
result.creatorUrl = creatorMatch[1];
|
|
result.creator = creatorMatch[2];
|
|
}
|
|
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';
|
|
}
|
|
results.push(result);
|
|
});
|
|
return results;
|
|
}
|
|
else {
|
|
console.error('请求失败:', response.status, response.statusText);
|
|
}
|
|
}
|
|
catch (error) {
|
|
console.error('搜索请求出错:', error);
|
|
}
|
|
return [];
|
|
}
|
|
function getInterpolatedColor(similarity) {
|
|
const clampedSimilarity = Math.max(80, Math.min(90, similarity));
|
|
const t = (clampedSimilarity - 80) / 10;
|
|
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;
|
|
}
|
|
const attachmentObject = {
|
|
title: {
|
|
value: `${result.title} (${result.similarity}%)`,
|
|
link: result.sourceUrl,
|
|
},
|
|
author: {
|
|
name: result.creator,
|
|
link: result.creatorUrl,
|
|
},
|
|
text: textStr,
|
|
thumbnailUrl: result.thumbnailUrl,
|
|
collapsed: index !== 0,
|
|
color,
|
|
};
|
|
if (!result.sourceUrl) {
|
|
(_a = attachmentObject.title) === null || _a === void 0 ? true : delete _a.link;
|
|
}
|
|
if (!result.creatorUrl) {
|
|
(_b = attachmentObject.author) === null || _b === void 0 ? true : delete _b.link;
|
|
}
|
|
if (!result.thumbnailUrl) {
|
|
delete attachmentObject.thumbnailUrl;
|
|
}
|
|
if (textStr === ': ') {
|
|
delete attachmentObject.text;
|
|
delete attachmentObject.collapsed;
|
|
}
|
|
return attachmentObject;
|
|
});
|
|
const text = results.length > 0 ? '以下是查找到的结果:' : '没有找到相关结果';
|
|
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;
|