diff --git a/src/components/icons/GlobeIcon.tsx b/src/components/icons/GlobeIcon.tsx new file mode 100644 index 0000000..4577619 --- /dev/null +++ b/src/components/icons/GlobeIcon.tsx @@ -0,0 +1,5 @@ +import { Globe } from 'react-feather'; + +export default function GlobeIcon({ ...props }) { + return ; +} diff --git a/src/components/icons/index.tsx b/src/components/icons/index.tsx index b3d6839..4287ed3 100644 --- a/src/components/icons/index.tsx +++ b/src/components/icons/index.tsx @@ -38,6 +38,7 @@ import InfoIcon from './InfoIcon'; import FolderIcon from './FolderIcon'; import FolderMinusIcon from './FolderMinusIcon'; import FolderPlusIcon from './FolderPlusIcon'; +import GlobeIcon from './GlobeIcon'; export { ActivityIcon, @@ -80,4 +81,5 @@ export { FolderIcon, FolderMinusIcon, FolderPlusIcon, + GlobeIcon, }; diff --git a/src/components/pages/Manage/Flameshot.tsx b/src/components/pages/Manage/Flameshot.tsx index 806d7b5..fb94614 100644 --- a/src/components/pages/Manage/Flameshot.tsx +++ b/src/components/pages/Manage/Flameshot.tsx @@ -61,6 +61,12 @@ export default function Flameshot({ user, open, setOpen }) { delete extraHeaders['Original-Name']; } + if (values.overrideDomain && values.overrideDomain.trim() !== '') { + extraHeaders['Override-Domain'] = values.overrideDomain; + } else { + delete extraHeaders['Override-Domain']; + } + for (const [key, value] of Object.entries(extraHeaders)) { curl.push('-H'); curl.push(`"${key}: ${value}"`); diff --git a/src/components/pages/Manage/GeneratorModal.tsx b/src/components/pages/Manage/GeneratorModal.tsx index 9312732..e3bf311 100644 --- a/src/components/pages/Manage/GeneratorModal.tsx +++ b/src/components/pages/Manage/GeneratorModal.tsx @@ -10,13 +10,16 @@ import { Stack, Switch, Text, + TextInput, Title, } from '@mantine/core'; import { useForm } from '@mantine/form'; -import { DownloadIcon } from 'components/icons'; +import { DownloadIcon, GlobeIcon } from 'components/icons'; import Link from 'components/Link'; import MutedText from 'components/MutedText'; -import { useState } from 'react'; +import { useReducer, useState } from 'react'; + +const DEFAULT_OD_DESC = 'Override the default domain(s). Type in a URL, e.g https://example.com'; export function GeneratorModal({ opened, onClose, title, onSubmit, ...other }) { const form = useForm({ @@ -30,6 +33,7 @@ export function GeneratorModal({ opened, onClose, title, onSubmit, ...other }) { wlCompositorNotSupported: false, noJSON: false, originalName: false, + overrideDomain: null, }, }); @@ -40,6 +44,42 @@ export function GeneratorModal({ opened, onClose, title, onSubmit, ...other }) { form.setFieldValue('type', value); }; + const [odState, setODState] = useReducer((state, newState) => ({ ...state, ...newState }), { + description: DEFAULT_OD_DESC, + error: '', + domain: '', + }); + + const handleOD = (e) => { + setODState({ error: '' }); + + if (e.currentTarget.value === '') { + setODState({ description: DEFAULT_OD_DESC, error: '', domain: null }); + form.setFieldValue('overrideDomain', null); + return; + } + + try { + const url = new URL(e.currentTarget.value); + setODState({ + description: ( + <> + {DEFAULT_OD_DESC} +
+
+ Using domain "{url.hostname}" + + ), + error: '', + domain: url.hostname, + }); + form.setFieldValue('overrideDomain', url.hostname); + } catch (e) { + setODState({ error: 'Invalid URL', domain: '' }); + form.setFieldValue('overrideDomain', null); + } + }; + return ( {title}} size='lg'> {other.desc && ( @@ -85,6 +125,14 @@ export function GeneratorModal({ opened, onClose, title, onSubmit, ...other }) { {...form.getInputProps('imageCompression')} /> + } + description={odState.description} + error={odState.error} + /> + - + {OptionsModal} Upload Files setFiles([...files, ...f])}> diff --git a/src/components/pages/Upload/Text.tsx b/src/components/pages/Upload/Text.tsx index bd661a4..381183e 100644 --- a/src/components/pages/Upload/Text.tsx +++ b/src/components/pages/Upload/Text.tsx @@ -71,13 +71,14 @@ export default function Text() { options.zeroWidth && req.setRequestHeader('Zws', 'true'); options.format !== 'default' && req.setRequestHeader('Format', options.format); options.originalName && req.setRequestHeader('Original-Name', 'true'); + options.overrideDomain && req.setRequestHeader('Override-Domain', options.overrideDomain); req.send(body); }; return ( <> - + {OptionsModal} Upload Text diff --git a/src/components/pages/Upload/useUploadOptions.tsx b/src/components/pages/Upload/useUploadOptions.tsx index 1ecd557..39311d0 100644 --- a/src/components/pages/Upload/useUploadOptions.tsx +++ b/src/components/pages/Upload/useUploadOptions.tsx @@ -7,52 +7,72 @@ import { Select, Stack, Switch, + TextInput, Title, } from '@mantine/core'; -import { ClockIcon, ImageIcon, KeyIcon, TypeIcon, UserIcon } from 'components/icons'; +import { ClockIcon, ImageIcon, KeyIcon, TypeIcon, UserIcon, GlobeIcon } from 'components/icons'; import React, { Dispatch, SetStateAction, useReducer, useState } from 'react'; -export default function useUploadOptions(): [ - { - expires: string; - password: string; - maxViews: number; - compression: string; - zeroWidth: boolean; - embedded: boolean; - format: string; - originalName: boolean; - }, - Dispatch>, - React.FC -] { - const [state, setState] = useReducer((state, newState) => ({ ...state, ...newState }), { - expires: 'never', - password: '', - maxViews: 0, - compression: 'none', - zeroWidth: false, - embedded: false, - format: 'default', - originalName: false, +export type UploadOptionsState = { + expires: string; + password: string; + maxViews: number; + compression: string; + zeroWidth: boolean; + embedded: boolean; + format: string; + originalName: boolean; + overrideDomain: string; +}; + +const DEFAULT_OD_DESC = 'Override the default domain(s). Type in a URL, e.g https://example.com'; + +export function OptionsModal({ + opened, + setOpened, + state, + setState, + reset, +}: { + opened: boolean; + setOpened: Dispatch>; + state: UploadOptionsState; + setState: Dispatch>; + reset: () => void; +}) { + const [odState, setODState] = useReducer((state, newState) => ({ ...state, ...newState }), { + description: DEFAULT_OD_DESC, + error: '', }); - const [opened, setOpened] = useState(false); + const handleOD = (e) => { + setODState({ error: '' }); - const reset = () => { - setState({ - expires: 'never', - password: '', - maxViews: 0, - compression: 'none', - zeroWidth: false, - embedded: false, - format: 'default', - originalName: false, - }); + if (e.currentTarget.value === '') { + setODState({ description: DEFAULT_OD_DESC, error: '' }); + setState({ overrideDomain: '' }); + return; + } + + try { + const url = new URL(e.currentTarget.value); + setODState({ + description: ( + <> + {DEFAULT_OD_DESC} +
+
+ Using domain "{url.hostname}" + + ), + }); + setState({ overrideDomain: url.hostname }); + } catch (e) { + setODState({ error: 'Invalid URL' }); + } }; - const OptionsModal: React.FC = () => ( + return ( Upload Options} size='lg' opened={opened} onClose={() => setOpened(false)}> setState({ password: e.currentTarget.value })} icon={} /> + } + description={odState.description} + error={odState.error} + /> ); - - return [state, setOpened, OptionsModal]; +} + +export default function useUploadOptions(): [UploadOptionsState, Dispatch>, any] { + const [state, setState] = useReducer((state, newState) => ({ ...state, ...newState }), { + expires: 'never', + password: '', + maxViews: 0, + compression: 'none', + zeroWidth: false, + embedded: false, + format: 'default', + originalName: false, + overrideDomain: '', + } as UploadOptionsState); + + const [opened, setOpened] = useState(false); + + const reset = () => { + setState({ + expires: 'never', + password: '', + maxViews: 0, + compression: 'none', + zeroWidth: false, + embedded: false, + format: 'default', + originalName: false, + overrideDomain: '', + }); + }; + + return [ + state, + setOpened, + , + ]; } diff --git a/src/pages/api/shorten.ts b/src/pages/api/shorten.ts index a074898..e90c0e5 100644 --- a/src/pages/api/shorten.ts +++ b/src/pages/api/shorten.ts @@ -56,27 +56,31 @@ async function handler(req: NextApiReq, res: NextApiRes) { logger.info(`User ${user.username} (${user.id}) shortenned a url ${url.destination} (${url.id})`); - if (config.discord?.shorten) { - await sendShorten( - user, - url, - `${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}${ - zconfig.urls.route === '/' ? '/' : `${zconfig.urls.route}/` - }${req.body.vanity ? req.body.vanity : invis ? invis.invis : url.id}` - ); + let domain; + if (req.headers['override-domain']) { + domain = `${zconfig.core.return_https ? 'https' : 'http'}://${req.headers['override-domain']}`; + } else if (user.domains.length) { + const randomDomain = user.domains[Math.floor(Math.random() * user.domains.length)]; + domain = `${zconfig.core.return_https ? 'https' : 'http'}://${randomDomain}`; + } else { + domain = `${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}`; } - const fullUrl = `${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}${ - zconfig.urls.route === '/' ? '/' : zconfig.urls.route - }/${req.body.vanity ? req.body.vanity : invis ? invis.invis : url.id}`; + const responseUrl = `${domain}${zconfig.uploader.route === '/' ? '/' : zconfig.uploader.route}${ + req.body.vanity ? req.body.vanity : invis ? invis.invis : url.id + }`; + + if (config.discord?.shorten) { + await sendShorten(user, url, responseUrl); + } if (req.headers['no-json']) { res.setHeader('Content-Type', 'text/plain'); - return res.end(fullUrl); + return res.end(responseUrl); } return res.json({ - url: fullUrl, + url: responseUrl, }); } diff --git a/src/pages/api/upload.ts b/src/pages/api/upload.ts index f31e036..8ec357c 100644 --- a/src/pages/api/upload.ts +++ b/src/pages/api/upload.ts @@ -171,32 +171,24 @@ async function handler(req: NextApiReq, res: NextApiRes) { await datasource.save(file.name, Buffer.from(chunks)); logger.info(`User ${user.username} (${user.id}) uploaded ${file.name} (${file.id}) (chunked)`); - if (user.domains.length) { - const domain = user.domains[Math.floor(Math.random() * user.domains.length)]; - response.files.push( - `${domain}${zconfig.uploader.route === '/' ? '/' : zconfig.uploader.route}/${ - invis ? invis.invis : file.name - }` - ); + let domain; + if (req.headers['override-domain']) { + domain = `${zconfig.core.return_https ? 'https' : 'http'}://${req.headers['override-domain']}`; + } else if (user.domains.length) { + const randomDomain = user.domains[Math.floor(Math.random() * user.domains.length)]; + domain = `${zconfig.core.return_https ? 'https' : 'http'}://${randomDomain}`; } else { - response.files.push( - `${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}${ - zconfig.uploader.route === '/' ? '/' : zconfig.uploader.route - }/${invis ? invis.invis : file.name}` - ); + domain = `${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}`; } + const responseUrl = `${domain}${zconfig.uploader.route === '/' ? '/' : zconfig.uploader.route}${ + invis ? invis.invis : file.name + }`; + + response.files.push(responseUrl); + if (zconfig.discord?.upload) { - await sendUpload( - user, - file, - `${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}/r/${ - invis ? invis.invis : file.name - }`, - `${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}${ - zconfig.uploader.route === '/' ? '/' : zconfig.uploader.route - }/${invis ? invis.invis : file.name}` - ); + await sendUpload(user, file, `${domain}/r/${invis ? invis.invis : file.name}`, responseUrl); } if (zconfig.exif.enabled && zconfig.exif.remove_gps && mimetype.startsWith('image/')) { @@ -318,34 +310,24 @@ async function handler(req: NextApiReq, res: NextApiRes) { } logger.info(`User ${user.username} (${user.id}) uploaded ${fileUpload.name} (${fileUpload.id})`); - if (user.domains.length) { - const domain = user.domains[Math.floor(Math.random() * user.domains.length)]; - response.files.push( - `${domain}${zconfig.uploader.route === '/' ? '' : zconfig.uploader.route}/${ - invis ? invis.invis : fileUpload.name - }` - ); + let domain; + if (req.headers['override-domain']) { + domain = `${zconfig.core.return_https ? 'https' : 'http'}://${req.headers['override-domain']}`; + } else if (user.domains.length) { + const randomDomain = user.domains[Math.floor(Math.random() * user.domains.length)]; + domain = `${zconfig.core.return_https ? 'https' : 'http'}://${randomDomain}`; } else { - response.files.push( - `${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}${ - zconfig.uploader.route === '/' ? '' : zconfig.uploader.route - }/${invis ? invis.invis : fileUpload.name}` - ); + domain = `${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}`; } - logger.debug(`sent response: ${JSON.stringify(response)}`); + const responseUrl = `${domain}${zconfig.uploader.route === '/' ? '/' : zconfig.uploader.route}${ + invis ? invis.invis : fileUpload.name + }`; + + response.files.push(responseUrl); if (zconfig.discord?.upload) { - await sendUpload( - user, - fileUpload, - `${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}/r/${ - invis ? invis.invis : fileUpload.name - }`, - `${zconfig.core.return_https ? 'https' : 'http'}://${req.headers.host}${ - zconfig.uploader.route === '/' ? '' : zconfig.uploader.route - }/${invis ? invis.invis : fileUpload.name}` - ); + await sendUpload(user, fileUpload, `${domain}/r/${invis ? invis.invis : fileUpload.name}`, responseUrl); } if (zconfig.exif.enabled && zconfig.exif.remove_gps && fileUpload.mimetype.startsWith('image/')) {