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());