├── .gitignore
├── LAINpls.user.js
├── LICENSE
├── Readme.md
├── admin.iptools.user.js
├── admin.reportsbinder.user.js
├── admin.unnotify-reports.user.js
├── baseMANY.js
├── bettersizes.user.js
├── dolosfile.css
├── dry.js
├── freakycolors.user.js
├── gallery-is-not-for-chat.user.js
├── old-file-list-clicks.css
├── ownerdelete.user.js
├── owners-on-hover.user.js
├── passiveevents.user.js
├── propernames.user.js
├── room-title-quirks.user.js
├── timestamps.user.js
├── volaban.user.js
├── volanail.user.js
├── volapg.user.js
└── youcompleteme.user.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .eslintrc*
2 | .stylelintrc
3 | .vscode/settings.json
4 | dav
5 | dav/
6 | node_modules/
7 | package-lock.json
8 | package.json
9 | Session.vim
10 |
--------------------------------------------------------------------------------
/LAINpls.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Lain is annoying
3 | // @namespace https://volafile.org/
4 | // @version 4
5 | // @description He really is
6 | // @author topkuk productions
7 | // @match https://volafile.org/r/*
8 | // @grant unsafeWindow
9 | // @require https://rawgit.com/RealDolos/volascripts/1dd689f72763c0e59f567fdf93865837e35964d6/dry.js
10 | // @run-at document-start
11 | // @icon https://volafile.org/favicon.ico
12 | // ==/UserScript==
13 |
14 | dry.once("dom", () => {
15 | "use strict";
16 | function unannoy() { return false; }
17 |
18 | for (let m of ["adultWarningNeeded", "showOldRoom"]) {
19 | try {
20 | dry.replaceEarly("messages", m, unannoy);
21 | }
22 | catch (ex) {
23 | console.log("coun't bind", m);
24 | }
25 | }
26 | });
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | CC0 1.0 Universal
2 |
3 | Statement of Purpose
4 |
5 | The laws of most jurisdictions throughout the world automatically confer
6 | exclusive Copyright and Related Rights (defined below) upon the creator and
7 | subsequent owner(s) (each and all, an "owner") of an original work of
8 | authorship and/or a database (each, a "Work").
9 |
10 | Certain owners wish to permanently relinquish those rights to a Work for the
11 | purpose of contributing to a commons of creative, cultural and scientific
12 | works ("Commons") that the public can reliably and without fear of later
13 | claims of infringement build upon, modify, incorporate in other works, reuse
14 | and redistribute as freely as possible in any form whatsoever and for any
15 | purposes, including without limitation commercial purposes. These owners may
16 | contribute to the Commons to promote the ideal of a free culture and the
17 | further production of creative, cultural and scientific works, or to gain
18 | reputation or greater distribution for their Work in part through the use and
19 | efforts of others.
20 |
21 | For these and/or other purposes and motivations, and without any expectation
22 | of additional consideration or compensation, the person associating CC0 with a
23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
25 | and publicly distribute the Work under its terms, with knowledge of his or her
26 | Copyright and Related Rights in the Work and the meaning and intended legal
27 | effect of CC0 on those rights.
28 |
29 | 1. Copyright and Related Rights. A Work made available under CC0 may be
30 | protected by copyright and related or neighboring rights ("Copyright and
31 | Related Rights"). Copyright and Related Rights include, but are not limited
32 | to, the following:
33 |
34 | i. the right to reproduce, adapt, distribute, perform, display, communicate,
35 | and translate a Work;
36 |
37 | ii. moral rights retained by the original author(s) and/or performer(s);
38 |
39 | iii. publicity and privacy rights pertaining to a person's image or likeness
40 | depicted in a Work;
41 |
42 | iv. rights protecting against unfair competition in regards to a Work,
43 | subject to the limitations in paragraph 4(a), below;
44 |
45 | v. rights protecting the extraction, dissemination, use and reuse of data in
46 | a Work;
47 |
48 | vi. database rights (such as those arising under Directive 96/9/EC of the
49 | European Parliament and of the Council of 11 March 1996 on the legal
50 | protection of databases, and under any national implementation thereof,
51 | including any amended or successor version of such directive); and
52 |
53 | vii. other similar, equivalent or corresponding rights throughout the world
54 | based on applicable law or treaty, and any national implementations thereof.
55 |
56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of,
57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
59 | and Related Rights and associated claims and causes of action, whether now
60 | known or unknown (including existing as well as future claims and causes of
61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum
62 | duration provided by applicable law or treaty (including future time
63 | extensions), (iii) in any current or future medium and for any number of
64 | copies, and (iv) for any purpose whatsoever, including without limitation
65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
66 | the Waiver for the benefit of each member of the public at large and to the
67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver
68 | shall not be subject to revocation, rescission, cancellation, termination, or
69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work
70 | by the public as contemplated by Affirmer's express Statement of Purpose.
71 |
72 | 3. Public License Fallback. Should any part of the Waiver for any reason be
73 | judged legally invalid or ineffective under applicable law, then the Waiver
74 | shall be preserved to the maximum extent permitted taking into account
75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
76 | is so judged Affirmer hereby grants to each affected person a royalty-free,
77 | non transferable, non sublicensable, non exclusive, irrevocable and
78 | unconditional license to exercise Affirmer's Copyright and Related Rights in
79 | the Work (i) in all territories worldwide, (ii) for the maximum duration
80 | provided by applicable law or treaty (including future time extensions), (iii)
81 | in any current or future medium and for any number of copies, and (iv) for any
82 | purpose whatsoever, including without limitation commercial, advertising or
83 | promotional purposes (the "License"). The License shall be deemed effective as
84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the
85 | License for any reason be judged legally invalid or ineffective under
86 | applicable law, such partial invalidity or ineffectiveness shall not
87 | invalidate the remainder of the License, and in such case Affirmer hereby
88 | affirms that he or she will not (i) exercise any of his or her remaining
89 | Copyright and Related Rights in the Work or (ii) assert any associated claims
90 | and causes of action with respect to the Work, in either case contrary to
91 | Affirmer's express Statement of Purpose.
92 |
93 | 4. Limitations and Disclaimers.
94 |
95 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
96 | surrendered, licensed or otherwise affected by this document.
97 |
98 | b. Affirmer offers the Work as-is and makes no representations or warranties
99 | of any kind concerning the Work, express, implied, statutory or otherwise,
100 | including without limitation warranties of title, merchantability, fitness
101 | for a particular purpose, non infringement, or the absence of latent or
102 | other defects, accuracy, or the present or absence of errors, whether or not
103 | discoverable, all to the greatest extent permissible under applicable law.
104 |
105 | c. Affirmer disclaims responsibility for clearing rights of other persons
106 | that may apply to the Work or any use thereof, including without limitation
107 | any person's Copyright and Related Rights in the Work. Further, Affirmer
108 | disclaims responsibility for obtaining any necessary consents, permissions
109 | or other rights required for any use of the Work.
110 |
111 | d. Affirmer understands and acknowledges that Creative Commons is not a
112 | party to this document and has no duty or obligation with respect to this
113 | CC0 or use of the Work.
114 |
115 | For more information, please see
116 |
117 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # volascripts
2 |
3 | To install and use scripts, you first need to install either [tampermonkey](https://www.tampermonkey.net/) or [violentmonkey](https://addons.mozilla.org/firefox/addon/violentmonkey/). Greasemonkey may or may not work.
4 |
5 | In order to install a script, click on any link in the list above that ends with `*.user.js`.
6 | it will take you to a page with script's source code. On that page in the top right corner, click on the `Raw` button.
7 | Tampermonkey/Violentmonkey will then prompt you to confirm the installation.
8 |
9 | After installing (or updating) a script, you need to reload all vola tabs.
10 |
11 | You may something have to reload tabs when you first start your browser and your previous session is restored. This is particularly true for Chrome.
12 |
13 | ## What do these scripts actually do?
14 |
15 | Check the [Wiki](https://github.com/volafiled/volascripts/wiki) for descriptions of some of the
16 | scripts. Stuff's work in progress tho!
17 |
--------------------------------------------------------------------------------
/admin.iptools.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Vola Admin/IP Tools
3 | // @version 56
4 | // @description Does a bunch of stuff for mods.
5 | // @namespace https://volafile.org
6 | // @icon https://volafile.org/favicon.ico
7 | // @author topkuk productions
8 | // @match https://volafile.org/r/*
9 | // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
10 | // @require https://cdn.rawgit.com/RealDolos/volascripts/51b76c05be26adca7b4a4897115f67f10d9df668/dry.js
11 | // @run-at document-start
12 | // @grant none
13 | // ==/UserScript==
14 |
15 | /* global window, document, localStorage, GM, dry, _templates, RoomInstance */
16 |
17 | dry.once("dom", () => {
18 | "use strict";
19 |
20 | const IS_BAN = /^\S+ (?:banned|muted|hellbanned|unbanned|un-muted|timed-in)\b/;
21 |
22 | function adjustBanPart(p, users, ips) {
23 | const {value} = p;
24 | let slash = value.indexOf(" / ");
25 | if (slash < 0) {
26 | slash = value.length;
27 | }
28 | const pre = value.slice(0, slash).replace("did nothing to", "did-nothing-to");
29 | const post = value.slice(slash);
30 | const pieces = pre.split(/ /g);
31 | let idx = 1;
32 | while (idx < pieces.length) {
33 | if (!pieces[idx++].endsWith(",")) {
34 | break;
35 | }
36 | }
37 | const userPieces = pieces.slice(idx).map(u => {
38 | u = u.replace(/(?:[()]|,$)/g, "").trim();
39 | if (/^\d+\.\d+\.\d+\.\d+$/.test(u)) {
40 | ips.push(u);
41 | return null;
42 | }
43 | if (u) {
44 | users.push(u);
45 | }
46 | return u;
47 | }).filter(e => e).sort();
48 |
49 | if (!userPieces.length) {
50 | userPieces.push("some cuck");
51 | }
52 |
53 | p.value = `${pieces.slice(0, idx).join(" ").replace("did-nothing-to", "did nothing to")} ${userPieces.join(", ")}${post}`;
54 | }
55 |
56 | function adjustLogMessage(message, options, data) {
57 | const users = [];
58 | const ips = [];
59 | if (message[0].type === "url" && message[0].href === "/reports") {
60 | if (message[1].value.startsWith(" / BLACKLIST ")) {
61 | adjustBanPart(message[4], users, ips);
62 | }
63 | else {
64 | const [, p] = message;
65 | const m = p.value.match(/ \((\d+\.\d+\.\d+\.\d+)\)/);
66 | if (m) {
67 | p.value = p.value.replace(m[0], "");
68 | ips.push(m[1]);
69 | }
70 | const key = message.reduce((p, c) => p + (c.value || c.text || c.url || c.id), "");
71 | if (known_reports.has(key)) {
72 | return REMOVE;
73 | }
74 | known_reports.add(key);
75 | if (known_reports.size > 100) {
76 | known_reports.delete(known_reports.entries().next().value);
77 | }
78 | }
79 | }
80 | else {
81 | for (const p of message) {
82 | if (p.type !== "text") {
83 | continue;
84 | }
85 | if (!IS_BAN.test(p.value)) {
86 | continue;
87 | }
88 |
89 | adjustBanPart(p, users, ips);
90 | }
91 | }
92 | if (users.length) {
93 | options.profile = users.sort().join(" user:");
94 | options.unprofile = true;
95 | }
96 | if (ips.length) {
97 | if (!data) {
98 | data = new dry.unsafeWindow.Object();
99 | }
100 | data.ip = ips.join(" ip:");
101 | }
102 |
103 | return data;
104 | }
105 |
106 | function nukeLogMessage(message, data) {
107 | const {ip} = data;
108 | setTimeout(() => {
109 | const {messages, scrollState} = RoomInstance.extensions.chat;
110 | for (let i = messages.length - 1; i >= 0; --i) {
111 | const m = messages[i];
112 | if (m.data.ip !== ip || m.nick !== "Log") {
113 | continue;
114 | }
115 | messages.splice(i, 1);
116 | if (m.elem && m.elem.parentElement) {
117 | scrollState.adjust(-m.elem.clientHeight);
118 | m.elem.parentElement.removeChild(m.elem);
119 | scrollState.restore();
120 | }
121 | }
122 | }, 0);
123 | }
124 |
125 | function $e(tag, attrs, text) {
126 | const rv = document.createElement(tag);
127 | attrs = attrs || {};
128 | for (const a in attrs) {
129 | rv.setAttribute(a, attrs[a]);
130 | }
131 | if (text) {
132 | rv.textContent = text;
133 | }
134 | return rv;
135 | }
136 |
137 | function nukeRoom() {
138 | dry.exts.ui.showQuestion({
139 | title: "Nuke this room?",
140 | text: "Are you sure you want to nuke this room?",
141 | positive: "NUUUUUUUKE!!!!",
142 | negative: "No Bulli"
143 | }, res => {
144 | if (!res) {
145 | return;
146 | }
147 | dry.unsafeWindow.Volafile.setRoomConfig({
148 | name: "closed",
149 | motd: "closed",
150 | owner: "noowner",
151 | janitors: [],
152 | disabled: true
153 | });
154 | });
155 | }
156 |
157 | function purgeMessageIds(ids) {
158 | if (!Array.isArray(ids)) {
159 | ids = [ids];
160 | }
161 | if (!ids.length) {
162 | return;
163 | }
164 | dry.exts.connection.call("removeMessages", ids, function(e) {
165 | if (e) {
166 | dry.exts.chat.showError(e);
167 | }
168 | });
169 | }
170 |
171 | function purgeMessages(msg) {
172 | const {messages} = dry.exts.chat;
173 |
174 | dry.exts.templates.renderPrompt("prompts.purgemessages", {
175 | user: msg.nick,
176 | "one"() {
177 | purgeMessageIds([msg.data.id]);
178 | },
179 | "ip"() {
180 | const {ip} = msg.data;
181 | purgeMessageIds(messages.filter(m => m.data.ip === ip && m.data.id).map(e => e.data.id));
182 | },
183 | "nick"() {
184 | const nick = msg.nick.toUpperCase();
185 | if (!nick) {
186 | return;
187 | }
188 | if (msg.options.user) {
189 | purgeMessageIds(messages.filter(m => m.nick.toUpperCase() === nick && m.data.id).map(e => e.data.id));
190 | }
191 | else {
192 | purgeMessageIds(messages.filter(m => m.nick.toUpperCase() === nick && m.data.id && !m.options.user).map(e => e.data.id));
193 | }
194 | },
195 | "cancel"() {}
196 | });
197 | }
198 |
199 | function tickAll() {
200 | RoomInstance.extensions.filelist.filelist.forEach(file => {
201 | file.setData("checked", true);
202 | });
203 | }
204 |
205 |
206 | const $ = (sel, el) => (el || document).querySelector(sel);
207 |
208 | console.log(
209 | "running", GM.info.script.name, GM.info.script.version, dry.version);
210 |
211 | const style = $e("style", {id: "iptools-style"}, `
212 | body[noipspls] .chat_message_ip,
213 | body[noipspls] .tag_key_ip {
214 | display: none;
215 | }
216 | .username.ban {
217 | display: inline-block;
218 | }
219 | @-moz-document url-prefix() {
220 | .username.ban {
221 | display: inline;
222 | }
223 | }
224 | .username.ban {
225 | vertical-align: top;
226 | color: white !important;
227 | font-size: 50%;
228 | padding: 0;
229 | opacity: 0.4;
230 | }
231 | .username.ban:hover {
232 | opacity: 1;
233 | }
234 | .username.ban icon-hammer {
235 | padding: 0;
236 | }
237 | .icon-untick {
238 | margin: 0 !important;
239 | }
240 | #files_header {
241 | display: flex;
242 | }
243 |
244 | #iptools-buttons {
245 | box-sizing: border-box;
246 | font-size: 0.8em !important;
247 | margin: 0;
248 | padding: 0;
249 | line-height: inherit;
250 | display: inline-flex;
251 | align-items: center;
252 | margin-right: 2.5ex;
253 | }
254 |
255 | .iptools-button {
256 | box-sizing: border-box;
257 | background-color: transparent !important;
258 | color: white !important;
259 | height: 100%;
260 | line-height: inherit;
261 | min-width: 2.5em;
262 | text-align: center;
263 | padding: 0;
264 | margin: 0;
265 | display: block;
266 | border-radius: 3px;
267 | }
268 |
269 | .iptools-button > span {
270 | vertical-align: middle;
271 | }
272 |
273 | .iptools-button:hover {
274 | background: rgba(120, 120, 120, 0.1) !important;
275 | }
276 |
277 | .iptools-button:active {
278 | background: rgba(120, 120, 120, 0.5) !important;
279 | }
280 |
281 | .iptools-button-group {
282 | height: 9px;
283 | border-right: 2px dotted rgba(255, 255, 255, 0.7) !important;
284 | margin-left: 0.5em;
285 | margin-right: 0.5em;
286 | }
287 | `);
288 | document.body.appendChild(style);
289 |
290 | let state = localStorage.getItem("noipspls") !== "false";
291 | if (state !== false) {
292 | state = true;
293 | }
294 | const update = () => {
295 | if (state) {
296 | document.body.setAttribute("noipspls", "true");
297 | }
298 | else {
299 | document.body.removeAttribute("noipspls");
300 | }
301 | localStorage.setItem("noipspls", state.toString());
302 | if (dry.exts) {
303 | dry.exts.chat.scrollState.bottom();
304 | }
305 | };
306 | const toggle = () => {
307 | state = !state;
308 | update();
309 | };
310 | update();
311 |
312 | const btn = document.createElement("a");
313 | btn.textContent = "IP";
314 | btn.style.padding = "0 1ex";
315 | const uc = document.querySelector("#user_count_icon");
316 | uc.parentElement.insertBefore(btn, uc.nextSibling);
317 | btn.addEventListener("click", toggle);
318 |
319 | const roomQueue = new class RoomQueue extends Map {
320 | constructor() {
321 | super();
322 | this.resolving = false;
323 | }
324 |
325 | add(room, el) {
326 | const l = this.get(room);
327 | if (!l) {
328 | this.set(room, [el]);
329 | }
330 | else {
331 | l.push(el);
332 | }
333 | }
334 |
335 | async resolve() {
336 | if (this.resolving) {
337 | return;
338 | }
339 | this.resolving = true;
340 |
341 | try {
342 | while (this.size) {
343 | const [room, elements] = this[Symbol.iterator]().next().value;
344 | try {
345 | const res = await dry.unsafeWindow.Volafile.makeAPIRequest("getRoomConfig", {
346 | room
347 | });
348 | for (const el of elements) {
349 | el.textContent = res.name;
350 | }
351 | }
352 | catch (ex) {
353 | console.error(ex);
354 | }
355 | this.delete(room);
356 | }
357 | }
358 | finally {
359 | this.resolving = false;
360 | }
361 | }
362 | }();
363 |
364 | const known_reports = new Set();
365 |
366 | dry.replaceEarly("chat", "showMessage",
367 | function(orig, nick, message, options, data, ...args) {
368 | try {
369 | if (nick === "Log" && options.staff && !options.profile && !(data && data.ip)) {
370 | data = adjustLogMessage(message, options, data) || data;
371 | if (data === REMOVE) {
372 | return null;
373 | }
374 | }
375 | if (nick === "Log" && options.staff && !options.profile && data && data.ip) {
376 | const key = message.reduce((p, c) => p + (c.value || c.text || c.url || c.id), "");
377 | if (key.includes("SPAMITY REPORT SPAM")) {
378 | nukeLogMessage(message, data);
379 | return null;
380 | }
381 | }
382 | }
383 | catch (ex) {
384 | console.error(ex);
385 | }
386 |
387 | const msg = orig(...[nick, message, options, data].concat(args));
388 |
389 | try {
390 | if (options.unprofile && msg.nick_elem) {
391 | const newNick = document.createElement("a");
392 | if (msg.ip_elem) {
393 | msg.ip_elem.parentElement.removeChild(msg.ip_elem);
394 | }
395 | newNick.textContent = msg.nick_elem.textContent;
396 | if (msg.ip_elem) {
397 | newNick.appendChild(msg.ip_elem);
398 | }
399 | newNick.className = msg.nick_elem.className;
400 | msg.nick_elem.parentElement.replaceChild(newNick, msg.nick_elem);
401 | msg.nick_elem = newNick;
402 | msg.elem.classList.remove("profile");
403 | }
404 | if (msg && (msg.ip_elem || msg.options.profile)) {
405 | const addHammer = function() {
406 | msg.ban_elem = document.createElement("span");
407 | const hammer = document.createElement("i");
408 | hammer.setAttribute("class", "chat_message_icon icon-hammer");
409 | msg.ban_elem.appendChild(hammer);
410 | msg.ban_elem.setAttribute("class", "username clickable ban unselectable");
411 | const showBanWindow = msg.showBanWindow.bind(msg);
412 | msg.ban_elem.addEventListener("click", function(e) {
413 | if (e.altKey) {
414 | e.preventDefault();
415 | e.stopPropagation();
416 | purgeMessages(msg);
417 | return false;
418 | }
419 |
420 | return showBanWindow(e);
421 | });
422 | msg.nick_elem.appendChild(msg.ban_elem);
423 | };
424 | addHammer();
425 | const update = msg.update.bind(msg);
426 | msg.update = function() {
427 | update();
428 | addHammer();
429 | };
430 | }
431 | if (msg && nick === "Log" && msg.elem && msg.elem.textContent.includes("Reports /")) {
432 | for (const el of msg.elem.children) {
433 | const m = /https:\/\/volafile.org\/r\/(.+)$/.exec(el.href);
434 | if (!m) {
435 | continue;
436 | }
437 | roomQueue.add(m[1], el);
438 | }
439 | }
440 | }
441 | catch (ex) {
442 | console.error(ex);
443 | }
444 | finally {
445 | roomQueue.resolve().catch(console.error);
446 | }
447 |
448 | return msg;
449 | });
450 |
451 | new class extends dry.Commands {
452 | ip() {
453 | toggle();
454 | return true;
455 | }
456 | }();
457 |
458 | // fixup ban templates
459 | for (const b of Object.values(window._templates.bans)) {
460 | b.lock = b.locked = false;
461 | if (b.upload && b.hours <= 12) {
462 | b.upload = false;
463 | }
464 | }
465 |
466 | const DO_ET = Symbol("We waz doing et");
467 | const REMOVE = Symbol();
468 | const row = $("#files_header");
469 | const cont = $e("div", {
470 | id: "iptools-buttons",
471 | });
472 | row.insertBefore(cont, row.firstChild);
473 |
474 |
475 | function installButton(id, icon, title, action, group) {
476 | const btn = $e("label", {
477 | for: id,
478 | id,
479 | class: `button iptools-button ${id}`,
480 | title
481 | });
482 | const span = $e("span");
483 | span.appendChild($e("span", {
484 | class: icon
485 | }));
486 | btn.appendChild(span);
487 | btn.addEventListener("click", action);
488 | cont.appendChild(btn);
489 | if (group) {
490 | cont.appendChild($e("div", {
491 | class: "iptools-button-group"
492 | }));
493 | }
494 | }
495 |
496 | installButton("nuke-button", "icon-lock", "Deploy nuke!",
497 | nukeRoom, true);
498 |
499 | installButton("tick-button", "icon-plus", "Tick all!",
500 | tickAll);
501 | installButton("untick-button", "icon-minus", "Untick all!",
502 | () => dry.exts.adminButtons.untickAll(DO_ET), true);
503 |
504 | installButton("ban-button", "icon-hammer", "Ban!", (e) => {
505 | if (e.shiftKey) {
506 | RoomInstance.extensions.adminButtons.onUnbanButtonClicked();
507 | }
508 | else {
509 | RoomInstance.extensions.adminButtons.onBanButtonClicked();
510 | }
511 | });
512 | installButton("blacklist-button", "icon-warning", "Blacklist!", (e) => {
513 | if (e.shiftKey) {
514 | RoomInstance.extensions.adminButtons.onWhitelistClicked();
515 | }
516 | else {
517 | RoomInstance.extensions.adminButtons.onBlacklistClicked();
518 | }
519 | });
520 | installButton("removefiles-button", "icon-trash", "Remove Files!",
521 | () => RoomInstance.extensions.adminButtons.onRemoveClicked());
522 |
523 | dry.replaceEarly("ui", "showContextMenu", function(orig, el, options) {
524 | try {
525 | if (options && options.dedupe === "admin_contextmenu" && dry.exts.user.info.admin) {
526 | options.buttons.push({
527 | icon: "icon-rules",
528 | text: "Nuke Room",
529 | admin: true,
530 | click: nukeRoom
531 | });
532 | }
533 | if (options && options.dedupe === "room_contextmenu" && dry.exts.user.info.admin) {
534 | const idx = options.buttons.findIndex(e => e.text === "Copy URL");
535 | if (idx >= 0) {
536 | options.buttons.splice(idx, 1);
537 | }
538 | }
539 | }
540 | catch (ex) {
541 | console.error("ex", ex);
542 | }
543 | return orig(el, options);
544 | });
545 |
546 | dry.replaceEarly("adminButtons", "untickAll", function(orig, kek) {
547 | if (kek !== DO_ET) {
548 | return;
549 | }
550 | orig();
551 | });
552 |
553 | _templates.prompts.purgemessages = {
554 | title: "Purge messages",
555 | content: "Are you sure you want to SHREKT {{user}}?",
556 | centered: true,
557 | dismissable: true,
558 | buttons: [
559 | {name: "This Message", click: "one", default: true},
560 | {name: "All by IP", add_class: "light", click: "ip"},
561 | {name: "All by Nick", add_class: "light", click: "nick"},
562 | {name: "It was a mistake", add_class: "light", click: "cancel"}
563 | ]
564 | };
565 | });
566 |
--------------------------------------------------------------------------------
/admin.reportsbinder.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Vola Tab Report Binder
3 | // @version 1
4 | // @namespace https://volafile.org
5 | // @icon https://volafile.org/favicon.ico
6 | // @author Hefty Chungus
7 | // @match https://volafile.org/r/*
8 | // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
9 | // @require https://cdn.jsdelivr.net/gh/volafiled/volascripts/dry.js
10 | // @grant none
11 | // @run-at document-start
12 | // ==/UserScript==
13 | /* globals dry GM config */
14 | dry.once("dom", () => {
15 | 'use strict';
16 | console.log("running", GM.info.script.name, GM.info.script.version, dry.version);
17 | const bindtab = GM.info.script.name;
18 | const tabbind = function () {
19 | return localStorage.getItem(bindtab);
20 | };
21 | new class extends dry.Commands {
22 | bindreports() {
23 | localStorage.setItem(bindtab, config.room_id);
24 | dry.appendMessage("Binder", "Reports are bound to this tab now");
25 | dry.exts.connection.call("command", dry.exts.user.info.nick, "reports", "");
26 | }
27 | }();
28 | new class extends dry.MessageFilter {
29 | showMessage(orig, nick, message, options) {
30 | if (nick === "Log" && tabbind() === config.room_id && !options.report) {
31 | dry.exts.connection.call("command", dry.exts.user.info.nick, "reports", "");
32 | }
33 | }
34 | }();
35 | });
36 |
--------------------------------------------------------------------------------
/admin.unnotify-reports.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Unnotify reports
3 | // @version 7
4 | // @description because it's annoying
5 | // @namespace https://volafile.org
6 | // @icon https://volafile.org/favicon.ico
7 | // @author topkuk productions
8 | // @match https://volafile.org/r/*
9 | // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
10 | // @require https://cdn.jsdelivr.net/gh/RealDolos/volascripts@1dd689f72763c0e59f567fdf93865837e35964d6/dry.js
11 | // @grant none
12 | // @run-at document-start
13 | // ==/UserScript==
14 | /* globals dry */
15 |
16 | dry.once("dom", () => {
17 | "use strict";
18 | console.log("running", GM.info.script.name, GM.info.script.version, dry.version);
19 | const BLACK = /\.(txt|html?|rtf|docx?|xlsx?|exe|rar|zip)[ "]|periscopefollower/;
20 |
21 | new class extends dry.MessageFilter {
22 | showMessage(orig, nick, message, options) {
23 | if (typeof message !== "string" && ((nick === "Report" && options.staff) || (message && message[0] && message[0].href === "/reports"))) {
24 | options.notify = options.highlight = false;
25 | try {
26 | let text = message.map(e => e.value || "").join("");
27 | let urls = message.map(e => e.href || (e.type === "room" && e.id) || "").join(" ");
28 | if (text.includes("BLACKLIST") && BLACK.test(text)) {
29 | console.error(text);
30 | return false;
31 | }
32 | if (text.includes("παρεακι")) {
33 | console.error(text);
34 | return false;
35 | }
36 | if (urls.includes("BEEPi")) {
37 | return false;
38 | }
39 | return;
40 | }
41 | catch (ex) {
42 | console.error("failed", message, ex);
43 | }
44 | }
45 | if (nick === "Log" && options.staff) {
46 | options.notify = options.highlight = false;
47 | }
48 | }
49 | }();
50 | });
51 |
--------------------------------------------------------------------------------
/bettersizes.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Better sizes
3 | // @namespace https://volafile.org/
4 | // @icon https://volafile.org/favicon.ico
5 | // @version 1
6 | // @description Because lain
7 | // @author Me
8 | // @match https://volafile.org/*
9 | // @run-at document-idle
10 | // @grant none
11 | // ==/UserScript==
12 |
13 | (function() {
14 | 'use strict';
15 | var prettySize = (function(uselocale) {
16 | function toLocaleStringSupportsLocales() {
17 | var number = 0;
18 | try {
19 | number.toLocaleString('i');
20 | } catch (e) {
21 | return e.name === 'RangeError';
22 | }
23 | return false;
24 | }
25 | var fixer = uselocale && toLocaleStringSupportsLocales() ?
26 | function (digits) {
27 | return this.toLocaleString(undefined, {
28 | minimumFractionDigits: digits,
29 | maximumFractionDigits: digits,
30 | useGrouping: false
31 | });
32 | } :
33 | Number.prototype.toFixed;
34 | var units = [
35 | ' B',
36 | ' KB',
37 | ' MB',
38 | ' GB',
39 | ' TB',
40 | ' PB',
41 | ' EB',
42 | ' MercoByte'
43 | ];
44 | return function prettySize(n) {
45 | var o = 0, f = 0, u;
46 | while (n > 1024) {
47 | n /= 1024;
48 | ++o;
49 | }
50 | if (!o) {
51 | return n.toFixed(0) + ' B';
52 | }
53 | u = units[o];
54 | if (n < 10) {
55 | f = 2;
56 | }
57 | else if (n < 100) {
58 | f = 1;
59 | }
60 | if (o > 3) {
61 | // large size force multiplier: adds +3cp
62 | ++f;
63 | }
64 | return fixer.call(n, f) + units[o];
65 | };
66 | })(false);
67 |
68 | function DOET() {
69 | if (window.format && window.format.prettySize) {
70 | window.format.prettySize = prettySize;
71 | return;
72 | }
73 | setTimeout(DOET, 0);
74 | }
75 | DOET();
76 | })();
77 |
--------------------------------------------------------------------------------
/dolosfile.css:
--------------------------------------------------------------------------------
1 | @-moz-document domain("volafile.org") {
2 | html {
3 | --main-bg-color: #303030;
4 | --snd-bg-color: #101010;
5 | --input-color: #131313;
6 | --input-disabled-color: #8e8e8e;
7 | --snd-input-color: #333;
8 | --box-color: #282828;
9 | --slider-color: #707070;
10 | --hi-color: #282828;
11 | --hi-hover-color: #505050;
12 | --file-list-color: #242424;
13 | --file-color: #3a3a3a;
14 | --file-hover-color: #505050;
15 | --header-hi-color: #404040;
16 | --header-disabled-color: #e0e0e0;
17 | --tag-color: rgba(164, 159, 159, 0.3);
18 | --tag-color-nick: rgba(140, 170, 121, 0.5);
19 | --button-color: #dcdcdc;
20 | --button-hi-color: #ececec;
21 | --button-light-color: #666;
22 | --button-light-hi-color: #777;
23 | --channel-color: #bd7fd7;
24 | --context-bg-color: #444;
25 | --action-color: #424242;
26 | --file-select-color: #464646;
27 | --control-disabled-color: #aaa;
28 | --radio-current-color: #555;
29 | --radio-progress-color: #5e5e5e;
30 | --radio-bar-color: #535353;
31 | --radio-slider-color: #777;
32 | --icon-image-color: lightskyblue;
33 | --icon-film-color: yellowgreen;
34 | --icon-book-color: lightsalmon;
35 | --icon-archive-color: bisque;
36 | --icon-music-color: orchid;
37 | --uploading-bg: #696969;
38 | --uploading-bg2: #6d6d6d;
39 | --uploading-done-bg: #567556;
40 | --uploading-done-bg2: #598059;
41 | --username-color: rgb(170, 170, 180);
42 | --username-green-color: #23d16f;
43 | --username-donor-color: #42ff96;
44 | --username-staff-color: #fc807e;
45 | --username-admin-color: #d880fc;
46 | --username-sys-color: #ff6c00;
47 |
48 | background-color: var(--snd-bg-color);
49 | }
50 |
51 | *,
52 | *::after {
53 | border-color: var(--main-bg-color) !important;
54 | }
55 |
56 | select {
57 | background-color: var(--action-color);
58 | }
59 |
60 | table th {
61 | background-color: var(--main-bg-color);
62 | }
63 |
64 | #chat_frame,
65 | #files_frame {
66 | border-top-color: var(--header-hi-color) !important;
67 | top: 2em;
68 | }
69 |
70 | #admin_button,
71 | #chat_header,
72 | #files_header,
73 | #name_container,
74 | #room_search {
75 | height: 2em;
76 | line-height: 2em;
77 | }
78 |
79 | #admin_button {
80 | background-color: var(--main-bg-color);
81 | width: 2em;
82 | }
83 |
84 | #admin_button_icon {
85 | font-size: 1em;
86 | vertical-align: middle;
87 | }
88 |
89 | #name_container {
90 | padding-left: 0.5ex;
91 | background-color: var(--main-bg-color);
92 | }
93 |
94 | #clearsearch,
95 | .header_row_element {
96 | line-height: 2em;
97 | }
98 |
99 | #home_button {
100 | height: 2em;
101 | width: 2em;
102 | padding: 0 0.8em;
103 | background-color: var(--main-bg-color);
104 | }
105 |
106 | .volafile_icon_svg.button .v_stroke {
107 | stroke-width: 80px;
108 | }
109 |
110 | #room_name {
111 | font-size: 1.3em;
112 | }
113 |
114 | #rename_container {
115 | line-height: 1.7em;
116 | }
117 |
118 | #rename_input {
119 | font-size: 1em;
120 | width: 20em;
121 | }
122 |
123 | .filelist_file {
124 | align-items: center;
125 | border-bottom: 1px solid var(--main-bg-color);
126 | }
127 |
128 | #file_list.even .filelist_file:nth-child(2n),
129 | #file_list.uneven .filelist_file:nth-child(2n+1) {
130 | background-color: var(--file-list-color);
131 | }
132 |
133 | .file_selected {
134 | background-color: var(--file-select-color) !important;
135 | }
136 |
137 | .filelist_file:hover::before {
138 | border-color: var(--file-hover-color);
139 | }
140 |
141 | .file_status {
142 | font-size: small;
143 | }
144 |
145 | .file_uploading::before {
146 | background-color: var(--uploading-bg);
147 | background-image: repeating-linear-gradient(-45deg, var(--uploading-bg), var(--uploading-bg) 25%, var(--uploading-bg2) 25%, var(--uploading-bg2) 50%);
148 | }
149 |
150 | .file_uploading::after {
151 | background-color: var(--uploading-done-bg);
152 | background-image: repeating-linear-gradient(-45deg, var(--uploading-done-bg), var(--uploading-done-bg) 25%, var(--uploading-done-bg2) 25%, var(--uploading-done-bg2) 50%);
153 | }
154 |
155 | .file_queued {
156 | background-color: #696969 !important;
157 | }
158 |
159 | .scroller-slider,
160 | .nano > .pane > .slider {
161 | background-color: var(--slider-color);
162 | }
163 |
164 | #chat_hbar_container {
165 | box-shadow: 0 -5px 5px 0 var(--snd-bg-color) !important;
166 | }
167 |
168 | #file_notifier,
169 | #filter_reminder,
170 | #chat_hbar {
171 | background-color: var(--main-bg-color);
172 | }
173 |
174 | #file_notifier:hover,
175 | #filter_reminder:hover {
176 | background-color: var(--hi-hover-color);
177 | }
178 |
179 | #chat_hbar {
180 | display: flex;
181 | justify-content: flex-end;
182 | align-items: flex-end;
183 | border: 0;
184 | }
185 |
186 | #chat_name {
187 | width: 16ex;
188 | margin: 0;
189 | height: auto;
190 | border: 0;
191 | margin-top: 1px;
192 | margin-bottom: 1px;
193 | padding-left: 1ex;
194 | padding-right: 1ex;
195 | }
196 |
197 | #user_count {
198 | margin-left: 1ex;
199 | margin-right: 1ex;
200 | min-width: 5ex;
201 | }
202 |
203 | #chat_input {
204 | padding: 0.5ex 1ex;
205 | font-size: inherit;
206 | border-bottom: 24px solid var(--input-color) !important;
207 | height: calc(5em + 24px) !important;
208 | }
209 |
210 | #toggles span.toggle_icon {
211 | color: var(--input-disabled-color) !important;
212 | }
213 |
214 | #toggles > .toggle.enabled span.toggle_icon {
215 | color: #f6f1f1 !important;
216 | }
217 |
218 | #search_input:hover,
219 | #toggles > .toggle:hover {
220 | background-color: rgba(120, 120, 120, 0.1);
221 | border-radius: 3px;
222 | }
223 |
224 | body:not(.mobile_filelist) #toggles span.toggle_container {
225 | margin-right: 1ex;
226 | }
227 |
228 | #toggles > .toggle:active {
229 | background: transparent;
230 | }
231 |
232 | #room_search {
233 | background-color: var(--input-color);
234 | }
235 |
236 | body:not(.mobile_filelist) #room_search {
237 | margin-left: 1em;
238 | }
239 |
240 | #search_input:active,
241 | #search_input:focus,
242 | #search_input:valid {
243 | background-color: transparent;
244 | width: 25em;
245 | }
246 |
247 | .ui_frame_contextmenu {
248 | border-radius: 0;
249 | border: 0;
250 | }
251 |
252 | .ui_frame_contextmenu,
253 | .ui_frame_contextmenu .button.light,
254 | .ui_frame_contextmenu .button.light:active,
255 | .ui_frame_contextmenu .button.light:visited {
256 | background-color: var(--context-bg-color);
257 | }
258 |
259 | input[type=text],
260 | input[type=password],
261 | textarea {
262 | background-color: var(--input-color);
263 | }
264 |
265 | input[type=text]::-webkit-input-placeholder,
266 | input[type=password]::-webkit-input-placeholder,
267 | textarea::-webkit-input-placeholder {
268 | color: var(--input-disabled-color);
269 | opacity: 1;
270 | }
271 |
272 | input[type=text]::placeholder,
273 | input[type=password]::placeholder,
274 | textarea::placeholder {
275 | color: var(--input-disabled-color);
276 | opacity: 1;
277 | }
278 |
279 | input[type=text][disabled],
280 | input[type=password][disabled],
281 | textarea[disabled] {
282 | color: var(--input-disabled-color);
283 | }
284 |
285 | input[type=radio]:disabled + span._icon::before,
286 | input[type=checkbox]:disabled + span._icon::before,
287 | .radio_toggle {
288 | color: var(--input-disabled-color);
289 | }
290 |
291 | .main > form input[type=text],
292 | .main > form input[type=password] {
293 | background-color: var(--snd-input-color);
294 | }
295 |
296 | input.defaultValue[type="text"],
297 | input[type="text"][disabled],
298 | input.defaultValue[type="password"],
299 | input[type="password"][disabled],
300 | textarea.defaultValue,
301 | textarea[disabled] {
302 | color: var(--control-disabled-color);
303 | }
304 |
305 | .chat_message > i {
306 | color: var(--channel-color);
307 | }
308 |
309 | a.chat_room {
310 | text-decoration: underline;
311 | display: inline-block;
312 | }
313 |
314 | a.chat_room::after {
315 | content: none;
316 | display: none;
317 | }
318 |
319 | a.chat_room::before {
320 | display: inline-block;
321 | content: '#';
322 | font-weight: bold;
323 | color: rgba(255, 255, 255, 0.4);
324 | padding-right: 0.3ex;
325 | text-decoration: none !important;
326 | }
327 |
328 | a.chat_room:hover::before,
329 | a.chat_room:active::before {
330 | color: rgba(255, 255, 255, 0.8);
331 | }
332 |
333 | .chat_file {
334 | background-color: var(--file-color);
335 | }
336 |
337 | .chat_file:hover,
338 | .chat_file:active {
339 | background-color: var(--file-hover-color);
340 | }
341 |
342 | #chat_messages_frame::before {
343 | border-left-color: var(--snd-bg-color);
344 | }
345 |
346 | .chat_message.highlight {
347 | border-left-color: var(--hi-color) !important;
348 | background-color: var(--hi-color);
349 | }
350 |
351 | .ui_frame,
352 | #overlay {
353 | background-color: var(--box-color);
354 | }
355 |
356 | .dropdown:hover,
357 | a.header_row_element:hover {
358 | background-color: var(--header-hi-color);
359 | }
360 |
361 | a.header_row_element:active {
362 | background-color: var(--header-hi-color);
363 | }
364 |
365 | .header_row_element.clickable:hover,
366 | a.header_row_element_disabled,
367 | a.header_row_element_disabled:active,
368 | a.header_row_element_disabled:hover,
369 | a.header_row_element_disabled:visited {
370 | background-color: var(--header-hi-color);
371 | color: var(--header-disabled-color);
372 | }
373 |
374 | .header_row_element_disabled.clickable,
375 | .header_row_element_disabled.clickable:hover {
376 | color: var(--input-disabled-color);
377 | }
378 |
379 | .dropdown_item:hover {
380 | background-color: var(--hi-hover-color);
381 | }
382 |
383 | .dropdown_item:active {
384 | background-color: var(--header-hi-color);
385 | }
386 |
387 | .dropdown_list {
388 | background-color: var(--header-hi-color);
389 | }
390 |
391 | .dropdown::after {
392 | border-color: var(--header-hi-color);
393 | }
394 |
395 | .file_status::before,
396 | hr {
397 | border: 1px solid var(--file-select-color);
398 | }
399 |
400 | .file_left_part {
401 | align-items: center;
402 | }
403 |
404 | .file_tags {
405 | flex-grow: 1;
406 | flex-shrink: 0;
407 | display: flex;
408 | align-items: center;
409 | justify-content: flex-end;
410 | }
411 |
412 | .file_queued .file_tags,
413 | .file_uploading .file_tags {
414 | display: none;
415 | }
416 |
417 | .file_right_part {
418 | text-align: right;
419 | float: none;
420 | }
421 |
422 | .file_tag {
423 | padding-left: 0.5ex;
424 | padding-right: 0.5ex;
425 | background-color: var(--tag-color) !important;
426 | }
427 |
428 | .file_tag.tag_key_user,
429 | .file_tag.tag_key_nick {
430 | order: 1000;
431 | }
432 |
433 | .file_tag.tag_key_user {
434 | background-color: var(--tag-color-nick) !important;
435 | }
436 |
437 | .file_tag.tag_key_hellban {
438 | text-decoration: line-through;
439 | }
440 |
441 | .file_control {
442 | padding-right: 0.2em;
443 | }
444 |
445 | .file_clock {
446 | font-size: 12px;
447 | }
448 |
449 | .button:not(.gallery_button),
450 | input[type=submit] {
451 | background-color: var(--button-color);
452 | }
453 |
454 | .button:visited:not(.gallery_button),
455 | input[type=submit]:visited {
456 | background-color: var(--button-color);
457 | }
458 |
459 | .button:hover:not(.gallery_button),
460 | input[type=submit]:hover {
461 | background-color: var(--button-hi-color);
462 | }
463 |
464 | .button:active:not(.gallery_button),
465 | input[type=submit]:active {
466 | background-color: var(--button-hi-color);
467 | }
468 |
469 | .gallery_button {
470 | font-size: x-small;
471 | }
472 |
473 | .gallery_area {
474 | border: 0;
475 | }
476 |
477 | .button.light,
478 | .button.light:visited,
479 | input[type=submit].light,
480 | input[type=submit].light:visited {
481 | background-color: var(--button-light-color);
482 | }
483 |
484 | .button.light:hover,
485 | input[type=submit].light:hover {
486 | background-color: var(--button-light-hi-color);
487 | }
488 |
489 | .ui_frame_content .button.light:hover {
490 | background-color: var(--button-light-hi-color);
491 | }
492 |
493 | #chat_header,
494 | #files_header {
495 | background-color: var(--main-bg-color);
496 | }
497 |
498 | #chat_name_container {
499 | background-color: var(--main-bg-color);
500 | order: 1;
501 | }
502 |
503 | #loading_frame {
504 | background-color: var(--main-bg-color);
505 | }
506 |
507 | #loading_text {
508 | color: white;
509 | }
510 |
511 | #room_name_angle_down,
512 | #chat_user_angle_up,
513 | #chat_user_icon,
514 | #clearsearch {
515 | opacity: 0.3;
516 | }
517 |
518 | #chat_user_angle_up {
519 | padding-left: 0.7ex;
520 | display: none;
521 | }
522 |
523 | #chat_user_icon {
524 | padding-right: 0.7ex;
525 | }
526 |
527 | #toggles span.toggle_text {
528 | display: none;
529 | }
530 |
531 | #chat_notifier {
532 | order: 2;
533 | }
534 |
535 | #chat_status {
536 | order: 3;
537 | }
538 |
539 | #gallery_title {
540 | font-size: small;
541 | }
542 |
543 | #main_small_links {
544 | background-color: #434343;
545 | }
546 |
547 | .top_nav_item,
548 | .top_nav_item:hover,
549 | .top_nav_item:visited,
550 | body {
551 | color: rgb(231, 231, 231);
552 | }
553 |
554 | .top_nav_item:hover {
555 | background: var(--header-hi-color);
556 | }
557 |
558 | .chat_message.staff:not(.profile) {
559 | word-wrap: break-word;
560 | opacity: 0.7;
561 | font-size: small;
562 | max-height: 4.5em;
563 | overflow-y: hidden;
564 | text-overflow: ellipsis;
565 | }
566 |
567 | .chat_message.staff:not(.profile):hover {
568 | max-height: none;
569 | }
570 |
571 | .chat_message .username .clickable:not(:hover) {
572 | color: inherit;
573 | }
574 |
575 | .chat_message .username {
576 | color: var(--username-color);
577 | float: none;
578 | }
579 |
580 | .chat_message.user .username {
581 | color: var(--username-green-color);
582 | }
583 |
584 | .chat_message.donator .username {
585 | color: var(--username-donor-color);
586 | }
587 |
588 | .chat_message.staff .username {
589 | color: var(--username-staff-color);
590 | }
591 |
592 | .chat_message.admin .username {
593 | color: var(--username-admin-color);
594 | }
595 |
596 | .chat_message.staff:not(.user) .username {
597 | color: var(--username-sys-color) !important;
598 | }
599 |
600 | .icon-image {
601 | color: var(--icon-image-color);
602 | }
603 |
604 | .icon-film {
605 | color: var(--icon-film-color);
606 | }
607 |
608 | .icon-book {
609 | color: var(--icon-book-color);
610 | }
611 |
612 | .icon-archive {
613 | color: var(--icon-archive-color);
614 | }
615 |
616 | .icon-music {
617 | color: var(--icon-music-color);
618 | }
619 |
620 | .chat_room_url {
621 | background-color: var(--hi-hover-color);
622 | }
623 |
624 | #radio_container {
625 | background-color: var(--main-bg-color);
626 | }
627 |
628 | #radio_current {
629 | background-color: var(--radio-current-color);
630 | }
631 |
632 | #radio_progress {
633 | background-color: var(--radio-progress-color);
634 | }
635 |
636 | #radio_volume_bar,
637 | #radio_load_progress {
638 | background-color: var(--radio-bar-color);
639 | }
640 |
641 | #radio_volume_slider,
642 | #radio_play_progress {
643 | background-color: var(--radio-slider-color);
644 | }
645 |
646 | body:not(.mobile_filelist) .file_right_part {
647 | min-width: 164px;
648 | }
649 |
650 | body:not(.mobile_filelist) .file_status {
651 | min-width: 90px;
652 | }
653 |
654 | .file_buttons::before,
655 | .file_status::before {
656 | border-color: var(--main-bg-color) !important;
657 | }
658 |
659 | #uploadButton {
660 | font-size: 18px;
661 | padding: 0 0.5em 1px 0.5em;
662 | }
663 |
664 | #clearsearch {
665 | padding-left: 0.6ex;
666 | }
667 |
668 | #call_to_action,
669 | #call_to_action_container.active #call_to_action {
670 | color: var(--action-color);
671 | }
672 |
673 | .drag_here_svg .drag_here_rect,
674 | #call_to_action_container.active .drag_here_rect {
675 | color: var(--action-color);
676 | stroke: var(--action-color);
677 | }
678 |
679 | #call_to_action_container::before {
680 | background-color: var(--snd-bg-color);
681 | }
682 |
683 | .page_admin_reports table,
684 | .page_admin_modlog table {
685 | table-layout: fixed;
686 | width: 100%;
687 | }
688 |
689 | .page_admin_modlog table th:nth-child(1) {
690 | max-width: 15%;
691 | }
692 |
693 | .page_admin_modlog table th:nth-child(3) {
694 | width: 56%;
695 | }
696 |
697 | .page_admin_modlog table th:nth-child(4) {
698 | width: 7ex;
699 | }
700 |
701 | .page_admin_reports table th:nth-child(1) {
702 | max-width: 9%;
703 | }
704 |
705 | .page_admin_reports table th:nth-child(4) {
706 | width: 10%;
707 | }
708 |
709 | .page_admin_reports table th:nth-child(3) {
710 | width: 53%;
711 | }
712 |
713 | #bottom_navigation_content {
714 | background-color: var(--main-bg-color);
715 | }
716 |
717 | #admin_button:not(:hover) {
718 | color: var(--input-disabled-color) !important;
719 | }
720 |
721 | #upload_button .icon-upload-button {
722 | margin: 0;
723 | }
724 |
725 | #upload_button > .on_small_header {
726 | display: none;
727 | }
728 |
729 | textarea[name="motd"] {
730 | width: 24em;
731 | height: 8em;
732 | }
733 |
734 | textarea[name="files"] {
735 | width: 30em;
736 | height: 12em;
737 | }
738 |
739 | .page_admin_adiscover tr:nth-child(odd) {
740 | background-color: rgba(255,255,255,0.04);
741 | }
742 |
743 | .page_admin_adiscover tr:hover {
744 | background-color: rgba(255,255,255,0.1);
745 | }
746 | }
747 |
--------------------------------------------------------------------------------
/dry.js:
--------------------------------------------------------------------------------
1 | const dry = (function() {
2 | "use strict";
3 | const unsafeWindow = this.unsafeWindow || window;
4 |
5 | const unique = function(a, key) {
6 | if (key) {
7 | return a.filter(function(e) {
8 | e = key(e);
9 | return !!e && (!this.has(e) && !!this.add(e));
10 | }, new Set());
11 | }
12 | return a.filter(function(e) {
13 | return !!e && (!this.has(e) && !!this.add(e));
14 | }, new Set());
15 | };
16 |
17 | const EventKeys = Symbol();
18 | class EventEmitter {
19 | constructor() {
20 | this[EventKeys] = new Map();
21 | }
22 | on(event, cb) {
23 | let handlers = this[EventKeys].get(event);
24 | if (!handlers) {
25 | this[EventKeys].set(event, handlers = new Set());
26 | }
27 | handlers.add(cb);
28 | }
29 | off(event, cb) {
30 | const keys = this[EventKeys];
31 | const handlers = keys.get(event);
32 | if (!handlers) {
33 | return;
34 | }
35 | handlers.delete(cb);
36 | if (!handlers.size) {
37 | keys.delete(event);
38 | }
39 | }
40 | once(event, cb, ...args) {
41 | let wrapped = (...args) => {
42 | try {
43 | return cb(...args);
44 | }
45 | finally {
46 | this.off(event, wrapped);
47 | }
48 | };
49 | return this.on(event, wrapped, ...args);
50 | }
51 | emit(event, ...args) {
52 | const handlers = this[EventKeys].get(event);
53 | if (!handlers) {
54 | return;
55 | }
56 | for (let e of Array.from(handlers)) {
57 | try {
58 | e(...args);
59 | }
60 | catch (ex) {
61 | console.error(`Event handler ${e} for ${event} failed`, ex);
62 | }
63 | }
64 | }
65 | emitSoon(event, ...args) {
66 | setTimeout(() => this.emit(event, ...args));
67 | }
68 | }
69 |
70 | const bus = new EventEmitter();
71 |
72 | if (document.readyState === "interactive" || document.readyState === "complete" ) {
73 | bus.emitSoon("dom", null, false);
74 | }
75 | else {
76 | addEventListener("DOMContentLoaded", function dom(evt) {
77 | removeEventListener("DOMContentLoaded", dom, true);
78 | bus.emit("dom", evt, true);
79 | }, true);
80 | }
81 |
82 | if (document.readyState === "complete") {
83 | bus.emitSoon("load", null, false);
84 | }
85 | else {
86 | addEventListener("load", function load(evt) {
87 | removeEventListener("load", load, true);
88 | bus.emit("load", evt, true);
89 | }, true);
90 | }
91 |
92 | let exts = null;
93 | try {
94 | exts = (window.Room || unsafeWindow.Room).prototype._extensions.connection.prototype.room.extensions;
95 | }
96 | catch (ex) {
97 | bus.on("load", () => {
98 | exts = (window.Room || unsafeWindow.Room).prototype._extensions.connection.prototype.room.extensions;
99 | });
100 | }
101 | let exportObject = function(o) {
102 | return unsafeWindow.JSON.parse(JSON.stringify(o));
103 | };
104 | let exportFunction = this.exportFunction;
105 |
106 | if (!exportFunction) {
107 | exportFunction = (fn, o) => fn;
108 | exportObject = o => window.JSON.parse(JSON.stringify(o));
109 | }
110 |
111 | const replace = function(proto, where, what, newfn) {
112 | where = where.split(".");
113 | let ext = this;
114 | for (let w of where) {
115 | if (!(w in ext)) {
116 | throw new Error(`Binding not available: ${w}`);
117 | }
118 | ext = ext[w];
119 | if (!ext) {
120 | throw new Error(`Binding not available: ${w}`);
121 | }
122 | }
123 | if (proto) {
124 | if (ext.prototype && document.readyState !== "complete") {
125 | ext = ext.prototype;
126 | }
127 | else {
128 | console.warn("binding late, skipping prototype, this might not work", where, what);
129 | return replace.call(exts, false, where, what, newfn);
130 | }
131 | if (!ext) {
132 | throw new Error("Binding prototype not available");
133 | }
134 | }
135 | let origfn = ext[what];
136 | if (!origfn) {
137 | throw new Error("Target not available");
138 | }
139 | ext[what] = exportFunction(function(...args) {
140 | return newfn.call(this, origfn.bind(this), ...args);
141 | }, unsafeWindow);
142 | return ext[what].bind(ext);
143 | };
144 |
145 | const replaceEarly = (...args) => {
146 | return replace.call((window.Room || unsafeWindow.Room).prototype._extensions, true, ...args);
147 | };
148 |
149 | class Commands {
150 | constructor() {
151 | replaceEarly("chat", "onCommand", (orig, command, e, ...args) => {
152 | let fn = this[command];
153 | if (fn && fn.call(this, e, args) !== false) {
154 | return;
155 | }
156 | args.unshift(e);
157 | args.unshift(command);
158 | return orig(...exportObject(args));
159 | });
160 | }
161 | }
162 |
163 | const appendMessage = (user, message, options) => {
164 | let o = {
165 | dontsave: true,
166 | staff: true,
167 | highlight: true
168 | };
169 | if (options) {
170 | Object.assign(o, options);
171 | }
172 | if (message.trim) {
173 | let m = new unsafeWindow.Array();
174 | m.push({type: "text", value: message});
175 | message = m;
176 | }
177 | return exts.chat.showMessage(user, exportObject(message), exportObject(o));
178 | };
179 |
180 | class MessageFilter {
181 | constructor() {
182 | this._replace("addMessage");
183 | this._replace("showMessage");
184 | }
185 | _replace(what, fn) {
186 | if (!this[what]) {
187 | return;
188 | }
189 | let my = this[what].bind(this);
190 | replaceEarly("chat", what, (...args) => {
191 | try {
192 | if (my(...args) === false) {
193 | return;
194 | }
195 | }
196 | catch (ex) {
197 | console.error(what, "threw", ex);
198 | }
199 | let orig = args.shift();
200 | return orig(...args);
201 | });
202 | }
203 | }
204 |
205 | let config = window.config || unsafeWindow.config;
206 | if (!config) {
207 | bus.once("dom", () => {
208 | config = window.config || unsafeWindow.config;
209 | if (!config) {
210 | bus.once("load", () => {
211 | config = window.config || unsafeWindow.config;
212 | });
213 | }
214 | });
215 | }
216 |
217 | return {
218 | on: bus.on.bind(bus),
219 | off: bus.off.bind(bus),
220 | once: bus.once.bind(bus),
221 | emit: bus.emit.bind(bus),
222 | get config() {
223 | return config;
224 | },
225 | get exts() {
226 | return exts;
227 | },
228 | unsafeWindow,
229 | replaceEarly,
230 | replaceLate(...args) {
231 | return replace.call(this.exts, false, ...args);
232 | },
233 | appendMessage,
234 | exportFunction,
235 | exportObject,
236 | unique,
237 | EventEmitter,
238 | Commands,
239 | MessageFilter,
240 | version: "0.4",
241 | };
242 | }).call(this);
243 |
--------------------------------------------------------------------------------
/freakycolors.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Freaky Names
3 | // @namespace http://jew.dance/
4 | // @version 0.13
5 | // @description ...and shit
6 | // @author RealDolos
7 | // @match https://volafile.org/r/*
8 | // @grant none
9 | // @require https://cdn.jsdelivr.net/gh/volafiled/volascripts@db222e0a836c6da9d5593c7fc93941c0e7a9d2a1/dry.js
10 | // @run-at document-start
11 | // @icon https://volafile.org/favicon.ico
12 | // ==/UserScript==
13 |
14 | /* globals dry */
15 |
16 | (function() {
17 | "use strict";
18 |
19 | const colors = {
20 | "getddosed": "blue",
21 | "^thejidf$|^jewmobile$|^mrshlomo$": {color: "pink", content:'✡'},
22 | "^starsheep": {color: "yellow", content: "🥖"},
23 | "^whitepride|llazarus$": "#7aa2ff",
24 | "31337h4x0r|realdolos|vagfacetrm|robocuck|(?:Red|Dong|Immor|lg188)dolos": "white",
25 | "^kreg$": "hotpink",
26 | "^robocop$": {color: "dodgerblue", content: '🤖'},
27 | "^lain$": "gold",
28 | "^red$": {color: "indianred", content: '💰'},
29 | "^thersanderia$": { color: "#e3dac9", content: '💀'},
30 | "^bain$": {color: "#00A693", content:'👳🏽♂️'},
31 | "^counselor$|^apha$|^couscous|^vaat$": {color: "rgb(210, 148, 44)", content: '💩'},
32 | "^lmmortal$": {color: "rgb(255, 108, 135)", content: '👸'},
33 | "^mercwmouth$|^deadpool$": { color: "lightbrown", content: "👳"},
34 | "^modchatbot": {content: '🗡️', color: "yellowgreen"},
35 | "^liquid$|^news$": {content: '🐑'},
36 | "^cyberia$": {content: 'λ'},
37 | "^someguy1992$": {color: "#EDDB17", content:"😑"},
38 | "^dad": {color: "lightskyblue"},
39 | "^heisenb3rg$": {content: "🏳🌈"},
40 | "^GitGood$": {color: "rgb(116, 161, 204)"},
41 | "^SolSelene$": {content: "🐇"},
42 | };
43 | const r_colors = [];
44 | for (let name in colors) {
45 | r_colors.push([new RegExp(name, "i"), colors[name]]);
46 | }
47 |
48 | console.log("running", GM_info.script.name, GM_info.script.version, dry.version);
49 | dry.once("dom", () => {
50 | new class extends dry.MessageFilter {
51 | addMessage(orig, m) {
52 | for (let r of r_colors) {
53 | if (m.options.user && r[0].test(m.nick)) {
54 | let color = r[1];
55 | let content = null;
56 | if (typeof color !== "string") {
57 | color = r[1].color;
58 | content = r[1].content;
59 | }
60 | if (content) {
61 | let star = m.elem.querySelector(".icon-star");
62 | if (star) {
63 | star.textContent = content.trim();
64 | star.classList.remove("icon-star");
65 | star.classList.add("custom-pro");
66 | }
67 | }
68 | if (color) {
69 | for (let n = m.nick_elem; n; n = n.previousSibling) {
70 | n.style.color = color;
71 | }
72 | }
73 | return;
74 | }
75 | }
76 | }
77 | }();
78 |
79 | let css = document.createElement("style");
80 | css.textContent = `
81 | .custom-pro {
82 | font-size: 80%;
83 | font-weight: bolder;
84 | margin-left: -.35em;
85 | margin-right: .15em;
86 | padding: 0;
87 | min-width: 18px;
88 | display: inline-block;
89 | text-align: center;
90 | }
91 | `;
92 | document.body.appendChild(css);
93 | });
94 | })();
95 |
--------------------------------------------------------------------------------
/gallery-is-not-for-chat.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name gallery is not for chat
3 | // @version 2
4 | // @description It really is not!
5 | // @namespace https://volafile.org
6 | // @icon https://volafile.org/favicon.ico
7 | // @author topkuk productions
8 | // @match https://volafile.org/r/*
9 | // @grant none
10 | // @require https://cdn.jsdelivr.net/gh/volafiled/volascripts@a9c0424e5498deea9fd437c15b2137c3bec07c61/dry.min.js
11 | // @run-at document-start
12 | // ==/UserScript==
13 | /* globals dry */
14 |
15 | dry.once("load", () => {
16 | "use strict";
17 | const $ = sel => document.querySelector(sel);
18 | const style = document.createElement("style");
19 | style.textContent = `
20 | .blur {
21 | -webkit-filter: none;
22 | -moz-filter: none;
23 | -o-filter: none;
24 | -ms-filter: none;
25 | filter: none;
26 | }
27 | .blur #call_to_action_container,
28 | .blur #files_scroller,
29 | .blur #radio_container {
30 | -webkit-filter: blur(5px);
31 | -moz-filter: blur(5px);
32 | -o-filter: blur(5px);
33 | -ms-filter: blur(5px);
34 | filter: progid:DXImageTransform.Microsoft.Blur(PixelRadius='5');
35 | }
36 | `;
37 | document.body.appendChild(style);
38 | const g = dry.exts.gallery;
39 | const scroll_files = function(e) {
40 | if (e.deltaY > 0) {
41 | g.next();
42 | }
43 | else {
44 | g.previous();
45 | }
46 | };
47 | const frame = $("#files_frame");
48 | const gframe = $("#gallery_frame");
49 | gframe.addEventListener("wheel", scroll_files, {passive: true});
50 | frame.appendChild(gframe);
51 | });
52 |
--------------------------------------------------------------------------------
/old-file-list-clicks.css:
--------------------------------------------------------------------------------
1 | @-moz-document domain("volafile.org") {
2 | .file_left_part {
3 | pointer-events: none;
4 | }
5 | div.filelist_file > a > span {
6 | pointer-events: auto !important;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/ownerdelete.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Mod EVERYTHING better, because reasons!
3 | // @namespace http://not.jew.dance/
4 | // @version 4
5 | // @description try to take over the world!
6 | // @author You
7 | // @match https://volafile.org/r/*
8 | // @icon https://volafile.org/favicon.ico
9 | // @require https://cdn.jsdelivr.net/gh/volafiled/volascripts@a9c0424e5498deea9fd437c15b2137c3bec07c61/dry.min.js
10 | // @require https://cdn.jsdelivr.net/gh/volafiled/node-parrot@acb622d5d9af34f0de648385e6ab4d2411373037/parrot/finally.min.js
11 | // @require https://cdn.jsdelivr.net/gh/volafiled/node-parrot@acb622d5d9af34f0de648385e6ab4d2411373037/parrot/pool.min.js
12 | // @grant none
13 | // @run-at document-start
14 | // ==/UserScript==
15 | /* globals dry, PromisePool */
16 | (function() {
17 | "use strict";
18 |
19 | function selected() {
20 | return Array.from(
21 | dry.exts.filelistManager.filelist.filelist.
22 | filter(e => e.getData("checked")).
23 | map(e => e.id)
24 | );
25 | }
26 |
27 | function $e(tag, attrs, text) {
28 | const rv = document.createElement(tag);
29 | attrs = attrs || {};
30 | for (const a in attrs) {
31 | rv.setAttribute(a, attrs[a]);
32 | }
33 | if (text) {
34 | rv.textContent = text;
35 | }
36 | return rv;
37 | }
38 |
39 | function debounce(fn, to) {
40 | if (fn.length) {
41 | throw new Error("cannot have params");
42 | }
43 | to = to || 100;
44 | let timer;
45 |
46 | const run = function() {
47 | timer = 0;
48 | fn.call(this);
49 | };
50 |
51 | return function() {
52 | if (timer) {
53 | return;
54 | }
55 | timer = setTimeout(run.bind(this), to);
56 | };
57 | }
58 |
59 | function timeout(delay) {
60 | return new Promise((_, rej) => {
61 | setTimeout(() => {
62 | rej(new Error(`Timeout of ${delay} milliseconds got reached.`));
63 | }, delay);
64 | });
65 | }
66 |
67 | const $ = document.querySelector.bind(document);
68 | const $$ = document.querySelectorAll.bind(document);
69 | let isOwner = false;
70 |
71 | const loadRekts = () => {
72 | let rv = localStorage.getItem("rekted");
73 | rv = JSON.parse(rv || "[]");
74 | try {
75 | return new Set(rv || []);
76 | }
77 | catch (ex) {
78 | return new Set();
79 | }
80 | };
81 |
82 | const rekt = loadRekts();
83 | const whitePurge = "-purgewhiteys-";
84 |
85 | const saveRekts = () => {
86 | localStorage.setItem("rekted", JSON.stringify(Array.from(rekt.values())));
87 | };
88 | dry.once("dom", () => {
89 | new class extends dry.MessageFilter {
90 | showMessage(fn, nick, msgObj, options, data) {
91 | try {
92 | if (!isOwner || !data || !data.id) {
93 | return;
94 | }
95 | nick = nick.toLowerCase().trim();
96 | if (options.user && rekt.has(`@${nick}`)) {
97 | dry.exts.connection.call("timeoutChat", data.id, 3600 * 24);
98 | return;
99 | }
100 | if (!options.user && rekt.has(nick)) {
101 | dry.exts.connection.call("timeoutChat", data.id, 3600 * 24);
102 | return;
103 | }
104 | if (!options.user && rekt.has(whitePurge)) {
105 | dry.exts.connection.call("timeoutChat", data.id, 3600 * 24);
106 | return;
107 | }
108 | }
109 | catch (ex) {
110 | console.error(ex);
111 | }
112 | }
113 | }();
114 |
115 | new class extends dry.Commands {
116 | rekt(user) {
117 | if (!isOwner) {
118 | return true;
119 | }
120 | user = user.toLowerCase().trim();
121 | if (rekt.has(user)) {
122 | dry.appendMessage("Rekt", `${user} is already rekt!`);
123 | return true;
124 | }
125 | if (user !== "") {
126 | dry.appendMessage("Rekt", `${user} got rekt`);
127 | rekt.add(user);
128 | saveRekts();
129 | }
130 | else {
131 | dry.appendMessage("Error", "Can't rekt an empty string");
132 | }
133 | return true;
134 | }
135 |
136 | unrekt(user) {
137 | if (!isOwner) {
138 | return true;
139 | }
140 | user = user.toLowerCase().trim();
141 | if (!rekt.has(user)) {
142 | dry.appendMessage("Unrekt", `${user} is not on rektlist!`);
143 | return true;
144 | }
145 | if (user !== "") {
146 | dry.appendMessage("Unrekt", `${user} got unrekt`);
147 | rekt.delete(user);
148 | saveRekts();
149 | }
150 | else {
151 | dry.appendMessage("Error", "Can't unrekt an empty string");
152 | }
153 | return true;
154 | }
155 |
156 | showrekts() {
157 | if (!isOwner) {
158 | return true;
159 | }
160 | if (!rekt.size) {
161 | dry.appendMessage("Showrekts", "Rektlist is empty!");
162 | return true;
163 | }
164 | dry.unsafeWindow.alert(
165 | `Rekt boys:\n${Array.from(rekt.values()).filter(el => el !== whitePurge)}`
166 | );
167 | return true;
168 | }
169 |
170 | killwhites() {
171 | if (!isOwner) {
172 | return;
173 | }
174 | if (rekt.has(whitePurge)) {
175 | dry.appendMessage("Purgatory", "Whiteposting is allowed now");
176 | rekt.delete(whitePurge);
177 | }
178 | else {
179 | dry.appendMessage("Purgatory", "All white posters will be timed out");
180 | rekt.add(whitePurge);
181 | }
182 | saveRekts();
183 | }
184 | }();
185 | });
186 |
187 | dry.once("load", () => {
188 | let last_file = null;
189 | const ownerFiles = new WeakMap();
190 | const pool = new PromisePool(6);
191 |
192 | const checksums = (function() {
193 | const rv = dry.unsafeWindow.sessionStorage.getItem("ownerChecksums");
194 | try {
195 | return new Map(rv && JSON.parse(rv));
196 | }
197 | catch (e) {
198 | return new Map();
199 | }
200 | }());
201 | const save_checksums = debounce(function() {
202 | dry.unsafeWindow.sessionStorage.setItem("ownerChecksums", JSON.stringify(Array.from(checksums)));
203 | }, 1000);
204 | const find_file = function(file) {
205 | const {id} = file;
206 | const fl = dry.exts.filelistManager.filelist.filelist;
207 | for (let i = 0; i < fl.length; ++i) {
208 | if (fl[i].id === id) {
209 | return { idx: i, item: fl[i] };
210 | }
211 | }
212 | return null;
213 | };
214 | const getFileFromEvent = function(e) {
215 | let file; let fileElement = e.target;
216 | while (!file) {
217 | if (!fileElement) {
218 | return null;
219 | }
220 | file = ownerFiles.get(fileElement);
221 | fileElement = fileElement.parentElement;
222 | }
223 | return file;
224 | };
225 | async function getInfo(file) {
226 | try {
227 | const info = await Promise.race([dry.exts.info.getFileInfo(file.id), timeout(5000)]);
228 | const {checksum} = info;
229 | checksums.set(file.id, checksum);
230 | file.checksum = checksum;
231 | save_checksums();
232 | }
233 | catch (e) {
234 | console.error(e);
235 | file.checksum = false;
236 | }
237 | }
238 | const file_click = function(e) {
239 | if (!e.target.classList.contains("filetype")) {
240 | return undefined;
241 | }
242 | const file = getFileFromEvent(e);
243 | if (!file) {
244 | return undefined;
245 | }
246 | e.stopPropagation();
247 | e.preventDefault();
248 | if (!e.shiftKey) {
249 | file.setData("checked", !file.getData("checked"));
250 | last_file = file;
251 | return false;
252 | }
253 | if (!last_file) {
254 | return false;
255 | }
256 | let lf = find_file(last_file); let cf = find_file(file);
257 | if (!lf || !cf) {
258 | return false;
259 | }
260 | [lf, cf] = [lf.idx, cf.idx];
261 | if (cf > lf) {
262 | [lf, cf] = [cf, lf];
263 | }
264 | const files = dry.exts.filelistManager.filelist.filelist.slice(cf, lf).
265 | filter(file => file.visible);
266 | const checked = file.getData("checked");
267 | files.forEach(el => {
268 | el.setData("checked", !checked);
269 | });
270 | file.setData("checked", !checked);
271 | last_file.setData("checked", !checked);
272 | return false;
273 | };
274 | const prepare_file = function(file) {
275 | try {
276 | if (!file.id) {
277 | return;
278 | }
279 | if (file.tags) {
280 | let subject = (file.tags.user || file.tags.nick).toLowerCase().trim();
281 | if (rekt.has(subject)) {
282 | dry.exts.connection.call("timeoutFile", file.id, 3600 * 24);
283 | dry.exts.connection.call("deleteFiles", [file.id]);
284 | }
285 | if (rekt.has(`@${subject}`)) {
286 | dry.exts.connection.call("timeoutFile", file.id, 3600 * 24);
287 | dry.exts.connection.call("deleteFiles", [file.id]);
288 | }
289 | }
290 | const fe = file.dom.fileElement;
291 | if (ownerFiles.has(fe)) {
292 | return;
293 | }
294 | const tags = Object.assign(file.tags, file.tags.nick ?
295 | {white: true} :
296 | {green: true});
297 | file.dom.setTags(tags);
298 | if (!file.checksum) {
299 | if (checksums.has(file.id)) {
300 | file.checksum = checksums.get(file.id);
301 | }
302 | else {
303 | pool.schedule(getInfo, file);
304 | }
305 | }
306 | fe.addEventListener("click", file_click, true);
307 | fe.setAttribute("contextmenu", "dolos_cuckmenu");
308 | ownerFiles.set(fe, file);
309 | }
310 | catch (ex) {
311 | console.error(ex);
312 | }
313 | };
314 |
315 | const createButtons = function(isOwnerOrAdminOrJanitor) {
316 | if (isOwner || !isOwnerOrAdminOrJanitor) {
317 | return;
318 | }
319 | isOwner = true;
320 |
321 | try {
322 | const cont = $("#upload_container");
323 | const btnel = $e("label", {
324 | for: "dolos_delete_input",
325 | id: "dolos_deleteButton",
326 | class: "button",
327 | style: "margin-right: 0.5em",
328 | });
329 | btnel.appendChild($e("span", {
330 | class: "icon-trash"
331 | }));
332 | btnel.appendChild($e("span", {
333 | class: "on_small_header"
334 | }, "Delete"));
335 | cont.insertBefore(btnel, cont.firstChild);
336 | btnel.addEventListener("click", function() {
337 | const ids = selected();
338 | dry.exts.connection.call("deleteFiles", ids);
339 | });
340 |
341 | const el = $e("menu", {
342 | id: "dolos_cuckmenu",
343 | type: "context"
344 | });
345 | let mi = $e("menuitem", null, "Select All Files From User");
346 | el.appendChild(mi);
347 | let user = null;
348 | const lists = $$("#file_list, #volanail-list");
349 | for (const list of lists) {
350 | list.addEventListener("contextmenu", function(e) {
351 | const file = getFileFromEvent(e);
352 | if (!file) {
353 | this.style.display = "none";
354 | return;
355 | }
356 | user = file.tags.user || file.tags.nick;
357 | if (!user) {
358 | this.style.display = "none";
359 | return;
360 | }
361 | this.textContent = `Select All Files From '${user}'`;
362 | user = user.toLowerCase();
363 | this.style.display = "";
364 | }.bind(mi));
365 | }
366 | mi.addEventListener("click", function() {
367 | dry.exts.filelistManager.filelist.filelist.forEach(
368 | e => e.setData("checked",
369 | (e.tags.user || e.tags.nick).toLowerCase() === user));
370 | });
371 |
372 | mi = $e("menuitem", null, "Select All");
373 | mi.addEventListener("click", function() {
374 | dry.exts.filelistManager.filelist.filelist.forEach(
375 | e => e.setData("checked", true));
376 | });
377 | el.appendChild(mi);
378 |
379 | mi = $e("menuitem", null, "Select None");
380 | mi.addEventListener("click", function() {
381 | dry.exts.filelistManager.filelist.filelist.forEach(
382 | e => e.setData("checked", false));
383 | });
384 | el.appendChild(mi);
385 |
386 | mi = $e("menuitem", null, "Invert Selection");
387 | mi.addEventListener("click", function() {
388 | dry.exts.filelistManager.filelist.filelist.forEach(e => {
389 | e.setData("checked", !e.getData("checked"));
390 | });
391 | });
392 | el.appendChild(mi);
393 |
394 | mi = $e("menuitem", null, "Select Dupes");
395 | mi.addEventListener("click", function() {
396 | if (pool.total > 0) {
397 | dry.unsafeWindow.alert("Data to perform this operation is not ready yet! Try again soon.");
398 | return;
399 | }
400 | const known = new Set();
401 | dry.exts.filelistManager.filelist.filelist.forEach(e => {
402 | const k = e.checksum;
403 | if (!k) {
404 | // just skip the iteration if checkusm isn't present
405 | return;
406 | }
407 | if (known.has(k)) {
408 | e.setData("checked", true);
409 | console.log(`marked ${e.name} from ${e.tags.user || e.tags.nick} for doom`);
410 | }
411 | else {
412 | known.add(k);
413 | e.setData("checked", false);
414 | }
415 | });
416 | });
417 | el.appendChild(mi);
418 |
419 | mi = $e("menuitem", null, "Select All White Name Files");
420 | mi.addEventListener("click", function() {
421 | dry.exts.filelistManager.filelist.filelist.forEach(e => {
422 | e.setData("checked", !!e.tags.nick);
423 | });
424 | });
425 | el.appendChild(mi);
426 |
427 | if (dry.exts.user.info.admin) {
428 | let ip = null;
429 | mi = $e("menuitem", null, "Select All Files For IP");
430 | for (const list of lists) {
431 | list.addEventListener("contextmenu", function(e) {
432 | const file = getFileFromEvent(e);
433 | if (!file) {
434 | this.style.display = "none";
435 | return;
436 | }
437 | ip = file.tags.ip || null;
438 | if (!ip) {
439 | this.style.display = "none";
440 | }
441 | this.textContent = `Select All Files For IP '${ip}'`;
442 | this.style.display = "";
443 | }.bind(mi));
444 | }
445 | mi.addEventListener("click", function() {
446 | dry.exts.filelistManager.filelist.filelist.forEach(e => {
447 | e.setData("checked", e.tags.ip === ip);
448 | });
449 | });
450 | el.appendChild(mi);
451 | }
452 | document.body.appendChild(el);
453 | }
454 | catch (ex) {
455 | console.error(ex);
456 | }
457 |
458 | dry.exts.filelistManager.on("fileAdded", prepare_file);
459 | dry.exts.filelistManager.on("fileUpdated", prepare_file);
460 | dry.exts.filelistManager.on("fileRemoved", file => {
461 | checksums.delete(file.id);
462 | });
463 | dry.exts.filelistManager.on("nail_init", file => {
464 | const te = file.dom.vnThumbElement;
465 | if (!te) {
466 | console.warn("got a nail, but no nail");
467 | return;
468 | }
469 | te.setAttribute("contextmenu", "dolos_cuckmenu");
470 | ownerFiles.set(te, file);
471 | });
472 | dry.exts.filelistManager.filelist.filelist.forEach(prepare_file);
473 | };
474 |
475 | dry.exts.user.on("info_owner", createButtons);
476 | dry.exts.user.on("info_admin", createButtons);
477 | dry.exts.user.on("info_janitor", createButtons);
478 | });
479 | })();
480 |
--------------------------------------------------------------------------------
/owners-on-hover.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Room owner + janitor hover
3 | // @namespace https://not.jew.dance
4 | // @version 4.1
5 | // @description Show da owner und janitors
6 | // @author Polish cuck
7 | // @icon https://volafile.org/favicon.ico
8 | // @match https://volafile.org/r/*
9 | // @require https://cdn.jsdelivr.net/gh/volafiled/volascripts@a9c0424e5498deea9fd437c15b2137c3bec07c61/dry.min.js
10 | // @grant none
11 | // @run-at document-start
12 | // ==/UserScript==
13 | /* globals dry */
14 |
15 | dry.once("load", () => {
16 | 'use strict';
17 | function wrapLines(arr, len) {
18 | len = len || 40;
19 | let cur = "";
20 | const rv = [];
21 | for (let i of arr) {
22 | if (!i) {
23 | continue;
24 | }
25 | let n = cur ? `${cur}, ${i}` : i;
26 | if (n.length > len && cur) {
27 | rv.push(cur);
28 | cur = i;
29 | continue;
30 | }
31 | cur = n;
32 | }
33 | if (cur) {
34 | rv.push(cur);
35 | }
36 | return rv.join("\n");
37 | }
38 |
39 | const room_title = document.getElementById("name_container");
40 |
41 | dry.exts.connection.on("config", cfg => {
42 | let rv = [cfg.owner ? `Room owner: ${cfg.owner}` : "No owner"];
43 | if (Array.isArray(cfg.janitors) && cfg.janitors.length) {
44 | rv.push("Janitors:");
45 | rv.push(wrapLines(cfg.janitors.slice(0).sort(), 36));
46 | }
47 | room_title.title = rv.join("\n");
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/passiveevents.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Passive events
3 | // @namespace https://volafile.org
4 | // @icon https://volafile.org/favicon.ico
5 | // @author topkuk productions
6 | // @include https://volafile.org/r/*
7 | // @match https://volafile.org/r/*
8 | // @version 1
9 | // @description Because performance!
10 | // @grant none
11 | // @run-at document-start
12 | // ==/UserScript==
13 |
14 | (function() {
15 | 'use strict';
16 | const unfucked = Object.freeze(new Set([
17 | "resize",
18 | "wheel",
19 | "scroll",
20 | "mouseenter",
21 | "mousemove",
22 | "mouseexit",
23 | "mouseleave",
24 | "mousewheel",
25 | "touchmove",
26 | ]));
27 | const addEventListener = EventTarget.prototype.addEventListener;
28 | EventTarget.prototype.addEventListener = function(type, fn, opts, ...args) {
29 | if (unfucked.has(type)) {
30 | if (typeof opts == "boolean") {
31 | opts = {
32 | capture: opts,
33 | passive: true
34 | };
35 | }
36 | else if (opts && typeof opts === "object") {
37 | opts = Object.assign({}, opts, {passive: true});
38 | }
39 | else {
40 | opts = {"passive": true};
41 | }
42 | }
43 | return addEventListener.call(this, type, fn, opts, ...args);
44 | }
45 | })();
46 |
--------------------------------------------------------------------------------
/propernames.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Proper user names
3 | // @namespace http://jew.dance/
4 | // @version 0.4
5 | // @description Properly name users
6 | // @author RealDolos
7 | // @match https://volafile.io/r/*
8 | // @grant none
9 | // @run-at document-start
10 | // ==/UserScript==
11 |
12 | "use strict";
13 |
14 | const replacements = [
15 | [/mercwmouth/gi, "NiggerOnly4Penis"],
16 | [/deadpool/gi, "PanSexFaggot"],
17 | [/mcgill/gi, "SuperMarky"],
18 | [/\bcounselor\b/gi, "CounselorPedro"],
19 | [/\bLewdBot\b/gi, "Dungmaster"],
20 | [/\b(?:system|news)\b/gi, "CucklordSupreme"]
21 | ];
22 |
23 |
24 | console.log("running", GM_info.script.name, GM_info.script.version);
25 |
26 | WebSocket = window.WebSocket = (function(ws) {
27 | // I'd usually use a proxy, but thanks Chrome for not supporting ecma-6
28 | const WebSocket = function(url, protocols) {
29 | this.ws = new ws(url, protocols);
30 | this.ws.onmessage = function(e) {
31 | try {
32 | let data = e.data;
33 | for (let i of replacements) {
34 | data = data.replace(i[0], i[1]);
35 | }
36 | if (data != e.data) {
37 | e = new MessageEvent(e.type, {
38 | data: data, origin: e.origin, lastEventId: e.lastEventId,
39 | channel: e.channel, source: e.source, ports: e.ports});
40 | }
41 | }
42 | catch (ex) {
43 | console.error(e, ex);
44 | }
45 | return this.onmessage && this.onmessage(e);
46 | }.bind(this);
47 | };
48 | WebSocket.prototype = {
49 | get url() { return this.ws.url; },
50 |
51 | CONNECTING: ws.CONNECTING,
52 | OPEN: ws.OPEN,
53 | CLOSING: ws.CLOSING,
54 | CLOSED: ws.CLOSED,
55 |
56 | get readyState() { return this.ws.readyState; },
57 | get bufferedAmount() { return this.ws.ufferedAmount; },
58 |
59 | get onopen() { return this.ws.onopen; },
60 | set onopen(nv) { return this.ws.onopen = nv; },
61 | get onerror() { return this.ws.onerror; },
62 | set onerror(nv) { return this.ws.onerror = nv; },
63 | get onclose() { return this.ws.onclose; },
64 | set onclose(nv) { return this.ws.onclose = nv; },
65 | get extensions() { return this.ws.extensions; },
66 | get protocol() { return this.ws.protocol; },
67 |
68 | close: function(code, reason) { this.ws.close(code || 1000, reason || ""); },
69 |
70 | // onmessage handled in ctor
71 | get binaryType() { return this.ws.binaryType; },
72 | set binaryType(nv) { return this.ws.binaryType = nv; },
73 |
74 | send: function(data) { this.ws.send(data); }
75 | };
76 | return WebSocket;
77 | })(window.WebSocket);
78 |
--------------------------------------------------------------------------------
/room-title-quirks.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Room Title Quirks
3 | // @namespace https://not.jew.dance
4 | // @version 2
5 | // @author Polish potato
6 | // @icon https://volafile.org/favicon.ico
7 | // @match https://volafile.org/r/*
8 | // @require https://cdn.jsdelivr.net/gh/volafiled/volascripts@a9c0424e5498deea9fd437c15b2137c3bec07c61/dry.min.js
9 | // @grant none
10 | // @run-at document-start
11 | // ==/UserScript==
12 | /* globals dry */
13 |
14 | dry.once("load", () => {
15 | 'use strict';
16 |
17 | const room_name = document.getElementById("room_name");
18 | const default_color = room_name.style.color;
19 |
20 | dry.exts.connection.on("config", cfg => {
21 | if (dry.exts.user.info.admin && typeof cfg.password !== "undefined") {
22 | // This only works for mods because
23 | // password is visible in the config for them
24 | room_name.textContent = cfg.password ?
25 | `🔒 ${dry.config.name}` : dry.config.name;
26 | }
27 | if (typeof cfg.disabled !== "undefined") {
28 | room_name.style.color = cfg.disabled ? "red" : default_color;
29 | }
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/timestamps.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Vola Timestamps
3 | // @version 11
4 | // @description Dongo said to make this
5 | // @namespace https://volafile.org
6 | // @icon https://volafile.org/favicon.ico
7 | // @author topkuk productions
8 | // @match https://volafile.org/r/*
9 | // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
10 | // @require https://cdn.jsdelivr.net/gh/RealDolos/volascripts@1dd689f72763c0e59f567fdf93865837e35964d6/dry.js
11 | // @grant none
12 | // @run-at document-start
13 | // ==/UserScript==
14 | /* globals dry */
15 |
16 | dry.once("dom", () => {
17 | "use strict";
18 | console.log("running", GM.info.script.name, GM.info.script.version, dry.version);
19 |
20 | const style = document.createElement("style");
21 | style.textContent = `
22 | .username.timestamp {
23 | font-size: 82%;
24 | padding-right: 1em;
25 | }
26 | [timestamps="false"] .username.timestamp {
27 | display: none;
28 | }
29 | `;
30 | document.body.appendChild(style);
31 |
32 | const config_key = `${dry.config.room_id}-timestamps`;
33 | const seconds_key = `timestamps-seconds`;
34 | let enabled = localStorage.getItem(config_key);
35 | let seconds = localStorage.getItem(seconds_key);
36 | enabled = enabled !== "disabled";
37 | document.body.setAttribute("timestamps", "" + enabled);
38 | seconds = seconds === "enabled";
39 |
40 | const SM = new Intl.DateTimeFormat("en-US", {
41 | hour12: false,
42 | hour: "2-digit",
43 | minute: "2-digit",
44 | second: seconds ? "2-digit" : undefined
45 | });
46 | const LG = new Intl.DateTimeFormat("eu");
47 |
48 | new class extends dry.MessageFilter {
49 | showMessage(orig, nick, message, options) {
50 | if (!options.timestamp) {
51 | options.timestamp = Date.now();
52 | }
53 | }
54 | addMessage(orig, m) {
55 | if (!m.nick || !m.options || !m.options.timestamp) {
56 | return;
57 | }
58 | const addTimestamp = function() {
59 | let span = document.createElement("span");
60 | span.classList.add("timestamp", "username", "unselectable");
61 | let d = new Date(m.options.timestamp);
62 | span.textContent = SM.format(d) + " ";
63 | span.setAttribute("title", LG.format(d));
64 | m.timestamp_elem = span;
65 | m.elem.insertBefore(span, m.elem.firstChild);
66 | };
67 | addTimestamp();
68 | const update = m.update.bind(m);
69 | m.update = function() {
70 | update();
71 | addTimestamp();
72 | };
73 | }
74 | }();
75 | new class extends dry.Commands {
76 | ts(e) {
77 | enabled = !enabled;
78 | localStorage.setItem(config_key, enabled ? "enabled" : "disabled");
79 | document.body.setAttribute("timestamps", "" + enabled);
80 | return true;
81 | }
82 | toggleseconds(e) {
83 | seconds = !seconds;
84 | localStorage.setItem(seconds_key, seconds ? "enabled" : "disabled");
85 | return true;
86 | }
87 | }();
88 | });
89 |
--------------------------------------------------------------------------------
/volaban.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name VolaBan
3 | // @version 4
4 | // @description Filter annoying users
5 | // @namespace https://volafile.org
6 | // @include https://volafile.org/r/*
7 | // @icon https://volafile.org/favicon.ico
8 | // @author topkuk productions
9 | // @match https://volafile.org/r/*
10 | // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
11 | // @require https://cdn.jsdelivr.net/gh/volafiled/volascripts@a9c0424e5498deea9fd437c15b2137c3bec07c61/dry.min.js
12 | // @grant none
13 | // @run-at document-start
14 | // ==/UserScript==
15 | /* globals dry, GM */
16 | (function() {
17 | "use strict";
18 |
19 | const basebans = {
20 | staff: ["News"],
21 | exact: ["DeadPool", "Wade"],
22 | whites: ["real", "dolos"],
23 | logs: ["davidbowie", "pinkb0t"]
24 | };
25 |
26 | console.log("running", GM.info.script.name, GM.info.script.version, dry.version);
27 |
28 | let bans;
29 |
30 | const make = function() {
31 | let base = JSON.parse(JSON.stringify(basebans));
32 | bans = JSON.parse(localStorage.getItem("bans"));
33 | if (!bans) {
34 | bans = base;
35 | }
36 | else {
37 | bans = Object.setPrototypeOf(bans, base);
38 | }
39 |
40 | for (let key in basebans) {
41 | bans["r" + key] = key === "whites" || key === "logs" ?
42 | new RegExp(`(?:${bans[key].join("|")})`, "i") :
43 | new RegExp(`^(?:${bans[key].join("|")})$`, "i");
44 | }
45 | console.log(bans);
46 | };
47 | make();
48 |
49 | const save = function() {
50 | localStorage.setItem("bans", JSON.stringify(bans));
51 | };
52 |
53 | const ignore = (nick, options, message) => {
54 | return (bans.exact.length && bans.rexact.test(nick)) ||
55 | (bans.staff.length && options.staff && bans.rstaff.test(nick)) ||
56 | (bans.logs.length && nick === "Log" && Array.isArray(message) &&
57 | message.length === 1 && bans.rlogs.test(message[0].value)) ||
58 | (bans.whites.length && !(options.staff || options.user) && bans.rwhites.test(nick));
59 | };
60 |
61 | dry.once("dom", () => {
62 | // Will get rid of messages but not of notifications
63 | new class extends dry.MessageFilter {
64 | showMessage(orig, nick, message, options) {
65 | if (!ignore(nick, options, message)) {
66 | return;
67 | }
68 | console.error("ignored", nick.toString(), JSON.stringify(message), JSON.stringify(options));
69 | return false;
70 | }
71 | }();
72 | const _ban = (what, type, who) => {
73 | if (!who) {
74 | return false;
75 | }
76 | who = who.trim();
77 | let a = bans[what === "w" ? "whites" : (what === "s" ? "staff" :
78 | (what === "l" ? "logs" : "exact"))];
79 | if (type === "block") {
80 | if (a.indexOf(who) < 0) {
81 | a.push(who);
82 | }
83 | else {
84 | dry.appendMessage("VolaBan", `${who}: Nick already in blocklist.`);
85 | return true;
86 | }
87 | }
88 | else if (type === "unblock") {
89 | let index = a.indexOf(who);
90 | if (index >= 0) {
91 | a.splice(index, 1);
92 | }
93 | else {
94 | dry.appendMessage("VolaBan", `${who}: No such nick in blocklist.`);
95 | return true;
96 | }
97 | }
98 | save();
99 | make();
100 | dry.appendMessage("VolaBan", `modified rules for ${what}:${who}`);
101 | return true;
102 | };
103 | new class extends dry.Commands {
104 | block(e) {
105 | return _ban("e", "block", e);
106 | }
107 | wblock(e) {
108 | return _ban("w", "block", e);
109 | }
110 | sblock(e) {
111 | return _ban("s", "block", e);
112 | }
113 | lblock(e) {
114 | return _ban("l", "block", e);
115 | }
116 | unblock(e) {
117 | return _ban("e", "unblock", e);
118 | }
119 | wunblock(e) {
120 | return _ban("w", "unblock", e);
121 | }
122 | sunblock(e) {
123 | return _ban("s", "unblock", e);
124 | }
125 | lunblock(e) {
126 | return _ban("l", "unblock", e);
127 | }
128 | blockreset() {
129 | localStorage.removeItem("bans");
130 | make();
131 | dry.appendMessage("VolaBan", "bans were reset!");
132 | return true;
133 | }
134 | blocklist() {
135 | let m = new window.Array();
136 | m.push({
137 | "type": "text",
138 | "value": `bans:`
139 | });
140 | m.push({"type": "break" });
141 | for (let i of ["whites", "exact", "staff", "logs"]) {
142 | m.push({
143 | "type": "text",
144 | "value": `${i}: ${JSON.stringify(bans[i])}`
145 | });
146 | m.push({"type": "break" });
147 | }
148 | dry.appendMessage("VolaBan", m);
149 | return true;
150 | }
151 | }();
152 | });
153 |
154 | dry.on("load", () => {
155 | // Hook the notifications listener too, which is an
156 | // anonymous function from a closure and hence we
157 | // have to find it within the registered events
158 | dry.exts.connection._events.chatMessage.some(e => {
159 | const onChatMessage = e.fn;
160 | if (!/sage/.test("" + onChatMessage)) {
161 | return false;
162 | }
163 | e.fn = function(...args) {
164 | if (ignore(args[0], args[1], args[2])) {
165 | return false;
166 | }
167 | return onChatMessage.apply(this, args);
168 | };
169 | return true;
170 | });
171 | });
172 | })();
173 |
--------------------------------------------------------------------------------
/volanail.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name VolaNailer
3 | // @namespace https://volafile.org
4 | // @include https://volafile.org/r/*
5 | // @icon https://volafile.org/favicon.ico
6 | // @author topkuk productions
7 | // @match https://volafile.org/r/*
8 | // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
9 | // @require https://cdn.jsdelivr.net/gh/volafiled/volascripts@a9c0424e5498deea9fd437c15b2137c3bec07c61/dry.min.js
10 | // @require https://cdn.jsdelivr.net/gh/volafiled/node-parrot@acb622d5d9af34f0de648385e6ab4d2411373037/parrot/finally.min.js
11 | // @require https://cdn.jsdelivr.net/gh/volafiled/node-parrot@acb622d5d9af34f0de648385e6ab4d2411373037/parrot/pool.min.js
12 | // @grant none
13 | // @version 8
14 | // ==/UserScript==
15 | /* globals GM, dry, format, PromisePool */
16 |
17 | "use strict";
18 |
19 | console.log("running", GM.info.script.name, GM.info.script.version);
20 |
21 | const SHEET = `
22 | .icon-vnthumb {
23 | margin: 0 !important;
24 | }
25 | .icon-vnthumb:before {
26 | content: "\\f03e"; /* XXX use actual icon class, but colors :*( */
27 | }
28 | .volanail-button {
29 | position: relative;
30 | z-index: 150;
31 | font-size: 18px;
32 | padding-bottom: 1px;
33 | margin-right: 1ex
34 | }
35 | .volanail-button[active] {
36 | box-shadow: inset 0px 0px 5px 3px rgba(47, 47, 47, 0.5);
37 | }
38 | #volanail-list {
39 | display: flex;
40 | flex-wrap: wrap;
41 | padding: 6px 10px;
42 | }
43 | .volanail-thumb > img, .volanail-thumb > video {
44 | display: inline-block;
45 | max-height: calc(100% - 1.4em - 4ex);
46 | max-width: calc(100% - 1.2em);
47 | object-fit: contain;
48 | }
49 | .volanail-thumb > div.volanail-media {
50 | height: calc(100% - 1.4em - 8ex);
51 | width: calc(100% - 1.2em - 4ex);
52 | border: 6px dashed white !important;
53 | border-radius: 26px;
54 | }
55 | .volanail-thumb > .file_name.file_hellbanned {
56 | text-decoration: line-through double;
57 | color: #ed7174;
58 | }
59 | .volanail-thumb > div.volanail-media > span {
60 | margin: auto;
61 | padding: 0;
62 | font-size: 90px;
63 | }
64 | .volanail-thumb {
65 | display: inline-flex;
66 | flex-direction: column;
67 | justify-content: space-between;
68 | align-items: center;
69 | width: calc(100% / 4 - 12px - 1ex);
70 | height: 260px;
71 | border-radius: 10px;
72 | background: rgba(255,255,255,0.1);
73 | padding: 0.5em 4px;
74 | margin: 0.5ex;
75 | text-decoration: none;
76 | text-align: center;
77 | border: 2px solid rgba(128,128,128,0.3) !important;
78 | }
79 | .volanail-thumb .tag_key_ip {
80 | display: inline-block;
81 | margin-left: 2ex;
82 | }
83 | .volanail-checked {
84 | border: 2px solid white !important;
85 | }
86 | .volanail-video {
87 | background: rgba(255,255,255,0.2);
88 | }
89 | .volanail-name, .volanail-infos {
90 | font-size: small;
91 | text-overflow: ellipsis;
92 | width: 100%;
93 | white-space: nowrap;
94 | overflow: hidden;
95 | margin-top: 0.4ex;
96 | }
97 | .volanail-name .file_control {
98 | padding: 0;
99 | padding-right: 1ex;
100 | }
101 | .volanail-infos {
102 | font-size: x-small;
103 | }
104 | `;
105 |
106 | const ICON_ERROR = "https://cdn.jsdelivr.net/gh/RealDolos/assets@5cd4f6f4c349e32e778da55a41928c0309ac4fd4/error.svg";
107 | const ICON_LOADING = "https://cdn.jsdelivr.net/gh/RealDolos/assets@5cd4f6f4c349e32e778da55a41928c0309ac4fd4/waiting.svg";
108 |
109 | function deepCloneClickable(node) {
110 | const nn = node.cloneNode(false);
111 | if (nn.classList && nn.classList.contains("clickable")) {
112 | nn.addEventListener("click", e => {
113 | e.stopPropagation();
114 | e.preventDefault();
115 | node.click();
116 | });
117 | }
118 | for (const c of Array.from(node.children).map(deepCloneClickable)) {
119 | nn.appendChild(c);
120 | }
121 | return nn;
122 | }
123 |
124 | const framePool = new class FramePool {
125 | constructor() {
126 | this.items = [];
127 | this.id = 0;
128 | this.run = this.run.bind(this);
129 | Object.seal(this);
130 | }
131 |
132 | run() {
133 | try {
134 | while (this.items.length) {
135 | const items = Array.from(this.items);
136 | this.items.length = 0;
137 | for (const item of items) {
138 | try {
139 | item.res(item.fn.call(item.ctx, ...item.args));
140 | }
141 | catch (ex) {
142 | item.rej(ex);
143 | }
144 | }
145 | }
146 | }
147 | finally {
148 | this.items.length = 0;
149 | this.id = 0;
150 | }
151 | }
152 |
153 | schedule(ctx, fn, ...args) {
154 | const item = { ctx, fn, args };
155 | const rv = new Promise((res, rej) => Object.assign(item, { res, rej }));
156 | this.items.push(Object.freeze(item));
157 |
158 | if (!this.id) {
159 | this.id = requestAnimationFrame(this.run);
160 | }
161 |
162 | return rv;
163 | }
164 | }();
165 |
166 | function $e(tag, attrs, text) {
167 | const rv = document.createElement(tag);
168 | if (attrs) {
169 | for (const [name, val] of Object.entries(attrs)) {
170 | rv.setAttribute(name, val);
171 | }
172 | }
173 | if (text) {
174 | rv.textContent = text;
175 | }
176 | return rv;
177 | }
178 |
179 | const $ = (sel, el) => (el || document).querySelector(sel);
180 | const $$ = (sel, el) => Array.from((el || document).querySelectorAll(sel));
181 |
182 | let active = false;
183 | let button;
184 | let file_list;
185 | let thumb_list;
186 |
187 | (function() {
188 | const sheet = $e("style", {id: "volanail-sheet"}, SHEET);
189 | document.body.appendChild(sheet);
190 |
191 | const cont = $("#upload_container");
192 | button = $e("label", {
193 | for: "volanail-button",
194 | id: "volanail-button",
195 | class: "button volanail-button"
196 | });
197 | button.appendChild($e("span", {
198 | class: "icon-vnthumb"
199 | }));
200 | button.appendChild($e("span", {
201 | class: "on_small_header"
202 | }/*, Thumbnail"*/));
203 | cont.insertBefore(button, cont.firstChild);
204 | file_list = $("#file_list");
205 | thumb_list = $e("div", {id: "volanail-list"});
206 | thumb_list.style.display = "none";
207 | file_list.parentElement.insertBefore(thumb_list, file_list);
208 |
209 | const files_frame = $("#files_frame");
210 | let oldCount = 0;
211 | const rule = Array.from(sheet.sheet.cssRules).find(
212 | e => e && e.selectorText === ".volanail-thumb" ? e : null);
213 | const update_columns = () => {
214 | framePool.schedule(null, () => {
215 | const columnCount = Math.max(2, Math.floor(files_frame.clientWidth / 220));
216 | if (oldCount === columnCount) {
217 | return;
218 | }
219 | oldCount = columnCount;
220 | rule.style.width = `calc(100% / ${columnCount} - 12px - 1ex)`;
221 | });
222 | };
223 |
224 | // Easier to observe mutations than hook into lain codes
225 | new MutationObserver(() => update_columns()).observe(files_frame, {
226 | attributes: true
227 | });
228 | update_columns();
229 | })();
230 |
231 | const force_update = () => {
232 | dry.exts.filelist.updateInfo.oldUnseenFiles = -1; // force the update m8
233 | dry.exts.filelist.scheduleDomUpdate();
234 | };
235 |
236 | class Thumbnail {
237 | constructor(file) {
238 | this.file = file;
239 | const container = this.container = $e("a", {
240 | href: file.link,
241 | target: "_blank",
242 | title: file.name,
243 | class: `volanail-thumb volanail-${file.type}`,
244 | });
245 | const name = $e("div", {
246 | class: `volanail-name ${file.dom.nameElement && file.dom.nameElement.className}`,
247 | }, file.name);
248 | const icon = this.icon = deepCloneClickable(file.dom.controlElement);
249 | icon.icon = file.dom.controlElement;
250 | name.insertBefore(icon, name.firstChild);
251 | name.onclick = e => {
252 | file.dom.controlElement.firstChild.dispatchEvent(new MouseEvent(e.type, e));
253 | e.preventDefault();
254 | e.stopPropagation();
255 | return false;
256 | };
257 | container.appendChild(name);
258 | const infos = this.infos = $e("div", {
259 | class: "volanail-infos"
260 | }, `${format.prettySize(file.size)} - ${file.tags.user || file.tags.nick || "n/a"}`);
261 | container.appendChild(infos);
262 | container.doLoad = () => {
263 | try {
264 | return this.doLoadInternal(file).catch(console.error);
265 | }
266 | finally {
267 | delete this.container.doLoad;
268 | }
269 | };
270 | container.onclick = e => {
271 | file.dom.nameElement.dispatchEvent(new MouseEvent(e.type, e));
272 | e.stopPropagation();
273 | e.preventDefault();
274 | return false;
275 | };
276 | file.on("data_checked", state => {
277 | container.classList[state ? "add" : "remove"]("volanail-checked");
278 | });
279 | const {fileElement} = file.dom;
280 | container.classList[
281 | fileElement.classList.contains("file_selected") ? "add" : "remove"
282 | ]("volanail-checked");
283 | this.setMedia(make_image(ICON_LOADING));
284 | }
285 |
286 | setMedia(el) {
287 | framePool.schedule(this, this.setMediaInternal, el);
288 | }
289 |
290 | setMediaInternal(el) {
291 | $$(".volanail-media", this.container).forEach(
292 | e => this.container.removeChild(e));
293 | this.container.insertBefore(el, this.infos);
294 | }
295 |
296 | async doLoadInternal(file) {
297 | try {
298 | if (file.upload || !file.id) {
299 | return;
300 | }
301 | await this.addInfo(await dry.exts.info.getFileInfo(file.id));
302 | }
303 | catch (ex) {
304 | this.setMedia(make_image(ICON_ERROR));
305 | throw ex;
306 | }
307 | }
308 |
309 | addInfo(info) {
310 | return framePool.schedule(this, this.addInfoAsPromised, info);
311 | }
312 |
313 | addInfoForGeneric(info, ip, cls) {
314 | const fmt = $e(
315 | "div",
316 | null,
317 | `Type: ${this.file.type.slice(0, 1).toUpperCase()}${this.file.type.slice(1)}`);
318 | if (ip) {
319 | fmt.appendChild(ip);
320 | }
321 | this.infos.insertBefore(fmt, this.infos.firstChild);
322 | const img = document.createElement("div");
323 | const icon = document.createElement("span");
324 | icon.className = cls;
325 | icon.classList.remove("clickable");
326 | img.classList.add("volanail-media");
327 | img.appendChild(icon);
328 | this.setMedia(img);
329 | }
330 |
331 | async addInfoForThumb(info, ip, name) {
332 | if (info.image) {
333 | const format = info.image.format ? `${info.image.format} - ` : "";
334 | const fmt = $e(
335 | "div",
336 | null,
337 | `${format} ${info.image.width || 0}×${info.image.height || 0}`);
338 | if (ip) {
339 | fmt.appendChild(ip);
340 | }
341 | this.infos.insertBefore(fmt, this.infos.firstChild);
342 | }
343 | else {
344 | this.addInfoForGeneric(info, ip, name);
345 | }
346 | const src = dry.unsafeWindow.makeAssetUrl(info.id, name, info.thumb.server);
347 | const img = new Image();
348 | img.classList.add("volanail-media");
349 | img.src = src;
350 | await new Promise((resolve, reject) => {
351 | img.onerror = error => {
352 | reject({error, src});
353 | };
354 | img.onload = () => {
355 | this.setMedia(img);
356 | resolve();
357 | };
358 | setTimeout(() => reject("timeout"), 10000);
359 | });
360 | }
361 |
362 | async addInfoForVideoThumb(info, ip, name) {
363 | if (info.video) {
364 | const fmt = $e(
365 | "div",
366 | null,
367 | `${info.video.codec} - ${format.duration((info.video.duration || 0) * 1000)} - ${info.video.width || 0}×${info.video.height || 0}`);
368 | if (ip) {
369 | fmt.appendChild(ip);
370 | }
371 | this.infos.insertBefore(fmt, this.infos.firstChild);
372 | }
373 | else {
374 | this.addInfoForGeneric(info, ip, name);
375 | }
376 |
377 | if (info.image) {
378 | const format = info.image.format ? `${info.image.format} - ` : "";
379 | const fmt = $e(
380 | "div",
381 | null,
382 | `${format} ${info.image.width || 0}×${info.image.height || 0}`);
383 | if (ip) {
384 | fmt.appendChild(ip);
385 | }
386 | this.infos.insertBefore(fmt, this.infos.firstChild);
387 | }
388 | const src = dry.unsafeWindow.makeAssetUrl(info.id, name, info.thumb.server);
389 | const video = $e("video", {
390 | class: "volanail-media",
391 | src
392 | });
393 | video.loop = true;
394 | video.muted = true;
395 |
396 | function setStart() {
397 | // work around "blank" start frames
398 | video.currentTime = video.duration > 0.1 ? video.duration / 3 : 0;
399 | }
400 |
401 | video.onmouseover = () => {
402 | video.currentTime = 0;
403 | video.play();
404 | };
405 | video.onmouseout = () => {
406 | video.pause();
407 | setStart();
408 | };
409 |
410 | await new Promise((resolve, reject) => {
411 | video.onloadeddata = () => {
412 | this.setMedia(video);
413 | setStart();
414 | resolve();
415 | };
416 | video.onstalled = () => {
417 | reject(src);
418 | };
419 | video.onerror = () => {
420 | reject(src);
421 | };
422 | setTimeout(() => reject(`timeout ${src}`), 10000);
423 | });
424 | }
425 |
426 | async addInfoAsPromised(info) {
427 | this.icon.firstChild.className = this.icon.icon.firstChild.className;
428 | delete this.icon.icon;
429 | let ip;
430 | if (info.uploader_ip) {
431 | ip = $e("span", {class: "tag_key_ip"}, info.uploader_ip);
432 | }
433 | const {thumb = {}} = info;
434 | const {type: thumbType = "", name = "thumb"} = thumb;
435 |
436 | if (thumbType.startsWith("image/")) {
437 | await this.addInfoForThumb(info, ip, name);
438 | return;
439 | }
440 |
441 | if (thumbType.startsWith("video/")) {
442 | await this.addInfoForVideoThumb(info, ip, name);
443 | return;
444 | }
445 |
446 | if (["thumb", "video_thumb"].some(a => this.file.assets.includes(a))) {
447 | throw new Error("No thumb");
448 | }
449 |
450 | this.addInfoForGeneric(info, ip, this.icon.firstChild.className);
451 | }
452 | }
453 |
454 | const make_image = src => {
455 | const img = new Image();
456 | img.src = src;
457 | img.classList.add("volanail-media");
458 | return img;
459 | };
460 |
461 | const prepare_file = dry.exportFunction(file => {
462 | try {
463 | if (!file.id || !file.dom || file.dom.vnThumbElement) {
464 | return;
465 | }
466 | if (active) {
467 | force_update();
468 | }
469 | }
470 | catch (ex) {
471 | console.error(file, ex);
472 | }
473 | }, dry.unsafeWindow);
474 |
475 | const update_file = dry.exportFunction(file => {
476 | try {
477 | const pe = file.dom.vnThumbElement && file.dom.vnThumbElement.parentElement;
478 | if (pe) {
479 | pe.removeChild(file.dom.vnThumbElement);
480 | }
481 | delete file.dom.vnThumbElement;
482 | if (file.upload || !file.id || !file.dom) {
483 | return;
484 | }
485 | if (active) {
486 | force_update();
487 | }
488 | }
489 | catch (ex) {
490 | console.error(file, ex);
491 | }
492 | }, dry.unsafeWindow);
493 |
494 | const remove_file = dry.exportFunction(file => {
495 | if (!file.dom || !file.dom.vnThumbElement) {
496 | return;
497 | }
498 | const parent = file.dom.vnThumbElement.parentElement;
499 | if (parent) {
500 | parent.removeChild(file.dom.vnThumbElement);
501 | }
502 | delete file.dom.vnThumbElement;
503 | if (active) {
504 | force_update();
505 | }
506 | }, dry.unsafeWindow);
507 |
508 | const loader = new class Loader {
509 | constructor() {
510 | this.load_one = PromisePool.wrapNew(5, this, this.load_one);
511 | this.remaining = [];
512 | }
513 |
514 | refresh() {
515 | this.remaining = $$(".volanail-thumb").filter(e => e.doLoad);
516 | if (!this.remaining.length) {
517 | return;
518 | }
519 | this.load().catch(console.error);
520 | }
521 |
522 | load_one(t) {
523 | if (!active || !t.doLoad) {
524 | return null;
525 | }
526 | return t.doLoad();
527 | }
528 |
529 | async load() {
530 | if (this.loading || !this.remaining.length) {
531 | return;
532 | }
533 | this.loading = true;
534 | try {
535 | while (this.remaining.length) {
536 | const jobs = this.remaining.map(this.load_one);
537 | this.remaining.length = 0;
538 | await Promise.all(jobs);
539 | }
540 | }
541 | finally {
542 | this.loading = false;
543 | }
544 | }
545 | }();
546 |
547 | dry.once("load", () => {
548 | dry.exts.filelistManager.on("fileAdded", prepare_file);
549 | dry.exts.filelistManager.on("fileUpdated", update_file);
550 | dry.exts.filelistManager.on("fileRemoved", remove_file);
551 |
552 | dry.replaceLate("filelist", "restoreScrollAnchor", function(orig, ...args) {
553 | // we don't wanna scroll when in thumb view
554 | return active ? null : orig(...args);
555 | });
556 |
557 | dry.replaceLate("filelist", "updateDom", function(orig, ...args) {
558 | let rv;
559 | try {
560 | rv = orig(...args);
561 | }
562 | catch (ex) {
563 | console.error("LAIN dun goofed", ex);
564 | }
565 | try {
566 | const off = 0;
567 | dry.exts.filelist.each((f, idx) => {
568 | let el = f.dom.vnThumbElement;
569 | if (!el) {
570 | el = f.dom.vnThumbElement = new Thumbnail(f).container;
571 | }
572 | const parent = el.parentElement;
573 | if (f.visible) {
574 | const an = thumb_list.childNodes[idx - off];
575 | if (!parent || an !== el) {
576 | thumb_list.insertBefore(el, an);
577 | }
578 | }
579 | else if (!f.visible && parent) {
580 | parent.removeChild(el);
581 | }
582 | dry.exts.filelistManager.emit("nail_init", f);
583 | });
584 | loader.refresh();
585 | }
586 | catch (ex) {
587 | console.error("something went wronk", ex);
588 | }
589 | return rv;
590 | });
591 |
592 | dry.replaceLate("music", "showIcons", function(orig, file, ...args) {
593 | const rv = orig(file, ...args);
594 | try {
595 | const el = file.dom.vnThumbElement;
596 | if (!el) {
597 | return rv;
598 | }
599 | const ctrl = el.querySelector(".file_control");
600 | if (!ctrl) {
601 | return rv;
602 | }
603 | const newControl = deepCloneClickable(file.dom.controlElement);
604 | ctrl.parentElement.replaceChild(newControl, ctrl);
605 | }
606 | catch (ex) {
607 | console.error("music icons", ex);
608 | }
609 | return rv;
610 | });
611 |
612 | button.addEventListener("click", () => {
613 | if (thumb_list.style.display !== "none") {
614 | file_list.style.display = "block";
615 | thumb_list.style.display = "none";
616 | button.removeAttribute("active");
617 | active = false;
618 | }
619 | else {
620 | file_list.style.display = "none";
621 | thumb_list.style.display = "flex";
622 | button.setAttribute("active", "true");
623 | active = true;
624 | force_update();
625 | }
626 | });
627 |
628 | Array.from(dry.exts.filelistManager.filelist.filelist).reverse().forEach(
629 | prepare_file);
630 | });
631 |
--------------------------------------------------------------------------------
/volapg.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name VolaPG - Best crypto ever!!!1!
3 | // @namespace https://volafile.org/
4 | // @version 39
5 | // @description If you think this will in any way protect you, you're wronk
6 | // @icon https://volafile.org/favicon.ico
7 | // @author topkuk productions
8 | // @match https://volafile.org/r/*
9 | // @grant GM_getValue
10 | // @grant GM_setValue
11 | // @grant GM_xmlhttpRequest
12 | // @grant GM.getValue
13 | // @grant GM.setValue
14 | // @grant GM.xmlHttpRequest
15 | // @grant unsafeWindow
16 | // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
17 | // @require https://cdn.rawgit.com/tonyg/js-nacl/334f0587103bd3b7e721b92fc1d2a38a8f23708d/lib/nacl_factory.js
18 | // @require https://cdn.rawgit.com/gregjacobs/Autolinker.js/424c3242d5c9675a5997ce62120820ba55e073b3/dist/Autolinker.min.js
19 | // @require https://cdn.rawgit.com/RealDolos/volascripts/1dd689f72763c0e59f567fdf93865837e35964d6/dry.js
20 | // @require https://cdn.rawgit.com/RealDolos/volascripts/1dd689f72763c0e59f567fdf93865837e35964d6/baseMANY.js
21 | // @run-at document-start
22 | // ==/UserScript==
23 | /* global GM, dry, Autolinker, nacl_factory, baseMANY */
24 |
25 | dry.once("dom", () => {
26 | "use strict";
27 |
28 | console.log(
29 | "running", GM.info.script.name, GM.info.script.version, dry.version);
30 |
31 | const reconstruct = (() => {
32 | this.Autolinker.matcher.Mention.prototype.matcherRegexes.instagram = new RegExp(`@[_.${Autolinker.RegexLib.alphaNumericCharsStr}-]{1,50}`, "g");
33 | const hue = new this.Autolinker({
34 | phone: false,
35 | twitter: false,
36 | hashtag: "twitter",
37 | mention: "instagram",
38 | });
39 | return function(text) {
40 | const rv = new dry.unsafeWindow.Array();
41 | const pieces = text.split("\n");
42 | for (let p of pieces) {
43 | p = p.trim();
44 | let lidx = 0;
45 | const matches = hue.removeUnwantedMatches(
46 | hue.compactMatches(hue.parseText(p)));
47 | for (const m of matches) {
48 | const idx = m.getOffset();
49 | if (idx !== lidx) {
50 | rv.push({type: "text", value: p.substring(lidx, idx)});
51 | }
52 | switch (m.getType()) {
53 | case "hashtag":
54 | rv.push({type: "room", id: m.getHashtag(), name: "Some Room"});
55 | break;
56 |
57 | case "mention":
58 | rv.push({
59 | type: "file",
60 | id: m.getMention(),
61 | name: "Some file (just hover it ffs)"});
62 | break;
63 | default:
64 | rv.push({
65 | type: "url",
66 | href: m.getAnchorHref(),
67 | text: m.getAnchorText()});
68 | break;
69 | }
70 | lidx = idx + m.getMatchedText().length;
71 | }
72 | const last = p.substring(lidx);
73 | if (last && last.length) {
74 | rv.push({type: "text", value: last});
75 | }
76 | rv.push({type: "break"});
77 | }
78 | rv.pop(); // last break;
79 | return rv;
80 | };
81 | })();
82 |
83 | dry.once("load", function() {
84 | dry.appendMessage("VolaPG",
85 | "is not secure, especially not in /c mode. " +
86 | "Use /pubkey to retrieve public key",
87 | {me: true, highlight: false});
88 | });
89 |
90 | class Shit {
91 | constructor() {
92 | this._pubcache = new Map();
93 | this._nacl = null;
94 | }
95 |
96 | init() {
97 | return new Promise(resolve => {
98 | nacl_factory.instantiate(n => {
99 | this._nacl = n;
100 | resolve(this);
101 | }, {
102 | requested_total_memory: 1 << 22,
103 | });
104 | });
105 | }
106 |
107 | serializeKeys(keys) {
108 | const nacl = this._nacl;
109 | const s = {
110 | boxSk: nacl.to_hex(keys.boxSk),
111 | boxPk: nacl.to_hex(keys.boxPk)
112 | };
113 | return JSON.stringify(s);
114 | }
115 |
116 | async getKeys(reset) {
117 | const nacl = this._nacl;
118 | let rv = await GM.getValue("vpgkeypair");
119 | if (!rv || reset) {
120 | rv = nacl.crypto_box_keypair();
121 | await this.setKeys(rv);
122 | return rv;
123 | }
124 | rv = JSON.parse(rv);
125 | return {
126 | boxSk: nacl.from_hex(rv.boxSk),
127 | boxPk: nacl.from_hex(rv.boxPk)
128 | };
129 | }
130 |
131 | async setKeys(keys) {
132 | await GM.setValue("vpgkeypair", this.serializeKeys(keys));
133 | }
134 |
135 | async getPubKey() {
136 | return `vgpk#${this._nacl.to_hex((await this.getKeys()).boxPk)}`;
137 | }
138 |
139 | async getSerializedKeys() {
140 | return this.serializeKeys(await this.getKeys());
141 | }
142 |
143 | combine(a, b) {
144 | const nacl = this._nacl;
145 | if (!(a instanceof Uint8Array)) {
146 | a = nacl.encode_utf8(a);
147 | }
148 | if (!(b instanceof Uint8Array)) {
149 | b = nacl.encode_utf8(b);
150 | }
151 | return nacl.from_hex(nacl.to_hex(a) + nacl.to_hex(b));
152 | }
153 |
154 | // ridicously shitty KDF, like a hmac lite but with A LOT MORE BUGS.
155 | // Totally secure, I swears, not that is really matters, are the PRNG of
156 | // most JS engines is shit anyway, even for the "crypto" variant
157 | mackdf(key, msg) {
158 | const nacl = this._nacl;
159 | const r = nacl.crypto_hash(this.combine(key, msg));
160 | for (let i = 0; i < r.byteLength; ++i) {
161 | r[i] ^= 54;
162 | }
163 | const o = new Uint8Array(key.buffer.slice(0)).subarray(0, key.byteLength);
164 | for (let i = 0; i < o.byteLength; ++i) {
165 | o[i] ^= 92;
166 | }
167 | return nacl.crypto_hash(this.combine(o, r));
168 | }
169 |
170 | // Wonder if truncating the nounce makes shit more secure?
171 | // I think it does!!!!1!
172 | obfuscate(key, msg) {
173 | const nacl = this._nacl;
174 | const rnounce = nacl.crypto_secretbox_random_nonce().subarray(0, 6);
175 | key = this.mackdf(rnounce, key);
176 | const nounce = this.combine(rnounce, key.subarray(32, 50));
177 | key = key.subarray(0, 32);
178 | let rv = nacl.crypto_secretbox(nacl.encode_utf8(msg), nounce, key);
179 | rv = this.combine(rnounce, rv);
180 | return `c#${baseMANY.encode(rv)}`;
181 | }
182 |
183 | getRemoteKey(nacl, user) {
184 | return new Promise((resolve, reject) => {
185 | GM.xmlHttpRequest({
186 | method: "GET",
187 | url: `https://volafile.org/user/${user}`,
188 | onload: r => {
189 | try {
190 | let m = r.responseText.match(/vgpk#[a-f0-9]+/g);
191 | if (!m) {
192 | throw new Error(`${user} did not provide public key`);
193 | }
194 | m = nacl.from_hex(m[0].substr(5).trim());
195 | if (!m) {
196 | throw new Error("Failed to decode public key");
197 | }
198 | this._pubcache.set(user, m);
199 | resolve(m);
200 | }
201 | catch (ex) {
202 | reject(ex);
203 | }
204 | },
205 | onerror () {
206 | reject(new Error("failed to get public key"));
207 | }
208 | });
209 | });
210 | }
211 |
212 | async encryptFor(user, msg) {
213 | const nacl = this._nacl;
214 | user = user.toLowerCase();
215 | let key = this._pubcache.get(user);
216 | if (!key) {
217 | key = await this.getRemoteKey(nacl, user);
218 | }
219 | const keys = await this.getKeys();
220 | const rnounce = nacl.crypto_box_random_nonce().subarray(0, 8);
221 | const nounce = this.combine(
222 | rnounce, keys.boxPk.subarray(0, 24 - rnounce.byteLength));
223 | msg = nacl.crypto_box(
224 | nacl.encode_utf8(msg), nounce, key, keys.boxSk);
225 | return `p#${baseMANY.encode(
226 | this.combine(this.combine(rnounce, keys.boxPk), msg))}`;
227 | }
228 |
229 | _decrypt(key, msg) {
230 | const nacl = this._nacl;
231 | if (!msg.startsWith("c#")) {
232 | throw new Error("not encrypted");
233 | }
234 | let data = baseMANY.decode(msg.substr(2));
235 | const rnounce = data.subarray(0, 6);
236 | data = data.subarray(6);
237 | key = this.mackdf(rnounce, key);
238 | const nounce = this.combine(rnounce, key.subarray(32, 50));
239 | key = key.subarray(0, 32);
240 | return {
241 | type: "PubPG",
242 | msg: nacl.decode_utf8(nacl.crypto_secretbox_open(data, nounce, key))
243 | };
244 | }
245 |
246 | async _decryptFrom(msg) {
247 | if (!msg.startsWith("p#")) {
248 | throw new Error("not encrypted");
249 | }
250 | let data = baseMANY.decode(msg.substr(2));
251 | const rnounce = data.subarray(0, 8);
252 | const pubkey = data.subarray(8, 40);
253 | data = data.subarray(40);
254 | const nounce = this.combine(
255 | rnounce, pubkey.subarray(0, 24 - rnounce.byteLength));
256 | const keys = await this.getKeys();
257 | msg = this._nacl.crypto_box_open(data, nounce, pubkey, keys.boxSk);
258 | return {
259 | type: "PrivPG",
260 | msg: this._nacl.decode_utf8(msg)
261 | };
262 | }
263 | }
264 |
265 | let _shit;
266 |
267 | function get_shit() {
268 | if (_shit) {
269 | return _shit;
270 | }
271 | const shit = new Shit();
272 | return (_shit = shit.init());
273 | }
274 |
275 | function decrypt(key, msg) {
276 | if (msg.startsWith("c#")) {
277 | return get_shit().then(shit => {
278 | return shit._decrypt(key, msg);
279 | });
280 | }
281 | if (msg.startsWith("p#")) {
282 | return get_shit().then(shit => {
283 | return shit._decryptFrom(msg);
284 | });
285 | }
286 | return null;
287 | }
288 |
289 | // Will get rid of messages but not of notifications
290 | dry.replaceEarly("chat", "showMessage",
291 | function(orig, nick, message, options, data, ...args) {
292 | let text = [];
293 | if (message.trim) {
294 | text = message;
295 | }
296 | else {
297 | for (const m of message) {
298 | switch (m.type) {
299 | case "text":
300 | text.push(m.value);
301 | break;
302 |
303 | case "break":
304 | text.push("\n");
305 | break;
306 |
307 | case "url":
308 | text.push(m.url);
309 | break;
310 | }
311 | }
312 | text = text.join("");
313 | }
314 | const decrypted = decrypt(
315 | nick + dry.config.room_id, text);
316 | if (decrypted) {
317 | decrypted.then(result => {
318 | message = dry.exportObject(reconstruct(result.msg));
319 | if (result.type === "PrivPG") {
320 | options.highlight = true;
321 | }
322 | data.channel = result.type;
323 | return orig(nick, message, options, data, ...args);
324 | }).catch(ex => {
325 | if (ex.message.indexOf("crypto_box_open signalled") > 0) {
326 | console.error(text, ex);
327 | return;
328 | }
329 | else if (ex.message !== "unhandled") {
330 | console.error(ex);
331 | dry.appendMessage("VolaPG", `Could not decode message: ${ex.message || ex}`, {
332 | highlight: false
333 | });
334 | }
335 | });
336 | return null;
337 | }
338 | return orig(nick, message, options, data, ...args);
339 | });
340 |
341 | new class extends dry.Commands {
342 | c(e) {
343 | get_shit().then(shit => {
344 | dry.exts.chat.applyNick();
345 | const enc = shit.obfuscate(dry.exts.user.info.nick + dry.config.room_id, e);
346 | dry.exts.chat.chatInput.emit("submit", enc);
347 | });
348 | return true;
349 | }
350 |
351 | p(e) {
352 | get_shit().then(shit => {
353 | try {
354 | dry.exts.chat.applyNick();
355 | e = e.split(/^(\S+) +((?:.|\n)+)$/);
356 | if (!e || e.length < 3) {
357 | throw Error("Invalid format");
358 | }
359 | shit.encryptFor(e[1], e[2]).then(function (m) {
360 | try {
361 | dry.exts.chat.applyNick();
362 | dry.exts.chat.chatInput.emit("submit", m);
363 |
364 | dry.appendMessage("VolaPG", `[Sent to ${e[1]}] ${e[2]}`);
365 | }
366 | catch (ex) {
367 | console.error(ex);
368 | }
369 | }, function (ex) {
370 | dry.appendMessage("VolaPG", ex.message || ex);
371 | });
372 | }
373 | catch (ex) {
374 | alert(ex);
375 | }
376 | });
377 | return true;
378 | }
379 |
380 | pubkey() {
381 | get_shit().then(async shit => {
382 | dry.appendMessage("VolaPG Pubkey", await shit.getPubKey());
383 | });
384 | return true;
385 | }
386 |
387 | keys() {
388 | get_shit().then(async shit => {
389 | dry.appendMessage(
390 | "VolaPG Keys (do not share)", await shit.getSerializedKeys());
391 | });
392 | return true;
393 | }
394 |
395 | newkeys() {
396 | dry.appendMessage("VolaPG", "Keys reset (not implemented)");
397 | return true;
398 | }
399 |
400 | setkeys(keys) {
401 | get_shit().then(async shit => {
402 | try {
403 | keys = JSON.parse(keys);
404 | keys.boxPk = shit._nacl.from_hex(keys.boxPk);
405 | keys.boxSk = shit._nacl.from_hex(keys.boxSk);
406 | await shit.setKeys(keys);
407 | dry.appendMessage("VolaPG", "Keys set");
408 | }
409 | catch (ex) {
410 | alert(ex);
411 | }
412 | });
413 | return true;
414 | }
415 |
416 | pghelp() {
417 | dry.appendMessage("VolaPG", new dry.unsafeWindow.Array(
418 | {type: "text", value: "pls welp!"},
419 | {type: "break"},
420 | {type: "text", value: "use /c to send a sekrit message"},
421 | {type: "break"},
422 | {type: "text", value: "use /p to send a private sekrit message"},
423 | {type: "break"},
424 | {type: "text", value: "use /pubkey to get your public key"},
425 | {type: "break"},
426 | {type: "text", value: "use /keys to get your keys"},
427 | {type: "break"},
428 | {type: "text", value: "use /newkeys to generate new keys"},
429 | {type: "break"},
430 | {
431 | type: "text",
432 | value:
433 | "use /setkeys to set existing keys (you got from /keys earlier)"
434 | }
435 | ));
436 | return true;
437 | }
438 | }();
439 | });
440 |
--------------------------------------------------------------------------------
/youcompleteme.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name you complete me!
3 | // @namespace https://volafile.org/
4 | // @author topkuk productions
5 | // @icon https://volafile.org/favicon.ico
6 | // @version 8
7 | // @description So you can better ignore people
8 | // @match https://volafile.org/r/*
9 | // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
10 | // @require https://cdn.jsdelivr.net/gh/RealDolos/volascripts@6879f622f45d2b79dd9f004754c28ffa0075a12b/dry.js
11 | // @run-at document-idle
12 | // @grant none
13 | // ==/UserScript==
14 | /* global GM, dry, console */
15 |
16 | "use strict";
17 |
18 | console.log(
19 | "running", GM.info.script.name, GM.info.script.version, dry.version);
20 |
21 | const always = [
22 | "RealDolos", "TheJIDF", "Kreg", "roboCOP", "Lain",
23 | "Heisenb3rg", "Red", "dad", "MoDChatbot", "bain", "lmmortal",
24 | "someguy1992", "Szeraton", "NineBanger87", "Starsheep",
25 | "Dongmaster", "MercWMouth", "ptc",
26 | ];
27 | let never = new Set([
28 | "DavidBowie", "Record",
29 | "News", "Network", "MOTD", "System", "CucklordSupreme", "Report", "Log",
30 | "VolaPG",
31 | ]);
32 | const limit = 30;
33 |
34 | never = new Set(Array.from(never).map(e => e.toUpperCase()));
35 |
36 | const filter = function(current, e) {
37 | const u = e.toUpperCase();
38 | if (this.has(u) || never.has(u) || current === u) {
39 | return false;
40 | }
41 | this.add(u);
42 | return true;
43 | };
44 |
45 | const addn = dry.replaceLate("chat.chatInput", "addSuggestion",
46 | function(orig, str) {
47 | const current = dry.exts.user.info.nick.toUpperCase();
48 | const e = this.suggestions;
49 | str = str.toString();
50 | const ustr = str.toUpperCase();
51 | if (!str || never.has(ustr) || e.indexOf(str) === 0 || ustr === current) {
52 | return;
53 | }
54 | // Delay stuff a bit to avoid people stealing completes;
55 | // suggestion by the Redard
56 | setTimeout(() => {
57 | let newSuggestions = [...[str, ...e].slice(0, limit), ...always];
58 | try {
59 | newSuggestions = newSuggestions.filter(filter.bind(new Set(), current));
60 | this.suggestions = newSuggestions;
61 | }
62 | catch (ex) {
63 | console.error(ex);
64 | }
65 | }, 1000);
66 | });
67 |
68 | addn("");
69 | setTimeout(() => addn(""), 1000);
70 |
--------------------------------------------------------------------------------