mirror of https://github.com/AlphaNecron/Void.git
feat(api): url generator for shortener
This commit is contained in:
parent
02c40e0568
commit
2eba13ef48
|
@ -23,4 +23,5 @@ route = '/go'
|
|||
raw_route = '/r'
|
||||
length = 6
|
||||
directory = './uploads'
|
||||
max_size = 104857600
|
||||
blacklisted = ['exe']
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "void",
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.1",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
})();
|
|
@ -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'
|
||||
>
|
||||
<ModalOverlay/>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>ShareX config generator</ModalHeader>
|
||||
<ModalCloseButton/>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<Heading mb={1} size='sm'>Config name</Heading>
|
||||
<Input
|
||||
value={name}
|
||||
onChange={n => setName(n.target.value)}
|
||||
placeholder='Void'
|
||||
size='sm'
|
||||
/>
|
||||
<Heading mt={2} mb={1} size='sm'>URL generator</Heading>
|
||||
<Select
|
||||
value={generator}
|
||||
onChange={g => setGenerator(g.target.value)}
|
||||
size='sm'
|
||||
>
|
||||
<option value='random'>Random</option>
|
||||
<option value='zws'>Invisible</option>
|
||||
<option value='emoji'>Emoji</option>
|
||||
</Select>
|
||||
<Heading mt={2} mb={1} size='sm'>Preserve file name</Heading>
|
||||
<Switch isChecked={preserveFileName} onChange={p => setPreserveFileName(p.target.checked)}/>
|
||||
<Tabs index={tab} onChange={index => setTab(index)} size='sm'>
|
||||
<TabList>
|
||||
<Tab>Uploader</Tab>
|
||||
<Tab>Shortener</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<Heading mb={1} size='sm'>Config name</Heading>
|
||||
<Input
|
||||
value={name}
|
||||
onChange={n => setName(n.target.value)}
|
||||
placeholder='Void'
|
||||
size='sm'
|
||||
/>
|
||||
<Heading mt={2} mb={1} size='sm'>URL generator</Heading>
|
||||
<Select
|
||||
value={generator}
|
||||
onChange={g => setGenerator(g.target.value)}
|
||||
size='sm'
|
||||
>
|
||||
<option value='random'>Random</option>
|
||||
<option value='zws'>Invisible</option>
|
||||
<option value='emoji'>Emoji</option>
|
||||
</Select>
|
||||
<Heading mt={2} mb={1} size='sm'>Preserve file name</Heading>
|
||||
<Switch isChecked={preserveFileName} onChange={p => setPreserveFileName(p.target.checked)} />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<Heading mb={1} size='sm'>Config name</Heading>
|
||||
<Input
|
||||
value={name}
|
||||
onChange={n => setName(n.target.value)}
|
||||
placeholder='Void'
|
||||
size='sm'
|
||||
/>
|
||||
<Heading mt={2} mb={1} size='sm'>URL generator</Heading>
|
||||
<Select
|
||||
value={generator}
|
||||
onChange={g => setGenerator(g.target.value)}
|
||||
size='sm'
|
||||
>
|
||||
<option value='random'>Random</option>
|
||||
<option value='zws'>Invisible</option>
|
||||
<option value='emoji'>Emoji</option>
|
||||
</Select>
|
||||
<Heading mt={2} mb={1} size='sm'>Use password</Heading>
|
||||
<Switch isChecked={usePassword} onChange={p => setUsePassword(p.target.checked)} />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<ButtonGroup size='sm'>
|
||||
<Button onClick={onClose} leftIcon={<X size={16}/>}>Cancel</Button>
|
||||
<Button colorScheme='purple' leftIcon={<Download size={16}/>} onClick={() => generateConfig(true)}>Shortener</Button>
|
||||
<Button colorScheme='purple' leftIcon={<Download size={16}/>} onClick={() => generateConfig(false)} ref={ref}>Uploader</Button>
|
||||
<Button onClick={onClose} leftIcon={<X size={16} />}>Cancel</Button>
|
||||
<Button colorScheme='purple' leftIcon={<Download size={16} />} onClick={() => downloadConfig()} ref={ref}>Download</Button>
|
||||
</ButtonGroup>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
|||
</PopoverHeader>
|
||||
<PopoverArrow/>
|
||||
<PopoverCloseButton/>
|
||||
<Formik validationSchema={schema} initialValues={{ destination: '', vanity: '', urlPassword: '' }} onSubmit={(values, actions) => { handleSubmit(values, actions); }}>
|
||||
<Formik validationSchema={schema} initialValues={{ destination: '', vanity: '', generator: 'random', urlPassword: '' }} onSubmit={(values, actions) => { handleSubmit(values, actions); }}>
|
||||
{props => (
|
||||
<Form>
|
||||
<PopoverBody>
|
||||
|
@ -90,7 +92,7 @@ export default function URLs() {
|
|||
{({ field, form }) => (
|
||||
<FormControl isInvalid={form.errors.destination && form.touched.destination} isRequired>
|
||||
<FormLabel htmlFor='destination'>Destination</FormLabel>
|
||||
<Input {...field} size='sm' id='destination' mb={4} placeholder='Destination'/>
|
||||
<Input {...field} size='sm' id='destination' mb={2} placeholder='Destination'/>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
|
@ -98,7 +100,19 @@ export default function URLs() {
|
|||
{({ field }) => (
|
||||
<FormControl>
|
||||
<FormLabel htmlFor='vanity'>Vanity URL</FormLabel>
|
||||
<Input {...field} size='sm' id='vanity' mb={4} placeholder='Leave blank for random'/>
|
||||
<Input {...field} size='sm' id='vanity' mb={2} placeholder='Leave blank for random'/>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
<Field name='generator'>
|
||||
{({ field }) => (
|
||||
<FormControl>
|
||||
<FormLabel htmlFor='generator'>URL generator</FormLabel>
|
||||
<Select {...field} size='sm' id='generator' mb={2}>
|
||||
<option value='random'>Random</option>
|
||||
<option value='zws'>Invisible</option>
|
||||
<option value='emoji'>Emoji</option>
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
|
@ -106,7 +120,7 @@ export default function URLs() {
|
|||
{({ field }) => (
|
||||
<FormControl>
|
||||
<FormLabel htmlFor='urlPassword'>Password</FormLabel>
|
||||
<Input {...field} size='sm' id='urlPassword' mb={4} placeholder='Password'/>
|
||||
<Input {...field} size='sm' id='urlPassword' mb={2} placeholder='Password'/>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
|
|
|
@ -15,6 +15,7 @@ const envValues = [
|
|||
e('UPLOADER_RAW_ROUTE', 'string', (c, v) => c.uploader.raw_route = v),
|
||||
e('UPLOADER_LENGTH', 'number', (c, v) => c.uploader.length = v),
|
||||
e('UPLOADER_DIRECTORY', 'string', (c, v) => c.uploader.directory = v),
|
||||
e('UPLOADER_MAX_SIZE', 'number', (c, v) => c.uploader.max_size = v),
|
||||
e('UPLOADER_BLACKLISTED', 'array', (c, v) => v ? c.uploader.blacklisted = v : c.uploader.blacklisted = []),
|
||||
|
||||
e('BOT_ENABLED', 'boolean', (c, v) => c.bot.enabled = v),
|
||||
|
|
|
@ -26,6 +26,7 @@ export interface Uploader {
|
|||
raw_route: string;
|
||||
length: number;
|
||||
directory: string;
|
||||
max_size: number;
|
||||
blacklisted: string[];
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ const validator = yup.object({
|
|||
secret: yup.string().min(8).required(),
|
||||
host: yup.string().default('0.0.0.0'),
|
||||
port: yup.number().default(3000),
|
||||
database_url: yup.string().required(),
|
||||
database_url: yup.string().required()
|
||||
}).required(),
|
||||
bot: yup.object({
|
||||
enabled: yup.bool().default(false),
|
||||
|
@ -26,7 +26,8 @@ const validator = yup.object({
|
|||
raw_route: yup.string().required(),
|
||||
length: yup.number().default(6),
|
||||
directory: yup.string().required(),
|
||||
blacklisted: yup.array().default([]),
|
||||
max_size: yup.number().default(104857600),
|
||||
blacklisted: yup.array().default([])
|
||||
}).required(),
|
||||
});
|
||||
|
||||
|
|
|
@ -115,6 +115,7 @@ export const getServerSideProps: GetServerSideProps = async context => {
|
|||
const slug = context.params.id[0];
|
||||
if (slug === config.shortener.route.split('/').pop()) {
|
||||
const short = context.params.id[1];
|
||||
if ((short ?? '').trim() === '') return { notFound: true };
|
||||
const url = await prisma.url.findFirst({
|
||||
where: {
|
||||
short
|
||||
|
|
|
@ -4,7 +4,7 @@ import { verifyPassword } from 'lib/utils';
|
|||
import { NextApiReq, NextApiRes, withVoid } from 'middleware/withVoid';
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
if (req.method !== 'POST') return res.status(405).end();
|
||||
if (req.method !== 'POST') return res.forbid('Invalid method');
|
||||
const { username, password } = req.body as { username: string, password: string };
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { default as config } from 'lib/config';
|
||||
import generate from 'lib/generators';
|
||||
import config from 'lib/config';
|
||||
import generate, { emoji, zws } from 'lib/generators';
|
||||
import { info } from 'lib/logger';
|
||||
import { NextApiReq, NextApiRes, withVoid } from 'lib/middleware/withVoid';
|
||||
import prisma from 'lib/prisma';
|
||||
|
@ -25,11 +25,12 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
});
|
||||
if (existing) return res.error('Vanity is already taken');
|
||||
}
|
||||
const rand = generate(config.shortener.length);
|
||||
const generator = req.headers.generator || req.body.generator;
|
||||
const rand = generator === 'zws' ? zws(config.shortener.length) : generator === 'emoji' ? emoji(config.shortener.length) : generate(config.shortener.length);
|
||||
if (req.body.password) var password = await hashPassword(req.body.password);
|
||||
const url = await prisma.url.create({
|
||||
data: {
|
||||
short: req.body.vanity ? req.body.vanity : rand,
|
||||
short: req.body.vanity || rand,
|
||||
destination: req.body.destination,
|
||||
userId: user.id,
|
||||
password
|
||||
|
|
|
@ -23,23 +23,10 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
if (!user) return res.forbid('Unauthorized');
|
||||
if (!req.file) return res.error('No file specified');
|
||||
const ext = req.file.originalname.includes('.') ? req.file.originalname.split('.').pop() : req.file.originalname;
|
||||
if (cfg.uploader.blacklisted.includes(ext)) return res.error('Blacklisted extension received: ' + ext);
|
||||
if (cfg.uploader.blacklisted.includes(ext) && !user.isAdmin) return res.error(`Blacklisted extension received: ${ext}`);
|
||||
if (req.file.size > cfg.uploader.max_size && !user.isAdmin) return res.error('The file is too big');
|
||||
const rand = generate(cfg.uploader.length);
|
||||
let slug;
|
||||
switch (req.headers.generator) {
|
||||
case 'zws': {
|
||||
slug = zws(cfg.uploader.length);
|
||||
break;
|
||||
}
|
||||
case 'emoji': {
|
||||
slug = emoji(cfg.uploader.length);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
slug = rand;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const slug = req.headers.generator === 'zws' ? zws(cfg.uploader.length) : req.headers.generator === 'emoji' ? emoji(cfg.uploader.length) : rand;
|
||||
const deletionToken = generate(15);
|
||||
function getMimetype(current, ext) {
|
||||
if (current === 'application/octet-stream') {
|
||||
|
|
|
@ -28,10 +28,9 @@ const shorten = {
|
|||
id: 1
|
||||
}
|
||||
});
|
||||
const rand = generate(config.shortener.length);
|
||||
const url = await prisma.url.create({
|
||||
data: {
|
||||
short: vanity ? vanity : rand,
|
||||
short: vanity ?? generate(config.shortener.length),
|
||||
destination: schemify(dest),
|
||||
userId: config.bot.default_uid,
|
||||
}
|
||||
|
|
|
@ -29,21 +29,7 @@ const upload = {
|
|||
const fileName = url.parse(args[0]).pathname;
|
||||
const ext = extname(fileName);
|
||||
const rand = generate(config.uploader.length);
|
||||
let slug;
|
||||
switch (args[1] ?? 'normal') {
|
||||
case 'zws': {
|
||||
slug = zws(config.uploader.length);
|
||||
break;
|
||||
}
|
||||
case 'emoji': {
|
||||
slug = emoji(config.uploader.length);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
slug = rand;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const slug = args[1] === 'zws' ? zws(config.uploader.length) : args[1] === 'emoji' ? emoji(config.uploader.length) : rand;
|
||||
const deletionToken = generate(15);
|
||||
function getMimetype(current, ext) {
|
||||
if (current === 'application/octet-stream') {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Discord, { Message, MessageEmbed } from 'discord.js';
|
||||
import { readdir } from 'fs';
|
||||
import { readdirSync } from 'fs';
|
||||
import { error, info } from '../src/lib/logger';
|
||||
import { Logger } from './utils/logger';
|
||||
|
||||
|
@ -16,14 +16,13 @@ client.once('ready', () => {
|
|||
avatarUrl = client.user.displayAvatarURL();
|
||||
global.logger = new Logger(client);
|
||||
global.logger.log('Twilight is ready');
|
||||
readdir(`${__dirname}/commands`, (err, files) => {
|
||||
if(err) error('BOT', err.message);
|
||||
files.forEach(file => {
|
||||
if (file.toString().includes('.ts')) {
|
||||
import(`${__dirname}/commands/${file.toString()}`).then(command => commands.push(command.default));
|
||||
info('COMMAND', `Loaded command: ${file.toString().split('.').shift()}`);}
|
||||
readdirSync(`${__dirname}/commands`)
|
||||
.map(file => file.toString())
|
||||
.filter(file => file.endsWith('.ts'))
|
||||
.forEach(file => {
|
||||
import(`${__dirname}/commands/${file.toString()}`).then(command => commands.push(command.default));
|
||||
info('COMMAND', `Loaded command: ${file.toString().split('.').shift()}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
client.on('message', (msg: Message) => {
|
||||
|
|
Loading…
Reference in New Issue