}
144 | */
145 | async write(stream) {
146 | const parts = []
147 | // This function will be executed to process each chunk for file
148 | const processChunk = async (data, chunkCount) => {
149 | // Encrypt the data if secret is provided
150 | let iv
151 | let encrypted
152 | if (this.secret)({ iv, encrypted } = this._encrypt(this.secret, data))
153 | // Upload file to discord
154 | const part = { name: uuid(), data: encrypted || data }
155 | const { attachments: [attachment] } = await this._uploadFile(part)
156 | // Push part object into array and return later
157 | parts[chunkCount] = { url: attachment.url, size: attachment.size, iv }
158 | }
159 |
160 | return new Promise((resolve, reject) => {
161 | stream
162 | .on('aborted', () => reject(new Error('file upload aborted'))) // On HTTP request abort delete all the messages and reject promise
163 | .pipe(new StreamChunker(this.chunkSize))
164 | .pipe(new AsyncStreamProcessorWithConcurrency(processChunk, this.maxUploadConc))
165 | .on('finish', () => resolve(parts))
166 | .on('error', (err) => reject(err))
167 | })
168 | }
169 | }
170 |
171 | module.exports = DiscordFileSystem
172 |
--------------------------------------------------------------------------------
/src/http/html/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /* Document
4 | ========================================================================== */
5 |
6 | /**
7 | * 1. Correct the line height in all browsers.
8 | * 2. Prevent adjustments of font size after orientation changes in iOS.
9 | */
10 |
11 | html {
12 | line-height: 1.15; /* 1 */
13 | -webkit-text-size-adjust: 100%; /* 2 */
14 | }
15 |
16 | /* Sections
17 | ========================================================================== */
18 |
19 | /**
20 | * Remove the margin in all browsers.
21 | */
22 |
23 | body {
24 | margin: 0;
25 | }
26 |
27 | /**
28 | * Render the `main` element consistently in IE.
29 | */
30 |
31 | main {
32 | display: block;
33 | }
34 |
35 | /**
36 | * Correct the font size and margin on `h1` elements within `section` and
37 | * `article` contexts in Chrome, Firefox, and Safari.
38 | */
39 |
40 | h1 {
41 | font-size: 2em;
42 | margin: 0.67em 0;
43 | }
44 |
45 | /* Grouping content
46 | ========================================================================== */
47 |
48 | /**
49 | * 1. Add the correct box sizing in Firefox.
50 | * 2. Show the overflow in Edge and IE.
51 | */
52 |
53 | hr {
54 | box-sizing: content-box; /* 1 */
55 | height: 0; /* 1 */
56 | overflow: visible; /* 2 */
57 | }
58 |
59 | /**
60 | * 1. Correct the inheritance and scaling of font size in all browsers.
61 | * 2. Correct the odd `em` font sizing in all browsers.
62 | */
63 |
64 | pre {
65 | font-family: monospace, monospace; /* 1 */
66 | font-size: 1em; /* 2 */
67 | }
68 |
69 | /* Text-level semantics
70 | ========================================================================== */
71 |
72 | /**
73 | * Remove the gray background on active links in IE 10.
74 | */
75 |
76 | a {
77 | background-color: transparent;
78 | }
79 |
80 | /**
81 | * 1. Remove the bottom border in Chrome 57-
82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
83 | */
84 |
85 | abbr[title] {
86 | border-bottom: none; /* 1 */
87 | text-decoration: underline; /* 2 */
88 | text-decoration: underline dotted; /* 2 */
89 | }
90 |
91 | /**
92 | * Add the correct font weight in Chrome, Edge, and Safari.
93 | */
94 |
95 | b,
96 | strong {
97 | font-weight: bolder;
98 | }
99 |
100 | /**
101 | * 1. Correct the inheritance and scaling of font size in all browsers.
102 | * 2. Correct the odd `em` font sizing in all browsers.
103 | */
104 |
105 | code,
106 | kbd,
107 | samp {
108 | font-family: monospace, monospace; /* 1 */
109 | font-size: 1em; /* 2 */
110 | }
111 |
112 | /**
113 | * Add the correct font size in all browsers.
114 | */
115 |
116 | small {
117 | font-size: 80%;
118 | }
119 |
120 | /**
121 | * Prevent `sub` and `sup` elements from affecting the line height in
122 | * all browsers.
123 | */
124 |
125 | sub,
126 | sup {
127 | font-size: 75%;
128 | line-height: 0;
129 | position: relative;
130 | vertical-align: baseline;
131 | }
132 |
133 | sub {
134 | bottom: -0.25em;
135 | }
136 |
137 | sup {
138 | top: -0.5em;
139 | }
140 |
141 | /* Embedded content
142 | ========================================================================== */
143 |
144 | /**
145 | * Remove the border on images inside links in IE 10.
146 | */
147 |
148 | img {
149 | border-style: none;
150 | }
151 |
152 | /* Forms
153 | ========================================================================== */
154 |
155 | /**
156 | * 1. Change the font styles in all browsers.
157 | * 2. Remove the margin in Firefox and Safari.
158 | */
159 |
160 | button,
161 | input,
162 | optgroup,
163 | select,
164 | textarea {
165 | font-family: inherit; /* 1 */
166 | font-size: 100%; /* 1 */
167 | line-height: 1.15; /* 1 */
168 | margin: 0; /* 2 */
169 | }
170 |
171 | /**
172 | * Show the overflow in IE.
173 | * 1. Show the overflow in Edge.
174 | */
175 |
176 | button,
177 | input { /* 1 */
178 | overflow: visible;
179 | }
180 |
181 | /**
182 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
183 | * 1. Remove the inheritance of text transform in Firefox.
184 | */
185 |
186 | button,
187 | select { /* 1 */
188 | text-transform: none;
189 | }
190 |
191 | /**
192 | * Correct the inability to style clickable types in iOS and Safari.
193 | */
194 |
195 | button,
196 | [type="button"],
197 | [type="reset"],
198 | [type="submit"] {
199 | -webkit-appearance: button;
200 | }
201 |
202 | /**
203 | * Remove the inner border and padding in Firefox.
204 | */
205 |
206 | button::-moz-focus-inner,
207 | [type="button"]::-moz-focus-inner,
208 | [type="reset"]::-moz-focus-inner,
209 | [type="submit"]::-moz-focus-inner {
210 | border-style: none;
211 | padding: 0;
212 | }
213 |
214 | /**
215 | * Restore the focus styles unset by the previous rule.
216 | */
217 |
218 | button:-moz-focusring,
219 | [type="button"]:-moz-focusring,
220 | [type="reset"]:-moz-focusring,
221 | [type="submit"]:-moz-focusring {
222 | outline: 1px dotted ButtonText;
223 | }
224 |
225 | /**
226 | * Correct the padding in Firefox.
227 | */
228 |
229 | fieldset {
230 | padding: 0.35em 0.75em 0.625em;
231 | }
232 |
233 | /**
234 | * 1. Correct the text wrapping in Edge and IE.
235 | * 2. Correct the color inheritance from `fieldset` elements in IE.
236 | * 3. Remove the padding so developers are not caught out when they zero out
237 | * `fieldset` elements in all browsers.
238 | */
239 |
240 | legend {
241 | box-sizing: border-box; /* 1 */
242 | color: inherit; /* 2 */
243 | display: table; /* 1 */
244 | max-width: 100%; /* 1 */
245 | padding: 0; /* 3 */
246 | white-space: normal; /* 1 */
247 | }
248 |
249 | /**
250 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
251 | */
252 |
253 | progress {
254 | vertical-align: baseline;
255 | }
256 |
257 | /**
258 | * Remove the default vertical scrollbar in IE 10+.
259 | */
260 |
261 | textarea {
262 | overflow: auto;
263 | }
264 |
265 | /**
266 | * 1. Add the correct box sizing in IE 10.
267 | * 2. Remove the padding in IE 10.
268 | */
269 |
270 | [type="checkbox"],
271 | [type="radio"] {
272 | box-sizing: border-box; /* 1 */
273 | padding: 0; /* 2 */
274 | }
275 |
276 | /**
277 | * Correct the cursor style of increment and decrement buttons in Chrome.
278 | */
279 |
280 | [type="number"]::-webkit-inner-spin-button,
281 | [type="number"]::-webkit-outer-spin-button {
282 | height: auto;
283 | }
284 |
285 | /**
286 | * 1. Correct the odd appearance in Chrome and Safari.
287 | * 2. Correct the outline style in Safari.
288 | */
289 |
290 | [type="search"] {
291 | -webkit-appearance: textfield; /* 1 */
292 | outline-offset: -2px; /* 2 */
293 | }
294 |
295 | /**
296 | * Remove the inner padding in Chrome and Safari on macOS.
297 | */
298 |
299 | [type="search"]::-webkit-search-decoration {
300 | -webkit-appearance: none;
301 | }
302 |
303 | /**
304 | * 1. Correct the inability to style clickable types in iOS and Safari.
305 | * 2. Change font properties to `inherit` in Safari.
306 | */
307 |
308 | ::-webkit-file-upload-button {
309 | -webkit-appearance: button; /* 1 */
310 | font: inherit; /* 2 */
311 | }
312 |
313 | /* Interactive
314 | ========================================================================== */
315 |
316 | /*
317 | * Add the correct display in Edge, IE 10+, and Firefox.
318 | */
319 |
320 | details {
321 | display: block;
322 | }
323 |
324 | /*
325 | * Add the correct display in all browsers.
326 | */
327 |
328 | summary {
329 | display: list-item;
330 | }
331 |
332 | /* Misc
333 | ========================================================================== */
334 |
335 | /**
336 | * Add the correct display in IE 10+.
337 | */
338 |
339 | template {
340 | display: none;
341 | }
342 |
343 | /**
344 | * Add the correct display in IE 10.
345 | */
346 |
347 | [hidden] {
348 | display: none;
349 | }
350 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | DDRIVE
2 |
3 | Turn Discord into a datastore that can manage and store your files.
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | ##### **DDrive** A lightweight cloud storage system using discord as storage device written in nodejs. Supports an unlimited file size and unlimited storage, Implemented using node js streams with multi-part up & download.
27 |
28 | https://user-images.githubusercontent.com/59018146/167635903-48cdace0-c383-4e7d-a037-4a32eaa4ab69.mp4
29 |
30 | #### Current stable branch `4.x`
31 |
32 | ### Live demo at [ddrive.forscht.dev](https://ddrive.forscht.dev/)
33 |
34 | ### Features
35 | - Theoretically unlimited file size, thanks to splitting the file in 24mb chunks using nodejs streams API.
36 | - Simple yet robust HTTP front end
37 | - Rest API with OpenAPI 3.1 specifications.
38 | - Tested with storing 4000 GB of data on single discord channel (With max file size of 16GB).
39 | - Supports basic auth with read only public access to panel.
40 | - Easily deployable on heroku/replit and use as private cloud storage.
41 |
42 | ## New Version 4.0
43 |
44 |
45 | This next major version release 4.0 is ddrive written from scratch. It comes with most requested features and several improvements.
46 |
47 | - Now uses `postgres` to store files metadata. Why?
48 | - Once you have huge amount of data stored on ddrive it makes ddrive significantly slow to start since ddrive have to fetch all the metadata from discord channel (For 3 TB of data it takes me 30+ minutes.)
49 | - With postgres, deleting file is extremely faster because now ddrive don't have to delete files on discord channel and just need to remove from metadata only.
50 | - With postgres now it's possible to move or rename files/folders which was impossible with older version.
51 | - Added support for `rename` files/folders.
52 | - Added support to `move` file/folder (Only via API, Not sure how to do it with frontend, PR welcomes.)
53 | - Now uses `webhooks` instead of `bot/user tokens` to bypass the discord rate limit
54 | - DDrive now uploads file chunks in parallel with limit. Which significantly increase the upload speed. I was able to upload file with `5GB of size in just 85 seconds`.
55 | - Public access mode - It is now possible to provide users read-only access with just one config var
56 | - Batch upload files - Now you can upload multiple files at once from panel. (DClone support has been removed from this version)
57 | - Bug fix - `download reset` for few mobile devices
58 | - Added support for optional encryption to files uploaded to discord
59 | - DDrive now has proper rest API with OpenAPI 3.1 standards
60 | - Added support for dark/light mode on panel
61 |
62 | I spent several weeks finalizing this new version. Any support is highly appreciated - [Buy me a coffee](https://www.buymeacoffee.com/forscht)
63 |
64 | ### Requirements
65 | - NodeJS v16.x or Docker
66 | - Postgres Database, Discord Webhook URLs
67 | - Avg technical knowledge
68 |
69 | ## Setup Guide
70 | 1. Clone this project
71 | 2. Create few webhook urls. For better performance and to avoid rate limit at least create 5 with 1 webhook / text channel. ([How to create webhook url](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks))
72 | 3. Setup postgres using docker, if you already don't have it running
73 | - `cd .devcontainer`
74 | - `docker-compose up -d`
75 | 4. Copy `config/.env_sample` to `config/.env` and make necessary changes
76 | 5. Optional - If you have lots of webhookURLs you can put those in `webhook.txt` with `\n` seperated.
77 | 6. Run - `npm run migration:up`
78 | 7. Run - `node bin/ddrive`
79 | 8. Navigate to `http://localhost:3000` in your browser.
80 |
81 | ### How to keep it running forever
82 | 1. Install pm2 with `npm install -g pm2`
83 | 2. Run - `pm2 start bin/ddrive`
84 | 3. Run - `pm2 list` to check status of ddrive
85 | 4. Run - `pm2 logs` to check ddrive logs
86 |
87 | ### Config variables explanation
88 | ```shell
89 | # config/.env
90 |
91 | # Required params
92 | DATABASE_URL= # Database URL of postgres with valid postgres uri
93 |
94 | WEBHOOKS={url1},{url2} # Webhook urls seperated by ","
95 |
96 | # Optional params
97 | PORT=3000 # HTTP Port where ddrive panel will start running
98 |
99 | REQUEST_TIMEOUT=60000 # Time in ms after which ddrive will abort request to discord api server. Set it high if you have very slow internet
100 |
101 | CHUNK_SIZE=25165824 # ChunkSize in bytes. You should probably never touch this and if you do don't set it to more than 25MB, with discord webhooks you can't upload file bigger than 25MB
102 |
103 | SECRET=someverysecuresecret # If you set this every files on discord will be stored using strong encryption, but it will cause significantly high cpu usage, so don't use it unless you're storing important stuff
104 |
105 | AUTH=admin:admin # Username password seperated by ":". If you set this panel will ask for username password before access
106 |
107 | PUBLIC_ACCESS=READ_ONLY_FILE # If you want to give read only access to panel or file use this option. Check below for valid options.
108 | # READ_ONLY_FILE - User will be only access download links of file and not panel
109 | # READ_ONLY_PANEL - User will be able to browse the panel for files/directories but won't be able to upload/delete/rename any file/folder.
110 |
111 | UPLOAD_CONCURRENCY=3 # ddrive will upload this many chunks in parallel to discord. If you have fast internet increasing it will significantly increase performance at cost of cpu/disk usage
112 |
113 | ```
114 |
115 | ### Run using docker
116 | ```shell
117 | docker run -rm -it -p 8080:8080 \
118 | -e PORT=8080 \
119 | -e WEBHOOKS={url1},{url2} \
120 | -e DATABASE_URL={database url} \
121 | --name ddrive forscht/ddrive
122 | ```
123 | ### One Click Deploy with Railway
124 | [](https://railway.app/new/template/tL53xa)
125 |
126 | ### Setup tutorials
127 | - Setup under 4 minutes in local/cloud server using `neon.tech` postgres - [Youtube](https://youtu.be/Zvr1BHjrYC0)
128 | ## API Usage
129 | `npm install @forscht/ddrive`
130 | ```javascript
131 | const { DFs, HttpServer } = require('@forscht/ddrive')
132 |
133 | const DFsConfig = {
134 | chunkSize: 25165824,
135 | webhooks: 'webhookURL1,webhookURL2',
136 | secret: 'somerandomsecret',
137 | maxConcurrency: 3, // UPLOAD_CONCURRENCY
138 | restOpts: {
139 | timeout: '60000',
140 | },
141 | }
142 |
143 | const httpConfig = {
144 | authOpts: {
145 | auth: { user: 'admin', pass: 'admin' },
146 | publicAccess: 'READ_ONLY_FILE', // or 'READ_ONLY_PANEL'
147 | },
148 | port: 8080,
149 | }
150 |
151 | const run = async () => {
152 | // Create DFs Instance
153 | const dfs = new DFs(DFsConfig)
154 | // Create HTTP Server instance
155 | const httpServer = HttpServer(dfs, httpConfig)
156 |
157 | return httpServer.listen({ host: '0.0.0.0', port: httpConfig.port })
158 | }
159 |
160 | run().then()
161 |
162 | ```
163 |
164 | ## Migrate from v3 to v4
165 | Migrating ddrive v3 to v4 is one way process once you migrate ddrive to v4 and add new files you can't migrate new files to v3 again but you can still use v3 with old files.
166 |
167 | 1. Clone this project
168 | 2. Create few webhooks (1 webhook/text channel). Do not create webhook on old text channel where you have already stored v3 data.
169 | 3. Take pull of latest ddrive v3
170 | 4. Start ddrive v3 with option `--metadata=true`. Ex - `ddrive --channelId {id} --token {token} --metadata=true`
171 | 5. Open `localhost:{ddrive-port}/metadata` in browser
172 | 6. Save JSON as old_data.json in cloned ddrive directory
173 | 7. Put valid `DATABASE_URL` in `config/.env`
174 | 8. Run `node bin/migrate old_data.json`
175 | 9. After few seconds once process is done you should see the message `Migration is done`
176 |
177 | Feel free to create [new issue](https://github.com/forscht/ddrive/issues/new) if it's not working for you or need any help.
178 |
179 | [Discord Support server](https://discord.gg/3TCZRYafhW)
180 |
--------------------------------------------------------------------------------
/src/http/html/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-restricted-syntax,no-loop-func */
2 | const table = document.getElementById('fm-table')
3 | const tbody = document.getElementById('tbody')
4 | const lightModeBtn = document.getElementById('light-mode-btn')
5 | const darkModeBtn = document.getElementById('dark-mode-btn')
6 |
7 | const fileInput = document.getElementById('file-input')
8 |
9 | const prevBtn = document.getElementById('previous-btn')
10 |
11 | const createFolderBtn = document.getElementById('create-btn')
12 | const uploadBtn = document.getElementById('upload-btn')
13 |
14 | const trashBtn = document.getElementById('trash-btn')
15 | const renameBtn = document.getElementById('rename-btn')
16 | const clipboardBtn = document.getElementById('clipboard-btn')
17 |
18 | let currDirectory = ''
19 | let parentDirectory = ''
20 |
21 | //
22 | // Utility functions
23 | //
24 | function makeid(length) {
25 | let result = ''
26 | const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
27 | const charactersLength = characters.length
28 | for (let i = 0; i < length; i += 1) {
29 | result += characters.charAt(Math.floor(Math.random() * charactersLength))
30 | }
31 |
32 | return result
33 | }
34 |
35 | function sort(arr, key) {
36 | return arr.sort((a, b) => {
37 | if (a[key] < b[key]) return -1
38 | if (a[key] > b[key]) return 1
39 |
40 | return 0
41 | })
42 | }
43 | function download(url) {
44 | const a = document.createElement('a')
45 | a.href = url
46 | document.body.appendChild(a)
47 | a.click()
48 | document.body.removeChild(a)
49 | }
50 |
51 | function fallbackCopyTextToClipboard(text) {
52 | const textArea = document.createElement('textarea')
53 | textArea.value = text
54 |
55 | // Avoid scrolling to bottom
56 | textArea.style.top = '0'
57 | textArea.style.left = '0'
58 | textArea.style.position = 'fixed'
59 |
60 | document.body.appendChild(textArea)
61 | textArea.focus()
62 | textArea.select()
63 | document.execCommand('copy')
64 | document.body.removeChild(textArea)
65 | }
66 |
67 | function copyTextToClipboard(text) {
68 | navigator.clipboard?.writeText(text).catch(() => fallbackCopyTextToClipboard(text))
69 | }
70 |
71 | //
72 | // Helper functions
73 | //
74 | function getSelected() {
75 | const [selected] = table.getElementsByClassName('selected')
76 | if (!selected) return undefined
77 | if (selected.classList.contains('file')) selected.type = 'file'
78 | else selected.type = 'folder'
79 |
80 | return selected
81 | }
82 |
83 | function resetBtns() {
84 | createFolderBtn.disabled = false
85 | uploadBtn.disabled = false
86 | const selected = getSelected()
87 | trashBtn.disabled = !selected
88 | renameBtn.disabled = !selected
89 | clipboardBtn.disabled = !(selected && selected.type === 'file')
90 | }
91 |
92 | function disableAllBtns() {
93 | prevBtn.disabled = true
94 | createFolderBtn.disabled = true
95 | uploadBtn.disabled = true
96 | trashBtn.disabled = true
97 | renameBtn.disabled = true
98 | clipboardBtn.disabled = true
99 | }
100 |
101 | function clearTableAndResetButtons() {
102 | while (tbody.hasChildNodes()) tbody.removeChild(tbody.lastChild)
103 | resetBtns()
104 | }
105 |
106 | function setTheme(mode) {
107 | let currMode = mode
108 | if (!currMode) currMode = window.localStorage.getItem('mode') || 'dark'
109 | document.documentElement.classList.add(currMode)
110 | document.documentElement.classList.remove(currMode === 'dark' ? 'light' : 'dark')
111 | window.localStorage.setItem('mode', currMode)
112 | if (currMode === 'dark') {
113 | lightModeBtn.parentNode.classList.add('active')
114 | darkModeBtn.parentNode.classList.remove('active')
115 | } else {
116 | lightModeBtn.parentNode.classList.remove('active')
117 | darkModeBtn.parentNode.classList.add('active')
118 | }
119 | }
120 |
121 | //
122 | // API Operations
123 | //
124 | async function refreshTable() {
125 | const resp = await fetch(`/api/directories/${currDirectory}`)
126 | const body = await resp.json()
127 | if (resp.status !== 200) {
128 | disableAllBtns()
129 |
130 | return
131 | }
132 | currDirectory = body.id
133 | parentDirectory = body.parentId
134 | prevBtn.disabled = !parentDirectory
135 | clearTableAndResetButtons()
136 | for (const directory of sort(body.child.directories, 'name')) {
137 | tbody.appendChild(prepareFolderTR(directory))
138 | }
139 | for (const file of sort(body.child.files, 'name')) {
140 | tbody.appendChild(prepareFileTR(file))
141 | }
142 | }
143 |
144 | async function deleteFileOrFolder(type = 'file', id) {
145 | const url = `/api/${type === 'file' ? 'files' : 'directories'}/${id}`
146 | await fetch(url, { method: 'DELETE' })
147 | await refreshTable()
148 | }
149 |
150 | async function uploadFile() {
151 | for (const file of fileInput.files) {
152 | // Create unique id for progress bar
153 | const id = makeid(20)
154 |
155 | // Prepare form data and create progress bar
156 | const formData = new FormData()
157 | formData.append('file', file)
158 | createProgressBar(id, `Uploading ${file.name}`)
159 |
160 | // Create XHR request
161 | const xhr = new XMLHttpRequest()
162 | xhr.open('POST', `/api/files/${currDirectory}`, true)
163 |
164 | // handle update progress
165 | xhr.upload.onprogress = (e) => {
166 | const progress = (e.total === 0) ? 0 : (e.loaded / e.total) * 100
167 | updateProgressBar(id, Math.floor(progress))
168 | }
169 |
170 | // Non 200 status handler
171 | xhr.onload = async () => {
172 | deleteProgressBar(id)
173 | await refreshTable()
174 | }
175 |
176 | // Network error handler
177 | xhr.onerror = async () => {
178 | deleteProgressBar(id)
179 | await refreshTable()
180 | }
181 |
182 | // Finally send data
183 | xhr.send(formData)
184 | }
185 | }
186 |
187 | async function navigatePrevFolder() {
188 | currDirectory = parentDirectory
189 | await refreshTable()
190 | }
191 |
192 | async function createFolder() {
193 | const id = makeid(20) // Temp id
194 | const input = createFolderTR(id)
195 | input.addEventListener('keypress', async (e) => {
196 | if (e.key === 'Enter') {
197 | await fetch(`/api/directories`, {
198 | method: 'POST',
199 | body: JSON.stringify({ name: input.value, parentId: currDirectory }),
200 | headers: { 'Content-Type': 'application/json' },
201 | })
202 | await refreshTable()
203 | }
204 | })
205 | input.focus()
206 | disableAllBtns()
207 | }
208 |
209 | function copyToClipboard() {
210 | const selected = getSelected()
211 | if (selected && selected.type === 'file') {
212 | // eslint-disable-next-line no-restricted-globals
213 | const url = `${location.href.replace(/\/$/, '')}/api/files/${selected.id}/download`
214 | copyTextToClipboard(url)
215 | }
216 | }
217 | async function renameFileOrFolder() {
218 | const selected = getSelected()
219 | const inputText = document.createElement('input')
220 | inputText.style.height = '1rem'
221 | inputText.style.width = '80%'
222 | inputText.setAttribute('type', 'text')
223 | inputText.setAttribute('value', selected.textContent)
224 | selected.textContent = ''
225 | inputText.addEventListener('keypress', async (e) => {
226 | if (e.key === 'Enter') {
227 | await fetch(`/api/${selected.type === 'file' ? 'files' : 'directories'}/${selected.id}`, {
228 | method: 'PUT',
229 | body: JSON.stringify({ name: inputText.value }),
230 | headers: { 'Content-Type': 'application/json' },
231 | })
232 | await refreshTable()
233 | }
234 | })
235 | selected.appendChild(inputText)
236 | inputText.focus()
237 | disableAllBtns()
238 | }
239 |
240 | //
241 | // Event handlers
242 | //
243 | async function handleClick(e) {
244 | const selected = getSelected()
245 | if (selected && e.target.id === trashBtn.id) {
246 | await deleteFileOrFolder(selected.type, selected.id)
247 | }
248 | if (e.target.id === prevBtn.id && parentDirectory) {
249 | await navigatePrevFolder()
250 | }
251 | if (selected && e.target.id === renameBtn.id) {
252 | await renameFileOrFolder()
253 | }
254 | if (e.target.id === createFolderBtn.id) {
255 | await createFolder()
256 | }
257 | if (e.target.id === clipboardBtn.id) {
258 | copyToClipboard()
259 | }
260 | if (e.target.id === lightModeBtn.id) {
261 | setTheme('light')
262 | }
263 | if (e.target.id === darkModeBtn.id) {
264 | setTheme('dark')
265 | }
266 | if (selected) {
267 | selected.classList.remove('selected')
268 | resetBtns()
269 | }
270 | const { classList } = e.target
271 | if (classList.contains('file') || classList.contains('folder')) {
272 | classList.add('selected')
273 | resetBtns()
274 | }
275 | }
276 |
277 | async function handleDoubleClick(e) {
278 | const { classList } = e.target
279 | if (classList.contains('file')) {
280 | download(`/api/files/${e.target.id}/download`)
281 | }
282 | if (classList.contains('folder')) {
283 | currDirectory = e.target.id
284 | await refreshTable()
285 | }
286 | }
287 |
288 | function loadDataTable() {
289 | document.onclick = handleClick
290 | document.ondblclick = handleDoubleClick
291 | clearTableAndResetButtons()
292 | refreshTable().then()
293 | fileInput.addEventListener('change', () => uploadFile())
294 |
295 | setTheme()
296 | }
297 |
298 | loadDataTable()
299 |
--------------------------------------------------------------------------------
/src/http/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | DDrive - Encrypted Open Source File Sharing
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
59 |
60 | Cloud storage system using discord.
61 |
62 |
63 |
64 |
293 |
294 |
295 |
--------------------------------------------------------------------------------