├── README.md ├── SendAudio.plugin.js └── src ├── config.json └── index.js /README.md: -------------------------------------------------------------------------------- 1 | # Send Audio Plugin 2 | Record and send audios in chat 3 | 4 | ### Preview 5 | ![preview](https://i.imgur.com/fxTrb1D.gif) 6 | 7 | ### Send 8 | ![send](https://i.imgur.com/toZHQub.gif) 9 | 10 | ### Settings 11 | ![settings](https://i.imgur.com/bzx5w1d.png) 12 | -------------------------------------------------------------------------------- /SendAudio.plugin.js: -------------------------------------------------------------------------------- 1 | //META{"name":"SendAudio","displayName":"SendAudio","website":"https://github.com/MKSx/EnviarAudio-BetterDiscord","source":""}*// 2 | /*@cc_on 3 | @if (@_jscript) 4 | 5 | // Offer to self-install for clueless users that try to run this directly. 6 | var shell = WScript.CreateObject("WScript.Shell"); 7 | var fs = new ActiveXObject("Scripting.FileSystemObject"); 8 | var pathPlugins = shell.ExpandEnvironmentStrings("%APPDATA%\BetterDiscord\plugins"); 9 | var pathSelf = WScript.ScriptFullName; 10 | // Put the user at ease by addressing them in the first person 11 | shell.Popup("It looks like you've mistakenly tried to run me directly. \n(Don't do that!)", 0, "I'm a plugin for BetterDiscord", 0x30); 12 | if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) { 13 | shell.Popup("I'm in the correct folder already.", 0, "I'm already installed", 0x40); 14 | } else if (!fs.FolderExists(pathPlugins)) { 15 | shell.Popup("I can't find the BetterDiscord plugins folder.\nAre you sure it's even installed?", 0, "Can't install myself", 0x10); 16 | } else if (shell.Popup("Should I copy myself to BetterDiscord's plugins folder for you?", 0, "Do you need some help?", 0x34) === 6) { 17 | fs.CopyFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf)), true); 18 | // Show the user where to put plugins in the future 19 | shell.Exec("explorer " + pathPlugins); 20 | shell.Popup("I'm installed!", 0, "Successfully installed", 0x40); 21 | } 22 | WScript.Quit(); 23 | 24 | @else@*/ 25 | 26 | if(!('SendAudio_downloadModal' in global)) 27 | global.SendAudio_downloadModal = false; 28 | 29 | if(!('SendAudio_ZLibrary' in global)) 30 | global.SendAudio_ZLibrary = ('ZLibrary' in global); 31 | 32 | module.exports = (() => { 33 | const config = {"info":{"name":"Send Audio","authors":[{"name":"Matues","discord_id":"301016626579505162","github_username":"MKSx"}],"version":"1.1.5","description":"Record and send audios in chat","github":"https://github.com/MKSx/EnviarAudio-BetterDiscord","github_raw":"https://raw.githubusercontent.com/MKSx/Send-Audio-Plugin-BetterDiscord/master/SendAudio.plugin.js"},"main":"index.js","defaultConfig":[{"type":"switch","name":"Preview record","id":"preview","value":false,"note":"Allows audio to be heard before being sent"},{"type":"switch","name":"Using nitro","id":"nitro","value":false,"note":"If you are using discord nitro increases the file size that can be used from 8 MB to 50 MB"},{"type":"dropdown","name":"Audio input","id":"devices","note":"The audio recording device that will be used","options":[{"label":"Default","value":"default"}],"value":"default"},{"type":"dropdown","name":"File format","id":"mimetype","note":"The type of file that will be sent","options":[{"label":"mp3","value":"audio/mp3"},{"label":"ogg","value":"audio/ogg"},{"label":"wav","value":"audio/wav"},{"label":"opus","value":"audio/webm;codecs=opus"},{"label":"webm","value":"audio/webm"}],"value":"audio/mp3"}]}; 34 | 35 | 36 | //local lib not found 37 | if(typeof global.ZeresPluginLibrary != 'function'){ 38 | //Add remote lib 39 | if (!global.ZLibrary && !global.ZLibraryPromise) global.ZLibraryPromise = new Promise((resolve, reject) => { 40 | require("request").get({url: "https://rauenzi.github.io/BDPluginLibrary/release/ZLibrary.js", timeout: 10000}, (err, res, body) => { 41 | if (err || 200 !== res.statusCode) return reject(err || res.statusMessage); 42 | try {const vm = require("vm"), script = new vm.Script(body, {displayErrors: true}); resolve(script.runInThisContext());} 43 | catch(err) {reject(err);} 44 | }); 45 | }); 46 | 47 | const title = "Library Missing"; 48 | const ModalStack = BdApi.findModuleByProps("push", "update", "pop", "popWithKey"); 49 | const TextElement = BdApi.findModuleByProps("Sizes", "Weights"); 50 | const ConfirmationModal = BdApi.findModule(m => m.defaultProps && m.key && m.key() == "confirm-modal"); 51 | 52 | 53 | if (!ModalStack || !ConfirmationModal || !TextElement) { 54 | return BdApi.alert(title, `The library plugin needed for ${config.info.name} is missing.

Click here to download the library!`); 55 | } 56 | 57 | //download lib 58 | if(!global.SendAudio_downloadModal){ 59 | global.SendAudio_downloadModal = true; 60 | ModalStack.push(function(props){ 61 | 62 | return BdApi.React.createElement(ConfirmationModal, Object.assign({ 63 | header: title, 64 | children: [BdApi.React.createElement(TextElement, {color: TextElement.Colors.PRIMARY, children: [`The library plugin needed for ${config.info.name} is missing. Please click Download Now to install it.`]})], 65 | red: false, 66 | confirmText: "Download Now", 67 | cancelText: "Cancel", 68 | onConfirm: () => { 69 | require("request").get("https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js", async (error, response, body) => { 70 | if (error) 71 | return require("electron").shell.openExternal("https://betterdiscord.net/ghdl?url=https://raw.githubusercontent.com/rauenzi/BDPluginLibrary/master/release/0PluginLibrary.plugin.js"); 72 | 73 | await new Promise(r => require("fs").writeFile(require("path").join(ContentManager.pluginsFolder, "0PluginLibrary.plugin.js"), body, r)); 74 | 75 | //remove remote lib 76 | if(!global.SendAudio_ZLibrary){ 77 | setTimeout(_ => { 78 | global.ZLibrary = undefined; 79 | }, 10000); 80 | } 81 | 82 | }); 83 | } 84 | }, props)); 85 | }); 86 | } 87 | } 88 | const compilePlugin = ([Plugin, Api]) => { 89 | const plugin = (Plugin, Api) => { 90 | const { DOMTools, Logger, DiscordAPI, DiscordModules, PluginUtilities } = Api; 91 | 92 | const SelectedChannelStore = DiscordModules.SelectedChannelStore; 93 | const ChannelStore = DiscordModules.ChannelStore; 94 | const Upload = BdApi.findModule(m => m.upload && typeof m.upload === 'function'); 95 | 96 | const addButton = (attr, svg) => DOMTools.createElement(``); 97 | const addPanel = (html, attr, remove=false) => { 98 | let dom = document.querySelector('.panels-j1Uci_'); 99 | 100 | if(!(dom instanceof Element)) 101 | return false; 102 | 103 | if(document.querySelector(`div[${attr}]`) instanceof Element){ 104 | if(!remove) 105 | return false; 106 | 107 | document.querySelector(`div[${attr}]`).remove(); 108 | } 109 | return dom.insertBefore(DOMTools.createElement(`
${html}
`), dom.firstChild); 110 | }; 111 | 112 | const getChannelName = () => { 113 | const channel = DiscordAPI.currentChannel; 114 | 115 | if(channel.type == 'GROUP_DM'){ 116 | return (DiscordAPI.currentChannel ? DiscordAPI.currentChannel.name : 'Unknown channel'); 117 | } 118 | else if(channel.type == 'DM'){ 119 | const recipient = DiscordAPI.currentChannel.recipient; 120 | return '@' + recipient.username + '#' + recipient.discriminator; 121 | } 122 | return '#' + channel.name; 123 | }; 124 | const getGuildName = () => { 125 | const channel = DiscordAPI.currentChannel; 126 | 127 | if(channel.type != 'GUILD_TEXT'){ 128 | return channel.type; 129 | } 130 | 131 | if(DiscordAPI.currentGuild) 132 | return DiscordAPI.currentGuild.name; 133 | 134 | return 'Unknown server'; 135 | }; 136 | const convertToTime = (seconds) => { 137 | seconds = parseInt(seconds); 138 | return typeof seconds == 'number' && seconds > -1 ? new Date(parseInt(seconds) * 1000).toISOString().substr(11, 8) : '00:00:00'; 139 | }; 140 | const convertData = (size) => { 141 | //MB 142 | if(size >= 1048576){ 143 | temp = (size / 1024) % 1024; 144 | 145 | return `${parseInt((size / 1024) / 1024)}${temp != 0 ? ('.' + temp).slice(0, 3) : ''} MB`; 146 | } 147 | temp = size % 1024; 148 | return `${parseInt(size / 1024)}${temp != 0 ? ('.' + temp).slice(0,3) : ''} KB`; 149 | }; 150 | const randomInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; 151 | const randomKey = (len) => { 152 | if(len < 1) 153 | return ''; 154 | 155 | let ret = ''; 156 | for(let i = 0, j = parseInt(len / 3) + (len % 3); i < j; ++i) 157 | ret = ret + String.fromCharCode(randomInt(65, 90)) + String.fromCharCode(randomInt(48, 57)) + String.fromCharCode(randomInt(97, 122)); 158 | 159 | return ret.slice(0, len); 160 | }; 161 | const sendAudio = (channel, blob) => { 162 | 163 | let date = new Date(); 164 | 165 | date = new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString(); 166 | 167 | return Upload.upload(channel, new File([blob], randomKey(3) + '-' + date.substr(0, 10) + '-' + date.substr(11, 8).replace(/:/g, '-') + '.mp3'), {content: '', tts: false}); 168 | }; 169 | 170 | const DiscordPlayer = (() => { 171 | 172 | const addPlayer = (attr) => { 173 | return addPanel(` 174 |

Loading audio

175 |
176 |
177 | 178 |
179 | 180 | 181 | 182 |
183 |
184 |
185 |
186 | 187 |
188 |
189 | 00:00:00 190 | / 191 | --:--:-- 192 |
193 |
194 |
195 | 196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 | 204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 | 216 |
217 |
00:00:00
218 |
219 |
220 |
221 |
`, attr); 222 | }; 223 | return class{ 224 | constructor(){ 225 | this.audio = false; 226 | 227 | this.events = { 228 | 'end': null, 229 | 'start': null, 230 | 'close': null 231 | }; 232 | this.setDuration = false; 233 | this.id = DiscordModules.KeyGenerator(); 234 | this.audioReady = false; 235 | this.playerRef = addPlayer(`id='${this.id}'`); 236 | this.playerLoading = this.playerRef.find(`h2[class='DiscordPlayerLoading']`); 237 | this.playerTime = {current: null, duration: null}; 238 | this.playerVolume = {lastValue: 1, moving: false, timer: null, button: null, bar: null, ref: null, bounding: null, svg: null}; 239 | this.playerDuration = {ref: null, moving: false, current: null, preview: null, tooltip: null, bounding: null, tooltipHeight: 0, lastUpdate: 0, paused: false}; 240 | this.playerButton = null; 241 | this.playerClose = null; 242 | this.playerTitle = null; 243 | 244 | this.playerVolume.button = this.playerRef.find(`div[class="flex-1O1GKY da-flex"]`); 245 | this.playerVolume.ref = this.playerRef.find(`div[class~="audioVolumeWrapper-2t9juP"]`); 246 | this.playerVolume.bar = this.playerVolume.ref.find(`div[class~='mediaBarProgress-1xaPtl']`); 247 | this.playerVolume.svg = this.playerVolume.button.find(`div[class="flex-1O1GKY da-flex"] svg`); 248 | this.playerVolume.bounding = this.playerVolume.bar.parentNode.getBoundingClientRect(); 249 | 250 | this.playerDuration.ref = this.playerRef.find(`div[class="mediaBarInteraction-37i2O4 da-mediaBarInteraction"] div`); 251 | this.playerDuration.current = this.playerDuration.ref.find(`div[class~="mediaBarProgress-1xaPtl"]`); 252 | this.playerDuration.preview = this.playerDuration.ref.find(`div[class~="mediaBarPreview-1jfyFs"]`); 253 | this.playerDuration.tooltip = this.playerDuration.ref.find(`div[class~="bubble-3qRl2J"]`); 254 | this.playerDuration.bounding = this.playerDuration.ref.parentNode.getBoundingClientRect(); 255 | this.playerDuration.tooltipHeight = this.playerDuration.tooltip.getBoundingClientRect().height; 256 | 257 | this.playerDuration.lastUpdate = Date.now(); 258 | this.playerClose = this.playerRef.find(`div[role=header] div`); 259 | this.playerTitle = this.playerRef.find(`div[role=header] input`); 260 | 261 | this.playerClose.addEventListener('click', _ => this.close()); 262 | 263 | this.playerTime.duration = this.playerRef.find(`div[class='durationTimeWrapper-OugPFt da-durationTimeWrapper']`).lastElementChild; 264 | this.playerTime.current = this.playerRef.find(`div[class='durationTimeWrapper-OugPFt da-durationTimeWrapper']`).firstElementChild; 265 | 266 | this.playerButton = this.playerRef.find(`div[role="button"]`); 267 | 268 | this.playerButton.addEventListener('click', _ => { 269 | if(!(this.audio instanceof Audio) || this.audio.readyState == 0) 270 | return; 271 | 272 | if(this.audio.paused || this.audio.ended) 273 | this.audio.play(); 274 | else 275 | this.audio.pause(); 276 | }); 277 | this.playerVolume.button.addEventListener('click', _ => { 278 | if(!(this.audio instanceof Audio) || this.audio.readyState == 0) 279 | return; 280 | 281 | if(this.audio.volume > 0){ 282 | this.playerVolume.lastValue = this.audio.volume; 283 | this.audio.volume = 0; 284 | } 285 | else{ 286 | this.audio.volume = this.playerVolume.lastValue; 287 | } 288 | }); 289 | 290 | const showVolumeBar = () => { 291 | this.playerVolume.ref.style.visibility = 'visible'; 292 | this.playerVolume.svg.style.opacity = '1'; 293 | 294 | if(this.playerVolume.timer != null){ 295 | clearInterval(this.playerVolume.timer); 296 | this.playerVolume.timer = null; 297 | } 298 | }; 299 | const hideVolumeBar = () => { 300 | if(this.playerVolume.timer == null){ 301 | this.playerVolume.timer = setTimeout(plugin => { 302 | plugin.playerVolume.ref.style.visibility = ''; 303 | plugin.playerVolume.svg.style.opacity = ''; 304 | this.playerVolume.timer = null; 305 | }, 1000, this); 306 | } 307 | }; 308 | const moveVolumeBar = (e, click=false) => { 309 | if(!(this.audio instanceof Audio) || this.audio.readyState == 0) 310 | return; 311 | 312 | const value = Math.min(1, Math.max(0, (this.playerVolume.bounding.bottom - e.clientY) / this.playerVolume.bounding.height)) * 100; 313 | 314 | if(click || this.playerVolume.moving){ 315 | //player.volume.bar.style.width = `${value}%`; 316 | this.audio.volume = value / 100; 317 | } 318 | }; 319 | const moveBar = (e, click=false) => { 320 | if(!(this.audio instanceof Audio) || this.audio.readyState == 0) 321 | return; 322 | 323 | const value = Math.min(1, Math.max(0, (e.clientX - this.playerDuration.bounding.left) / this.playerDuration.bounding.width)) * 100; 324 | 325 | if(click || this.playerDuration.moving){ 326 | if(this.playerDuration.current instanceof Element){ 327 | //player.duration.current.style.width = `${value}%`; 328 | //player.audio.currentTime = (player.audio.duration / 100) * value; 329 | this.audio.currentTime = (value / 100) * this.audio.duration; 330 | 331 | if(this.audio.currentTime != this.audio.duration) 332 | if(this.playerButton.firstElementChild.getAttribute('name') == "Replay") 333 | this.playerButton.innerHTML = ``; 334 | 335 | } 336 | } 337 | if(this.playerDuration.tooltip instanceof Element){ 338 | if(Date.now() - this.playerDuration.lastUpdate > 10000) 339 | this.playerDuration.bounding = this.playerDuration.ref.parentNode.getBoundingClientRect(); 340 | 341 | this.playerDuration.tooltip.innerHTML = convertToTime((value / 100) * this.audio.duration); 342 | this.playerDuration.tooltip.style.left = `${this.playerDuration.bounding.left + ((this.playerDuration.bounding.width / 100) * value)}px`; 343 | this.playerDuration.tooltip.style.top = `${this.playerDuration.bounding.top - this.playerDuration.tooltipHeight - 10}px`; 344 | } 345 | if(this.playerDuration.preview instanceof Element) 346 | this.playerDuration.preview.style.width = `${value}%`; 347 | 348 | }; 349 | 350 | this.playerVolume.button.addEventListener('mouseover', showVolumeBar); 351 | this.playerVolume.button.addEventListener('mouseout', hideVolumeBar); 352 | this.playerVolume.ref.firstElementChild.addEventListener('mouseover', showVolumeBar); 353 | this.playerVolume.ref.firstElementChild.addEventListener('mouseout', hideVolumeBar); 354 | 355 | this.playerVolume.bar.parentNode.addEventListener('click', e => moveVolumeBar(e, true)); 356 | this.playerVolume.bar.parentNode.addEventListener('mousemove', e => moveVolumeBar(e)); 357 | this.playerVolume.bar.parentNode.addEventListener('mousedown', _ => this.playerVolume.moving = true); 358 | this.playerVolume.bar.parentNode.addEventListener('mouseup', _ => this.playerVolume.moving = false); 359 | this.playerVolume.bar.parentNode.addEventListener('mouseleave', _ => this.playerVolume.moving = false); 360 | 361 | this.playerDuration.ref.addEventListener('click', e => moveBar(e, true)); 362 | this.playerDuration.ref.addEventListener('mousemove', e => moveBar(e)); 363 | 364 | this.playerDuration.ref.addEventListener('mousedown', _ => { 365 | if(this.audio instanceof Audio && this.audio.readyState != 0){ 366 | this.playerDuration.moving = true; 367 | 368 | if(!(this.playerDuration.paused = this.audio.paused || this.audio.ended)) 369 | this.audio.pause(); 370 | } 371 | }); 372 | const dragEnd = () => { 373 | if(this.playerDuration.preview instanceof Element) 374 | this.playerDuration.preview.style.width = `0%`; 375 | 376 | if(this.playerDuration.moving){ 377 | if(!this.playerDuration.paused) 378 | this.audio.play().catch(err => Logger.error(err)); 379 | 380 | this.playerDuration.moving = false; 381 | } 382 | }; 383 | 384 | this.playerDuration.ref.addEventListener('mouseup', dragEnd); 385 | this.playerDuration.ref.addEventListener('mouseleave', dragEnd); 386 | 387 | this.playerRef.style.display = 'none'; 388 | 389 | } 390 | close(){ 391 | if(this.audio instanceof Audio && (!this.audio.paused && !this.audio.ended)) 392 | this.audio.pause(); 393 | 394 | if(this.playerRef instanceof Element) 395 | this.playerRef.remove(); 396 | 397 | if(typeof this.events.close == 'function') 398 | this.events.close(); 399 | } 400 | async setAudio(audio){ 401 | if(!(audio instanceof Audio)) 402 | return false; 403 | 404 | if(this.audio instanceof Audio){ 405 | if(!this.audio.paused && !this.audio.ended) 406 | this.audio.pause(); 407 | 408 | this.playerDuration.moving = false; 409 | this.playerVolume.moving = false; 410 | 411 | this.audio.currentTime = 0; 412 | this.audio.volume = 1; 413 | this.audio = null; 414 | } 415 | 416 | /* 417 | Fix audio duration: 418 | https://stackoverflow.com/questions/21522036/html-audio-tag-duration-always-infinity/52375280#52375280 419 | */ 420 | const fix_audio = async () => { 421 | this.playerLoading.style.display = 'block'; 422 | this.playerRef.find(`div[class='DiscordPlayer']`).style.display = 'none'; 423 | 424 | while(audio.duration == Infinity || isNaN(audio.duration)){ 425 | audio.currentTime = 10000000 * Math.random(); 426 | await new Promise(resolve => setTimeout(resolve, 1000)); 427 | } 428 | audio.currentTime = 0; 429 | this.playerButton.innerHTML = ``; 430 | await new Promise(resolve => setTimeout(resolve, 1000)); 431 | this.playerLoading.style.display = 'none'; 432 | this.playerRef.find(`div[class='DiscordPlayer']`).style.display = 'block'; 433 | }; 434 | 435 | this.audioReady = audio.readyState != 0; 436 | if(audio.readyState == 0){ 437 | this.playerTime.duration.innerHTML = '--:--:--'; 438 | audio.addEventListener('loadedmetadata', async _ => { 439 | await fix_audio(); 440 | this.audioReady = this.audio.readyState != 0; 441 | this.playerTime.duration.innerHTML = convertToTime(audio.duration); 442 | 443 | }); 444 | } 445 | else{ 446 | await fix_audio(); 447 | this.playerTime.duration.innerHTML = convertToTime(audio.duration); 448 | } 449 | this.audio = audio; 450 | this.playerVolume.bar.style.width = `${this.audio.volume * 100}%`; 451 | this.playerTime.current.innerHTML = '00:00:00'; 452 | this.audio.addEventListener('timeupdate', _ => { 453 | this.playerDuration.current.style.width = `${(this.audio.currentTime / this.audio.duration) * 100}%`; 454 | this.playerTime.current.innerHTML = convertToTime(this.audio.currentTime); 455 | }); 456 | this.audio.addEventListener('volumechange', _ => { 457 | this.playerVolume.bar.style.width = `${this.audio.volume * 100}%`; 458 | if(this.audio.volume == 0){ 459 | this.playerVolume.button.innerHTML = `
`; 460 | 461 | this.playerVolume.svg = this.playerVolume.button.find('svg'); 462 | this.playerVolume.svg.style.opacity = '1'; 463 | } 464 | else{ 465 | if(this.playerVolume.svg.getAttribute('name') != 'Speaker'){ 466 | this.playerVolume.button.innerHTML = `
`; 467 | this.playerVolume.svg = this.playerVolume.button.find('svg'); 468 | this.playerVolume.svg.style.opacity = '1'; 469 | } 470 | } 471 | }); 472 | this.audio.addEventListener('play', _ => { 473 | this.playerButton.innerHTML = ``; 474 | }); 475 | 476 | this.audio.addEventListener('pause', _ => { 477 | this.playerButton.innerHTML = ``; 478 | }); 479 | 480 | this.audio.addEventListener('ended', _ => { 481 | this.playerButton.innerHTML = ``; 482 | }); 483 | return true; 484 | } 485 | setAudioLink(link){return this.setAudio(new Audio(link));} 486 | setTitle(title){this.playerTitle.value = title;} 487 | getTitle(){return this.playerTitle.value;} 488 | getAudio(){return this.audio;} 489 | play(){ 490 | if(this.audio instanceof Audio && this.audio.readyState != 0){ 491 | if(this.audio.paused || this.audio.ended) 492 | this.audio.play(); 493 | 494 | return true; 495 | } 496 | return false; 497 | } 498 | pause(){ 499 | if(this.audio instanceof Audio && this.audio.readyState != 0){ 500 | if(!(this.audio.paused && this.audio.ended)) 501 | this.audio.pause(); 502 | 503 | return true; 504 | } 505 | return false; 506 | } 507 | setTime(time){ 508 | if(this.audio instanceof Audio && this.audio.readyState != 0){ 509 | this.audio.currentTime = time; 510 | return true; 511 | } 512 | return false; 513 | } 514 | setVolume(volume){ 515 | if(this.audio instanceof Audio){ 516 | this.audio.volume = volume; 517 | return true; 518 | } 519 | return false; 520 | } 521 | getTime(){return this.audio instanceof Audio ? this.audio.currentTime : 0;} 522 | setMuted(e){ 523 | if(this.audio instanceof Audio){ 524 | if(e){ 525 | this.playerVolume.lastValue = this.audio.volume > 0 ? this.audio.volume : 1; 526 | this.audio.volume = 0; 527 | } 528 | else 529 | this.audio.volume = this.playerVolume.lastValue; 530 | 531 | return true; 532 | } 533 | return false; 534 | } 535 | getVolume(){ return this.audio instanceof Audio ? this.audio.volume : 0;} 536 | on(event, call){ 537 | if(event in this.events) 538 | this.events[event] = call; 539 | } 540 | display(value){this.playerRef.style.display = value;} 541 | }; 542 | })(); 543 | 544 | const Tooltip = ((KeyGenerator, DOMTools) => { 545 | const classList = { 546 | 'top': 'tooltipBottom-3ARrEK', 547 | 'left': 'tooltipRight-2JM5PQ', 548 | 'bottom': 'tooltipTop-XDDSxx', 549 | 'right': 'tooltipLeft-3EDOk1' 550 | }; 551 | 552 | const root = document.querySelector(`div[class='layerContainer-yqaFcK da-layerContainer']`); 553 | 554 | if(!root instanceof Element) 555 | return null; 556 | 557 | const calcAxis = (type, node, tooltip, tooltipPointer) => { 558 | if(type == 'bottom') 559 | return {'top': `${node.y - tooltipPointer.height - tooltip.height}px`,'left': `${node.x + (node.width / 2) - (tooltip.width / 2)}px`}; 560 | else if(type == 'top') 561 | return {'top': `${node.y + node.height + tooltipPointer.height}px`,'left': `${node.x + (node.width / 2) - (tooltip.width / 2)}px`}; 562 | else if(type == 'right') 563 | return {'top': `${node.y + (node.height / 2) - (tooltip.height / 2)}px`,'left': `${node.x - tooltip.width - tooltipPointer.width}px`}; 564 | else if(type == 'left') 565 | return {'top': `${node.y + (node.height / 2) - (tooltip.height / 2)}px`,'left': `${node.x + node.width + tooltipPointer.width}px`}; 566 | 567 | return {'top': '0px', 'left': '0px'}; 568 | }; 569 | return class Tooltip{ 570 | constructor(node, text, type='bottom'){ 571 | if(!(type in classList)) 572 | type = 'bottom'; 573 | 574 | this.props = { 575 | 'id': KeyGenerator(), 576 | 'type': type, 577 | 'text': text, 578 | 'ref': null, 579 | 'target': node, 580 | 'lastUpdate': 0, 581 | 'update': 10000 //10 seconds 582 | }; 583 | this.props.ref = root.appendChild(DOMTools.createElement(`
${text}
`)); 584 | 585 | this.removed = false; 586 | this.enabled = true; 587 | this.Offsets = { 588 | target: null, 589 | ref: null, 590 | pointerRef: null 591 | }; 592 | 593 | this.props.ref.style.display = 'none'; 594 | if(node instanceof Element){ 595 | this.update(); 596 | this.props.target.setAttribute('tooltip-id', this.props.id); 597 | node.addEventListener('mouseenter', this.show); 598 | node.addEventListener('mouseleave', this.hide); 599 | } 600 | } 601 | show(){ 602 | if(this.enabled && !this.removed && this.props.ref instanceof Element){ 603 | if(this.props.lastUpdate + this.props.update < Date.now()) 604 | this.update(); 605 | 606 | this.props.ref.style.display = 'block'; 607 | return true; 608 | } 609 | return false; 610 | } 611 | hide(){ 612 | if(this.enabled && !this.removed && this.props.ref instanceof Element){ 613 | this.props.ref.style.display = 'none'; 614 | return true; 615 | } 616 | return false; 617 | } 618 | enable(e){ 619 | this.enabled = e; 620 | } 621 | toggle(){ 622 | if(this.enabled && this.props.ref instanceof Element) 623 | return this.props.ref.style.display == 'none' ? this.show() : this.hide(); 624 | 625 | return false; 626 | } 627 | create(force=false){ 628 | if(this.removed || force){ 629 | this.props.ref = root.appendChild(DOMTools.createElement(`
${this.props.text}
`)) 630 | this.props.ref.style.display = 'none'; 631 | this.removed = false; 632 | 633 | this.update(); 634 | } 635 | } 636 | setTarget(node){ 637 | if(node instanceof Element){ 638 | if(this.props.target instanceof Element){ 639 | this.props.target.removeEventListener('mouseenter', this.show); 640 | this.props.target.removeEventListener('mouseleave', this.hide); 641 | } 642 | this.props.target = node; 643 | node.addEventListener('mouseenter', this.show); 644 | node.addEventListener('mouseleave', this.hide); 645 | 646 | if(this.removed) 647 | this.create(); 648 | else 649 | this.update(); 650 | 651 | return true; 652 | } 653 | return false; 654 | } 655 | setText(text){ 656 | if(!this.removed){ 657 | this.props.text = text; 658 | 659 | if(this.props.ref instanceof Element){ 660 | const node = this.props.ref.find('div[role=text]') 661 | if(node instanceof Element){ 662 | node.innerHTML = text; 663 | return true; 664 | } 665 | 666 | } 667 | } 668 | return false; 669 | } 670 | setType(type){ 671 | if(!this.removed && type in classList){ 672 | if(this.props.ref instanceof Element){ 673 | this.props.ref.removeClass(classList[this.props.type]); 674 | this.props.ref.addClass(classList[type]); 675 | this.props.type = type; 676 | 677 | this.update(); 678 | return true; 679 | } 680 | } 681 | return false; 682 | } 683 | remove(){ 684 | if(!this.removed){ 685 | 686 | if(this.props.target instanceof Element){ 687 | this.props.target.removeEventListener('mouseenter', this.show); 688 | this.props.target.removeEventListener('mouseleave', this.hide); 689 | } 690 | 691 | if(this.props.ref instanceof Element) 692 | this.props.ref.remove(); 693 | 694 | this.props.ref = null; 695 | this.props.target = null; 696 | 697 | this.removed = true; 698 | } 699 | } 700 | update(){ 701 | let displayValue = [false, false]; 702 | 703 | if(this.props.ref.style.display != 'none') 704 | displayValue[0] = true; 705 | else 706 | this.props.ref.style.display = 'block'; 707 | 708 | if(this.props.target.style.display != 'none') 709 | displayValue[1] = true; 710 | else 711 | this.props.target.style.display = 'block'; 712 | 713 | this.Offsets = { 714 | target: this.props.target.getBoundingClientRect(), 715 | ref: this.props.ref.getBoundingClientRect(), 716 | pointerRef: this.props.ref.querySelector(`div[class~='da-tooltipPointer']`).getBoundingClientRect() 717 | }; 718 | 719 | const { top, left } = calcAxis(this.props.type, this.Offsets.target, this.Offsets.ref, this.Offsets.pointerRef); 720 | 721 | this.props.ref.style.top = top; 722 | this.props.ref.style.left = left; 723 | 724 | if(!displayValue[0]) 725 | this.props.ref.style.display = 'none'; 726 | 727 | if(!displayValue[1]) 728 | this.props.target.style.display = 'none'; 729 | 730 | this.props.lastUpdate = Date.now(); 731 | } 732 | }; 733 | })(DiscordModules.KeyGenerator, DOMTools); 734 | return class SendAudio extends Plugin { 735 | 736 | onStart() { 737 | if(!document.getElementById('css-' + this.getName())){ 738 | PluginUtilities.addStyle('css-' + this.getName(),` 739 | :root{--sendaudio-record: var(--interactive-normal);--sendaudio-record-hover: var(--interactive-active);--sendaudio-cancel: var(--interactive-normal);--sendaudio-cancel-hover: var(--interactive-active);--sendaudio-pause: var(--interactive-normal);--sendaudio-pause-hover: var(--interactive-active);--sendaudio-play: var(--interactive-normal);--sendaudio-play-hover: var(--interactive-active);--sendaudio-save: var(--interactive-normal);--sendaudio-save-hover: var(--interactive-active);--sendaudio-send: var(--interactive-normal);--sendaudio-send-hover: var(--interactive-active);} 740 | .flash-record{width: 14px;height: 14px;border-radius:50%;position: relative;top: 2px;margin: 0 2px;animation: flash linear 1s infinite;} 741 | @keyframes flash{0% {opacity: 1;}50% {opacity: .1;}100% {opacity: 1;}} 742 | .DiscordPlayer div[role='header']{color: var(--text-normal);display: flex;-webkit-box-align: center;align-items: center;border-bottom: 5px;} 743 | .DiscordPlayer div[role='header'] input{border: none;background: transparent;color: var(--header-primary);font-weight: 600;} 744 | .DiscordPlayer div[role='header'] div{position: absolute;right: 0;opacity: .3;cursor: pointer;} 745 | .DiscordPlayer div[role='header'] div:hover{opacity: 1 !important;} 746 | .DiscordPlayer div[role='controls']{color: white;display: flex;-webkit-box-align: center;align-items: center;} 747 | .DiscordPlayerLoading{display:none;color: var(--header-primary);font-weight: 600;text-align: center;padding: 5px;} 748 | .DiscordPlayerLoading path{fill: var(--header-primary);stroke: var(--header-primary);stroke-width: 5px;} 749 | .DiscordPlayerLoading svg{width: 16px;height: 16px;} 750 | .DiscordPlayer div[class='durationTimeWrapper-OugPFt da-durationTimeWrapper']{color: var(--text-normal);} 751 | .DiscordPlayer div[role=button] svg{color: var(--interactive-normal);opacity: 1;} 752 | .DiscordPlayer div[role=button]:hover svg{color: var(--interactive-active);opacity: 1;} 753 | .DiscordPlayer div[role='control-bar'], .DiscordPlayer div[role='control-bar']:after, .DiscordPlayer div[role='control-bar']:before{background-color: var(--interactive-muted);} 754 | .DiscordPlayer div[role='control-bar'] div[class~='mediaBarPreview-1jfyFs'], .DiscordPlayer div[role='control-bar'] div[class~='mediaBarPreview-1jfyFs']:after, .DiscordPlayer div[role='control-bar'] div[class~='mediaBarPreview-1jfyFs']:before{background-color: var(--text-normal);} 755 | .SendAudio-Info input{border: none;background: transparent;color: var(--header-primary);font-weight: 600;} 756 | #sendAudioButtons button[role=send] polygon{fill: var(--sendaudio-send) !important;} 757 | #sendAudioButtons button[role=send]:hover polygon{fill: var(--sendaudio-send-hover) !important;} 758 | #sendAudioButtons button[role=save] svg{border: 1px solid var(--sendaudio-save) !important;} 759 | #sendAudioButtons button[role=save] svg path:last-child{fill: var(--sendaudio-save) !important;} 760 | 761 | #sendAudioButtons button[role=save]:hover svg{border: 1px solid var(--sendaudio-save-hover) !important;} 762 | #sendAudioButtons button[role=save]:hover svg path:last-child{fill: var(--sendaudio-save-hover) !important;} 763 | #sendAudioButtons button[role=play] svg{color: var(--sendaudio-play) !important;} 764 | #sendAudioButtons button[role=play]:hover svg{color: var(--sendaudio-play-hover) !important;} 765 | #sendAudioButtons button[role=pause] path{fill: var(--sendaudio-pause) !important;} 766 | #sendAudioButtons button[role=pause]:hover path{fill: var(--sendaudio-pause-hover) !important;} 767 | #sendAudioButtons button[role=cancel] path{fill: var(--sendaudio-cancel) !important;} 768 | #sendAudioButtons button[role=cancel]:hover path{fill: var(--sendaudio-cancel-hover) !important;} 769 | #sendAudioButtons button[role=record] svg{color: var(--sendaudio-record) !important;} 770 | #sendAudioButtons button[role=record]:hover svg{color: var(--sendaudio-record-hover) !important;} 771 | .plugin-settings[id="plugin-settings-${this.getName()}"] > div{padding: 20px;} 772 | `); 773 | } 774 | 775 | this.devices = false; 776 | 777 | this.media = false; 778 | 779 | this.mediaInfo = { 780 | ready: false, 781 | error: false 782 | }; 783 | this.panelInfo = [false, false]; 784 | 785 | this.player = null; 786 | 787 | this.record = { 788 | chunks: [], 789 | blob: null, 790 | max_size: this.settings.nitro ? 52428800 : 8388608, 791 | size: 0, 792 | time: 0, 793 | limitStop: false, 794 | notSave: false, 795 | channel: null, 796 | previewing: false 797 | }; 798 | 799 | this.tooltips = { 800 | 'record': new Tooltip(null, 'Record'), 801 | 'cancel': new Tooltip(null, 'Cancel'), 802 | 'pause': new Tooltip(null, 'Pause'), 803 | 'play': new Tooltip(null, 'Play'), 804 | 'resume': new Tooltip(null, 'Resume'), 805 | 'save': new Tooltip(null, 'Save'), 806 | 'send': new Tooltip(null, 'Send'), 807 | 'info': new Tooltip(null, 'Info') 808 | }; 809 | 810 | //Init devices 811 | 812 | let plugin = this; 813 | 814 | navigator.mediaDevices.enumerateDevices().then(devices => { 815 | plugin.devices = devices.filter(device => device.kind == 'audioinput' && device.deviceId != 'default'); 816 | 817 | let addeds = []; 818 | 819 | plugin.devices.forEach(device => { 820 | if(addeds.indexOf(device.groupId) == -1){ 821 | plugin._config.defaultConfig.filter(k => k.id == 'devices')[0].options.push({label: device.label, value: device.deviceId}); 822 | addeds.push(device.groupId); 823 | } 824 | }); 825 | }).catch(error => { 826 | Logger.warn('Unable to get recording devices:', error); 827 | }); 828 | 829 | this.onSwitch(); 830 | 831 | this.changeMedia(); 832 | } 833 | 834 | onStop() { 835 | 836 | this.mediaInfo.notSave = true; 837 | this.recordStop(); 838 | 839 | if(typeof this.buttons == 'object' && this.buttons.group instanceof Element) 840 | this.buttons.group.remove(); 841 | 842 | this.recordReset(); 843 | 844 | PluginUtilities.removeStyle('css-' + this.getName()); 845 | 846 | for(let k in this.tooltips){ 847 | this.tooltips[k].remove(); 848 | } 849 | } 850 | 851 | onSwitch(){ 852 | if(!this.addButtons()) 853 | return; 854 | } 855 | 856 | addButtons(){ 857 | if(document.getElementById('sendAudioButtons') instanceof Element) 858 | return false; 859 | 860 | let buttons_ = document.querySelector(`div[class="buttons-3JBrkn da-buttons"]`); 861 | let uploadButton = document.getElementsByClassName('attachButton-2WznTc'); 862 | 863 | if(!(buttons_ instanceof Element) || (!uploadButton || !(uploadButton[0] instanceof Element))) 864 | return false; 865 | 866 | this.buttons = { 867 | group: DOMTools.createElement(`
`), 868 | record: addButton(`role='record'`, ``), 869 | cancel: addButton(`role='cancel' style='display: none;'`, ``), 870 | pause: addButton(`role='pause' style='display: none;'`, ``), 871 | play: addButton(`role='play' style='display: none;'`, ``), 872 | save: addButton(`role='save' style='display: none;'`, ``), 873 | send: addButton(`role='send' style='display: none;'`, ``) 874 | }; 875 | 876 | this.buttons.group.append(this.buttons.record); 877 | this.buttons.group.append(this.buttons.cancel); 878 | this.buttons.group.append(this.buttons.pause); 879 | this.buttons.group.append(this.buttons.play); 880 | this.buttons.group.append(this.buttons.save); 881 | this.buttons.group.append(this.buttons.send); 882 | 883 | buttons_.append(this.buttons.group); 884 | 885 | for(let k in this.tooltips){ 886 | if(k in this.buttons){ 887 | this.tooltips[k].setTarget(this.buttons[k]); 888 | this.tooltips[k].props.lastUpdate = 0; 889 | } 890 | } 891 | 892 | const plugin = this; 893 | 894 | 895 | this.buttons.record.addEventListener('click', _ => { 896 | if(!plugin.recordStart()){ 897 | Logger.warn("Cannot start recording"); 898 | } 899 | }); 900 | this.buttons.cancel.addEventListener('click', _ => { 901 | if(plugin.record.previewing == true){ 902 | plugin.recordReset(); 903 | 904 | plugin.setDisplay({all: 'none', record: ''}); 905 | } 906 | else{ 907 | plugin.mediaInfo.notSave = true; 908 | plugin.recordStop(); 909 | } 910 | }); 911 | this.buttons.pause.addEventListener('click', _ => plugin.recordPause()); 912 | this.buttons.play.addEventListener('click', _ => plugin.recordResume()); 913 | this.buttons.save.addEventListener('click', _ => plugin.recordStop()); 914 | this.buttons.send.addEventListener('click', _ => { 915 | if(plugin.record.previewing == true){ 916 | if(plugin.record.channel){ 917 | sendAudio(plugin.record.channel.id, plugin.record.blob); 918 | 919 | plugin.recordReset(); 920 | plugin.setDisplay({all: 'none', record: ''}); 921 | } 922 | } 923 | else plugin.recordStop(); 924 | }); 925 | if(!this.mediaInfo.ready || this.mediaInfo.error) 926 | this.buttons.record.disabled = true; 927 | 928 | if(this.mediaInfo.ready && !this.mediaInfo.error && this.media instanceof MediaRecorder){ 929 | const channel = DiscordAPI.currentChannel; 930 | 931 | if(this.record.previewing) 932 | this.setDisplay({record: 'none', cancel: '', send: ''}); 933 | 934 | else if(this.media.state != 'inactive'){ 935 | if(this.settings.preview) 936 | this.setDisplay({record: 'none', cancel: '', save: ''}); 937 | else 938 | this.setDisplay({record: 'none', cancel: '', send: ''}); 939 | 940 | if(typeof channel == 'object' && typeof this.record.channel == 'object' && channel.id != this.record.channel.id){ 941 | this.buttons.send.disabled = true; 942 | this.buttons.save.disabled = true; 943 | } 944 | if(this.media.state == 'paused') 945 | this.setDisplay({pause: 'none', play: ''}); 946 | else 947 | this.setDisplay({pause: '', play: 'none'}); 948 | } 949 | 950 | } 951 | return true; 952 | } 953 | addPanelRecording(){ 954 | const data = `Recording in ${getChannelName()} (${getGuildName()})`; 955 | 956 | const panel = addPanel(`
`, `id='SendAudio-RecordingInfo'`); 957 | 958 | if(panel instanceof Element){ 959 | panel.addClass('SendAudio-Info'); 960 | this.panelInfo[0] = panel; 961 | this.panelInfo[1] = panel.querySelector('input[role=time]'); 962 | 963 | this.tooltips.info.setText(data); 964 | this.tooltips.info.setTarget(panel); 965 | return true; 966 | } 967 | return false; 968 | } 969 | removePanelRecording(){ 970 | if(this.panelInfo[0] instanceof Element) 971 | this.panelInfo[0].remove(); 972 | 973 | this.panelInfo = [false, false]; 974 | } 975 | 976 | setDisplay(values){ 977 | if(typeof values != 'object' || typeof this.buttons != 'object' || !(this.buttons.group instanceof Element)) 978 | return false; 979 | 980 | if(values.all) 981 | for(let key in this.buttons) 982 | if(key != 'group' && this.buttons[key] instanceof Element) 983 | this.buttons[key].style.display = values.all; 984 | 985 | delete values.all; 986 | 987 | for(let key in values) 988 | if(key in this.buttons && this.buttons[key] instanceof Element) 989 | this.buttons[key].style.display = values[key]; 990 | 991 | return true; 992 | } 993 | 994 | getSettingsPanel(){ 995 | const settings = this.buildSettingsPanel(); 996 | if(settings){ 997 | settings.addListener((id, value) => { 998 | if(id == 'devices'){ 999 | this.changeMedia(); 1000 | } 1001 | else if(id == 'preview'){ 1002 | if(this.media instanceof MediaRecorder && this.media.state != 'inactive'){ 1003 | if(value) 1004 | this.setDisplay({save: '', send: 'none'}); 1005 | else 1006 | this.setDisplay({save: 'none', send: ''}); 1007 | } 1008 | } 1009 | else if(id == 'nitro'){ 1010 | this.record.max_size = value ? 52428800 : 8388608; 1011 | } 1012 | }); 1013 | return settings.getElement(); 1014 | } 1015 | Logger.warn("Failed to display settings"); 1016 | } 1017 | 1018 | changeMedia(device=false, changing=false){ 1019 | const plugin = this; 1020 | 1021 | const change = () => { 1022 | plugin.media = null; 1023 | plugin.mediaInfo.ready = false; 1024 | plugin.mediaInfo.error = false; 1025 | 1026 | navigator.mediaDevices.getUserMedia({audio: (device != false ? device : plugin.settings.devices)}).then(s => { 1027 | plugin.media = new MediaRecorder(s); 1028 | Logger.log("MediaRecorder started successfully"); 1029 | 1030 | plugin.media.addEventListener('dataavailable', e => plugin.onRecordingData(e.data)); 1031 | plugin.media.addEventListener('start', _ => plugin.onRecordingStateChange(0)); 1032 | plugin.media.addEventListener('stop', _ => plugin.onRecordingStateChange(1)); 1033 | plugin.media.addEventListener('pause', _ => plugin.onRecordingStateChange(2)); 1034 | plugin.media.addEventListener('resume', _ => plugin.onRecordingStateChange(3)); 1035 | 1036 | plugin.media.addEventListener('error', error => { 1037 | Logger.error('Recording error:', error); 1038 | }); 1039 | plugin.mediaInfo.ready = true; 1040 | plugin.mediaInfo.error = false; 1041 | if(typeof plugin.buttons == 'object' && plugin.buttons.record instanceof Element) 1042 | plugin.buttons.record.disabled = false; 1043 | 1044 | }).catch(err => { 1045 | plugin.mediaInfo.ready = false; 1046 | plugin.mediaInfo.error = true; 1047 | if(typeof plugin.buttons == 'object' &&plugin.buttons.record instanceof Element) 1048 | plugin.buttons.record.disabled = true; 1049 | 1050 | Logger.error("Failed to start MediaRecorder:", err.message); 1051 | 1052 | if(!changing){ 1053 | Logger.log("Changing to the default"); 1054 | plugin.changeMedia('default', true); 1055 | } 1056 | }); 1057 | }; 1058 | 1059 | if(this.media instanceof MediaRecorder){ 1060 | this.mediaInfo.notSave = true; 1061 | if(this.media.state != 'inactive') 1062 | this.media.stop().then(_ => change()).catch(_ => change()); 1063 | } 1064 | else{ 1065 | change(); 1066 | } 1067 | } 1068 | 1069 | /* Record methods */ 1070 | recordStart(){ 1071 | if(this.media instanceof MediaRecorder){ 1072 | if(this.media.state == 'inactive'){ 1073 | this.record.chunks = []; 1074 | this.record.size = 0; 1075 | this.record.time = 0; 1076 | this.media.start(1000); 1077 | } 1078 | return this.media.state == 'recording'; 1079 | } 1080 | return false; 1081 | } 1082 | recordStop(){ 1083 | if(this.media instanceof MediaRecorder){ 1084 | if(this.media.state != 'inactive') 1085 | this.media.stop(); 1086 | 1087 | return this.media.state == 'inactive'; 1088 | } 1089 | return false; 1090 | } 1091 | recordPause(){ 1092 | if(this.media instanceof MediaRecorder){ 1093 | if(this.media.state == 'recording') 1094 | this.media.pause(); 1095 | 1096 | return this.media.state == 'paused'; 1097 | } 1098 | return false; 1099 | } 1100 | recordResume(){ 1101 | if(this.media instanceof MediaRecorder){ 1102 | if(this.media.state == 'paused') 1103 | this.media.resume(); 1104 | 1105 | return this.media.state == 'recording'; 1106 | } 1107 | return false; 1108 | } 1109 | recordReset(){ 1110 | this.record.chunks = []; 1111 | this.record.time = 0; 1112 | this.record.size = 0; 1113 | this.record.notSave = false; 1114 | this.record.blob = null; 1115 | this.record.channel = null; 1116 | this.record.previewing = false; 1117 | this.record.limitStop = false; 1118 | 1119 | if(typeof this.buttons == 'object' && 'group' in this.buttons && this.buttons.group instanceof Element){ 1120 | this.buttons.save.disabled = false; 1121 | this.buttons.send.disabled = false; 1122 | } 1123 | 1124 | if(this.player != null) 1125 | this.player.close(); 1126 | } 1127 | 1128 | /* Record Event */ 1129 | onRecordingStateChange(new_state){ 1130 | //start 1131 | switch(new_state){ 1132 | //start 1133 | case 0:{ 1134 | this.addPanelRecording(); 1135 | this.record.channel = DiscordAPI.currentChannel; 1136 | if(this.settings.preview) 1137 | this.setDisplay({record: 'none', cancel: 'block', pause: 'block', save: 'block'}); 1138 | else 1139 | this.setDisplay({record: 'none', cancel: 'block', pause: 'block', send: 'block'}); 1140 | break; 1141 | } 1142 | //stop 1143 | case 1:{ 1144 | this.removePanelRecording(); 1145 | 1146 | if(this.mediaInfo.notSave){ 1147 | this.mediaInfo.notSave = false; 1148 | this.setDisplay({all: 'none', record: ''}); 1149 | this.recordReset(); 1150 | return; 1151 | } 1152 | if(this.record.size > 0){ 1153 | 1154 | if(this.record.size > this.record.max_size){ 1155 | let tempChunks = []; 1156 | let tempSize = 0; 1157 | 1158 | for(let i = 0; i < this.record.chunks.length; ++i){ 1159 | if(tempSize + this.record.chunks[i].size <= this.record.max_size){ 1160 | tempChunks.push(this.record.chunks[i]); 1161 | tempSize += this.record.chunks[i].size; 1162 | } 1163 | } 1164 | this.record.chunks = tempChunks; 1165 | this.record.size = tempSize; 1166 | } 1167 | 1168 | this.record.blob = new Blob(this.record.chunks, {type: this.settings.mimetype}) 1169 | if(!this.settings.preview){ 1170 | if(this.record.channel) 1171 | sendAudio(this.record.channel.id, this.record.blob); 1172 | } 1173 | else{ 1174 | Logger.log("PREVIEW"); 1175 | this.player = new DiscordPlayer(); 1176 | this.player.setAudio(new Audio(URL.createObjectURL(this.record.blob))); 1177 | this.player.display('block'); 1178 | this.record.previewing = true; 1179 | this.setDisplay({all: 'none', cancel: 'block', send: 'block'}); 1180 | return; 1181 | } 1182 | } 1183 | this.recordReset(); 1184 | this.setDisplay({all: 'none', record: ''}); 1185 | break; 1186 | } 1187 | //pause 1188 | case 2:{ 1189 | this.setDisplay({play: '', pause: 'none'}); 1190 | break; 1191 | } 1192 | //resume 1193 | case 3:{ 1194 | this.setDisplay({play: 'none', pause: ''}); 1195 | break; 1196 | } 1197 | default: 1198 | break; 1199 | } 1200 | 1201 | } 1202 | onRecordingData(data){ 1203 | this.record.time ++; 1204 | 1205 | if(data.size > 0){ 1206 | if(this.record.size + data.size > this.record.max_size){ 1207 | this.record.limitStop = true; 1208 | this.recordStop(); 1209 | return; 1210 | } 1211 | this.record.chunks.push(data); 1212 | this.record.size += data.size; 1213 | } 1214 | if(this.panelInfo[1] instanceof Element) 1215 | this.panelInfo[1].value = `${convertToTime(this.record.time)} - ${convertData(this.record.size)}`; 1216 | } 1217 | }; 1218 | }; 1219 | return plugin(Plugin, Api); 1220 | }; 1221 | if(!global.ZLibrary && typeof global.ZeresPluginLibrary != 'function'){ 1222 | return class { 1223 | getName() {return config.info.name.replace(" ", "");} getAuthor() {return config.info.authors.map(a => a.name).join(", ");} getDescription() {return config.info.description;} getVersion() {return config.info.version;} stop() {} 1224 | showAlert() {window.BdApi.alert("Loading Error",`Something went wrong trying to load the library for the plugin. You can try using a local copy of the library to fix this.

Click here to download the library!`);} 1225 | async load() { 1226 | try {await global.ZLibraryPromise;} 1227 | catch(err) {return this.showAlert();} 1228 | const vm = require("vm"), plugin = compilePlugin(global.ZLibrary.buildPlugin(config)); 1229 | try {new vm.Script(plugin, {displayErrors: true});} catch(err) {return bdpluginErrors.push({name: this.getName(), file: this.getName() + ".plugin.js", reason: "Plugin could not be compiled.", error: {message: err.message, stack: err.stack}});} 1230 | global[this.getName()] = plugin; 1231 | try {new vm.Script(`new global["${this.getName()}"]();`, {displayErrors: true});} catch(err) {return bdpluginErrors.push({name: this.getName(), file: this.getName() + ".plugin.js", reason: "Plugin could not be constructed", error: {message: err.message, stack: err.stack}});} 1232 | bdplugins[this.getName()].plugin = new global[this.getName()](); 1233 | bdplugins[this.getName()].plugin.load(); 1234 | } 1235 | async start() { 1236 | try {await global.ZLibraryPromise;} 1237 | catch(err) {return this.showAlert();} 1238 | bdplugins[this.getName()].plugin.start(); 1239 | } 1240 | }; 1241 | } 1242 | //Loading plugin using remote lib or local lib 1243 | return typeof global.ZeresPluginLibrary != 'function' ? compilePlugin(global.ZLibrary.buildPlugin(config)) : compilePlugin(global.ZeresPluginLibrary.buildPlugin(config)); 1244 | })(); 1245 | /*@end@*/ 1246 | -------------------------------------------------------------------------------- /src/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "name": "Send Audio", 4 | "authors": [{ 5 | "name": "Matues", 6 | "discord_id": "301016626579505162", 7 | "github_username": "MKSx" 8 | }], 9 | "version": "1.1.4", 10 | "description": "Record and send audios in chat", 11 | "github": "https://github.com/MKSx/EnviarAudio-BetterDiscord", 12 | "github_raw": "https://raw.githubusercontent.com/MKSx/Send-Audio-Plugin-BetterDiscord/master/SendAudio.plugin.js" 13 | }, 14 | "main": "index.js", 15 | "defaultConfig": [ 16 | {"type": "switch", "name": "Preview record", "id": "preview", "value": false, "note": "Allows audio to be heard before being sent"}, 17 | {"type": "switch", "name": "Using nitro", "id": "nitro", "value": false, "note": "If you are using discord nitro increases the file size that can be used from 8 MB to 50 MB"}, 18 | {"type": "dropdown", "name": "Audio input", "id": "devices", "note": "The audio recording device that will be used", "options": [{"label": "Default", "value": "default"}], "value": "default"}, 19 | {"type": "dropdown", "name": "File format", "id": "mimetype", "note": "The type of file that will be sent", "options": [{"label": "mp3", "value": "audio/mp3"}, {"label": "ogg", "value": "audio/ogg"}, {"label": "wav", "value": "audio/wav"}, {"label": "opus", "value": "audio/webm;codecs=opus"}, {"label": "webm", "value": "audio/webm"}], "value": "audio/mp3"} 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (Plugin, Api) => { 2 | const { DOMTools, Logger, DiscordAPI, DiscordModules, PluginUtilities } = Api; 3 | 4 | const SelectedChannelStore = DiscordModules.SelectedChannelStore; 5 | const ChannelStore = DiscordModules.ChannelStore; 6 | const Upload = BdApi.findModule(m => m.upload && typeof m.upload === 'function'); 7 | 8 | const addButton = (attr, svg) => DOMTools.createElement(``); 9 | const addPanel = (html, attr, remove=false) => { 10 | let dom = document.querySelector('.panels-j1Uci_'); 11 | 12 | if(!(dom instanceof Element)) 13 | return false; 14 | 15 | if(document.querySelector(`div[${attr}]`) instanceof Element){ 16 | if(!remove) 17 | return false; 18 | 19 | document.querySelector(`div[${attr}]`).remove(); 20 | } 21 | return dom.insertBefore(DOMTools.createElement(`
${html}
`), dom.firstChild); 22 | }; 23 | 24 | const getChannelName = () => { 25 | const channel = DiscordAPI.currentChannel; 26 | 27 | if(channel.type == 'GROUP_DM'){ 28 | return (DiscordAPI.currentChannel ? DiscordAPI.currentChannel.name : 'Unknown channel'); 29 | } 30 | else if(channel.type == 'DM'){ 31 | const recipient = DiscordAPI.currentChannel.recipient; 32 | return '@' + recipient.username + '#' + recipient.discriminator; 33 | } 34 | return '#' + channel.name; 35 | }; 36 | const getGuildName = () => { 37 | const channel = DiscordAPI.currentChannel; 38 | 39 | if(channel.type != 'GUILD_TEXT'){ 40 | return channel.type; 41 | } 42 | 43 | if(DiscordAPI.currentGuild) 44 | return DiscordAPI.currentGuild.name; 45 | 46 | return 'Unknown server'; 47 | }; 48 | const convertToTime = (seconds) => { 49 | seconds = parseInt(seconds); 50 | return typeof seconds == 'number' && seconds > -1 ? new Date(parseInt(seconds) * 1000).toISOString().substr(11, 8) : '00:00:00'; 51 | }; 52 | const convertData = (size) => { 53 | //MB 54 | if(size >= 1048576){ 55 | temp = (size / 1024) % 1024; 56 | 57 | return `${parseInt((size / 1024) / 1024)}${temp != 0 ? ('.' + temp).slice(0, 3) : ''} MB`; 58 | } 59 | temp = size % 1024; 60 | return `${parseInt(size / 1024)}${temp != 0 ? ('.' + temp).slice(0,3) : ''} KB`; 61 | }; 62 | const randomInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; 63 | const randomKey = (len) => { 64 | if(len < 1) 65 | return ''; 66 | 67 | let ret = ''; 68 | for(let i = 0, j = parseInt(len / 3) + (len % 3); i < j; ++i) 69 | ret = ret + String.fromCharCode(randomInt(65, 90)) + String.fromCharCode(randomInt(48, 57)) + String.fromCharCode(randomInt(97, 122)); 70 | 71 | return ret.slice(0, len); 72 | }; 73 | const sendAudio = (channel, blob) => { 74 | 75 | let date = new Date(); 76 | 77 | date = new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString(); 78 | 79 | return Upload.upload(channel, new File([blob], randomKey(3) + '-' + date.substr(0, 10) + '-' + date.substr(11, 8).replace(/:/g, '-') + '.mp3'), {content: '', tts: false}); 80 | }; 81 | 82 | const DiscordPlayer = (() => { 83 | 84 | const addPlayer = (attr) => { 85 | return addPanel(` 86 |

Loading audio

87 |
88 |
89 | 90 |
91 | 92 | 93 | 94 |
95 |
96 |
97 |
98 | 99 |
100 |
101 | 00:00:00 102 | / 103 | --:--:-- 104 |
105 |
106 |
107 | 108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 | 116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 | 128 |
129 |
00:00:00
130 |
131 |
132 |
133 |
`, attr); 134 | }; 135 | return class{ 136 | constructor(){ 137 | this.audio = false; 138 | 139 | this.events = { 140 | 'end': null, 141 | 'start': null, 142 | 'close': null 143 | }; 144 | this.setDuration = false; 145 | this.id = DiscordModules.KeyGenerator(); 146 | this.audioReady = false; 147 | this.playerRef = addPlayer(`id='${this.id}'`); 148 | this.playerLoading = this.playerRef.find(`h2[class='DiscordPlayerLoading']`); 149 | this.playerTime = {current: null, duration: null}; 150 | this.playerVolume = {lastValue: 1, moving: false, timer: null, button: null, bar: null, ref: null, bounding: null, svg: null}; 151 | this.playerDuration = {ref: null, moving: false, current: null, preview: null, tooltip: null, bounding: null, tooltipHeight: 0, lastUpdate: 0, paused: false}; 152 | this.playerButton = null; 153 | this.playerClose = null; 154 | this.playerTitle = null; 155 | 156 | this.playerVolume.button = this.playerRef.find(`div[class="flex-1O1GKY da-flex"]`); 157 | this.playerVolume.ref = this.playerRef.find(`div[class~="audioVolumeWrapper-2t9juP"]`); 158 | this.playerVolume.bar = this.playerVolume.ref.find(`div[class~='mediaBarProgress-1xaPtl']`); 159 | this.playerVolume.svg = this.playerVolume.button.find(`div[class="flex-1O1GKY da-flex"] svg`); 160 | this.playerVolume.bounding = this.playerVolume.bar.parentNode.getBoundingClientRect(); 161 | 162 | this.playerDuration.ref = this.playerRef.find(`div[class="mediaBarInteraction-37i2O4 da-mediaBarInteraction"] div`); 163 | this.playerDuration.current = this.playerDuration.ref.find(`div[class~="mediaBarProgress-1xaPtl"]`); 164 | this.playerDuration.preview = this.playerDuration.ref.find(`div[class~="mediaBarPreview-1jfyFs"]`); 165 | this.playerDuration.tooltip = this.playerDuration.ref.find(`div[class~="bubble-3qRl2J"]`); 166 | this.playerDuration.bounding = this.playerDuration.ref.parentNode.getBoundingClientRect(); 167 | this.playerDuration.tooltipHeight = this.playerDuration.tooltip.getBoundingClientRect().height; 168 | 169 | this.playerDuration.lastUpdate = Date.now(); 170 | this.playerClose = this.playerRef.find(`div[role=header] div`); 171 | this.playerTitle = this.playerRef.find(`div[role=header] input`); 172 | 173 | this.playerClose.on('click', _ => this.close()); 174 | 175 | this.playerTime.duration = this.playerRef.find(`div[class='durationTimeWrapper-OugPFt da-durationTimeWrapper']`).lastElementChild; 176 | this.playerTime.current = this.playerRef.find(`div[class='durationTimeWrapper-OugPFt da-durationTimeWrapper']`).firstElementChild; 177 | 178 | this.playerButton = this.playerRef.find(`div[role="button"]`); 179 | 180 | this.playerButton.on('click', _ => { 181 | if(!(this.audio instanceof Audio) || this.audio.readyState == 0) 182 | return; 183 | 184 | if(this.audio.paused || this.audio.ended) 185 | this.audio.play(); 186 | else 187 | this.audio.pause(); 188 | }); 189 | this.playerVolume.button.on('click', _ => { 190 | if(!(this.audio instanceof Audio) || this.audio.readyState == 0) 191 | return; 192 | 193 | if(this.audio.volume > 0){ 194 | this.playerVolume.lastValue = this.audio.volume; 195 | this.audio.volume = 0; 196 | } 197 | else{ 198 | this.audio.volume = this.playerVolume.lastValue; 199 | } 200 | }); 201 | 202 | const showVolumeBar = () => { 203 | this.playerVolume.ref.style.visibility = 'visible'; 204 | this.playerVolume.svg.style.opacity = '1'; 205 | 206 | if(this.playerVolume.timer != null){ 207 | clearInterval(this.playerVolume.timer); 208 | this.playerVolume.timer = null; 209 | } 210 | }; 211 | const hideVolumeBar = () => { 212 | if(this.playerVolume.timer == null){ 213 | this.playerVolume.timer = setTimeout(plugin => { 214 | plugin.playerVolume.ref.style.visibility = ''; 215 | plugin.playerVolume.svg.style.opacity = ''; 216 | this.playerVolume.timer = null; 217 | }, 1000, this); 218 | } 219 | }; 220 | const moveVolumeBar = (e, click=false) => { 221 | if(!(this.audio instanceof Audio) || this.audio.readyState == 0) 222 | return; 223 | 224 | const value = Math.min(1, Math.max(0, (this.playerVolume.bounding.bottom - e.clientY) / this.playerVolume.bounding.height)) * 100; 225 | 226 | if(click || this.playerVolume.moving){ 227 | //player.volume.bar.style.width = `${value}%`; 228 | this.audio.volume = value / 100; 229 | } 230 | }; 231 | const moveBar = (e, click=false) => { 232 | if(!(this.audio instanceof Audio) || this.audio.readyState == 0) 233 | return; 234 | 235 | const value = Math.min(1, Math.max(0, (e.clientX - this.playerDuration.bounding.left) / this.playerDuration.bounding.width)) * 100; 236 | 237 | if(click || this.playerDuration.moving){ 238 | if(this.playerDuration.current instanceof Element){ 239 | //player.duration.current.style.width = `${value}%`; 240 | //player.audio.currentTime = (player.audio.duration / 100) * value; 241 | this.audio.currentTime = (value / 100) * this.audio.duration; 242 | 243 | if(this.audio.currentTime != this.audio.duration) 244 | if(this.playerButton.firstElementChild.getAttribute('name') == "Replay") 245 | this.playerButton.innerHTML = ``; 246 | 247 | } 248 | } 249 | if(this.playerDuration.tooltip instanceof Element){ 250 | if(Date.now() - this.playerDuration.lastUpdate > 10000) 251 | this.playerDuration.bounding = this.playerDuration.ref.parentNode.getBoundingClientRect(); 252 | 253 | this.playerDuration.tooltip.innerHTML = convertToTime((value / 100) * this.audio.duration); 254 | this.playerDuration.tooltip.style.left = `${this.playerDuration.bounding.left + ((this.playerDuration.bounding.width / 100) * value)}px`; 255 | this.playerDuration.tooltip.style.top = `${this.playerDuration.bounding.top - this.playerDuration.tooltipHeight - 10}px`; 256 | } 257 | if(this.playerDuration.preview instanceof Element) 258 | this.playerDuration.preview.style.width = `${value}%`; 259 | 260 | }; 261 | 262 | this.playerVolume.button.on('mouseover', showVolumeBar); 263 | this.playerVolume.button.on('mouseout', hideVolumeBar); 264 | this.playerVolume.ref.firstElementChild.on('mouseover', showVolumeBar); 265 | this.playerVolume.ref.firstElementChild.on('mouseout', hideVolumeBar); 266 | 267 | this.playerVolume.bar.parentNode.on('click', e => moveVolumeBar(e, true)); 268 | this.playerVolume.bar.parentNode.on('mousemove', e => moveVolumeBar(e)); 269 | this.playerVolume.bar.parentNode.on('mousedown', _ => this.playerVolume.moving = true); 270 | this.playerVolume.bar.parentNode.on('mouseup', _ => this.playerVolume.moving = false); 271 | this.playerVolume.bar.parentNode.on('mouseleave', _ => this.playerVolume.moving = false); 272 | 273 | this.playerDuration.ref.on('click', e => moveBar(e, true)); 274 | this.playerDuration.ref.on('mousemove', e => moveBar(e)); 275 | 276 | this.playerDuration.ref.on('mousedown', _ => { 277 | if(this.audio instanceof Audio && this.audio.readyState != 0){ 278 | this.playerDuration.moving = true; 279 | 280 | if(!(this.playerDuration.paused = this.audio.paused || this.audio.ended)) 281 | this.audio.pause(); 282 | } 283 | }); 284 | const dragEnd = () => { 285 | if(this.playerDuration.preview instanceof Element) 286 | this.playerDuration.preview.style.width = `0%`; 287 | 288 | if(this.playerDuration.moving){ 289 | if(!this.playerDuration.paused) 290 | this.audio.play().catch(err => Logger.error(err)); 291 | 292 | this.playerDuration.moving = false; 293 | } 294 | }; 295 | 296 | this.playerDuration.ref.on('mouseup', dragEnd); 297 | this.playerDuration.ref.on('mouseleave', dragEnd); 298 | 299 | this.playerRef.style.display = 'none'; 300 | 301 | } 302 | close(){ 303 | if(this.audio instanceof Audio && (!this.audio.paused && !this.audio.ended)) 304 | this.audio.pause(); 305 | 306 | if(this.playerRef instanceof Element) 307 | this.playerRef.remove(); 308 | 309 | if(typeof this.events.close == 'function') 310 | this.events.close(); 311 | } 312 | async setAudio(audio){ 313 | if(!(audio instanceof Audio)) 314 | return false; 315 | 316 | if(this.audio instanceof Audio){ 317 | if(!this.audio.paused && !this.audio.ended) 318 | this.audio.pause(); 319 | 320 | this.playerDuration.moving = false; 321 | this.playerVolume.moving = false; 322 | 323 | this.audio.currentTime = 0; 324 | this.audio.volume = 1; 325 | this.audio = null; 326 | } 327 | 328 | /* 329 | Fix audio duration: 330 | https://stackoverflow.com/questions/21522036/html-audio-tag-duration-always-infinity/52375280#52375280 331 | */ 332 | const fix_audio = async () => { 333 | this.playerLoading.style.display = 'block'; 334 | this.playerRef.find(`div[class='DiscordPlayer']`).style.display = 'none'; 335 | 336 | while(audio.duration == Infinity || isNaN(audio.duration)){ 337 | audio.currentTime = 10000000 * Math.random(); 338 | await new Promise(resolve => setTimeout(resolve, 1000)); 339 | } 340 | audio.currentTime = 0; 341 | this.playerButton.innerHTML = ``; 342 | await new Promise(resolve => setTimeout(resolve, 1000)); 343 | this.playerLoading.style.display = 'none'; 344 | this.playerRef.find(`div[class='DiscordPlayer']`).style.display = 'block'; 345 | }; 346 | 347 | this.audioReady = audio.readyState != 0; 348 | if(audio.readyState == 0){ 349 | this.playerTime.duration.innerHTML = '--:--:--'; 350 | audio.on('loadedmetadata', async _ => { 351 | await fix_audio(); 352 | this.audioReady = this.audio.readyState != 0; 353 | this.playerTime.duration.innerHTML = convertToTime(audio.duration); 354 | 355 | }); 356 | } 357 | else{ 358 | await fix_audio(); 359 | this.playerTime.duration.innerHTML = convertToTime(audio.duration); 360 | } 361 | this.audio = audio; 362 | this.playerVolume.bar.style.width = `${this.audio.volume * 100}%`; 363 | this.playerTime.current.innerHTML = '00:00:00'; 364 | this.audio.on('timeupdate', _ => { 365 | this.playerDuration.current.style.width = `${(this.audio.currentTime / this.audio.duration) * 100}%`; 366 | this.playerTime.current.innerHTML = convertToTime(this.audio.currentTime); 367 | }); 368 | this.audio.on('volumechange', _ => { 369 | this.playerVolume.bar.style.width = `${this.audio.volume * 100}%`; 370 | if(this.audio.volume == 0){ 371 | this.playerVolume.button.innerHTML = `
`; 372 | 373 | this.playerVolume.svg = this.playerVolume.button.find('svg'); 374 | this.playerVolume.svg.style.opacity = '1'; 375 | } 376 | else{ 377 | if(this.playerVolume.svg.getAttribute('name') != 'Speaker'){ 378 | this.playerVolume.button.innerHTML = `
`; 379 | this.playerVolume.svg = this.playerVolume.button.find('svg'); 380 | this.playerVolume.svg.style.opacity = '1'; 381 | } 382 | } 383 | }); 384 | this.audio.on('play', _ => { 385 | this.playerButton.innerHTML = ``; 386 | }); 387 | 388 | this.audio.on('pause', _ => { 389 | this.playerButton.innerHTML = ``; 390 | }); 391 | 392 | this.audio.on('ended', _ => { 393 | this.playerButton.innerHTML = ``; 394 | }); 395 | return true; 396 | } 397 | setAudioLink(link){return this.setAudio(new Audio(link));} 398 | setTitle(title){this.playerTitle.value = title;} 399 | getTitle(){return this.playerTitle.value;} 400 | getAudio(){return this.audio;} 401 | play(){ 402 | if(this.audio instanceof Audio && this.audio.readyState != 0){ 403 | if(this.audio.paused || this.audio.ended) 404 | this.audio.play(); 405 | 406 | return true; 407 | } 408 | return false; 409 | } 410 | pause(){ 411 | if(this.audio instanceof Audio && this.audio.readyState != 0){ 412 | if(!(this.audio.paused && this.audio.ended)) 413 | this.audio.pause(); 414 | 415 | return true; 416 | } 417 | return false; 418 | } 419 | setTime(time){ 420 | if(this.audio instanceof Audio && this.audio.readyState != 0){ 421 | this.audio.currentTime = time; 422 | return true; 423 | } 424 | return false; 425 | } 426 | setVolume(volume){ 427 | if(this.audio instanceof Audio){ 428 | this.audio.volume = volume; 429 | return true; 430 | } 431 | return false; 432 | } 433 | getTime(){return this.audio instanceof Audio ? this.audio.currentTime : 0;} 434 | setMuted(e){ 435 | if(this.audio instanceof Audio){ 436 | if(e){ 437 | this.playerVolume.lastValue = this.audio.volume > 0 ? this.audio.volume : 1; 438 | this.audio.volume = 0; 439 | } 440 | else 441 | this.audio.volume = this.playerVolume.lastValue; 442 | 443 | return true; 444 | } 445 | return false; 446 | } 447 | getVolume(){ return this.audio instanceof Audio ? this.audio.volume : 0;} 448 | on(event, call){ 449 | if(event in this.events) 450 | this.events[event] = call; 451 | } 452 | display(value){this.playerRef.style.display = value;} 453 | }; 454 | })(); 455 | 456 | const Tooltip = ((KeyGenerator, DOMTools) => { 457 | const classList = { 458 | 'top': 'tooltipBottom-3ARrEK', 459 | 'left': 'tooltipRight-2JM5PQ', 460 | 'bottom': 'tooltipTop-XDDSxx', 461 | 'right': 'tooltipLeft-3EDOk1' 462 | }; 463 | 464 | const root = document.querySelector(`div[class='layerContainer-yqaFcK da-layerContainer']`); 465 | 466 | if(!root instanceof Element) 467 | return null; 468 | 469 | const calcAxis = (type, node, tooltip, tooltipPointer) => { 470 | if(type == 'bottom') 471 | return {'top': `${node.y - tooltipPointer.height - tooltip.height}px`,'left': `${node.x + (node.width / 2) - (tooltip.width / 2)}px`}; 472 | else if(type == 'top') 473 | return {'top': `${node.y + node.height + tooltipPointer.height}px`,'left': `${node.x + (node.width / 2) - (tooltip.width / 2)}px`}; 474 | else if(type == 'right') 475 | return {'top': `${node.y + (node.height / 2) - (tooltip.height / 2)}px`,'left': `${node.x - tooltip.width - tooltipPointer.width}px`}; 476 | else if(type == 'left') 477 | return {'top': `${node.y + (node.height / 2) - (tooltip.height / 2)}px`,'left': `${node.x + node.width + tooltipPointer.width}px`}; 478 | 479 | return {'top': '0px', 'left': '0px'}; 480 | }; 481 | return class Tooltip{ 482 | constructor(node, text, type='bottom'){ 483 | if(!(type in classList)) 484 | type = 'bottom'; 485 | 486 | this.props = { 487 | 'id': KeyGenerator(), 488 | 'type': type, 489 | 'text': text, 490 | 'ref': null, 491 | 'target': node, 492 | 'lastUpdate': 0, 493 | 'update': 10000 //10 seconds 494 | }; 495 | this.props.ref = root.appendChild(DOMTools.createElement(`
${text}
`)); 496 | 497 | this.removed = false; 498 | this.enabled = true; 499 | this.Offsets = { 500 | target: null, 501 | ref: null, 502 | pointerRef: null 503 | }; 504 | 505 | this.props.ref.style.display = 'none'; 506 | if(node instanceof Element){ 507 | this.update(); 508 | this.props.target.setAttribute('tooltip-id', this.props.id); 509 | node.on('mouseenter.Tooltip', _ => this.show()); 510 | node.on('mouseleave.Tooltip', _ => this.hide()); 511 | } 512 | } 513 | show(){ 514 | if(this.enabled && !this.removed && this.props.ref instanceof Element){ 515 | if(this.props.lastUpdate + this.props.update < Date.now()) 516 | this.update(); 517 | 518 | this.props.ref.style.display = 'block'; 519 | return true; 520 | } 521 | return false; 522 | } 523 | hide(){ 524 | if(this.enabled && !this.removed && this.props.ref instanceof Element){ 525 | this.props.ref.style.display = 'none'; 526 | return true; 527 | } 528 | return false; 529 | } 530 | enable(e){ 531 | this.enabled = e; 532 | } 533 | toggle(){ 534 | if(this.enabled && this.props.ref instanceof Element) 535 | return this.props.ref.style.display == 'none' ? this.show() : this.hide(); 536 | 537 | return false; 538 | } 539 | create(force=false){ 540 | if(this.removed || force){ 541 | this.props.ref = root.appendChild(DOMTools.createElement(`
${this.props.text}
`)) 542 | this.props.ref.style.display = 'none'; 543 | this.removed = false; 544 | 545 | this.update(); 546 | } 547 | } 548 | setTarget(node){ 549 | if(node instanceof Element){ 550 | if(this.props.target instanceof Element){ 551 | this.props.target.off('mouseenter.Tooltip'); 552 | this.props.target.off('mouseleave.Tooltip'); 553 | } 554 | this.props.target = node; 555 | node.on('mouseenter.Tooltip', _ => this.show()); 556 | node.on('mouseleave.Tooltip', _ => this.hide()); 557 | 558 | if(this.removed) 559 | this.create(); 560 | else 561 | this.update(); 562 | 563 | return true; 564 | } 565 | return false; 566 | } 567 | setText(text){ 568 | if(!this.removed){ 569 | this.props.text = text; 570 | 571 | if(this.props.ref instanceof Element){ 572 | const node = this.props.ref.find('div[role=text]') 573 | if(node instanceof Element){ 574 | node.innerHTML = text; 575 | return true; 576 | } 577 | 578 | } 579 | } 580 | return false; 581 | } 582 | setType(type){ 583 | if(!this.removed && type in classList){ 584 | if(this.props.ref instanceof Element){ 585 | this.props.ref.removeClass(classList[this.props.type]); 586 | this.props.ref.addClass(classList[type]); 587 | this.props.type = type; 588 | 589 | this.update(); 590 | return true; 591 | } 592 | } 593 | return false; 594 | } 595 | remove(){ 596 | if(!this.removed){ 597 | 598 | if(this.props.target instanceof Element){ 599 | this.props.target.off('mouseenter.Tooltip'); 600 | this.props.target.off('mouseleave.Tooltip'); 601 | } 602 | 603 | if(this.props.ref instanceof Element) 604 | this.props.ref.remove(); 605 | 606 | this.props.ref = null; 607 | this.props.target = null; 608 | 609 | this.removed = true; 610 | } 611 | } 612 | update(){ 613 | let displayValue = [false, false]; 614 | 615 | if(this.props.ref.style.display != 'none') 616 | displayValue[0] = true; 617 | else 618 | this.props.ref.style.display = 'block'; 619 | 620 | if(this.props.target.style.display != 'none') 621 | displayValue[1] = true; 622 | else 623 | this.props.target.style.display = 'block'; 624 | 625 | this.Offsets = { 626 | target: this.props.target.getBoundingClientRect(), 627 | ref: this.props.ref.getBoundingClientRect(), 628 | pointerRef: this.props.ref.find(`div[class~='da-tooltipPointer']`).getBoundingClientRect() 629 | }; 630 | 631 | const { top, left } = calcAxis(this.props.type, this.Offsets.target, this.Offsets.ref, this.Offsets.pointerRef); 632 | 633 | this.props.ref.style.top = top; 634 | this.props.ref.style.left = left; 635 | 636 | if(!displayValue[0]) 637 | this.props.ref.style.display = 'none'; 638 | 639 | if(!displayValue[1]) 640 | this.props.target.style.display = 'none'; 641 | 642 | this.props.lastUpdate = Date.now(); 643 | } 644 | }; 645 | })(DiscordModules.KeyGenerator, DOMTools); 646 | return class SendAudio extends Plugin { 647 | 648 | onStart() { 649 | if(!document.getElementById('css-' + this.getName())){ 650 | PluginUtilities.addStyle('css-' + this.getName(),` 651 | :root{--sendaudio-record: var(--interactive-normal);--sendaudio-record-hover: var(--interactive-active);--sendaudio-cancel: var(--interactive-normal);--sendaudio-cancel-hover: var(--interactive-active);--sendaudio-pause: var(--interactive-normal);--sendaudio-pause-hover: var(--interactive-active);--sendaudio-play: var(--interactive-normal);--sendaudio-play-hover: var(--interactive-active);--sendaudio-save: var(--interactive-normal);--sendaudio-save-hover: var(--interactive-active);--sendaudio-send: var(--interactive-normal);--sendaudio-send-hover: var(--interactive-active);} 652 | .flash-record{width: 14px;height: 14px;border-radius:50%;position: relative;top: 2px;margin: 0 2px;animation: flash linear 1s infinite;} 653 | @keyframes flash{0% {opacity: 1;}50% {opacity: .1;}100% {opacity: 1;}} 654 | .DiscordPlayer div[role='header']{color: var(--text-normal);display: flex;-webkit-box-align: center;align-items: center;border-bottom: 5px;} 655 | .DiscordPlayer div[role='header'] input{border: none;background: transparent;color: var(--header-primary);font-weight: 600;} 656 | .DiscordPlayer div[role='header'] div{position: absolute;right: 0;opacity: .3;cursor: pointer;} 657 | .DiscordPlayer div[role='header'] div:hover{opacity: 1 !important;} 658 | .DiscordPlayer div[role='controls']{color: white;display: flex;-webkit-box-align: center;align-items: center;} 659 | .DiscordPlayerLoading{display:none;color: var(--header-primary);font-weight: 600;text-align: center;padding: 5px;} 660 | .DiscordPlayerLoading path{fill: var(--header-primary);stroke: var(--header-primary);stroke-width: 5px;} 661 | .DiscordPlayerLoading svg{width: 16px;height: 16px;} 662 | .DiscordPlayer div[class='durationTimeWrapper-OugPFt da-durationTimeWrapper']{color: var(--text-normal);} 663 | .DiscordPlayer div[role=button] svg{color: var(--interactive-normal);opacity: 1;} 664 | .DiscordPlayer div[role=button]:hover svg{color: var(--interactive-active);opacity: 1;} 665 | .DiscordPlayer div[role='control-bar'], .DiscordPlayer div[role='control-bar']:after, .DiscordPlayer div[role='control-bar']:before{background-color: var(--interactive-muted);} 666 | .DiscordPlayer div[role='control-bar'] div[class~='mediaBarPreview-1jfyFs'], .DiscordPlayer div[role='control-bar'] div[class~='mediaBarPreview-1jfyFs']:after, .DiscordPlayer div[role='control-bar'] div[class~='mediaBarPreview-1jfyFs']:before{background-color: var(--text-normal);} 667 | .SendAudio-Info input{border: none;background: transparent;color: var(--header-primary);font-weight: 600;} 668 | #sendAudioButtons button[role=send] polygon{fill: var(--sendaudio-send) !important;} 669 | #sendAudioButtons button[role=send]:hover polygon{fill: var(--sendaudio-send-hover) !important;} 670 | #sendAudioButtons button[role=save] svg{border: 1px solid var(--sendaudio-save) !important;} 671 | #sendAudioButtons button[role=save] svg path:last-child{fill: var(--sendaudio-save) !important;} 672 | 673 | #sendAudioButtons button[role=save]:hover svg{border: 1px solid var(--sendaudio-save-hover) !important;} 674 | #sendAudioButtons button[role=save]:hover svg path:last-child{fill: var(--sendaudio-save-hover) !important;} 675 | #sendAudioButtons button[role=play] svg{color: var(--sendaudio-play) !important;} 676 | #sendAudioButtons button[role=play]:hover svg{color: var(--sendaudio-play-hover) !important;} 677 | #sendAudioButtons button[role=pause] path{fill: var(--sendaudio-pause) !important;} 678 | #sendAudioButtons button[role=pause]:hover path{fill: var(--sendaudio-pause-hover) !important;} 679 | #sendAudioButtons button[role=cancel] path{fill: var(--sendaudio-cancel) !important;} 680 | #sendAudioButtons button[role=cancel]:hover path{fill: var(--sendaudio-cancel-hover) !important;} 681 | #sendAudioButtons button[role=record] svg{color: var(--sendaudio-record) !important;} 682 | #sendAudioButtons button[role=record]:hover svg{color: var(--sendaudio-record-hover) !important;} 683 | .plugin-settings[id="plugin-settings-SendAudio 3"] > div{padding: 20px;} 684 | `); 685 | } 686 | 687 | this.devices = false; 688 | 689 | this.media = false; 690 | 691 | this.mediaInfo = { 692 | ready: false, 693 | error: false 694 | }; 695 | this.panelInfo = [false, false]; 696 | 697 | this.player = null; 698 | 699 | this.record = { 700 | chunks: [], 701 | blob: null, 702 | max_size: this.settings.nitro ? 52428800 : 8388608, 703 | size: 0, 704 | time: 0, 705 | limitStop: false, 706 | notSave: false, 707 | channel: null, 708 | previewing: false 709 | }; 710 | 711 | this.tooltips = { 712 | 'record': new Tooltip(null, 'Record'), 713 | 'cancel': new Tooltip(null, 'Cancel'), 714 | 'pause': new Tooltip(null, 'Pause'), 715 | 'play': new Tooltip(null, 'Play'), 716 | 'resume': new Tooltip(null, 'Resume'), 717 | 'save': new Tooltip(null, 'Save'), 718 | 'send': new Tooltip(null, 'Send'), 719 | 'info': new Tooltip(null, 'Info') 720 | }; 721 | 722 | //Init devices 723 | 724 | let plugin = this; 725 | 726 | navigator.mediaDevices.enumerateDevices().then(devices => { 727 | plugin.devices = devices.filter(device => device.kind == 'audioinput' && device.deviceId != 'default'); 728 | 729 | let addeds = []; 730 | 731 | plugin.devices.forEach(device => { 732 | if(addeds.indexOf(device.groupId) == -1){ 733 | plugin._config.defaultConfig.filter(k => k.id == 'devices')[0].options.push({label: device.label, value: device.deviceId}); 734 | addeds.push(device.groupId); 735 | } 736 | }); 737 | }).catch(error => { 738 | Logger.warn('Unable to get recording devices:', error); 739 | }); 740 | 741 | this.onSwitch(); 742 | 743 | this.changeMedia(); 744 | } 745 | 746 | onStop() { 747 | 748 | this.mediaInfo.notSave = true; 749 | this.recordStop(); 750 | 751 | if(typeof this.buttons == 'object' && this.buttons.group instanceof Element) 752 | this.buttons.group.remove(); 753 | 754 | this.recordReset(); 755 | 756 | PluginUtilities.removeStyle('css-' + this.getName()); 757 | 758 | for(let k in this.tooltips){ 759 | this.tooltips[k].remove(); 760 | } 761 | } 762 | 763 | onSwitch(){ 764 | if(!this.addButtons()) 765 | return; 766 | } 767 | 768 | addButtons(){ 769 | if(document.getElementById('sendAudioButtons') instanceof Element) 770 | return false; 771 | 772 | let buttons_ = document.querySelector(`div[class="buttons-3JBrkn da-buttons"]`); 773 | 774 | if(!(buttons_ instanceof Element)) 775 | return false; 776 | 777 | this.buttons = { 778 | group: DOMTools.createElement(`
`), 779 | record: addButton(`role='record'`, ``), 780 | cancel: addButton(`role='cancel' style='display: none;'`, ``), 781 | pause: addButton(`role='pause' style='display: none;'`, ``), 782 | play: addButton(`role='play' style='display: none;'`, ``), 783 | save: addButton(`role='save' style='display: none;'`, ``), 784 | send: addButton(`role='send' style='display: none;'`, ``) 785 | }; 786 | 787 | this.buttons.group.append(this.buttons.record); 788 | this.buttons.group.append(this.buttons.cancel); 789 | this.buttons.group.append(this.buttons.pause); 790 | this.buttons.group.append(this.buttons.play); 791 | this.buttons.group.append(this.buttons.save); 792 | this.buttons.group.append(this.buttons.send); 793 | 794 | buttons_.append(this.buttons.group); 795 | 796 | for(let k in this.tooltips){ 797 | if(k in this.buttons){ 798 | this.tooltips[k].setTarget(this.buttons[k]); 799 | this.tooltips[k].props.lastUpdate = 0; 800 | } 801 | } 802 | 803 | const plugin = this; 804 | 805 | 806 | this.buttons.record.on('click', _ => { 807 | if(!plugin.recordStart()){ 808 | Logger.warn("Cannot start recording"); 809 | } 810 | }); 811 | this.buttons.cancel.on('click', _ => { 812 | if(plugin.record.previewing == true){ 813 | plugin.recordReset(); 814 | 815 | plugin.setDisplay({all: 'none', record: ''}); 816 | } 817 | else{ 818 | plugin.mediaInfo.notSave = true; 819 | plugin.recordStop(); 820 | } 821 | }); 822 | this.buttons.pause.on('click', _ => plugin.recordPause()); 823 | this.buttons.play.on('click', _ => plugin.recordResume()); 824 | this.buttons.save.on('click', _ => plugin.recordStop()); 825 | this.buttons.send.on('click', _ => { 826 | if(plugin.record.previewing == true){ 827 | if(plugin.record.channel){ 828 | sendAudio(plugin.record.channel.id, plugin.record.blob); 829 | 830 | plugin.recordReset(); 831 | plugin.setDisplay({all: 'none', record: ''}); 832 | } 833 | } 834 | else plugin.recordStop(); 835 | }); 836 | if(!this.mediaInfo.ready || this.mediaInfo.error) 837 | this.buttons.record.disabled = true; 838 | 839 | if(this.mediaInfo.ready && !this.mediaInfo.error && this.media instanceof MediaRecorder){ 840 | const channel = DiscordAPI.currentChannel; 841 | 842 | if(this.record.previewing) 843 | this.setDisplay({record: 'none', cancel: '', send: ''}); 844 | 845 | else if(this.media.state != 'inactive'){ 846 | if(this.settings.preview) 847 | this.setDisplay({record: 'none', cancel: '', save: ''}); 848 | else 849 | this.setDisplay({record: 'none', cancel: '', send: ''}); 850 | 851 | if(typeof channel == 'object' && typeof this.record.channel == 'object' && channel.id != this.record.channel.id){ 852 | this.buttons.send.disabled = true; 853 | this.buttons.save.disabled = true; 854 | } 855 | if(this.media.state == 'paused') 856 | this.setDisplay({pause: 'none', play: ''}); 857 | else 858 | this.setDisplay({pause: '', play: 'none'}); 859 | } 860 | 861 | } 862 | return true; 863 | } 864 | addPanelRecording(){ 865 | const data = `Recording in ${getChannelName()} (${getGuildName()})`; 866 | 867 | const panel = addPanel(`
`, `id='SendAudio-RecordingInfo'`); 868 | 869 | if(panel instanceof Element){ 870 | panel.addClass('SendAudio-Info'); 871 | this.panelInfo[0] = panel; 872 | this.panelInfo[1] = panel.querySelector('input[role=time]'); 873 | 874 | this.tooltips.info.setText(data); 875 | this.tooltips.info.setTarget(panel); 876 | return true; 877 | } 878 | return false; 879 | } 880 | removePanelRecording(){ 881 | if(this.panelInfo[0] instanceof Element) 882 | this.panelInfo[0].remove(); 883 | 884 | this.panelInfo = [false, false]; 885 | } 886 | 887 | setDisplay(values){ 888 | if(typeof values != 'object' || typeof this.buttons != 'object' || !(this.buttons.group instanceof Element)) 889 | return false; 890 | 891 | if(values.all) 892 | for(let key in this.buttons) 893 | if(key != 'group' && this.buttons[key] instanceof Element) 894 | this.buttons[key].style.display = values.all; 895 | 896 | delete values.all; 897 | 898 | for(let key in values) 899 | if(key in this.buttons && this.buttons[key] instanceof Element) 900 | this.buttons[key].style.display = values[key]; 901 | 902 | return true; 903 | } 904 | 905 | getSettingsPanel(){ 906 | const settings = this.buildSettingsPanel(); 907 | if(settings){ 908 | settings.addListener((id, value) => { 909 | if(id == 'devices'){ 910 | this.changeMedia(); 911 | } 912 | else if(id == 'preview'){ 913 | if(this.media instanceof MediaRecorder && this.media.state != 'inactive'){ 914 | if(value) 915 | this.setDisplay({save: '', send: 'none'}); 916 | else 917 | this.setDisplay({save: 'none', send: ''}); 918 | } 919 | } 920 | else if(id == 'nitro'){ 921 | this.record.max_size = value ? 52428800 : 8388608; 922 | } 923 | }); 924 | return settings.getElement(); 925 | } 926 | Logger.warn("Failed to display settings"); 927 | } 928 | 929 | changeMedia(device=false, changing=false){ 930 | const plugin = this; 931 | 932 | const change = () => { 933 | plugin.media = null; 934 | plugin.mediaInfo.ready = false; 935 | plugin.mediaInfo.error = false; 936 | 937 | navigator.mediaDevices.getUserMedia({audio: (device != false ? device : plugin.settings.devices)}).then(s => { 938 | plugin.media = new MediaRecorder(s); 939 | Logger.log("MediaRecorder started successfully"); 940 | 941 | plugin.media.addEventListener('dataavailable', e => plugin.onRecordingData(e.data)); 942 | plugin.media.addEventListener('start', _ => plugin.onRecordingStateChange(0)); 943 | plugin.media.addEventListener('stop', _ => plugin.onRecordingStateChange(1)); 944 | plugin.media.addEventListener('pause', _ => plugin.onRecordingStateChange(2)); 945 | plugin.media.addEventListener('resume', _ => plugin.onRecordingStateChange(3)); 946 | 947 | plugin.media.addEventListener('error', error => { 948 | Logger.error('Recording error:', error); 949 | }); 950 | plugin.mediaInfo.ready = true; 951 | plugin.mediaInfo.error = false; 952 | if(typeof plugin.buttons == 'object' && plugin.buttons.record instanceof Element) 953 | plugin.buttons.record.disabled = false; 954 | 955 | }).catch(err => { 956 | plugin.mediaInfo.ready = false; 957 | plugin.mediaInfo.error = true; 958 | if(typeof plugin.buttons == 'object' &&plugin.buttons.record instanceof Element) 959 | plugin.buttons.record.disabled = true; 960 | 961 | Logger.error("Failed to start MediaRecorder:", err.message); 962 | 963 | if(!changing){ 964 | Logger.log("Changing to the default"); 965 | plugin.changeMedia('default', true); 966 | } 967 | }); 968 | }; 969 | 970 | if(this.media instanceof MediaRecorder){ 971 | this.mediaInfo.notSave = true; 972 | if(this.media.state != 'inactive') 973 | this.media.stop().then(_ => change()).catch(_ => change()); 974 | } 975 | else{ 976 | change(); 977 | } 978 | } 979 | 980 | /* Record methods */ 981 | recordStart(){ 982 | if(this.media instanceof MediaRecorder){ 983 | if(this.media.state == 'inactive'){ 984 | this.record.chunks = []; 985 | this.record.size = 0; 986 | this.record.time = 0; 987 | this.media.start(1000); 988 | } 989 | return this.media.state == 'recording'; 990 | } 991 | return false; 992 | } 993 | recordStop(){ 994 | if(this.media instanceof MediaRecorder){ 995 | if(this.media.state != 'inactive') 996 | this.media.stop(); 997 | 998 | return this.media.state == 'inactive'; 999 | } 1000 | return false; 1001 | } 1002 | recordPause(){ 1003 | if(this.media instanceof MediaRecorder){ 1004 | if(this.media.state == 'recording') 1005 | this.media.pause(); 1006 | 1007 | return this.media.state == 'paused'; 1008 | } 1009 | return false; 1010 | } 1011 | recordResume(){ 1012 | if(this.media instanceof MediaRecorder){ 1013 | if(this.media.state == 'paused') 1014 | this.media.resume(); 1015 | 1016 | return this.media.state == 'recording'; 1017 | } 1018 | return false; 1019 | } 1020 | recordReset(){ 1021 | this.record.chunks = []; 1022 | this.record.time = 0; 1023 | this.record.size = 0; 1024 | this.record.notSave = false; 1025 | this.record.blob = null; 1026 | this.record.channel = null; 1027 | this.record.previewing = false; 1028 | this.record.limitStop = false; 1029 | 1030 | if(typeof this.buttons == 'object' && 'group' in this.buttons && this.buttons.group instanceof Element){ 1031 | this.buttons.save.disabled = false; 1032 | this.buttons.send.disabled = false; 1033 | } 1034 | 1035 | if(this.player != null) 1036 | this.player.close(); 1037 | } 1038 | 1039 | /* Record Event */ 1040 | onRecordingStateChange(new_state){ 1041 | //start 1042 | switch(new_state){ 1043 | //start 1044 | case 0:{ 1045 | this.addPanelRecording(); 1046 | this.record.channel = DiscordAPI.currentChannel; 1047 | if(this.settings.preview) 1048 | this.setDisplay({record: 'none', cancel: 'block', pause: 'block', save: 'block'}); 1049 | else 1050 | this.setDisplay({record: 'none', cancel: 'block', pause: 'block', send: 'block'}); 1051 | break; 1052 | } 1053 | //stop 1054 | case 1:{ 1055 | this.removePanelRecording(); 1056 | 1057 | if(this.mediaInfo.notSave){ 1058 | this.mediaInfo.notSave = false; 1059 | this.setDisplay({all: 'none', record: ''}); 1060 | this.recordReset(); 1061 | return; 1062 | } 1063 | if(this.record.size > 0){ 1064 | 1065 | if(this.record.size > this.record.max_size){ 1066 | let tempChunks = []; 1067 | let tempSize = 0; 1068 | 1069 | for(let i = 0; i < this.record.chunks.length; ++i){ 1070 | if(tempSize + this.record.chunks[i].size <= this.record.max_size){ 1071 | tempChunks.push(this.record.chunks[i]); 1072 | tempSize += this.record.chunks[i].size; 1073 | } 1074 | } 1075 | this.record.chunks = tempChunks; 1076 | this.record.size = tempSize; 1077 | } 1078 | 1079 | this.record.blob = new Blob(this.record.chunks, {type: this.settings.mimetype}) 1080 | if(!this.settings.preview){ 1081 | if(this.record.channel) 1082 | sendAudio(this.record.channel.id, this.record.blob); 1083 | } 1084 | else{ 1085 | Logger.log("PREVIEW"); 1086 | this.player = new DiscordPlayer(); 1087 | this.player.setAudio(new Audio(URL.createObjectURL(this.record.blob))); 1088 | this.player.display('block'); 1089 | this.record.previewing = true; 1090 | this.setDisplay({all: 'none', cancel: 'block', send: 'block'}); 1091 | return; 1092 | } 1093 | } 1094 | this.recordReset(); 1095 | this.setDisplay({all: 'none', record: ''}); 1096 | break; 1097 | } 1098 | //pause 1099 | case 2:{ 1100 | this.setDisplay({play: '', pause: 'none'}); 1101 | break; 1102 | } 1103 | //resume 1104 | case 3:{ 1105 | this.setDisplay({play: 'none', pause: ''}); 1106 | break; 1107 | } 1108 | default: 1109 | break; 1110 | } 1111 | 1112 | } 1113 | onRecordingData(data){ 1114 | this.record.time ++; 1115 | 1116 | if(data.size > 0){ 1117 | if(this.record.size + data.size > this.record.max_size){ 1118 | this.record.limitStop = true; 1119 | this.recordStop(); 1120 | return; 1121 | } 1122 | this.record.chunks.push(data); 1123 | this.record.size += data.size; 1124 | } 1125 | if(this.panelInfo[1] instanceof Element) 1126 | this.panelInfo[1].value = `${convertToTime(this.record.time)} - ${convertData(this.record.size)}`; 1127 | } 1128 | }; 1129 | }; 1130 | --------------------------------------------------------------------------------