FileShot Uploader (fileshot.io)
JavaScriptPublicby zx-apiAug 16, 2026, 04:28 AMExpires: Never55 views
1/**
2 * FileShot.io ZKE Uploader
3 * Node.js (native fetch, Blob, FormData)
4 * Credit by zx
5 * Usage: node fileshot-upload.js <file>
6 * Sumber: https://whatsapp.com/channel/0029VbDLqe7EquiSF4STU13o/299
7 */
8
9const fs = require('fs');
10const path = require('path');
11const crypto = require('crypto');
12
13const API = 'https://api.fileshot.io/api';
14const SITE = 'https://fileshot.io';
15const ZKE_CHUNK = 524288; // 512 KB — ZKE encryption chunk
16const UP_CHUNK = 2 * 1024 * 1024; // 2 MB — upload transport chunk
17
18const b64url = b =>
19 Buffer.from(b).toString('base64')
20 .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
21
22function chunkIv(base, idx) {
23 const iv = Buffer.from(base);
24 iv.writeUInt32BE((iv.readUInt32BE(8) + idx) >>> 0, 8);
25 return iv;
26}
27
28function zkeEncrypt(buf, name, mime) {
29 const key = crypto.randomBytes(32);
30 const iv = crypto.randomBytes(12);
31
32 const hdrObj = {
33 v: 1, magic: 'FSZK', chunkSize: ZKE_CHUNK,
34 fileSize: buf.length, name,
35 mime: mime || 'application/octet-stream',
36 iv: b64url(iv), keyMode: 'raw', kdf: null,
37 createdAt: Date.now()
38 };
39 const hj = Buffer.from(JSON.stringify(hdrObj), 'utf-8');
40 const lb = Buffer.alloc(4);
41 lb.writeUInt32BE(hj.length, 0);
42
43 const parts = [
44 Buffer.concat([Buffer.from('FSZK', 'ascii'), Buffer.from([1]), lb, hj])
45 ];
46
47 const n = Math.ceil(buf.length / ZKE_CHUNK);
48 for (let i = 0; i < n; i++) {
49 const s = i * ZKE_CHUNK;
50 const e = Math.min(s + ZKE_CHUNK, buf.length);
51 const c = crypto.createCipheriv(
52 'aes-256-gcm', key, chunkIv(iv, i), { authTagLength: 16 }
53 );
54 parts.push(Buffer.concat([
55 c.update(buf.subarray(s, e)),
56 c.final(),
57 c.getAuthTag() // 16-byte GCM tag (sama dgn WebCrypto)
58 ]));
59 }
60
61 return { blob: Buffer.concat(parts), key: b64url(key) };
62}
63
64class FileShotUploader {
65 constructor(o = {}) {
66 this.api = o.api || API;
67 this.site = o.site || SITE;
68 this.token = o.authToken || null;
69 }
70
71 _h(extra = {}) {
72 const h = { ...extra };
73 if (this.token) h.Authorization = `Bearer ${this.token}`;
74 return h;
75 }
76
77 async preUpload(meta) {
78 const r = await fetch(`${this.api}/files/pre-upload`, {
79 method: 'POST',
80 headers: this._h({ 'Content-Type': 'application/json' }),
81 body: JSON.stringify(meta)
82 });
83 if (!r.ok) throw new Error(`Pre-upload ${r.status}: ${await r.text()}`);
84 return r.json();
85 }
86
87 async uploadChunk(fid, data, idx, total) {
88 const fd = new FormData();
89 fd.append('chunk',
90 new Blob([data], { type: 'application/octet-stream' }), 'chunk.bin');
91 fd.append('totalChunks', String(total));
92 fd.append('isLastChunk', String(idx === total - 1));
93
94 const r = await fetch(
95 `${this.api}/files/upload-chunk/${fid}/${idx}`,
96 { method: 'POST', headers: this._h(), body: fd }
97 );
98 if (!r.ok) throw new Error(`Chunk ${idx} → ${r.status}: ${await r.text()}`);
99 return r.json();
100 }
101
102 async finalize(fid) {
103 const r = await fetch(`${this.api}/files/finalize-upload/${fid}`, {
104 method: 'POST',
105 headers: this._h({ 'Content-Type': 'application/json' })
106 });
107 if (!r.ok) throw new Error(`Finalize ${r.status}: ${await r.text()}`);
108 return r.json();
109 }
110
111 async status(fid) {
112 const r = await fetch(`${this.api}/files/upload-status/${fid}`, {
113 headers: this._h()
114 });
115 if (!r.ok) throw new Error(`Status ${r.status}: ${await r.text()}`);
116 return r.json();
117 }
118
119 async hint(fid, sent, total) {
120 await fetch(`${this.api}/files/upload-hint/${fid}`, {
121 method: 'POST',
122 headers: this._h({ 'Content-Type': 'application/json' }),
123 body: JSON.stringify({ bytesSent: sent, totalBytes: total })
124 }).catch(() => {});
125 }
126
127 async upload(fp, opts = {}) {
128 const name = path.basename(fp);
129 const buf = fs.readFileSync(fp);
130 const mime = opts.mime || 'application/octet-stream';
131
132 console.log(`[*] Encrypting : ${name} (${buf.length} bytes)`);
133 const { blob, key } = zkeEncrypt(buf, name, mime);
134
135 const total = Math.ceil(blob.length / UP_CHUNK);
136 console.log(`[*] Encrypted : ${blob.length} bytes → ${total} upload chunk(s)`);
137
138 const pre = await this.preUpload({
139 fileName: name + '.encrypted',
140 fileSize: blob.length,
141 mimeType: 'application/octet-stream',
142 isZeroKnowledge: 'true',
143 originalFileName: name,
144 originalFileSize: buf.length,
145 originalMimeType: mime,
146 chunkSize: Math.min(blob.length, UP_CHUNK),
147 totalChunks: total,
148 expirationHours: opts.expirationHours ?? 2160
149 });
150 const fid = pre.fileId;
151 console.log(`[+] Pre-upload OK fileId=${fid}`);
152
153 let sent = 0;
154 for (let i = 0; i < total; i++) {
155 const s = i * UP_CHUNK, e = Math.min(s + UP_CHUNK, blob.length);
156 await this.uploadChunk(fid, blob.subarray(s, e), i, total);
157 sent += e - s;
158 await this.hint(fid, sent, blob.length);
159 console.log(`[>] Chunk ${i + 1}/${total} uploaded (${sent} B)`);
160 }
161
162 await this.finalize(fid);
163
164 let st;
165 do {
166 await new Promise(r => setTimeout(r, 600));
167 st = await this.status(fid);
168 } while (st.uploadStatus === 'pending' || st.uploadStatus === 'uploading');
169
170 const url = `${this.site}/f/${fid}#k=${key}`;
171 console.log(`[+] Done → ${url}`);
172 return { fileId: fid, url, key };
173 }
174}
175
176/**
177const f = process.argv[2];
178if (!f) { console.error('Usage: node fileshot-upload.js <file>'); process.exit(1); }
179
180new FileShotUploader()
181 .upload(f)
182 .then(r => console.log('\nDownload:', r.url))
183 .catch(e => { console.error(`[ERROR] ${e.message}`); process.exit(1); });
184
185**/185 lines·5,894 chars·5.8 KB
Wwrap·Ffullscreen