358 lines
12 KiB
JavaScript
358 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;
|
||
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;
|