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