Add Data Bank manager

This commit is contained in:
Cohee 2024-04-16 02:14:34 +03:00
parent 71041ec764
commit 242d57c14b
13 changed files with 598 additions and 64 deletions

View File

@ -491,6 +491,10 @@ textarea:disabled {
font-size: calc(var(--mainFontSize) * 1.2) !important;
}
.fontsize90p {
font-size: calc(var(--mainFontSize) * 0.9) !important;
}
.fontsize80p {
font-size: calc(var(--mainFontSize) * 0.8) !important;
}

View File

@ -7361,47 +7361,6 @@ export function cancelTtsPlay() {
}
}
async function deleteMessageImage() {
const value = await callPopup('<h3>Delete image from message?<br>This action can\'t be undone.</h3>', 'confirm');
if (!value) {
return;
}
const mesBlock = $(this).closest('.mes');
const mesId = mesBlock.attr('mesid');
const message = chat[mesId];
delete message.extra.image;
delete message.extra.inline_image;
mesBlock.find('.mes_img_container').removeClass('img_extra');
mesBlock.find('.mes_img').attr('src', '');
await saveChatConditional();
}
function enlargeMessageImage() {
const mesBlock = $(this).closest('.mes');
const mesId = mesBlock.attr('mesid');
const message = chat[mesId];
const imgSrc = message?.extra?.image;
const title = message?.extra?.title;
if (!imgSrc) {
return;
}
const img = document.createElement('img');
img.classList.add('img_enlarged');
img.src = imgSrc;
const imgContainer = $('<div><pre><code></code></pre></div>');
imgContainer.prepend(img);
imgContainer.addClass('img_enlarged_container');
imgContainer.find('code').addClass('txt').text(title);
const titleEmpty = !title || title.trim().length === 0;
imgContainer.find('pre').toggle(!titleEmpty);
addCopyToCodeBlocks(imgContainer);
callPopup(imgContainer, 'text', '', { wide: true, large: true });
}
function updateAlternateGreetingsHintVisibility(root) {
const numberOfGreetings = root.find('.alternate_greetings_list .alternate_greeting').length;
$(root).find('.alternate_grettings_hint').toggle(numberOfGreetings == 0);
@ -10392,9 +10351,6 @@ jQuery(async function () {
$('#char-management-dropdown').prop('selectedIndex', 0);
});
$(document).on('click', '.mes_img_enlarge', enlargeMessageImage);
$(document).on('click', '.mes_img_delete', deleteMessageImage);
$(window).on('beforeunload', () => {
cancelTtsPlay();
if (streamingProcessor) {

View File

@ -18,6 +18,8 @@ import {
saveSettingsDebounced,
showSwipeButtons,
this_chid,
saveChatConditional,
chat_metadata,
} from '../script.js';
import { selected_group } from './group-chats.js';
import { power_user } from './power-user.js';
@ -29,9 +31,25 @@ import {
getStringHash,
humanFileSize,
saveBase64AsFile,
isValidUrl,
} from './utils.js';
import { extension_settings, renderExtensionTemplateAsync, saveMetadataDebounced, writeExtensionField } from './extensions.js';
import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from './popup.js';
/**
* @typedef {Object} FileAttachment
* @property {string} url File URL
* @property {number} size File size
* @property {string} name File name
* @property {string} [text] File text
*/
const fileSizeLimit = 1024 * 1024 * 10; // 10 MB
const ATTACHMENT_SOURCE = {
GLOBAL: 'global',
CHAT: 'chat',
CHARACTER: 'character',
};
const converters = {
'application/pdf': extractTextFromPDF,
@ -39,6 +57,11 @@ const converters = {
'text/markdown': extractTextFromMarkdown,
};
/**
* Determines if the file type has a converter function.
* @param {string} type MIME type
* @returns {boolean} True if the file type is convertible, false otherwise.
*/
function isConvertible(type) {
return Object.keys(converters).includes(type);
}
@ -275,9 +298,9 @@ async function onFileAttach() {
* @param {number} messageId Message ID
*/
async function deleteMessageFile(messageId) {
const confirm = await callPopup('Are you sure you want to delete this file?', 'confirm');
const confirm = await callGenericPopup('Are you sure you want to delete this file?', POPUP_TYPE.CONFIRM);
if (!confirm) {
if (confirm !== POPUP_RESULT.AFFIRMATIVE) {
console.debug('Delete file cancelled');
return;
}
@ -289,11 +312,15 @@ async function deleteMessageFile(messageId) {
return;
}
const url = message.extra.file.url;
delete message.extra.file;
$(`.mes[mesid="${messageId}"] .mes_file_container`).remove();
saveChatDebounced();
await saveChatConditional();
await deleteFileFromServer(url);
}
/**
* Opens file from message in a modal.
* @param {number} messageId Message ID
@ -306,14 +333,7 @@ async function viewMessageFile(messageId) {
return;
}
const fileText = messageFile.text || (await getFileAttachment(messageFile.url));
const modalTemplate = $('<div><pre><code></code></pre></div>');
modalTemplate.find('code').addClass('txt').text(fileText);
modalTemplate.addClass('file_modal');
addCopyToCodeBlocks(modalTemplate);
callPopup(modalTemplate, 'text', '', { wide: true, large: true });
await openFilePopup(messageFile);
}
/**
@ -348,7 +368,7 @@ function embedMessageFile(messageId, messageBlock) {
await populateFileAttachment(message, 'embed_file_input');
appendMediaToMessage(message, messageBlock);
saveChatDebounced();
await saveChatConditional();
}
}
@ -476,6 +496,366 @@ export function isExternalMediaAllowed() {
return !power_user.forbid_external_images;
}
function enlargeMessageImage() {
const mesBlock = $(this).closest('.mes');
const mesId = mesBlock.attr('mesid');
const message = chat[mesId];
const imgSrc = message?.extra?.image;
const title = message?.extra?.title;
if (!imgSrc) {
return;
}
const img = document.createElement('img');
img.classList.add('img_enlarged');
img.src = imgSrc;
const imgContainer = $('<div><pre><code></code></pre></div>');
imgContainer.prepend(img);
imgContainer.addClass('img_enlarged_container');
imgContainer.find('code').addClass('txt').text(title);
const titleEmpty = !title || title.trim().length === 0;
imgContainer.find('pre').toggle(!titleEmpty);
addCopyToCodeBlocks(imgContainer);
callGenericPopup(imgContainer, POPUP_TYPE.TEXT, '', { wide: true, large: true });
}
async function deleteMessageImage() {
const value = await callGenericPopup('<h3>Delete image from message?<br>This action can\'t be undone.</h3>', POPUP_TYPE.CONFIRM);
if (value !== POPUP_RESULT.AFFIRMATIVE) {
return;
}
const mesBlock = $(this).closest('.mes');
const mesId = mesBlock.attr('mesid');
const message = chat[mesId];
delete message.extra.image;
delete message.extra.inline_image;
mesBlock.find('.mes_img_container').removeClass('img_extra');
mesBlock.find('.mes_img').attr('src', '');
await saveChatConditional();
}
/**
* Deletes file from the server.
* @param {string} url Path to the file on the server
* @returns {Promise<boolean>} True if file was deleted, false otherwise.
*/
async function deleteFileFromServer(url) {
try {
const result = await fetch('/api/files/delete', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({ path: url }),
});
if (!result.ok) {
const error = await result.text();
throw new Error(error);
}
return true;
} catch (error) {
toastr.error(String(error), 'Could not delete file');
console.error('Could not delete file', error);
return false;
}
}
/**
* Opens file attachment in a modal.
* @param {FileAttachment} attachment File attachment
*/
async function openFilePopup(attachment) {
const fileText = attachment.text || (await getFileAttachment(attachment.url));
const modalTemplate = $('<div><pre><code></code></pre></div>');
modalTemplate.find('code').addClass('txt').text(fileText);
modalTemplate.addClass('file_modal').addClass('textarea_compact').addClass('fontsize90p');
addCopyToCodeBlocks(modalTemplate);
callGenericPopup(modalTemplate, POPUP_TYPE.TEXT, '', { wide: true, large: true });
}
/**
* Deletes an attachment from the server and the chat.
* @param {FileAttachment} attachment Attachment to delete
* @param {string} source Source of the attachment
* @param {function} callback Callback function
* @returns {Promise<void>} A promise that resolves when the attachment is deleted.
*/
async function deleteAttachment(attachment, source, callback) {
const confirm = await callGenericPopup('Are you sure you want to delete this attachment?', POPUP_TYPE.CONFIRM);
if (confirm !== POPUP_RESULT.AFFIRMATIVE) {
return;
}
ensureAttachmentsExist();
switch (source) {
case 'global':
extension_settings.attachments = extension_settings.attachments.filter((a) => a.url !== attachment.url);
saveSettingsDebounced();
break;
case 'chat':
chat_metadata.attachments = chat_metadata.attachments.filter((a) => a.url !== attachment.url);
saveMetadataDebounced();
break;
case 'character':
characters[this_chid].data.extensions.attachments = characters[this_chid].data.extensions.attachments.filter((a) => a.url !== attachment.url);
await writeExtensionField(this_chid, 'attachments', characters[this_chid].data.extensions.attachments);
break;
}
await deleteFileFromServer(attachment.url);
callback();
}
/**
* Opens the attachment manager.
*/
async function openAttachmentManager() {
/**
*
* @param {FileAttachment[]} attachments List of attachments
* @param {string} source Source of the attachments
*/
async function renderList(attachments, source) {
const sources = {
[ATTACHMENT_SOURCE.GLOBAL]: '.globalAttachmentsList',
[ATTACHMENT_SOURCE.CHARACTER]: '.characterAttachmentsList',
[ATTACHMENT_SOURCE.CHAT]: '.chatAttachmentsList',
};
template.find(sources[source]).empty();
for (const attachment of attachments) {
const attachmentTemplate = template.find('.attachmentListItemTemplate .attachmentListItem').clone();
attachmentTemplate.find('.attachmentListItemName').text(attachment.name);
attachmentTemplate.find('.attachmentListItemSize').text(humanFileSize(attachment.size));
attachmentTemplate.find('.viewAttachmentButton').on('click', () => openFilePopup(attachment));
attachmentTemplate.find('.deleteAttachmentButton').on('click', () => deleteAttachment(attachment, source, renderAttachments));
template.find(sources[source]).append(attachmentTemplate);
}
}
async function renderAttachments() {
/** @type {FileAttachment[]} */
const globalAttachments = extension_settings.attachments ?? [];
/** @type {FileAttachment[]} */
const chatAttachments = chat_metadata.attachments ?? [];
/** @type {FileAttachment[]} */
const characterAttachments = characters[this_chid]?.data?.extensions?.attachments ?? [];
await renderList(globalAttachments, ATTACHMENT_SOURCE.GLOBAL);
await renderList(chatAttachments, ATTACHMENT_SOURCE.CHAT);
await renderList(characterAttachments, ATTACHMENT_SOURCE.CHARACTER);
const isNotCharacter = this_chid === undefined || selected_group;
const isNotInChat = getCurrentChatId() === undefined;
template.find('.characterAttachmentsBlock').toggle(!isNotCharacter);
template.find('.chatAttachmentsBlock').toggle(!isNotInChat);
}
const hasFandomPlugin = await isFandomPluginAvailable();
const template = $(await renderExtensionTemplateAsync('attachments', 'manager', {}));
template.find('.scrapeWebpageButton').on('click', function () {
openWebpageScraper(String($(this).data('attachment-manager-target')), renderAttachments);
});
template.find('.scrapeFandomButton').toggle(hasFandomPlugin).on('click', function () {
openFandomScraper(String($(this).data('attachment-manager-target')), renderAttachments);
});
template.find('.uploadFileButton').on('click', function () {
openFileUploader(String($(this).data('attachment-manager-target')), renderAttachments);
});
await renderAttachments();
callGenericPopup(template, POPUP_TYPE.TEXT, '', { wide: true, large: true, okButton: 'Close' });
}
/**
* Scrapes a webpage for attachments.
* @param {string} target Target for the attachment
* @param {function} callback Callback function
*/
async function openWebpageScraper(target, callback) {
const template = $(await renderExtensionTemplateAsync('attachments', 'web-scrape', {}));
const link = await callGenericPopup(template, POPUP_TYPE.INPUT, '', { wide: false, large: false });
if (!link) {
return;
}
try {
if (!isValidUrl(link)) {
toastr.error('Invalid URL');
return;
}
const result = await fetch('/api/serpapi/visit', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({ url: link }),
});
const blob = await result.blob();
const domain = new URL(link).hostname;
const timestamp = Date.now();
const title = await getTitleFromHtmlBlob(blob) || 'webpage';
const file = new File([blob], `${title} - ${domain} - ${timestamp}.html`, { type: 'text/html' });
await uploadFileAttachmentToServer(file, target);
callback();
} catch (error) {
console.error('Scraping failed', error);
toastr.error('Check browser console for details.', 'Scraping failed');
}
}
/**
*
* @param {Blob} blob Blob of the HTML file
* @returns {Promise<string>} Title of the HTML file
*/
async function getTitleFromHtmlBlob(blob) {
const text = await blob.text();
const titleMatch = text.match(/<title>(.*?)<\/title>/i);
return titleMatch ? titleMatch[1] : '';
}
/**
* Scrapes a Fandom page for attachments.
* @param {string} target Target for the attachment
* @param {function} callback Callback function
*/
async function openFandomScraper(target, callback) {
toastr.info('Not implemented yet', target);
callback();
}
/**
* Uploads a file attachment.
* @param {string} target File upload target
* @param {function} callback Callback function
*/
async function openFileUploader(target, callback) {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.txt, .md, .pdf, .html, .htm';
fileInput.onchange = async function () {
const file = fileInput.files[0];
if (!file) return;
await uploadFileAttachmentToServer(file, target);
callback();
};
fileInput.click();
}
/**
* Uploads a file attachment to the server.
* @param {File} file File to upload
* @param {string} target Target for the attachment
* @returns
*/
async function uploadFileAttachmentToServer(file, target) {
const isValid = await validateFile(file);
if (!isValid) {
return;
}
let base64Data = await getBase64Async(file);
const slug = getStringHash(file.name);
const uniqueFileName = `${Date.now()}_${slug}.txt`;
if (isConvertible(file.type)) {
try {
const converter = converters[file.type];
const fileText = await converter(file);
base64Data = window.btoa(unescape(encodeURIComponent(fileText)));
} catch (error) {
toastr.error(String(error), 'Could not convert file');
console.error('Could not convert file', error);
}
} else {
const fileText = await file.text();
base64Data = window.btoa(unescape(encodeURIComponent(fileText)));
}
const fileUrl = await uploadFileAttachment(uniqueFileName, base64Data);
if (!fileUrl) {
return;
}
const attachment = {
url: fileUrl,
size: file.size,
name: file.name,
};
ensureAttachmentsExist();
switch (target) {
case ATTACHMENT_SOURCE.GLOBAL:
extension_settings.attachments.push(attachment);
saveSettingsDebounced();
break;
case ATTACHMENT_SOURCE.CHAT:
chat_metadata.attachments.push(attachment);
saveMetadataDebounced();
break;
case ATTACHMENT_SOURCE.CHARACTER:
characters[this_chid].data.extensions.attachments.push(attachment);
await writeExtensionField(this_chid, 'attachments', characters[this_chid].data.extensions.attachments);
break;
}
}
function ensureAttachmentsExist() {
if (!Array.isArray(extension_settings.attachments)) {
extension_settings.attachments = [];
}
if (!Array.isArray(chat_metadata.attachments)) {
chat_metadata.attachments = [];
}
if (this_chid !== undefined && characters[this_chid]) {
if (!characters[this_chid].data) {
characters[this_chid].data = {};
}
if (!characters[this_chid].data.extensions) {
characters[this_chid].data.extensions = {};
}
if (!Array.isArray(characters[this_chid]?.data?.extensions?.attachments)) {
characters[this_chid].data.extensions.attachments = [];
}
}
}
/**
* Probes the server to check if the Fandom plugin is available.
* @returns {Promise<boolean>} True if the plugin is available, false otherwise.
*/
async function isFandomPluginAvailable() {
try {
const result = await fetch('/api/plugins/fandom/probe', {
method: 'POST',
headers: getRequestHeaders(),
});
return result.ok;
} catch (error) {
console.debug('Could not probe Fandom plugin', error);
return false;
}
}
jQuery(function () {
$(document).on('click', '.mes_hide', async function () {
const messageBlock = $(this).closest('.mes');
@ -506,6 +886,11 @@ jQuery(function () {
$('#file_form_input').trigger('click');
});
// Do not change. #manageAttachments is added by extension.
$(document).on('click', '#manageAttachments', function () {
openAttachmentManager();
});
$(document).on('click', '.mes_embed', function () {
const messageBlock = $(this).closest('.mes');
const messageId = Number(messageBlock.attr('mesid'));
@ -597,6 +982,9 @@ jQuery(function () {
reloadCurrentChat();
});
$(document).on('click', '.mes_img_enlarge', enlargeMessageImage);
$(document).on('click', '.mes_img_delete', deleteMessageImage);
$('#file_form_input').on('change', onFileAttach);
$('#file_form').on('reset', function () {
$('#file_form').addClass('displayNone');

View File

@ -145,6 +145,7 @@ const extension_settings = {
variables: {
global: {},
},
attachments: [],
};
let modules = [];

View File

@ -0,0 +1,9 @@
<div id="attachFile" class="list-group-item flex-container flexGap5" title="Attach a file or image to a current chat.">
<div class="fa-fw fa-solid fa-paperclip extensionsMenuExtensionButton"></div>
<span data-i18n="Attach a File">Attach a File</span>
</div>
<div id="manageAttachments" class="list-group-item flex-container flexGap5" title="View global, character, or data files.">
<div class="fa-fw fa-solid fa-book-open-reader extensionsMenuExtensionButton"></div>
<span data-i18n="Open Data Bank">Open Data Bank</span>
</div>

View File

@ -0,0 +1,6 @@
import { renderExtensionTemplateAsync } from '../../extensions.js';
jQuery(async () => {
const buttons = await renderExtensionTemplateAsync('attachments', 'buttons', {});
$('#extensionsMenu').prepend(buttons);
});

View File

@ -0,0 +1,116 @@
<div class="wide100p paddingTopBot5">
<h2 class="marginBot5">
<span data-i18n="Data Bank">
Data Bank
</span>
</h2>
<div data-i18n="These files will be available for extensions that support attachments (e.g. Vector Storage).">
These files will be available for extensions that support attachments (e.g. Vector Storage).
</div>
<div data-i18n="Supported file types: Plain Text, PDF, Markdown, HTML." class="marginTopBot5">
Supported file types: Plain Text, PDF, Markdown, HTML.
</div>
<div class="justifyLeft globalAttachmentsBlock marginBot10">
<h3 class="margin0 title_restorable">
<span data-i18n="Global Attachments">
Global Attachments
</span>
<div class="flex-container flexGap10">
<div class="scrapeWebpageButton menu_button_icon menu_button" data-attachment-manager-target="global" title="Download a page from the web.">
<i class="fa-fw fa-solid fa-globe"></i>
<span data-i18n="From Web">
From Web
</span>
</div>
<div class="scrapeFandomButton menu_button_icon menu_button" data-attachment-manager-target="global" title="Download a page from the Fandom wiki.">
<i class="fa-fw fa-solid fa-fan"></i>
<span data-i18n="From Fandom">
From Fandom
</span>
</div>
<div class="uploadFileButton menu_button_icon menu_button" data-attachment-manager-target="global" title="Upload a file from your computer.">
<i class="fa-fw fa-solid fa-upload"></i>
<span data-i18n="From File">
From File
</span>
</div>
</div>
</h3>
<small data-i18n="These files are available for all characters in all chats.">
These files are available for all characters in all chats.
</small>
<div class="globalAttachmentsList attachmentsList"></div>
<hr>
</div>
<div class="justifyLeft characterAttachmentsBlock marginBot10">
<h3 class="margin0 title_restorable">
<span data-i18n="Character Attachments">Character Attachments</span>
<div class="flex-container flexGap10">
<div class="scrapeWebpageButton menu_button_icon menu_button" data-attachment-manager-target="character" title="Download a page from the web.">
<i class="fa-fw fa-solid fa-globe"></i>
<span data-i18n="From Web">
From Web
</span>
</div>
<div class="scrapeFandomButton menu_button_icon menu_button" data-attachment-manager-target="character" title="Download a page from the Fandom wiki.">
<i class="fa-fw fa-solid fa-fan"></i>
<span data-i18n="From Fandom">
From Fandom
</span>
</div>
<div class="uploadFileButton menu_button_icon menu_button" data-attachment-manager-target="character" title="Upload a file from your computer.">
<i class="fa-fw fa-solid fa-upload"></i>
<span data-i18n="From File">
From File
</span>
</div>
</div>
</h3>
<small data-i18n="These files are available the current character in all chats they are in.">
These files are available the current character in all chats they are in.
</small>
<div class="characterAttachmentsList attachmentsList"></div>
<hr>
</div>
<div class="justifyLeft chatAttachmentsBlock marginBot10">
<h3 class="margin0 title_restorable">
<span data-i18n="Chat Attachments">
Chat Attachments
</span>
<div class="flex-container flexGap10">
<div class="scrapeWebpageButton menu_button_icon menu_button" data-attachment-manager-target="chat" title="Download a page from the web.">
<i class="fa-fw fa-solid fa-globe"></i>
<span data-i18n="From Web">
From Web
</span>
</div>
<div class="scrapeFandomButton menu_button_icon menu_button" data-attachment-manager-target="chat" title="Download a page from the Fandom wiki.">
<i class="fa-fw fa-solid fa-fan"></i>
<span data-i18n="From Fandom">
From Fandom
</span>
</div>
<div class="uploadFileButton menu_button_icon menu_button" data-attachment-manager-target="chat" title="Upload a file from your computer.">
<i class="fa-fw fa-solid fa-upload"></i>
<span data-i18n="From File">
From File
</span>
</div>
</div>
</h3>
<small data-i18n="These files are available to all characters in the current chat.">
These files are available to all characters in the current chat.
</small>
<div class="chatAttachmentsList attachmentsList"></div>
</div>
<div class="attachmentListItemTemplate template_element">
<div class="attachmentListItem flex-container alignItemsCenter flexGap10">
<div class="attachmentFileIcon fa-solid fa-file-alt"></div>
<div class="attachmentListItemName flex1"></div>
<small class="attachmentListItemSize"></small>
<div class="viewAttachmentButton right_menu_button fa-solid fa-magnifying-glass" title="View attachment content"></div>
<div class="deleteAttachmentButton right_menu_button fa-solid fa-trash" title="Delete attachment"></div>
</div>
</div>
</div>

View File

@ -0,0 +1,11 @@
{
"display_name": "Chat Attachments",
"loading_order": 3,
"requires": [],
"optional": [],
"js": "index.js",
"css": "style.css",
"author": "Cohee1207",
"version": "1.0.0",
"homePage": "https://github.com/SillyTavern/SillyTavern"
}

View File

@ -0,0 +1,20 @@
.attachmentsList:empty {
width: 100%;
height: 100%;
}
.attachmentsList:empty::before {
display: flex;
align-items: center;
justify-content: center;
content: "No data";
font-weight: bolder;
width: 100%;
height: 100%;
opacity: 0.8;
min-height: 3rem;
}
.attachmentListItem {
padding: 10px;
}

View File

@ -0,0 +1,3 @@
<div data-i18n="Enter a web address to scrape:">
Enter a web address to scrape:
</div>

View File

@ -270,14 +270,8 @@ jQuery(function () {
<div class="fa-solid fa-image extensionsMenuExtensionButton"></div>
Generate Caption
</div>`);
const attachFileButton = $(`
<div id="attachFile" class="list-group-item flex-container flexGap5">
<div class="fa-solid fa-paperclip extensionsMenuExtensionButton"></div>
Attach a File
</div>`);
$('#extensionsMenu').prepend(sendButton);
$('#extensionsMenu').prepend(attachFileButton);
$(sendButton).on('click', () => {
const hasCaptionModule =
(modules.includes('caption') && extension_settings.caption.source === 'extras') ||

View File

@ -1,4 +1,5 @@
const path = require('path');
const fs = require('fs');
const writeFileSyncAtomic = require('write-file-atomic').sync;
const express = require('express');
const router = express.Router();
@ -24,6 +25,7 @@ router.post('/upload', jsonParser, async (request, response) => {
const pathToUpload = path.join(request.user.directories.files, request.body.name);
writeFileSyncAtomic(pathToUpload, request.body.data, 'base64');
const url = clientRelativePath(request.user.directories.root, pathToUpload);
console.log(`Uploaded file: ${url} from ${request.user.profile.handle}`);
return response.send({ path: url });
} catch (error) {
console.log(error);
@ -31,4 +33,28 @@ router.post('/upload', jsonParser, async (request, response) => {
}
});
router.post('/delete', jsonParser, async (request, response) => {
try {
if (!request.body.path) {
return response.status(400).send('No path specified');
}
const pathToDelete = path.join(request.user.directories.root, request.body.path);
if (!pathToDelete.startsWith(request.user.directories.files)) {
return response.status(400).send('Invalid path');
}
if (!fs.existsSync(pathToDelete)) {
return response.status(404).send('File not found');
}
fs.rmSync(pathToDelete);
console.log(`Deleted file: ${request.body.path} from ${request.user.profile.handle}`);
return response.sendStatus(200);
} catch (error) {
console.log(error);
return response.sendStatus(500);
}
});
module.exports = { router };

View File

@ -5,10 +5,10 @@ const { jsonParser } = require('../express-common');
const router = express.Router();
// Cosplay as Firefox
// Cosplay as Chrome
const visitHeaders = {
'Accept': 'text/html',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:120.0) Gecko/20100101 Firefox/120.0',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive',