From 0f0895f345eb14c60840d382370c66f339145aa9 Mon Sep 17 00:00:00 2001 From: Bronya-Rand Date: Mon, 22 Apr 2024 19:11:00 +0100 Subject: [PATCH] feat: implement miHoYo scraper --- public/img/mihoyo.svg | 3 + public/scripts/chats.js | 8 +- .../extensions/attachments/mihoyo-scrape.html | 27 +++ public/scripts/scrapers.js | 157 +++++++++++++++++- 4 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 public/img/mihoyo.svg create mode 100644 public/scripts/extensions/attachments/mihoyo-scrape.html diff --git a/public/img/mihoyo.svg b/public/img/mihoyo.svg new file mode 100644 index 000000000..37b266529 --- /dev/null +++ b/public/img/mihoyo.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/scripts/chats.js b/public/scripts/chats.js index 19e316fa5..e9d402a1d 100644 --- a/public/scripts/chats.js +++ b/public/scripts/chats.js @@ -789,7 +789,13 @@ async function openAttachmentManager() { } const buttonTemplate = template.find('.actionButtonTemplate .actionButton').clone(); - buttonTemplate.find('.actionButtonIcon').addClass(scraper.iconClass); + if (scraper.iconAvailable) { + buttonTemplate.find('.actionButtonIcon').addClass(scraper.iconClass); + buttonTemplate.find('.actionButtonImg').remove(); + } else { + buttonTemplate.find('.actionButtonImg').attr('src', scraper.iconClass); + buttonTemplate.find('.actionButtonIcon').remove(); + } buttonTemplate.find('.actionButtonText').text(scraper.name); buttonTemplate.attr('title', scraper.description); buttonTemplate.on('click', () => { diff --git a/public/scripts/extensions/attachments/mihoyo-scrape.html b/public/scripts/extensions/attachments/mihoyo-scrape.html new file mode 100644 index 000000000..edd097545 --- /dev/null +++ b/public/scripts/extensions/attachments/mihoyo-scrape.html @@ -0,0 +1,27 @@ +
+
+

miHoYo/HoYoverse HoYoLAB Scraper

+
+

Select a Wiki to parse through.

+
+ +
+
+

+ Enter the Wiki Page ID. +

+
+
+ This is the last digit in the HoYoLAB URL i.e. + https://wiki.hoyolab.com/pc/hsr/entry/X + + Example: + 14 + +
+ +
diff --git a/public/scripts/scrapers.js b/public/scripts/scrapers.js index 10529ee89..4a7311e88 100644 --- a/public/scripts/scrapers.js +++ b/public/scripts/scrapers.js @@ -9,6 +9,7 @@ import { isValidUrl } from './utils.js'; * @property {string} name * @property {string} description * @property {string} iconClass + * @property {boolean} iconAvailable * @property {() => Promise} isAvailable * @property {() => Promise} scrape */ @@ -19,6 +20,7 @@ import { isValidUrl } from './utils.js'; * @property {string} name * @property {string} description * @property {string} iconClass + * @property {boolean} iconAvailable */ export class ScraperManager { @@ -45,7 +47,7 @@ export class ScraperManager { * @returns {ScraperInfo[]} List of scrapers available for the Data Bank */ static getDataBankScrapers() { - return ScraperManager.#scrapers.map(s => ({ id: s.id, name: s.name, description: s.description, iconClass: s.iconClass })); + return ScraperManager.#scrapers.map(s => ({ id: s.id, name: s.name, description: s.description, iconClass: s.iconClass, iconAvailable: s.iconAvailable})); } /** @@ -87,6 +89,7 @@ class Notepad { this.name = 'Notepad'; this.description = 'Create a text file from scratch.'; this.iconClass = 'fa-solid fa-note-sticky'; + this.iconAvailable = true; } /** @@ -133,6 +136,7 @@ class WebScraper { this.name = 'Web'; this.description = 'Download a page from the web.'; this.iconClass = 'fa-solid fa-globe'; + this.iconAvailable = true; } /** @@ -207,6 +211,7 @@ class FileScraper { this.name = 'File'; this.description = 'Upload a file from your computer.'; this.iconClass = 'fa-solid fa-upload'; + this.iconAvailable = true; } /** @@ -243,6 +248,7 @@ class FandomScraper { this.name = 'Fandom'; this.description = 'Download a page from the Fandom wiki.'; this.iconClass = 'fa-solid fa-fire'; + this.iconAvailable = true; } /** @@ -339,6 +345,153 @@ class FandomScraper { } } +/** + * Scrapes data from the miHoYo/HoYoverse HoYoLAB wiki. + * @implements {Scraper} + */ +class miHoYoScraper { + constructor() { + this.id = 'mihoyo'; + this.name = 'miHoYo'; + this.description = 'Scrapes a page from the miHoYo/HoYoverse HoYoLAB wiki.'; + this.iconClass = 'img/mihoyo.svg'; + this.iconAvailable = false; // There is no miHoYo icon in Font Awesome + } + + /** + * Check if the scraper is available. + * @returns {Promise} + */ + async isAvailable() { + try { + const result = await fetch('/api/plugins/hoyoverse/probe', { + method: 'POST', + headers: getRequestHeaders(), + }); + + return result.ok; + } catch (error) { + console.debug('Could not probe miHoYo plugin', error); + return false; + } + } + + /** + * Outputs Data Information in a human-readable format. + * @param {Object} m Data to be parsed + * @returns {string} Human-readable format of the data + */ + parseOutput(m) { + let temp = ''; + for (const d in m) { + if (m[d].key === "") { + temp += `- ${m[d].value}\n`; + continue; + } + temp += `- ${m[d].key}: ${m[d].value}\n`; + } + return temp; + } + + /** Scrape data from the miHoYo/HoYoverse HoYoLAB wiki. + * @returns {Promise} File attachments scraped from the wiki. + */ + + async scrape() { + let miHoYoWiki = ''; + let miHoYoWikiID = ''; + + const template = $(await renderExtensionTemplateAsync('attachments', 'mihoyo-scrape', {})); + + template.find('select[name="mihoyoScrapeWikiDropdown"]').on('change', function () { + miHoYoWiki = String($(this).val()); + }); + template.find('input[name="mihoyoScrapeWikiID"]').on('input', function () { + miHoYoWikiID = String($(this).val()); + }); + + const confirm = await callGenericPopup(template, POPUP_TYPE.CONFIRM, '', { wide: false, large: false }); + + if (confirm !== POPUP_RESULT.AFFIRMATIVE) { + return; + } + + if (!miHoYoWiki) { + toastr.error('A specific HoYoLab wiki is required'); + return; + } + + if (!miHoYoWikiID) { + toastr.error('A specific HoYoLab wiki ID is required'); + return; + } + + if (miHoYoWiki === 'genshin') { + toastr.error('The Genshin Impact parser has not been implemented *yet*'); + return; + } + + let toast; + if (miHoYoWiki === 'hsr') { + toast = toastr.info(`Scraping the Honkai: Star Rail HoYoLAB wiki for Wiki Entry ID: ${miHoYoWikiID}`); + } else { + toast = toastr.info(`Scraping the Genshin Impact wiki for Wiki Entry ID: ${miHoYoWikiID}`); + } + + let result; + if (miHoYoWiki === 'hsr') { + result = await fetch('/api/plugins/hoyoverse/silver-wolf', { + method: 'POST', + headers: getRequestHeaders(), + body: JSON.stringify({ miHoYoWiki, miHoYoWikiID }), + }); + } else if (miHoYoWiki === 'genshin') { + result = await fetch('/api/plugins/hoyoverse/furina', { + method: 'POST', + headers: getRequestHeaders(), + body: JSON.stringify({ miHoYoWiki, miHoYoWikiID }), + }); + } else { + throw new Error('Unknown wiki name identifier'); + } + + if (!result.ok) { + const error = await result.text(); + throw new Error(error); + } + + const data = await result.json(); + toastr.clear(toast); + + const fileName = data[0].name; + const dataContent = data[0].content; + + //parse the data as a long string of data + let combinedContent = ''; + combinedContent += `Name: ${data[0].name}\n`; + + if (dataContent.description !== "") { + combinedContent += `Description: ${dataContent.description}\n\n`; + } + + if (dataContent.modules != []) { + for (const m in dataContent.modules) { + if (dataContent.modules[m].data.length === 0) { + continue; + } + combinedContent += dataContent.modules[m].name + '\n'; + combinedContent += this.parseOutput(dataContent.modules[m].data); + combinedContent += '\n'; + } + } + + const file = new File([combinedContent], `${fileName}.txt`, { type: 'text/plain' }); + + return [file]; + } +} + + /** * Scrape transcript from a YouTube video. * @implements {Scraper} @@ -349,6 +502,7 @@ class YouTubeScraper { this.name = 'YouTube'; this.description = 'Download a transcript from a YouTube video.'; this.iconClass = 'fa-solid fa-closed-captioning'; + this.iconAvailable = true; } /** @@ -413,4 +567,5 @@ ScraperManager.registerDataBankScraper(new FileScraper()); ScraperManager.registerDataBankScraper(new Notepad()); ScraperManager.registerDataBankScraper(new WebScraper()); ScraperManager.registerDataBankScraper(new FandomScraper()); +ScraperManager.registerDataBankScraper(new miHoYoScraper()); ScraperManager.registerDataBankScraper(new YouTubeScraper());