Merge remote-tracking branch 'upstream/staging' into staging

This commit is contained in:
DonMoralez 2023-12-15 20:15:29 +02:00
commit d6e5ceaf93
22 changed files with 190 additions and 190 deletions

View File

@ -31,10 +31,9 @@ RUN \
echo "*** Create symbolic links to config directory ***" && \
for R in $RESOURCES; do ln -s "../config/$R" "public/$R"; done || true && \
\
rm -f "config.yaml" "public/settings.json" "public/css/bg_load.css" || true && \
rm -f "config.yaml" "public/settings.json" || true && \
ln -s "./config/config.yaml" "config.yaml" || true && \
ln -s "../config/settings.json" "public/settings.json" || true && \
ln -s "../../config/bg_load.css" "public/css/bg_load.css" || true && \
mkdir "config" || true
# Cleanup unnecessary files

View File

@ -1 +0,0 @@
#bg1 {background-image: url(../backgrounds/__transparent.png);}

View File

@ -19,11 +19,6 @@ if [ ! -e "config/settings.json" ]; then
cp -r "default/settings.json" "config/settings.json"
fi
if [ ! -e "config/bg_load.css" ]; then
echo "Resource not found, copying from defaults: bg_load.css"
cp -r "default/bg_load.css" "config/bg_load.css"
fi
CONFIG_FILE="config.yaml"
echo "Starting with the following config:"

9
package-lock.json generated
View File

@ -20,7 +20,6 @@
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"csrf-csrf": "^2.2.3",
"device-detector-js": "^3.0.3",
"express": "^4.18.2",
"form-data": "^4.0.0",
"google-translate-api-browser": "^3.0.1",
@ -1777,14 +1776,6 @@
"node": ">=8"
}
},
"node_modules/device-detector-js": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/device-detector-js/-/device-detector-js-3.0.3.tgz",
"integrity": "sha512-jM89LJAvP6uOd84at8OlD9dWP8KeYCCHUde0RT0HQo/stdoRH4b54Xl/fntx2nEXCmqiFhmo+/cJetS2VGUHPw==",
"engines": {
"node": ">= 8.11.4"
}
},
"node_modules/digest-fetch": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/digest-fetch/-/digest-fetch-1.3.0.tgz",

View File

@ -10,7 +10,6 @@
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"csrf-csrf": "^2.2.3",
"device-detector-js": "^3.0.3",
"express": "^4.18.2",
"form-data": "^4.0.0",
"google-translate-api-browser": "^3.0.1",

View File

@ -107,7 +107,6 @@ function addMissingConfigValues() {
function createDefaultFiles() {
const files = {
settings: './public/settings.json',
bg_load: './public/css/bg_load.css',
config: './config.yaml',
user: './public/css/user.css',
};
@ -168,6 +167,29 @@ function copyWasmFiles() {
}
}
/**
* Moves the custom background into settings.json.
*/
function migrateBackground() {
if (!fs.existsSync('./public/css/bg_load.css')) return;
const bgCSS = fs.readFileSync('./public/css/bg_load.css', 'utf-8');
const bgMatch = /url\('([^']*)'\)/.exec(bgCSS);
if (!bgMatch) return;
const bgFilename = bgMatch[1].replace('../backgrounds/', '');
const settings = fs.readFileSync('./public/settings.json', 'utf-8');
const settingsJSON = JSON.parse(settings);
if (Object.hasOwn(settingsJSON, 'background')) {
console.log(color.yellow('Both bg_load.css and the "background" setting exist. Please delete bg_load.css manually.'));
return;
}
settingsJSON.background = { name: bgFilename, url: `url('backgrounds/${bgFilename}')` };
fs.writeFileSync('./public/settings.json', JSON.stringify(settingsJSON, null, 4));
fs.rmSync('./public/css/bg_load.css');
}
try {
// 0. Convert config.conf to config.yaml
convertConfig();
@ -177,6 +199,8 @@ try {
copyWasmFiles();
// 3. Add missing config values
addMissingConfigValues();
// 4. Migrate bg_load.css to settings.json
migrateBackground();
} catch (error) {
console.error(error);
}

View File

@ -1,4 +1,4 @@
#loader {
#loader, #preloader {
position: fixed;
margin: 0;
padding: 0;
@ -22,4 +22,4 @@
#load-spinner {
transition: all 300ms ease-out;
opacity: 1;
}
}

View File

@ -61,7 +61,6 @@
<link rel="stylesheet" type="text/css" href="css/extensions-panel.css">
<link rel="stylesheet" type="text/css" href="css/select2-overrides.css">
<link rel="stylesheet" type="text/css" href="css/mobile-styles.css">
<link rel="stylesheet" href="css/bg_load.css">
<link rel="stylesheet" type="text/css" href="css/user.css">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<script type="module" src="scripts/i18n.js"></script>
@ -91,6 +90,7 @@
</head>
<body class="no-blur">
<div id="preloader"></div>
<div id="bg_custom"></div>
<div id="bg1"></div>
<div id="character_context_menu" class="hidden">

14
public/lib/bowser.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -169,7 +169,6 @@ import {
import { EventEmitter } from './lib/eventemitter.js';
import { markdownExclusionExt } from './scripts/showdown-exclusion.js';
import { NOTE_MODULE_NAME, initAuthorsNote, metadata_keys, setFloatingPrompt, shouldWIAddPrompt } from './scripts/authors-note.js';
import { getDeviceInfo } from './scripts/RossAscends-mods.js';
import { registerPromptManagerMigration } from './scripts/PromptManager.js';
import { getRegexedString, regex_placement } from './scripts/extensions/regex/engine.js';
import { FILTER_TYPES, FilterHelper } from './scripts/filters.js';
@ -187,7 +186,7 @@ import {
import { applyLocale, initLocales } from './scripts/i18n.js';
import { getFriendlyTokenizerName, getTokenCount, getTokenizerModel, initTokenizers, saveTokenCache } from './scripts/tokenizers.js';
import { createPersona, initPersonas, selectCurrentPersona, setPersonaDescription } from './scripts/personas.js';
import { getBackgrounds, initBackgrounds } from './scripts/backgrounds.js';
import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js';
import { hideLoader, showLoader } from './scripts/loader.js';
import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js';
import { loadMancerModels } from './scripts/mancer-settings.js';
@ -266,8 +265,9 @@ export {
countOccurrences,
};
// Cohee: Uncomment when we decide to use loader
showLoader();
// Yoink preloader entirely; it only exists to cover up unstyled content while loading JS
document.getElementById('preloader').remove();
// Allow target="_blank" in links
DOMPurify.addHook('afterSanitizeAttributes', function (node) {
@ -5673,6 +5673,9 @@ async function getSettings() {
// Load character tags
loadTagsSettings(settings);
// Load background
loadBackgroundSettings(settings);
// Allow subscribers to mutate settings
eventSource.emit(event_types.SETTINGS_LOADED_AFTER, settings);
@ -5755,6 +5758,9 @@ async function saveSettings(type) {
console.warn('Settings not ready, aborting save');
return;
}
console.log(background_settings);
//console.log('Entering settings with name1 = '+name1);
return jQuery.ajax({
@ -5784,6 +5790,7 @@ async function saveSettings(type) {
nai_settings: nai_settings,
kai_settings: kai_settings,
oai_settings: oai_settings,
background: background_settings,
}, null, 4),
beforeSend: function () { },
cache: false,
@ -6834,8 +6841,7 @@ function openCharacterWorldPopup() {
template.find('.character_name').text(name);
// Not needed on mobile
const deviceInfo = getDeviceInfo();
if (deviceInfo && deviceInfo.device.type === 'desktop') {
if (!isMobile()) {
$(extraSelect).select2({
width: '100%',
placeholder: 'No auxillary Lorebooks set. Click here to select.',
@ -7812,7 +7818,7 @@ function addDebugFunctions() {
jQuery(async function () {
if (isMobile() === true) {
if (isMobile()) {
console.debug('hiding movingUI and sheldWidth toggles for mobile');
$('#sheldWidthToggleBlock').hide();
$('#movingUIModeCheckBlock').hide();

View File

@ -36,6 +36,7 @@ import { chat_completion_sources, oai_settings } from './openai.js';
import { getTokenCount } from './tokenizers.js';
import { textgen_types, textgenerationwebui_settings as textgen_settings } from './textgen-settings.js';
import Bowser from '../lib/bowser.min.js';
var RPanelPin = document.getElementById('rm_button_panel_pin');
var LPanelPin = document.getElementById('lm_button_panel_pin');
@ -98,43 +99,22 @@ export function humanizeGenTime(total_gen_time) {
return time_spent;
}
let parsedUA = null;
try {
parsedUA = Bowser.parse(navigator.userAgent);
} catch {
// In case the user agent is an empty string or Bowser can't parse it for some other reason
}
/**
* Checks if the device is a mobile device.
* @returns {boolean} - True if the device is a mobile device, false otherwise.
*/
export function isMobile() {
const mobileTypes = ['smartphone', 'tablet', 'phablet', 'feature phone', 'portable media player'];
const deviceInfo = getDeviceInfo();
const mobileTypes = ['mobile', 'tablet'];
return mobileTypes.includes(deviceInfo?.device?.type);
}
/**
* Loads device info from the server. Caches the result in sessionStorage.
* @returns {object} - The device info object.
*/
export function getDeviceInfo() {
let deviceInfo = null;
if (sessionStorage.getItem('deviceInfo')) {
deviceInfo = JSON.parse(sessionStorage.getItem('deviceInfo'));
} else {
$.ajax({
url: '/deviceinfo',
dataType: 'json',
async: false,
cache: true,
success: function (result) {
sessionStorage.setItem('deviceInfo', JSON.stringify(result));
deviceInfo = result;
},
error: function () {
console.log('Couldn\'t load device info. Defaulting to desktop');
deviceInfo = { device: { type: 'desktop' } };
},
});
}
return deviceInfo;
return mobileTypes.includes(parsedUA?.platform?.type);
}
function shouldSendOnEnter() {
@ -431,8 +411,7 @@ function RA_autoconnect(PrevApi) {
}
function OpenNavPanels() {
const deviceInfo = getDeviceInfo();
if (deviceInfo && deviceInfo.device.type === 'desktop') {
if (!isMobile()) {
//auto-open R nav if locked and previously open
if (LoadLocalBool('NavLockOn') == true && LoadLocalBool('NavOpened') == true) {
//console.log("RA -- clicking right nav to open");
@ -508,7 +487,7 @@ export function dragElement(elmnt) {
|| Number((String(target.height).replace('px', ''))) < 50
|| Number((String(target.width).replace('px', ''))) < 50
|| power_user.movingUI === false
|| isMobile() === true
|| isMobile()
) {
console.debug('aborting mutator');
return;
@ -716,7 +695,7 @@ export function dragElement(elmnt) {
}
export async function initMovingUI() {
if (isMobile() === false && power_user.movingUI === true) {
if (!isMobile() && power_user.movingUI === true) {
console.debug('START MOVING UI');
dragElement($('#sheld'));
dragElement($('#left-nav-panel'));

View File

@ -1,4 +1,4 @@
import { callPopup, chat_metadata, eventSource, event_types, generateQuietPrompt, getCurrentChatId, getRequestHeaders, getThumbnailUrl } from '../script.js';
import { callPopup, chat_metadata, eventSource, event_types, generateQuietPrompt, getCurrentChatId, getRequestHeaders, getThumbnailUrl, saveSettingsDebounced } from '../script.js';
import { saveMetadataDebounced } from './extensions.js';
import { registerSlashCommand } from './slash-commands.js';
import { stringFormat } from './utils.js';
@ -6,6 +6,19 @@ import { stringFormat } from './utils.js';
const BG_METADATA_KEY = 'custom_background';
const LIST_METADATA_KEY = 'chat_backgrounds';
export let background_settings = {
name: '__transparent.png',
url: generateUrlParameter('__transparent.png', false),
};
export function loadBackgroundSettings(settings) {
let backgroundSettings = settings.background;
if (!backgroundSettings || !backgroundSettings.name || !backgroundSettings.url) {
backgroundSettings = background_settings;
}
setBackground(backgroundSettings.name, backgroundSettings.url);
}
/**
* Sets the background for the current chat and adds it to the list of custom backgrounds.
* @param {{url: string, path:string}} backgroundInfo
@ -141,9 +154,8 @@ function onSelectBackgroundClick() {
saveBackgroundMetadata(relativeBgImage);
setCustomBackground();
highlightLockedBackground();
} else {
highlightLockedBackground();
}
highlightLockedBackground();
const customBg = window.getComputedStyle(document.getElementById('bg_custom')).backgroundImage;
@ -157,8 +169,7 @@ function onSelectBackgroundClick() {
// Fetching to browser memory to reduce flicker
fetch(backgroundUrl).then(() => {
$('#bg1').css('background-image', relativeBgImage);
setBackground(bgFile);
setBackground(bgFile, relativeBgImage);
}).catch(() => {
console.log('Background could not be set: ' + backgroundUrl);
});
@ -333,7 +344,7 @@ export async function getBackgrounds() {
'': '',
}),
});
if (response.ok === true) {
if (response.ok) {
const getData = await response.json();
//background = getData;
//console.log(getData.length);
@ -346,7 +357,7 @@ export async function getBackgrounds() {
}
/**
* Gets the URL of the background
* Gets the CSS URL of the background
* @param {Element} block
* @returns {string} URL of the background
*/
@ -354,6 +365,10 @@ function getUrlParameter(block) {
return $(block).closest('.bg_example').data('url');
}
function generateUrlParameter(bg, isCustom) {
return isCustom ? `url("${encodeURI(bg)}")` : `url("${getBackgroundPath(bg)}")`;
}
/**
* Instantiates a background template
* @param {string} bg Path to background
@ -363,7 +378,7 @@ function getUrlParameter(block) {
function getBackgroundFromTemplate(bg, isCustom) {
const template = $('#background_template .bg_example').clone();
const thumbPath = isCustom ? bg : getThumbnailUrl('bg', bg);
const url = isCustom ? `url("${encodeURI(bg)}")` : `url("${getBackgroundPath(bg)}")`;
const url = generateUrlParameter(bg, isCustom);
const title = isCustom ? bg.split('/').pop() : bg;
const friendlyTitle = title.slice(0, title.lastIndexOf('.'));
template.attr('title', title);
@ -375,26 +390,11 @@ function getBackgroundFromTemplate(bg, isCustom) {
return template;
}
async function setBackground(bg) {
jQuery.ajax({
type: 'POST', //
url: '/api/backgrounds/set', //
data: JSON.stringify({
bg: bg,
}),
beforeSend: function () {
},
cache: false,
dataType: 'json',
contentType: 'application/json',
//processData: false,
success: function (html) { },
error: function (jqXHR, exception) {
console.log(exception);
console.log(jqXHR);
},
});
async function setBackground(bg, url) {
$('#bg1').css('background-image', url);
background_settings.name = bg;
background_settings.url = url;
saveSettingsDebounced();
}
async function delBackground(bg) {
@ -435,8 +435,7 @@ function uploadBackground(formData) {
contentType: false,
processData: false,
success: async function (bg) {
setBackground(bg);
$('#bg1').css('background-image', `url("${getBackgroundPath(bg)}"`);
setBackground(bg, generateUrlParameter(bg, false));
await getBackgrounds();
highlightNewBackground(bg);
},

View File

@ -9,7 +9,7 @@ import {
} from '../script.js';
import { SECRET_KEYS, writeSecret } from './secrets.js';
import { delay } from './utils.js';
import { getDeviceInfo } from './RossAscends-mods.js';
import { isMobile } from './RossAscends-mods.js';
import { autoSelectInstructPreset } from './instruct-mode.js';
export {
@ -41,7 +41,7 @@ const getRequestArgs = () => ({
},
});
async function getWorkers() {
async function getWorkers(workerType) {
const response = await fetch('https://horde.koboldai.net/api/v2/workers?type=text', getRequestArgs());
const data = await response.json();
return data;
@ -303,8 +303,7 @@ jQuery(function () {
$('#horde_kudos').on('click', showKudos);
// Not needed on mobile
const deviceInfo = getDeviceInfo();
if (deviceInfo && deviceInfo.device.type === 'desktop') {
if (!isMobile()) {
$('#horde_model').select2({
width: '100%',
placeholder: 'Select Horde models',

View File

@ -1,5 +1,5 @@
import { setGenerationParamsFromPreset } from '../script.js';
import { getDeviceInfo } from './RossAscends-mods.js';
import { isMobile } from './RossAscends-mods.js';
import { textgenerationwebui_settings as textgen_settings } from './textgen-settings.js';
let models = [];
@ -52,8 +52,7 @@ function getMancerModelTemplate(option) {
jQuery(function () {
$('#mancer_model').on('change', onMancerModelSelect);
const deviceInfo = getDeviceInfo();
if (deviceInfo && deviceInfo.device.type === 'desktop') {
if (!isMobile()) {
$('#mancer_model').select2({
placeholder: 'Select a model',
searchInputPlaceholder: 'Search models...',

View File

@ -57,7 +57,7 @@ const MAX_CONTEXT_UNLOCKED = 200 * 1024;
const MAX_RESPONSE_UNLOCKED = 16 * 1024;
const unlockedMaxContextStep = 512;
const maxContextMin = 512;
const maxContextStep = 256;
const maxContextStep = 64;
const defaultStoryString = '{{#if system}}{{system}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}\'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}';
const defaultExampleSeparator = '***';
@ -1530,7 +1530,7 @@ async function loadCharListState() {
}
function loadMovingUIState() {
if (isMobile() === false
if (!isMobile()
&& power_user.movingUIState
&& power_user.movingUI === true) {
console.debug('loading movingUI state');

View File

@ -637,9 +637,19 @@ function lenValuesCallback(value) {
return parsedValue.length;
}
function randValuesCallback(from, to) {
function randValuesCallback(from, to, args) {
const range = to - from;
return from + Math.random() * range;
const value = from + Math.random() * range;
if (args.round == 'round') {
return Math.round(value);
}
if (args.round == 'ceil') {
return Math.ceil(value);
}
if (args.round == 'floor') {
return Math.floor(value);
}
return value;
}
export function registerVariableCommands() {
@ -673,5 +683,5 @@ export function registerVariableCommands() {
registerSlashCommand('sqrt', (_, value) => sqrtValuesCallback(value), [], '<span class="monospace">(a)</span> performs a square root operation of a value and passes the result down the pipe, can use variable names, e.g. <tt>/sqrt i</tt>', true, true);
registerSlashCommand('round', (_, value) => roundValuesCallback(value), [], '<span class="monospace">(a)</span> rounds a value and passes the result down the pipe, can use variable names, e.g. <tt>/round i</tt>', true, true);
registerSlashCommand('len', (_, value) => lenValuesCallback(value), [], '<span class="monospace">(a)</span> gets the length of a value and passes the result down the pipe, can use variable names, e.g. <tt>/len i</tt>', true, true);
registerSlashCommand('rand', (args, value) => randValuesCallback(Number(args.from ?? 0), Number(args.to ?? (value.length ? value : 1))), [], '<span class="monospace">(from=number=0 to=number=1)</span> returns a random number between from and to, e.g. <tt>/rand</tt> or <tt>/rand 10</tt> or <tt>/rand from=5 to=10</tt>', true, true);
registerSlashCommand('rand', (args, value) => randValuesCallback(Number(args.from ?? 0), Number(args.to ?? (value.length ? value : 1)), args), [], '<span class="monospace">(from=number=0 to=number=1 round=round|ceil|floor)</span> returns a random number between from and to, e.g. <tt>/rand</tt> or <tt>/rand 10</tt> or <tt>/rand from=5 to=10</tt>', true, true);
}

View File

@ -3,7 +3,7 @@ import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile,
import { extension_settings, getContext } from './extensions.js';
import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
import { registerSlashCommand } from './slash-commands.js';
import { getDeviceInfo } from './RossAscends-mods.js';
import { isMobile } from './RossAscends-mods.js';
import { FILTER_TYPES, FilterHelper } from './filters.js';
import { getTokenCount } from './tokenizers.js';
import { power_user } from './power-user.js';
@ -896,8 +896,8 @@ function getWorldEntry(name, data, entry) {
const characterFilter = template.find('select[name="characterFilter"]');
characterFilter.data('uid', entry.uid);
const deviceInfo = getDeviceInfo();
if (deviceInfo && deviceInfo.device.type === 'desktop') {
if (!isMobile()) {
$(characterFilter).select2({
width: '100%',
placeholder: 'All characters will pull from this entry.',
@ -2551,8 +2551,7 @@ jQuery(() => {
$(document).on('click', '.chat_lorebook_button', assignLorebookToChat);
// Not needed on mobile
const deviceInfo = getDeviceInfo();
if (deviceInfo && deviceInfo.device.type === 'desktop') {
if (!isMobile()) {
$('#world_info').select2({
width: '100%',
placeholder: 'No Worlds active. Click here to select.',

View File

@ -419,7 +419,7 @@ hr {
}
#bg1 {
background-image: url('backgrounds/tavern day.jpg');
background-image: url('backgrounds/__transparent.png');
z-index: -3;
}

View File

@ -27,10 +27,7 @@ const responseTime = require('response-time');
// net related library imports
const net = require('net');
const dns = require('dns');
const DeviceDetector = require('device-detector-js');
const fetch = require('node-fetch').default;
const ipaddr = require('ipaddr.js');
const ipMatching = require('ip-matching');
// image processing related library imports
const jimp = require('jimp');
@ -40,7 +37,8 @@ util.inspect.defaultOptions.maxArrayLength = null;
util.inspect.defaultOptions.maxStringLength = null;
// local library imports
const basicAuthMiddleware = require('./src/middleware/basicAuthMiddleware');
const basicAuthMiddleware = require('./src/middleware/basicAuth');
const whitelistMiddleware = require('./src/middleware/whitelist');
const { jsonParser, urlencodedParser } = require('./src/express-common.js');
const contentManager = require('./src/endpoints/content-manager');
const {
@ -105,19 +103,6 @@ app.use(responseTime());
const server_port = process.env.SILLY_TAVERN_PORT || getConfigValue('port', 8000);
const whitelistPath = path.join(process.cwd(), './whitelist.txt');
let whitelist = getConfigValue('whitelist', []);
if (fs.existsSync(whitelistPath)) {
try {
let whitelistTxt = fs.readFileSync(whitelistPath, 'utf-8');
whitelist = whitelistTxt.split('\n').filter(ip => ip).map(ip => ip.trim());
} catch (e) {
// Ignore errors that may occur when reading the whitelist (e.g. permissions)
}
}
const whitelistMode = getConfigValue('whitelistMode', true);
const autorun = (getConfigValue('autorun', false) || cliArguments.autorun) && !cliArguments.ssl;
const listen = getConfigValue('listen', false);
@ -133,48 +118,7 @@ app.use(CORS);
if (listen && getConfigValue('basicAuthMode', false)) app.use(basicAuthMiddleware);
// IP Whitelist //
let knownIPs = new Set();
function getIpFromRequest(req) {
let clientIp = req.connection.remoteAddress;
let ip = ipaddr.parse(clientIp);
// Check if the IP address is IPv4-mapped IPv6 address
if (ip.kind() === 'ipv6' && ip instanceof ipaddr.IPv6 && ip.isIPv4MappedAddress()) {
const ipv4 = ip.toIPv4Address().toString();
clientIp = ipv4;
} else {
clientIp = ip;
clientIp = clientIp.toString();
}
return clientIp;
}
app.use(function (req, res, next) {
const clientIp = getIpFromRequest(req);
if (listen && !knownIPs.has(clientIp)) {
const userAgent = req.headers['user-agent'];
console.log(color.yellow(`New connection from ${clientIp}; User Agent: ${userAgent}\n`));
knownIPs.add(clientIp);
// Write access log
const timestamp = new Date().toISOString();
const log = `${timestamp} ${clientIp} ${userAgent}\n`;
fs.appendFile('access.log', log, (err) => {
if (err) {
console.error('Failed to write access log:', err);
}
});
}
//clientIp = req.connection.remoteAddress.split(':').pop();
if (whitelistMode === true && !whitelist.some(x => ipMatching.matches(clientIp, ipMatching.getMatch(x)))) {
console.log(color.red('Forbidden: Connection attempt from ' + clientIp + '. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.yaml in root of SillyTavern folder.\n'));
return res.status(403).send('<b>Forbidden</b>: Connection attempt from <b>' + clientIp + '</b>. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.yaml in root of SillyTavern folder.');
}
next();
});
app.use(whitelistMiddleware);
// CSRF Protection //
if (!cliArguments.disableCsrf) {
@ -288,12 +232,6 @@ app.use(multer({ dest: UPLOADS_PATH, limits: { fieldSize: 10 * 1024 * 1024 } }).
app.get('/', function (request, response) {
response.sendFile(process.cwd() + '/public/index.html');
});
app.get('/deviceinfo', function (request, response) {
const userAgent = request.header('user-agent');
const deviceDetector = new DeviceDetector();
const deviceInfo = deviceDetector.parse(userAgent || '');
return response.send(deviceInfo);
});
app.get('/version', async function (_, response) {
const data = await getVersion();
response.send(data);
@ -544,7 +482,6 @@ redirect('/updatestats', '/api/stats/update');
// Redirect deprecated backgrounds API endpoints
redirect('/getbackgrounds', '/api/backgrounds/all');
redirect('/setbackground', '/api/backgrounds/set');
redirect('/delbackground', '/api/backgrounds/delete');
redirect('/renamebackground', '/api/backgrounds/rename');
redirect('/downloadbackground', '/api/backgrounds/upload'); // yes, the downloadbackground endpoint actually uploads one

View File

@ -2,7 +2,6 @@ const fs = require('fs');
const path = require('path');
const express = require('express');
const sanitize = require('sanitize-filename');
const writeFileAtomicSync = require('write-file-atomic').sync;
const { jsonParser, urlencodedParser } = require('../express-common');
const { DIRECTORIES, UPLOADS_PATH } = require('../constants');
@ -17,17 +16,6 @@ router.post('/all', jsonParser, function (request, response) {
});
router.post('/set', jsonParser, function (request, response) {
try {
const bg = `#bg1 {background-image: url('../backgrounds/${request.body.bg}');}`;
writeFileAtomicSync('public/css/bg_load.css', bg, 'utf8');
response.send({ result: 'ok' });
} catch (err) {
console.log(err);
response.send(err);
}
});
router.post('/delete', jsonParser, function (request, response) {
if (!request.body) return response.sendStatus(400);

View File

@ -2,7 +2,7 @@
* When applied, this middleware will ensure the request contains the required header for basic authentication and only
* allow access to the endpoint after successful authentication.
*/
const { getConfig } = require('./../util.js');
const { getConfig } = require('../util.js');
const unauthorizedResponse = (res) => {
res.set('WWW-Authenticate', 'Basic realm="SillyTavern", charset="UTF-8"');

View File

@ -0,0 +1,63 @@
const path = require('path');
const fs = require('fs');
const ipaddr = require('ipaddr.js');
const ipMatching = require('ip-matching');
const { color, getConfigValue } = require('../util');
const whitelistPath = path.join(process.cwd(), './whitelist.txt');
let whitelist = getConfigValue('whitelist', []);
let knownIPs = new Set();
const listen = getConfigValue('listen', false);
const whitelistMode = getConfigValue('whitelistMode', true);
if (fs.existsSync(whitelistPath)) {
try {
let whitelistTxt = fs.readFileSync(whitelistPath, 'utf-8');
whitelist = whitelistTxt.split('\n').filter(ip => ip).map(ip => ip.trim());
} catch (e) {
// Ignore errors that may occur when reading the whitelist (e.g. permissions)
}
}
function getIpFromRequest(req) {
let clientIp = req.connection.remoteAddress;
let ip = ipaddr.parse(clientIp);
// Check if the IP address is IPv4-mapped IPv6 address
if (ip.kind() === 'ipv6' && ip instanceof ipaddr.IPv6 && ip.isIPv4MappedAddress()) {
const ipv4 = ip.toIPv4Address().toString();
clientIp = ipv4;
} else {
clientIp = ip;
clientIp = clientIp.toString();
}
return clientIp;
}
const whitelistMiddleware = function (req, res, next) {
const clientIp = getIpFromRequest(req);
if (listen && !knownIPs.has(clientIp)) {
const userAgent = req.headers['user-agent'];
console.log(color.yellow(`New connection from ${clientIp}; User Agent: ${userAgent}\n`));
knownIPs.add(clientIp);
// Write access log
const timestamp = new Date().toISOString();
const log = `${timestamp} ${clientIp} ${userAgent}\n`;
fs.appendFile('access.log', log, (err) => {
if (err) {
console.error('Failed to write access log:', err);
}
});
}
//clientIp = req.connection.remoteAddress.split(':').pop();
if (whitelistMode === true && !whitelist.some(x => ipMatching.matches(clientIp, ipMatching.getMatch(x)))) {
console.log(color.red('Forbidden: Connection attempt from ' + clientIp + '. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.yaml in root of SillyTavern folder.\n'));
return res.status(403).send('<b>Forbidden</b>: Connection attempt from <b>' + clientIp + '</b>. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.yaml in root of SillyTavern folder.');
}
next();
};
module.exports = whitelistMiddleware;