diff --git a/config.example.toml b/config.example.toml
index bd01c83..6496fde 100644
--- a/config.example.toml
+++ b/config.example.toml
@@ -23,4 +23,5 @@ route = '/go'
raw_route = '/r'
length = 6
directory = './uploads'
+max_size = 104857600
blacklisted = ['exe']
\ No newline at end of file
diff --git a/package.json b/package.json
index 94720b2..600da17 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "void",
- "version": "0.5.0",
+ "version": "0.5.1",
"private": true,
"engines": {
"node": ">=14"
diff --git a/readme.md b/readme.md
index ea531b9..75a9939 100644
--- a/readme.md
+++ b/readme.md
@@ -91,7 +91,8 @@
raw_route = '/r' # Route to serve raw contents
length = 6 # Slug length
directory = './uploads' # The directory where images are stored
- blacklisted = ['exe'] # Blacklisted extensions
+ max_size = 104857600 # Max upload size (users only), in bytes
+ blacklisted = ['exe'] # Blacklisted file extensions (users only)
```
### Features
diff --git a/server/index.ts b/server/index.ts
deleted file mode 100644
index 6738a62..0000000
--- a/server/index.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import { PrismaClient } from '@prisma/client';
-import { mkdir, readFile, stat } from 'fs/promises';
-import { createServer } from 'http';
-import next from 'next';
-import { extname, join } from 'path';
-import deployDb from '../scripts/deployDb';
-import prismaRun from '../scripts/prismaRun';
-import { error, info } from '../src/lib/logger';
-import mimetypes from '../src/lib/mimetypes';
-import validate from '../src/lib/validateConfig';
-import readConfig from '../src/lib/configReader';
-import start from '../twilight/twilight';
-import { name, version } from '../package.json';
-
-info('SERVER', `Starting ${name}@${version}`);
-
-const dev = process.env.NODE_ENV === 'development';
-
-(async () => {
- try {
- const config = await validate(readConfig());
- const data = await prismaRun(config.core.database_url, ['migrate', 'status'], true);
- if (data.match(/Following migrations? have not yet been applied/)) {
- info('DB', 'Some migrations are not applied, applying them now...');
- await deployDb(config);
- info('DB', 'Finished applying migrations');
- await prismaRun(config.core.database_url, ['db', 'seed'])
- }
- process.env.DATABASE_URL = config.core.database_url;
- if (config.bot.enabled) {
- if (!config.bot.token) error('BOT', 'Token is not specified');
- else start(config);
- }
- await stat('./.next');
- await mkdir(config.uploader.directory, { recursive: true });
- const app = next({
- dir: '.',
- dev,
- quiet: dev
- });
- await app.prepare();
- const handle = app.getRequestHandler();
- const prisma = new PrismaClient();
- const srv = createServer(async (req, res) => {
- if (req.url.startsWith(config.uploader.raw_route)) {
- const parts = req.url.split('/');
- if (!parts[2] || parts[2] === '') return;
- let data;
- try {
- data = await readFile(join(process.cwd(), config.uploader.directory, parts[2]));
- }
- catch {
- app.render404(req, res);
- }
- if (!data) {
- app.render404(req, res);
- } else {
- let file = await prisma.file.findFirst({
- where: {
- fileName: parts[2],
- }
- });
- if (file) {
- res.setHeader('Content-Type', file.mimetype);
- } else {
- const mimetype = mimetypes[extname(parts[2])] ?? 'application/octet-stream';
- res.setHeader('Content-Type', mimetype);
- }
- res.setHeader('Content-Length', data.byteLength);
- res.end(data);
- }
- } else {
- handle(req, res);
- }
- if (!(req.url.startsWith('/_next') || req.url.startsWith('/__nextjs'))) {
- res.statusCode === 200 ? info('ROUTER', `${res.statusCode} ${req.url}`) : error('URL', `${res.statusCode} ${req.url}`);
- }
- });
- srv.on('error', (e) => {
- error('SERVER', e);
- process.exit(1);
- });
- srv.on('listening', async () => {
- info('SERVER', `Listening on ${config.core.host}:${config.core.port}`);
- });
- srv.listen(config.core.port, config.core.host);
- } catch (e) {
- if (e.message && e.message.startsWith('Could not find a production')) {
- console.log(e.message);
- error('WEB', 'There is no production build - run yarn build');
- } else if (e.code && e.code === 'ENOENT') {
- if (e.path === './.next') error('WEB', 'There is no production build - run yarn build');
- } else {
- error('SERVER', e);
- process.exit(1);
- }
- }
-})();
diff --git a/src/components/ShareXDialog.tsx b/src/components/ShareXDialog.tsx
index c9de2af..9e74b16 100644
--- a/src/components/ShareXDialog.tsx
+++ b/src/components/ShareXDialog.tsx
@@ -1,4 +1,4 @@
-import { Button, ButtonGroup, Heading, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Select, Switch } from '@chakra-ui/react';
+import { Button, ButtonGroup, Tab, TabList, TabPanels, TabPanel, Tabs, Heading, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Select, Switch } from '@chakra-ui/react';
import React, { useState } from 'react';
import { Download, X } from 'react-feather';
@@ -6,8 +6,10 @@ export default function ShareXDialog({ open, onClose, token }) {
const ref = React.useRef();
const [name, setName] = useState('Void');
const [generator, setGenerator] = useState('random');
+ const [tab, setTab] = useState(0);
+ const [usePassword, setUsePassword] = useState(false);
const [preserveFileName, setPreserveFileName] = useState(false);
- const generateConfig = shortener => {
+ const downloadConfig = () => {
const apiUrl = `${window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : '')}/api`;
const uploaderConfig = {
Version: '13.2.1',
@@ -34,7 +36,9 @@ export default function ShareXDialog({ open, onClose, token }) {
RequestMethod: 'POST',
RequestURL: `${apiUrl}/shorten`,
Headers: {
- Authorization: token
+ Authorization: token,
+ Generator: generator,
+ ...(usePassword && { Password: '$prompt:Password$' })
},
Body: 'FormURLEncoded',
Arguments: {
@@ -44,8 +48,8 @@ export default function ShareXDialog({ open, onClose, token }) {
ErrorMessage: '$json:error$'
};
const a = document.createElement('a');
- a.setAttribute('href', 'data:application/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(shortener ? shortenerConfig : uploaderConfig, null, '\t')));
- a.setAttribute('download', `${name.replaceAll(' ', '_')}.sxcu`);
+ a.setAttribute('href', 'data:application/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(tab === 0 ? uploaderConfig : shortenerConfig, null, '\t')));
+ a.setAttribute('download', `${name.replaceAll(' ', '_')}_${tab === 0 ? 'Uploader' : 'Shortener'}.sxcu`);
a.click();
};
return (
@@ -55,36 +59,66 @@ export default function ShareXDialog({ open, onClose, token }) {
isOpen={open}
scrollBehavior='inside'
>
-
+
ShareX config generator
-
+
- Config name
- setName(n.target.value)}
- placeholder='Void'
- size='sm'
- />
- URL generator
-
- Preserve file name
- setPreserveFileName(p.target.checked)}/>
+ setTab(index)} size='sm'>
+
+ Uploader
+ Shortener
+
+
+
+ Config name
+ setName(n.target.value)}
+ placeholder='Void'
+ size='sm'
+ />
+ URL generator
+
+ Preserve file name
+ setPreserveFileName(p.target.checked)} />
+
+
+ Config name
+ setName(n.target.value)}
+ placeholder='Void'
+ size='sm'
+ />
+ URL generator
+
+ Use password
+ setUsePassword(p.target.checked)} />
+
+
+
-
-
-
+ }>Cancel
+ } onClick={() => downloadConfig()} ref={ref}>Download
diff --git a/src/components/pages/Upload.tsx b/src/components/pages/Upload.tsx
index bf91c13..7e695f7 100644
--- a/src/components/pages/Upload.tsx
+++ b/src/components/pages/Upload.tsx
@@ -36,13 +36,13 @@ export default function Upload() {
body
});
const json = await res.json();
- if (res.ok && !json.error) {
+ if (!json.error) {
showToast('success', 'File uploaded', json.url);
if (copy(json.url))
showToast('info', 'Copied the URL to your clipboard');
- else
- showToast('error', 'Couldn\'t upload the file', json.error);
}
+ else
+ showToast('error', 'Couldn\'t upload the file', json.error);
}
catch (error) {
showToast('error', 'Error while uploading the file', error.message);
diff --git a/src/components/pages/Urls.tsx b/src/components/pages/Urls.tsx
index 4000ef9..8e0d5c4 100644
--- a/src/components/pages/Urls.tsx
+++ b/src/components/pages/Urls.tsx
@@ -1,4 +1,4 @@
-import { Button, ButtonGroup, FormControl, FormLabel, HStack, IconButton, Input, Link, Popover, PopoverArrow, PopoverBody, PopoverCloseButton, PopoverContent, PopoverFooter, PopoverHeader, PopoverTrigger, Skeleton, Table, Tbody, Td, Th, Thead, Tr, useDisclosure, useToast } from '@chakra-ui/react';
+import { Button, ButtonGroup, FormControl, FormLabel, HStack, Select, IconButton, Input, Link, Popover, PopoverArrow, PopoverBody, PopoverCloseButton, PopoverContent, PopoverFooter, PopoverHeader, PopoverTrigger, Skeleton, Table, Tbody, Td, Th, Thead, Tr, useDisclosure, useToast } from '@chakra-ui/react';
import copy from 'copy-to-clipboard';
import { Field, Form, Formik } from 'formik';
import useFetch from 'lib/hooks/useFetch';
@@ -16,6 +16,7 @@ export default function URLs() {
const schema = yup.object({
destination: yup.string().matches(/((?:(?:http?|ftp)[s]*:\/\/)?[a-z0-9-%\/\&=?\.]+\.[a-z]{2,4}\/?([^\s<>\#%"\,\{\}\\|\\\^\[\]`]+)?)/i).min(3).required(),
vanity: yup.string(),
+ generator: yup.string(),
urlPassword: yup.string()
});
const handleDelete = async u => {
@@ -45,7 +46,8 @@ export default function URLs() {
const data = {
destination: schemify(values.destination.trim()),
vanity: values.vanity.trim(),
- password: values.urlPassword.trim()
+ password: values.urlPassword.trim(),
+ generator: values.generator
};
setBusy(true);
const res = await useFetch('/api/shorten', 'POST', data);
@@ -82,7 +84,7 @@ export default function URLs() {
- { handleSubmit(values, actions); }}>
+ { handleSubmit(values, actions); }}>
{props => (