rocket.chat.picsearcher/PicSearcherApp.js
Nanako 15968a8ec6 修复可能的崩溃
修复ApiKey配置消失
2024-12-10 13:02:15 +08:00

358 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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;
const sauceNAOApiKeyID = 'sauceNAO_api_key';
class PicSearcherApp extends App_1.App {
constructor(info, logger, accessors) {
super(info, logger, accessors);
this.rootUrl = '';
this.sauceNAOApiKey = 'ac635e5f5011234871260aac1d37ac8360ed979c';
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(this.sauceNAOApiKey, imageBlobs[0].blob, imageBlobs[0].suffix);
await sendSearchResults(modify, message.room, result, author);
}
async onSettingUpdated(setting, configurationModify, read, http) {
if (setting.id === sauceNAOApiKeyID) {
this.sauceNAOApiKey = setting.value;
}
return super.onSettingUpdated(setting, configurationModify, read, http);
}
async extendConfiguration(configuration, environmentRead) {
await configuration.settings.provideSetting({
id: sauceNAOApiKeyID,
type: settings_1.SettingType.STRING,
packageValue: '',
required: true,
public: false,
i18nLabel: 'sauceNAO_api_key',
i18nDescription: 'sauceNAO_api_key_desc',
});
}
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 titleText = data.title || '未知标题';
const attachmentObject = {
title: {
value: `${titleText} (${header.similarity}%)`,
},
text: header.index_name,
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.member_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}`,
});
}
if (data.member_name !== undefined) {
attachmentObject.author = {
name: data.member_name,
};
}
if (data.author_name !== undefined) {
attachmentObject.author = {
name: data.author_name,
};
}
if (Array.isArray(data.creator)) {
attachmentObject.author = {
name: data.creator.join(', '),
};
}
if (data.eng_name !== undefined) {
attachmentObject.author = {
name: data.eng_name,
};
}
if (data.jp_name !== undefined) {
attachmentObject.author = {
name: data.jp_name,
};
}
attachments.push(attachmentObject);
}
const successStr = `搜索成功30秒内剩余查询次数${results.header.short_remaining}/${results.header.short_limit}24小时内剩余查询次数${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;