├── .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 | --------------------------------------------------------------------------------