rocket.chat.picsearcher/PicSearcherApp.js

331 lines
12 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 settings_1 = require("@rocket.chat/apps-engine/definition/settings");
var SourceType;
(function (SourceType) {
SourceType["Pixiv"] = "Pixiv";
SourceType["DeviantArt"] = "DeviantArt";
SourceType["AniDB"] = "AniDB";
SourceType["MyAnimeList"] = "MyAnimeList";
SourceType["AniList"] = "AniList";
SourceType["BCY"] = "BCY";
SourceType["EHentai"] = "E-Hentai";
SourceType["IMDB"] = "IMDB";
SourceType["Twitter"] = "Twitter";
SourceType["Danbooru"] = "Danbooru";
SourceType["YandeRe"] = "Yande.re";
SourceType["Gelbooru"] = "Gelbooru";
SourceType["AnimePictures"] = "AnimePictures";
SourceType["Unknown"] = "Unknown";
})(SourceType || (SourceType = {}));
function getUrlType(url) {
const lowerUrl = url.toLowerCase();
if (lowerUrl.includes('pixiv.net')) {
return SourceType.Pixiv;
}
if (lowerUrl.includes('twitter.com') || lowerUrl.includes('x.com')) {
return SourceType.Twitter;
}
if (lowerUrl.includes('danbooru.donmai.us')) {
return SourceType.Danbooru;
}
if (lowerUrl.includes('yande.re')) {
return SourceType.YandeRe;
}
if (lowerUrl.includes('gelbooru.com')) {
return SourceType.Gelbooru;
}
if (lowerUrl.includes('anime-pictures.net')) {
return SourceType.AnimePictures;
}
if (lowerUrl.includes('deviantart.com')) {
return SourceType.DeviantArt;
}
if (lowerUrl.includes('anidb.net')) {
return SourceType.AniDB;
}
if (lowerUrl.includes('myanimelist.net')) {
return SourceType.MyAnimeList;
}
if (lowerUrl.includes('anilist.co')) {
return SourceType.AniList;
}
if (lowerUrl.includes('bcy.net')) {
return SourceType.BCY;
}
if (lowerUrl.includes('e-hentai.org')) {
return SourceType.EHentai;
}
if (lowerUrl.includes('imdb.com')) {
return SourceType.IMDB;
}
return SourceType.Unknown;
}
const defaultSauceNAOResponse = {
header: {
user_id: '',
account_type: '',
short_limit: '',
long_limit: '',
long_remaining: 0,
short_remaining: 0,
status: 0,
results_requested: '',
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 = '';
this.sauceNAOApiKey = '';
myLogger = logger;
}
async initialize(configurationExtend, environmentRead) {
this.rootUrl = await environmentRead.getEnvironmentVariables().getValueByName('ROOT_URL');
this.sauceNAOApiKey = await environmentRead.getSettings().getValueById('SauceNAOApiKey');
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(this.sauceNAOApiKey, imageBlobs[0].blob, imageBlobs[0].suffix);
await sendSearchResults(modify, message.room, result, author);
}
async onSettingUpdated(setting, configurationModify, read, http) {
if (setting.id === 'SauceNAOApiKey') {
this.sauceNAOApiKey = setting.value;
}
return super.onSettingUpdated(setting, configurationModify, read, http);
}
async extendConfiguration(configuration, environmentRead) {
await configuration.settings.provideSetting({
id: 'SauceNAOApiKey',
type: settings_1.SettingType.STRING,
packageValue: '',
required: true,
public: false,
i18nLabel: 'searcher_api_key_label',
i18nDescription: 'searcher_api_key_description',
});
}
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(apiKey, image, suffix) {
const formData = new FormData();
formData.append('file', image, 'image.' + suffix);
formData.append('api_key', apiKey);
formData.append('output_type', '2');
try {
const response = await fetch('https://saucenao.com/search.php', {
method: 'POST',
body: formData,
});
if (response.ok) {
const json = await response.text();
const result = JSON.parse(json);
SauceNAOErrStr = '';
return result;
}
else {
console.error('请求失败:', response.status, response.statusText);
SauceNAOErrStr = '请求失败: ' + response.status + ' ' + response.statusText;
}
}
catch (error) {
console.error('搜索请求出错:', error);
SauceNAOErrStr = '搜索请求出错: ' + error;
}
return defaultSauceNAOResponse;
}
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.results.sort((a, b) => {
if (a.data.ext_urls && !b.data.ext_urls) {
return -1;
}
if (!a.data.ext_urls && b.data.ext_urls) {
return 1;
}
return b.header.similarity - a.header.similarity;
});
for (const result of results.results) {
const header = result.header;
const data = result.data;
const similarityValue = header.similarity;
if (similarityValue < 70) {
continue;
}
const color = getInterpolatedColor(similarityValue);
const attachmentObject = {
title: {
value: `${data.title} (${header.similarity}%)`,
},
thumbnailUrl: header.thumbnail,
collapsed: attachments.length !== 0,
color,
};
if (!header.thumbnail) {
delete attachmentObject.thumbnailUrl;
}
attachmentObject.actions = new Array();
if (data.ext_urls) {
for (const extUrl of data.ext_urls) {
const urlType = getUrlType(extUrl);
attachmentObject.actions.push({
type: messages_1.MessageActionType.BUTTON,
text: urlType.toString(),
url: extUrl,
});
}
}
if (data.pixiv_id) {
attachmentObject.actions.push({
type: messages_1.MessageActionType.BUTTON,
text: '作者Pixiv',
url: `https://www.pixiv.net/users/${data.member_id}`,
});
}
if (data.da_id) {
attachmentObject.actions.push({
type: messages_1.MessageActionType.BUTTON,
text: '作者DeviantArt',
url: data.author_url,
});
}
if (data.bcy_id) {
attachmentObject.actions.push({
type: messages_1.MessageActionType.BUTTON,
text: '作者BCY',
url: `https://bcy.net/u/${data.member_link_id}`,
});
}
if (data.anidb_aid) {
attachmentObject.actions.push({
type: messages_1.MessageActionType.BUTTON,
text: 'AniDB',
url: `https://anidb.net/anime/${data.anidb_aid}`,
});
}
if (data.mal_id) {
attachmentObject.actions.push({
type: messages_1.MessageActionType.BUTTON,
text: 'MyAnimeList',
url: `https://myanimelist.net/anime/${data.mal_id}`,
});
}
if (data.anilist_id) {
attachmentObject.actions.push({
type: messages_1.MessageActionType.BUTTON,
text: 'AniList',
url: `https://anilist.co/anime/${data.anilist_id}`,
});
}
if (data.imdb_id) {
attachmentObject.actions.push({
type: messages_1.MessageActionType.BUTTON,
text: 'IMDb',
url: `https://www.imdb.com/title/${data.imdb_id}`,
});
}
attachments.push(attachmentObject);
}
const successStr = `搜索成功,短时间内剩余查询次数:${results.header.short_remaining}/${results.header.short_limit},长时间内剩余查询次数:${results.header.long_remaining}/${results.header.long_limit}`;
const text = attachments.length > 0 ? successStr : SauceNAOErrStr.length > 0 ? SauceNAOErrStr : '没有找到相关结果';
const messageBuilder = modify.getCreator().startMessage()
.setRoom(room)
.setUsernameAlias(appUser.username)
.setText(text)
.setAttachments(attachments)
.setGroupable(true);
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('ac635e5f5011234871260aac1d37ac8360ed979c', blob, 'jpg');
console.log(result);
}
exports.default = PicSearcherApp;