"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;