MIDI Player Bot

Plays MIDI files!

  1. // ==UserScript==
  2. // @name MIDI Player Bot
  3. // @namespace https://thealiendrew.github.io/
  4. // @version 2.5.4
  5. // @description Plays MIDI files!
  6. // @author AlienDrew
  7. // @license GPL-3.0-or-later
  8. // @include /^https?://www\.multiplayerpiano\.com*/
  9. // @include /^https?://multiplayerpiano\.(com|net)*/
  10. // @include /^https?://mppclone\.com*/
  11. // @icon https://raw.githubusercontent.com/TheAlienDrew/Tampermonkey-Scripts/master/Multiplayer%20Piano/MPP-MIDI-Player-Bot/favicon.png
  12. // @grant GM_info
  13. // @grant GM_getResourceText
  14. // @grant GM_getResourceURL
  15. // @resource MIDIPlayerJS https://raw.githubusercontent.com/grimmdude/MidiPlayerJS/master/browser/midiplayer.js
  16. // @run-at document-idle
  17. // ==/UserScript==
  18.  
  19. /* Copyright (C) 2020 Andrew Larson (thealiendrew@gmail.com)
  20.  
  21. * This program is free software: you can redistribute it and/or modify
  22. * it under the terms of the GNU General Public License as published by
  23. * the Free Software Foundation, either version 3 of the License, or
  24. * (at your option) any later version.
  25. *
  26. * This program is distributed in the hope that it will be useful,
  27. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  28. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  29. * GNU General Public License for more details.
  30. *
  31. * You should have received a copy of the GNU General Public License
  32. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  33. */
  34.  
  35. /* globals MPP, MidiPlayer */
  36.  
  37. // =============================================== FILES
  38.  
  39. // midiplayer.js via https://github.com/grimmdude/MidiPlayerJS
  40. // (but I should maybe switch to https://github.com/mudcube/MIDI.js OR https://github.com/Tonejs/Midi)
  41. var stringMIDIPlayerJS = GM_getResourceText("MIDIPlayerJS");
  42. var scriptMIDIPlayerJS = document.createElement("script");
  43. scriptMIDIPlayerJS.type = 'text/javascript';
  44. scriptMIDIPlayerJS.appendChild(document.createTextNode(stringMIDIPlayerJS));
  45. (document.body || document.head || document.documentElement).appendChild(scriptMIDIPlayerJS);
  46.  
  47. // =============================================== CONSTANTS
  48.  
  49. // Script constants
  50. const SCRIPT = GM_info.script;
  51. const NAME = SCRIPT.name;
  52. const NAMESPACE = SCRIPT.namespace;
  53. const VERSION = SCRIPT.version;
  54. const DESCRIPTION = SCRIPT.description;
  55. const AUTHOR = SCRIPT.author;
  56. const DOWNLOAD_URL = SCRIPT.downloadURL;
  57.  
  58. // Time constants (in milliseconds)
  59. const TENTH_OF_SECOND = 100; // mainly for repeating loops
  60. const SECOND = 10 * TENTH_OF_SECOND;
  61. const CHAT_DELAY = 5 * TENTH_OF_SECOND; // needed since the chat is limited to 10 messages within less delay
  62. const SLOW_CHAT_DELAY = 2 * SECOND // when you are not the owner, your chat quota is lowered
  63. const REPEAT_DELAY = 2 * TENTH_OF_SECOND; // makes transitioning songs in repeat feel better
  64. const SONG_NAME_TIMEOUT = 10 * SECOND; // if a file doesn't play, then forget about showing the song name it after this time
  65.  
  66. // URLs
  67. const FEEDBACK_URL = "https://forms.gle/x4nqjynmRMEN2GSG7";
  68.  
  69. // Players listed by IDs (these are the _id strings)
  70. const BANNED_PLAYERS = []; // empty for now
  71. const LIMITED_PLAYERS = ["8c81505ab941e0760697d777"];
  72.  
  73. // Bot constants
  74. const CHAT_MAX_CHARS = 512; // there is a limit of this amount of characters for each message sent (DON'T CHANGE)
  75. const PERCUSSION_CHANNEL = 10; // (DON'T CHANGE)
  76. const MPP_ROOM_SETTINGS_ID = "room-settings-btn"; // (DON'T CHANGE)
  77. const MIDI_FILE_SIZE_LIMIT_BYTES = 5242880; // Maximum is roughly somewhere around 150 MB, but only black midi's get to that point
  78.  
  79. // Bot constant settings
  80. const ALLOW_ALL_INTRUMENTS = false; // removes percussion instruments (turning this on makes a lot of MIDIs sound bad)
  81. const BOT_SOLO_PLAY = true; // sets what play mode when the bot boots up on an owned room
  82.  
  83. // Bot custom constants
  84. const PREFIX = "/";
  85. const PREFIX_LENGTH = PREFIX.length;
  86. const BOT_KEYWORD = "MIDI"; // this is used for auto enabling the public commands in a room that contains the keyword (character case doesn't matter)
  87. const BOT_ACTIVATOR = BOT_KEYWORD.toLowerCase();
  88. const BOT_USERNAME = NAME + " [" + PREFIX + "help]";
  89. const BOT_NAMESPACE = '(' + NAMESPACE + ')';
  90. const BOT_DESCRIPTION = DESCRIPTION + " Made with JS via Tampermonkey, and thanks to grimmdude for the MIDIPlayerJS library."
  91. const BOT_AUTHOR = "Created by " + AUTHOR + '.';
  92. const BASE_COMMANDS = [
  93. ["help (command)", "displays info about command, but no command entered shows the commands"],
  94. ["about", "get information about this bot"],
  95. ["link", "get the download link for this bot"],
  96. ["feedback", "shows link to send feedback about the bot to the developer"],
  97. ["ping", "gets the milliseconds response time"]
  98. ];
  99. const BOT_COMMANDS = [
  100. ["play [MIDI URL]", "plays a specific song (URL must be a direct link to a MIDI file)"],
  101. ["stop", "stops all music from playing"],
  102. ["pause", "pauses the music at that moment in the song"],
  103. ["resume", "plays music right where pause left off"],
  104. ["song", "shows the current song playing and at what moment in time"],
  105. ["repeat", "toggles repeating current song on or off"],
  106. ["sustain", "toggles how sustain is controlled via either MIDI or by MPP"]
  107. ];
  108. const BOT_OWNER_COMMANDS = [
  109. ["loading", "toggles the MIDI loading progress audio, or text, on or off"],
  110. [BOT_ACTIVATOR, "toggles the public bot commands on or off"]
  111. ];
  112. const PRE_MSG = NAME + " (v" + VERSION + "): ";
  113. const PRE_HELP = PRE_MSG + "[Help]";
  114. const PRE_ABOUT = PRE_MSG + "[About]";
  115. const PRE_LINK = PRE_MSG + "[Link]";
  116. const PRE_FEEDBACK = PRE_MSG + "[Feedback]";
  117. const PRE_PING = PRE_MSG + "[Ping]";
  118. const PRE_PLAY = PRE_MSG + "[Play]";
  119. const PRE_STOP = PRE_MSG + "[Stop]";
  120. const PRE_PAUSE = PRE_MSG + "[Pause]";
  121. const PRE_RESUME = PRE_MSG + "[Resume]";
  122. const PRE_SONG = PRE_MSG + "[Song]";
  123. const PRE_REPEAT = PRE_MSG + "[Repeat]";
  124. const PRE_SUSTAIN = PRE_MSG + "[Sustain]";
  125. const PRE_DOWNLOADING = PRE_MSG + "[Downloading]";
  126. const PRE_LOAD_MUSIC = PRE_MSG + "[Load Music]";
  127. const PRE_PUBLIC = PRE_MSG + "[Public]";
  128. const PRE_LIMITED = PRE_MSG + "Limited!";
  129. const PRE_ERROR = PRE_MSG + "Error!";
  130. const WHERE_TO_FIND_MIDIS = "You can find some good MIDIs to upload from https://bitmidi.com/ and https://midiworld.com/, or you can use your own MIDI files via Google Drive/Dropbox/etc. with a direct download link";
  131. const NOT_OWNER = "The bot isn't the owner of the room";
  132. const NO_SONG = "Not currently playing anything";
  133. const LIST_BULLET = "• ";
  134. const DESCRIPTION_SEPARATOR = " - ";
  135. const CONSOLE_IMPORTANT_STYLE = "background-color: red; color: white; font-weight: bold";
  136.  
  137. // Element constants
  138. const CSS_VARIABLE_X_DISPLACEMENT = "--xDisplacement";
  139. const PRE_ELEMENT_ID = "aliendrew-midi-player-bot";
  140. // buttons have some constant styles/classes
  141. const ELEM_ON = "display:block;";
  142. const ELEM_OFF = "display:none;";
  143. const ELEM_POS = "position:absolute;";
  144. const BTN_PAD_LEFT = 8; // pixels
  145. const BTN_PAD_TOP = 4; // pixels
  146. const BTN_WIDTH = 112; // pixels
  147. const BTN_HEIGHT = 24; // pixels
  148. const BTN_SPACER_X = BTN_PAD_LEFT + BTN_WIDTH; //pixels
  149. const BTN_SPACER_Y = BTN_PAD_TOP + BTN_HEIGHT; //pixels
  150. const BTNS_START_X = 300; //pixels
  151. const BTNS_END_X = BTNS_START_X + 4 * BTN_SPACER_X; //pixels
  152. const BTNS_TOP_0 = BTN_PAD_TOP; //pixels
  153. const BTNS_TOP_1 = BTN_PAD_TOP + BTN_SPACER_Y; //pixels
  154. const BTN_STYLE = ELEM_POS + ELEM_OFF;
  155.  
  156. // Gets the correct note from MIDIPlayer to play on MPP
  157. const MIDIPlayerToMPPNote = {
  158. "A0": "a-1",
  159. "Bb0": "as-1",
  160. "B0": "b-1",
  161. "C1": "c0",
  162. "Db1": "cs0",
  163. "D1": "d0",
  164. "Eb1": "ds0",
  165. "E1": "e0",
  166. "F1": "f0",
  167. "Gb1": "fs0",
  168. "G1": "g0",
  169. "Ab1": "gs0",
  170. "A1": "a0",
  171. "Bb1": "as0",
  172. "B1": "b0",
  173. "C2": "c1",
  174. "Db2": "cs1",
  175. "D2": "d1",
  176. "Eb2": "ds1",
  177. "E2": "e1",
  178. "F2": "f1",
  179. "Gb2": "fs1",
  180. "G2": "g1",
  181. "Ab2": "gs1",
  182. "A2": "a1",
  183. "Bb2": "as1",
  184. "B2": "b1",
  185. "C3": "c2",
  186. "Db3": "cs2",
  187. "D3": "d2",
  188. "Eb3": "ds2",
  189. "E3": "e2",
  190. "F3": "f2",
  191. "Gb3": "fs2",
  192. "G3": "g2",
  193. "Ab3": "gs2",
  194. "A3": "a2",
  195. "Bb3": "as2",
  196. "B3": "b2",
  197. "C4": "c3",
  198. "Db4": "cs3",
  199. "D4": "d3",
  200. "Eb4": "ds3",
  201. "E4": "e3",
  202. "F4": "f3",
  203. "Gb4": "fs3",
  204. "G4": "g3",
  205. "Ab4": "gs3",
  206. "A4": "a3",
  207. "Bb4": "as3",
  208. "B4": "b3",
  209. "C5": "c4",
  210. "Db5": "cs4",
  211. "D5": "d4",
  212. "Eb5": "ds4",
  213. "E5": "e4",
  214. "F5": "f4",
  215. "Gb5": "fs4",
  216. "G5": "g4",
  217. "Ab5": "gs4",
  218. "A5": "a4",
  219. "Bb5": "as4",
  220. "B5": "b4",
  221. "C6": "c5",
  222. "Db6": "cs5",
  223. "D6": "d5",
  224. "Eb6": "ds5",
  225. "E6": "e5",
  226. "F6": "f5",
  227. "Gb6": "fs5",
  228. "G6": "g5",
  229. "Ab6": "gs5",
  230. "A6": "a5",
  231. "Bb6": "as5",
  232. "B6": "b5",
  233. "C7": "c6",
  234. "Db7": "cs6",
  235. "D7": "d6",
  236. "Eb7": "ds6",
  237. "E7": "e6",
  238. "F7": "f6",
  239. "Gb7": "fs6",
  240. "G7": "g6",
  241. "Ab7": "gs6",
  242. "A7": "a6",
  243. "Bb7": "as6",
  244. "B7": "b6",
  245. "C8": "c7"
  246. }
  247.  
  248. // =============================================== VARIABLES
  249.  
  250. var publicOption = false; // turn off the public bot commands if needed
  251. var pinging = false; // helps aid in getting response time
  252. var pingTime = 0; // changes after each ping
  253. var currentRoom = null; // updates when it connects to room
  254. var chatDelay = CHAT_DELAY; // for how long to wait until posting another message
  255. var endDelay; // used in multiline chats send commands
  256.  
  257. var loadingOption = false; // controls if loading music should be on or not
  258. var loadingProgress = 0; // updates when loading files
  259. var loadingMusicLoop = null; // this is to play notes while a song is (down)loading
  260. var loadingMusicPrematureStop = false; // this is used when we need to stop the music after errors
  261. var ended = true;
  262. var stopped = false;
  263. var paused = false;
  264. var uploadButton = null; // this gets an element after it's loaded
  265. var currentSongElapsedFormatted = "00:00"; // changes with the amount of song being played
  266. var currentSongDurationFormatted = "00:00"; // gets updated when currentSongDuration is updated
  267. var currentSongDuration = 0; // this changes after each song is loaded
  268. var currentSongData = null; // this contains the song as a data URI
  269. var currentFileLocation = null; // this leads to the MIDI location (local or by URL)
  270. var currentSongName = null; // extracted from the file name/end of URL
  271. var previousSongData = null; // grabs current when changing successfully
  272. var previousSongName = null; // grabs current when changing successfully
  273. var repeatOption = false; // allows for repeat of one song
  274. var sustainOption = true; // makes notes end according to the midi file
  275.  
  276. var mppRoomSettingsBtn = null; // tracks "Room Settings" element
  277. var xDisplacement = ""; // tracks xDisplacement value from CSS variables
  278.  
  279. // =============================================== PAGE VISIBILITY
  280.  
  281. var pageVisible = true;
  282. document.addEventListener('visibilitychange', function () {
  283. if (document.hidden) {
  284. pageVisible = false;
  285. } else {
  286. pageVisible = true;
  287. }
  288. });
  289.  
  290. // =============================================== OBJECTS
  291.  
  292. // The MIDIPlayer
  293. var Player = new MidiPlayer.Player(function(event) {
  294. if (MPP.client.preventsPlaying()) {
  295. if (Player.isPlaying()) pause();
  296. return;
  297. }
  298. var currentEvent = event.name;
  299. if (!exists(currentEvent) || currentEvent == "") return;
  300. if (currentEvent.indexOf("Note") == 0 && (ALLOW_ALL_INTRUMENTS || event.channel != PERCUSSION_CHANNEL)) {
  301. var currentNote = (exists(event.noteName) ? MIDIPlayerToMPPNote[event.noteName] : null);
  302. if (currentEvent == "Note on" && event.velocity > 0) { // start note
  303. MPP.press(currentNote, (event.velocity/100));
  304. if (!sustainOption) MPP.release(currentNote);
  305. } else if (sustainOption && (currentEvent == "Note off" || event.velocity == 0)) MPP.release(currentNote); // end note
  306. }
  307. if (!ended && !Player.isPlaying()) {
  308. ended = true;
  309. paused = false;
  310. if (!repeatOption) {
  311. currentSongData = null;
  312. currentSongName = null;
  313. }
  314. } else {
  315. var timeRemaining = Player.getSongTimeRemaining();
  316. var timeElapsed = currentSongDuration - (timeRemaining > 0 ? timeRemaining : 0);
  317. // BELOW TEMP: helps mitigate duration calculation issue, but still not fully fixed, see https://github.com/grimmdude/MidiPlayerJS/issues/64
  318. currentSongDuration = Player.getSongTime();
  319. currentSongDurationFormatted = timeClearZeros(secondsToHms(currentSongDuration));
  320. // ABOVE TEMP
  321. currentSongElapsedFormatted = timeSizeFormat(secondsToHms(timeElapsed), currentSongDurationFormatted);
  322. }
  323. });
  324. // see https://github.com/grimmdude/MidiPlayerJS/issues/25
  325. Player.sampleRate = 0; // this allows sequential notes that are supposed to play at the same time, do so when using fast MIDIs (e.g. some black MIDIs)
  326.  
  327. // =============================================== FUNCTIONS
  328.  
  329. // CORS Anywhere (allows downloading files where JS can't)
  330. var useCorsUrl = function(url) {
  331. var newUrl = null; // send null back if it's already a cors url
  332. var cors_api_url = 'https://cors-proxy.htmldriven.com/?url=';
  333. // prevents cors-anywhere-ifing a cors-anywhere link
  334. if (url.indexOf(cors_api_url) == -1) newUrl = cors_api_url + url;
  335. return newUrl;
  336. }
  337.  
  338. // Get visual loading progress, just enter the current progressing number (usually time elapsed in seconds)
  339. var getProgress = function(intProgress) {
  340. var progress = intProgress % 20;
  341. switch(progress) {
  342. case 0: return " █░░░░░░░░░░"; break;
  343. case 1: case 19: return " ░█░░░░░░░░░"; break;
  344. case 2: case 18: return " ░░█░░░░░░░░"; break;
  345. case 3: case 17: return " ░░░█░░░░░░░"; break;
  346. case 4: case 16: return " ░░░░█░░░░░░"; break;
  347. case 5: case 15: return " ░░░░░█░░░░░"; break;
  348. case 6: case 14: return " ░░░░░░█░░░░"; break;
  349. case 7: case 13: return " ░░░░░░░█░░░"; break;
  350. case 8: case 12: return " ░░░░░░░░█░░"; break;
  351. case 9: case 11: return " ░░░░░░░░░█░"; break;
  352. case 10: return " ░░░░░░░░░░█"; break;
  353. }
  354. }
  355.  
  356. // Checks if loading music should play
  357. var preventsLoadingMusic = function() {
  358. return !loadingMusicPrematureStop && !Player.isPlaying() && !MPP.client.preventsPlaying();
  359. }
  360.  
  361. // This is used when loading a song in the midi player, if it's been turned on
  362. var humanMusic = function() {
  363. setTimeout(function() {
  364. if (preventsLoadingMusic()) MPP.press("c5", 1);
  365. if (preventsLoadingMusic()) MPP.release("c5");
  366. }, 200);
  367. setTimeout(function() {
  368. if (preventsLoadingMusic()) MPP.press("d5", 1);
  369. if (preventsLoadingMusic()) MPP.release("d5");
  370. }, 700);
  371. setTimeout(function() {
  372. if (preventsLoadingMusic()) MPP.press("c5", 1);
  373. if (preventsLoadingMusic()) MPP.release("c5");
  374. loadingMusicPrematureStop = false;
  375. }, 1200);
  376. }
  377.  
  378. // Starts the loading music
  379. var startLoadingMusic = function() {
  380. if (loadingMusicLoop == null) {
  381. humanMusic();
  382. loadingMusicLoop = setInterval(function() {
  383. humanMusic();
  384. }, 2200);
  385. }
  386. }
  387.  
  388. // Stops the loading music
  389. var stopLoadingMusic = function() {
  390. if (loadingMusicLoop != null) {
  391. loadingMusicPrematureStop = true;
  392. clearInterval(loadingMusicLoop);
  393. loadingMusicLoop = null;
  394. }
  395. }
  396.  
  397. // Check to make sure variable is initialized with something
  398. var exists = function(element) {
  399. if (typeof(element) != "undefined" && element != null) return true;
  400. return false;
  401. }
  402.  
  403. // Format time to HH:MM:SS from seconds
  404. var secondsToHms = function(d) {
  405. d = Number(d);
  406.  
  407. var h, m, s;
  408. var hDisplay = "00";
  409. var mDisplay = hDisplay;
  410. var sDisplay = hDisplay;
  411.  
  412. if (d != null && d > 0) {
  413. h = Math.floor(d / 3600);
  414. m = Math.floor((d % 3600) / 60);
  415. s = Math.floor((d % 3600) % 60);
  416.  
  417. hDisplay = (h < 10 ? "0" : "") + h;
  418. mDisplay = (m < 10 ? "0" : "") + m;
  419. sDisplay = (s < 10 ? "0" : "") + s;
  420. }
  421.  
  422. return hDisplay + ':' + mDisplay + ':' + sDisplay;
  423. }
  424.  
  425. // Takes formatted time and removed preceeding zeros (only before minutes)
  426. var timeClearZeros = function(formattedHms) {
  427. var newTime = formattedHms;
  428. while (newTime.length > 5 && newTime.indexOf("00:") == 0) {
  429. newTime = newTime.substring(3);
  430. }
  431. return newTime;
  432. }
  433.  
  434. // Resizes a formatted HH:MM:SS time to the second formatted time
  435. var timeSizeFormat = function(timeCurrent, timeEnd) {
  436. var newTimeFormat = timeCurrent;
  437. var timeCurrentLength = timeCurrent.length;
  438. var timeEndLength = timeEnd.length;
  439. // lose or add 00's
  440. if (timeCurrentLength > timeEndLength) newTimeFormat = timeCurrent.substring(timeCurrentLength - timeEndLength);
  441. while (newTimeFormat.length < timeEndLength) {
  442. newTimeFormat = "00:" + newTimeFormat;
  443. }
  444. return newTimeFormat;
  445. }
  446.  
  447. // Generate a random number
  448. var randomNumber = function(min, max) {
  449. min = Math.ceil(min);
  450. max = Math.floor(max);
  451. return Math.floor(Math.random() * (max - min + 1)) + min;
  452. }
  453.  
  454. // Puts quotes around string
  455. var quoteString = function(string) {
  456. var newString = string;
  457. if (exists(string) && string != "") newString = '"' + string + '"';
  458. return newString
  459. }
  460.  
  461. // Gets file as a blob (data URI)
  462. var urlToBlob = function(url, callback) {
  463. // show file download progress
  464. var downloading = null;
  465. mppChatSend(PRE_DOWNLOADING + ' ' + url);
  466. if (loadingOption) startLoadingMusic();
  467. else {
  468. var progress = 0;
  469. downloading = setInterval(function() {
  470. mppChatSend(PRE_DOWNLOADING + getProgress(progress));
  471. progress++;
  472. }, chatDelay);
  473. }
  474.  
  475. fetch(url, {
  476. headers: {
  477. "Content-Disposition": "attachment" // this might not be doing anything
  478. }
  479. }).then(response => {
  480. stopLoadingMusic();
  481. clearInterval(downloading);
  482. if (!response.ok) {
  483. throw new Error("Network response was not ok");
  484. }
  485. return response.blob();
  486. }).then(blob => {
  487. stopLoadingMusic();
  488. clearInterval(downloading);
  489. callback(blob);
  490. }).catch(error => {
  491. console.error("Normal fetch couldn't get the file:", error);
  492. var corsUrl = useCorsUrl(url);
  493. if (corsUrl != null) {
  494. if (loadingOption) startLoadingMusic();
  495.  
  496. fetch(corsUrl, {
  497. headers: {
  498. "Content-Disposition": "attachment" // this might not be doing anything
  499. }
  500. }).then(response => {
  501. stopLoadingMusic();
  502. clearInterval(downloading);
  503. if (!response.ok) {
  504. throw new Error("Network response was not ok");
  505. }
  506. return response.blob();
  507. }).then(blob => {
  508. stopLoadingMusic();
  509. clearInterval(downloading);
  510. callback(blob);
  511. }).catch(error => {
  512. console.error("CORS Anywhere API fetch couldn't get the file:", error);
  513. stopLoadingMusic();
  514. clearInterval(downloading);
  515. callback(null);
  516. });
  517. }
  518. // callback(null); // disabled since the second fetch already should call the call back
  519. });
  520. }
  521.  
  522. // Converts files/blobs to base64 (data URI)
  523. var fileOrBlobToBase64 = function(raw, callback) {
  524. if (raw == null) {
  525. stopLoadingMusic();
  526. callback(null);
  527. }
  528.  
  529. // continue if we have a blob
  530. var reader = new FileReader();
  531. reader.readAsDataURL(raw);
  532. reader.onloadend = function() {
  533. var base64data = reader.result;
  534. callback(base64data);
  535. }
  536. }
  537.  
  538. // Validates file or blob is a MIDI
  539. var isMidi = function(raw) {
  540. if (exists(raw)) {
  541. var mimetype = raw.type;
  542. // acceptable mimetypes for midi files
  543. switch(mimetype) {
  544. case "@file/mid": case "@file/midi":
  545. case "application/mid": case "application/midi":
  546. case "application/x-mid": case "application/x-midi":
  547. case "audio/mid": case "audio/midi":
  548. case "audio/x-mid": case "audio/x-midi":
  549. case "music/crescendo":
  550. case "x-music/mid": case "x-music/midi":
  551. case "x-music/x-mid": case "x-music/x-midi": return true; break;
  552. }
  553. }
  554. return false;
  555. }
  556.  
  557. // Validates file or blob is application/octet-stream ... when using CORS
  558. var isOctetStream = function(raw) {
  559. if (exists(raw) && raw.type == "application/octet-stream") return true;
  560. else return false;
  561. }
  562.  
  563. // Makes all commands into one string
  564. var formattedCommands = function(commandsArray, prefix, spacing) { // needs to be 2D array with commands before descriptions
  565. if (!exists(prefix)) prefix = '';
  566. var commands = '';
  567. var i;
  568. for(i = 0; i < commandsArray.length; ++i) {
  569. commands += (spacing ? ' ' : '') + prefix + commandsArray[i][0];
  570. }
  571. return commands;
  572. }
  573.  
  574. // Gets 1 command and info about it into a string
  575. var formatCommandInfo = function(commandsArray, commandIndex) {
  576. return LIST_BULLET + PREFIX + commandsArray[commandIndex][0] + DESCRIPTION_SEPARATOR + commandsArray[commandIndex][1];
  577. }
  578.  
  579. // Send messages without worrying about timing
  580. var mppChatSend = function(str, delay) {
  581. setTimeout(function(){MPP.chat.send(str)}, (exists(delay) ? delay : 0));
  582. }
  583.  
  584. // Send multiline chats, and return final delay to make things easier for timings
  585. var mppChatMultiSend = function(strArray, optionalPrefix, initialDelay) {
  586. if (!exists(optionalPrefix)) optionalPrefix = '';
  587. var newDelay = 0;
  588. var i;
  589. for (i = 0; i < strArray.length; ++i) {
  590. var currentString = strArray[i];
  591. if (currentString != "") {
  592. ++newDelay;
  593. mppChatSend(optionalPrefix + strArray[i], chatDelay * newDelay);
  594. }
  595. }
  596. return chatDelay * newDelay;
  597. }
  598.  
  599. // Stops the current song if any are playing
  600. var stopSong = function() {
  601. stopped = true;
  602. if (!ended) {
  603. Player.stop();
  604. currentSongElapsedFormatted = timeSizeFormat(secondsToHms(0), currentSongDurationFormatted);
  605. ended = true;
  606. }
  607. if (paused) paused = false;
  608. }
  609.  
  610. // Gets song from data URI and plays it
  611. var playSong = function(songFileName, songData) {
  612. // stop any current songs from playing
  613. stopSong();
  614. // play song if it loaded correctly
  615. try {
  616. // load song
  617. Player.loadDataUri(songData);
  618. // play song
  619. Player.play();
  620. ended = false;
  621. stopped = false;
  622. var timeoutRecorder = 0;
  623. var showSongName = setInterval(function() {
  624. if (Player.isPlaying()) {
  625. clearInterval(showSongName);
  626.  
  627. // changes song
  628. //var hasExtension = songFileName.lastIndexOf('.');
  629. previousSongData = currentSongData;
  630. previousSongName = currentSongName;
  631. currentSongData = songData;
  632. currentSongName = /*(hasExtension > 0) ? songFileName.substring(0, hasExtension) :*/ songFileName;
  633. currentSongElapsedFormatted = timeSizeFormat(secondsToHms(0), currentSongDurationFormatted);
  634. currentSongDuration = Player.getSongTime();
  635. currentSongDurationFormatted = timeClearZeros(secondsToHms(currentSongDuration));
  636.  
  637. mppChatSend(PRE_PLAY + ' ' + getSongTimesFormatted(currentSongElapsedFormatted, currentSongDurationFormatted) + " Now playing " + quoteString(currentSongName));
  638. } else if (timeoutRecorder == SONG_NAME_TIMEOUT) {
  639. clearInterval(showSongName);
  640. } else timeoutRecorder++;
  641. }, 1);
  642. } catch(error) {
  643. stopLoadingMusic();
  644. // reload the previous working file if there is one
  645. if (previousSongData != null) Player.loadDataUri(previousSongData);
  646. mppChatSend(PRE_ERROR + " (play) " + error);
  647. }
  648. }
  649.  
  650. // Plays the song from a URL if it's a MIDI
  651. var playURL = function(songUrl, songData) {
  652. currentFileLocation = songUrl;
  653. var songFileName = decodeURIComponent(currentFileLocation.substring(currentFileLocation.lastIndexOf('/') + 1));
  654. playSong(songFileName, songData);
  655. }
  656.  
  657. // Plays the song from an uploaded file if it's a MIDI
  658. var playFile = function(songFile) {
  659. var songFileName = null;
  660.  
  661. var error = PRE_ERROR + " (play)";
  662. // load in the file
  663. if (exists(songFile)) {
  664. // check and limit file size, mainly to prevent browser tab crashing (not enough RAM to load) and deter black midi
  665. songFileName = songFile.name.split(/(\\|\/)/g).pop();
  666. if (songFile.size <= MIDI_FILE_SIZE_LIMIT_BYTES) {
  667. if (isMidi(songFile)) {
  668. fileOrBlobToBase64(songFile, function(base64data) {
  669. // play song only if we got data
  670. if (exists(base64data)) {
  671. currentFileLocation = songFile.name;
  672. playSong(songFileName, base64data);
  673. uploadButton.value = ""; // reset file input
  674. } else mppChatSend(error + " Unexpected result, MIDI file couldn't load");
  675. });
  676. } else mppChatSend(error + " The file choosen, \"" + songFileName + "\", is either corrupted, or it's not really a MIDI file");
  677. } else mppChatSend(error + " The file choosen, \"" + songFileName + "\", is too big (larger than " + MIDI_FILE_SIZE_LIMIT_BYTES + " bytes), please choose a file with a smaller size");
  678. } else mppChatSend(error + " MIDI file not found");
  679. }
  680.  
  681. // Creates the play, pause, resume, and stop button for the bot
  682. var createButtons = function() {
  683. // need the bottom area to append buttons to
  684. var buttonContainer = document.querySelector("#bottom div");
  685. // we need to keep track of the next button locations
  686. var nextLocationX = BTNS_END_X;
  687.  
  688. // need to initialize CSS_VARIABLE_X_DISPLACEMENT
  689. document.documentElement.style.setProperty(CSS_VARIABLE_X_DISPLACEMENT, "0px");
  690.  
  691. // play needs the div like all the other buttons
  692. // PLAY
  693. var playDiv = document.createElement("div");
  694. playDiv.id = PRE_ELEMENT_ID + "-play";
  695. playDiv.style = BTN_STYLE + "top:" + BTNS_TOP_0 + "px;left:calc(" + nextLocationX + "px + var(" + CSS_VARIABLE_X_DISPLACEMENT + "));";
  696. playDiv.classList.add("ugly-button");
  697. buttonContainer.appendChild(playDiv);
  698. // since we need upload files, there also needs to be an input element inside the play div
  699. var uploadBtn = document.createElement("input");
  700. var uploadBtnId = PRE_ELEMENT_ID + "-upload";
  701. uploadBtn.id = uploadBtnId;
  702. uploadBtn.style = "opacity:0;filter:alpha(opacity=0);position:absolute;top:0;left:0;width:110px;height:22px;border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px;";
  703. uploadBtn.title = " "; // removes the "No file choosen" tooltip
  704. uploadBtn.type = "file";
  705. uploadBtn.accept = ".mid,.midi";
  706. uploadBtn.onchange = function() {
  707. if (!MPP.client.preventsPlaying() && uploadBtn.files.length > 0) playFile(uploadBtn.files[0]);
  708. else console.log("No MIDI file selected");
  709. }
  710. // fix cursor on upload file button
  711. var head = document.getElementsByTagName('HEAD')[0];
  712. var uploadFileBtnFix = this.document.createElement('link');
  713. uploadFileBtnFix.setAttribute('rel', 'stylesheet');
  714. uploadFileBtnFix.setAttribute('type', 'text/css');
  715. uploadFileBtnFix.setAttribute('href', 'data:text/css;charset=UTF-8,' + encodeURIComponent('#' + uploadBtnId + ", #" + uploadBtnId + "::-webkit-file-upload-button {cursor:pointer}"));
  716. head.appendChild(uploadFileBtnFix);
  717. // continue with other html for play button
  718. var playTxt = document.createTextNode("Play");
  719. playDiv.appendChild(uploadBtn);
  720. playDiv.appendChild(playTxt);
  721. // then we need to let the rest of the script know it so it can reset it after loading files
  722. uploadButton = uploadBtn;
  723.  
  724. // other buttons can work fine without major adjustments
  725. // STOP
  726. nextLocationX += BTN_SPACER_X;
  727. var stopDiv = document.createElement("div");
  728. stopDiv.id = PRE_ELEMENT_ID + "-stop";
  729. stopDiv.style = BTN_STYLE + "top:" + BTNS_TOP_0 + "px;left:calc(" + nextLocationX + "px + var(" + CSS_VARIABLE_X_DISPLACEMENT + "));";
  730. stopDiv.classList.add("ugly-button");
  731. stopDiv.onclick = function() {
  732. if (!MPP.client.preventsPlaying()) stop();
  733. }
  734. var stopTxt = document.createTextNode("Stop");
  735. stopDiv.appendChild(stopTxt);
  736. buttonContainer.appendChild(stopDiv);
  737. // REPEAT
  738. nextLocationX += BTN_SPACER_X;
  739. var repeatDiv = document.createElement("div");
  740. repeatDiv.id = PRE_ELEMENT_ID + "-repeat";
  741. repeatDiv.style = BTN_STYLE + "top:" + BTNS_TOP_0 + "px;left:calc(" + nextLocationX + "px + var(" + CSS_VARIABLE_X_DISPLACEMENT + "));";
  742. repeatDiv.classList.add("ugly-button");
  743. repeatDiv.onclick = function() {
  744. if (!MPP.client.preventsPlaying()) repeat();
  745. }
  746. var repeatTxt = document.createTextNode("Repeat");
  747. repeatDiv.appendChild(repeatTxt);
  748. buttonContainer.appendChild(repeatDiv);
  749. // SONG
  750. nextLocationX += BTN_SPACER_X;
  751. var songDiv = document.createElement("div");
  752. songDiv.id = PRE_ELEMENT_ID + "-song";
  753. songDiv.style = BTN_STYLE + "top:" + BTNS_TOP_0 + "px;left:calc(" + nextLocationX + "px + var(" + CSS_VARIABLE_X_DISPLACEMENT + "));";
  754. songDiv.classList.add("ugly-button");
  755. songDiv.onclick = function() {
  756. if (!MPP.client.preventsPlaying()) song();
  757. }
  758. var songTxt = document.createTextNode("Song");
  759. songDiv.appendChild(songTxt);
  760. buttonContainer.appendChild(songDiv);
  761. // PAUSE
  762. nextLocationX = BTNS_END_X;
  763. var pauseDiv = document.createElement("div");
  764. pauseDiv.id = PRE_ELEMENT_ID + "-pause";
  765. pauseDiv.style = BTN_STYLE + "top:" + BTNS_TOP_1 + "px;left:calc(" + nextLocationX + "px + var(" + CSS_VARIABLE_X_DISPLACEMENT + "));";
  766. pauseDiv.classList.add("ugly-button");
  767. pauseDiv.onclick = function() {
  768. if (!MPP.client.preventsPlaying()) pause();
  769. }
  770. var pauseTxt = document.createTextNode("Pause");
  771. pauseDiv.appendChild(pauseTxt);
  772. buttonContainer.appendChild(pauseDiv);
  773. // RESUME
  774. nextLocationX += BTN_SPACER_X;
  775. var resumeDiv = document.createElement("div");
  776. resumeDiv.id = PRE_ELEMENT_ID + "-resume";
  777. resumeDiv.style = BTN_STYLE + "top:" + BTNS_TOP_1 + "px;left:calc(" + nextLocationX + "px + var(" + CSS_VARIABLE_X_DISPLACEMENT + "));";
  778. resumeDiv.classList.add("ugly-button");
  779. resumeDiv.onclick = function() {
  780. if (!MPP.client.preventsPlaying()) resume();
  781. }
  782. var resumeTxt = document.createTextNode("Resume");
  783. resumeDiv.appendChild(resumeTxt);
  784. buttonContainer.appendChild(resumeDiv);
  785. // SUSTAIN
  786. nextLocationX += BTN_SPACER_X;
  787. var sustainDiv = document.createElement("div");
  788. sustainDiv.id = PRE_ELEMENT_ID + "-sustain";
  789. sustainDiv.style = BTN_STYLE + "top:" + BTNS_TOP_1 + "px;left:calc(" + nextLocationX + "px + var(" + CSS_VARIABLE_X_DISPLACEMENT + "));";
  790. sustainDiv.classList.add("ugly-button");
  791. sustainDiv.onclick = function() {
  792. if (!MPP.client.preventsPlaying()) sustain();
  793. }
  794. var sustainTxt = document.createTextNode("Sustain");
  795. sustainDiv.appendChild(sustainTxt);
  796. buttonContainer.appendChild(sustainDiv);
  797. // PUBLIC
  798. nextLocationX += BTN_SPACER_X;
  799. var publicDiv = document.createElement("div");
  800. publicDiv.id = PRE_ELEMENT_ID + '-' + BOT_ACTIVATOR;
  801. publicDiv.style = BTN_STYLE + "top:" + BTNS_TOP_1 + "px;left:calc(" + nextLocationX + "px + var(" + CSS_VARIABLE_X_DISPLACEMENT + "));";
  802. publicDiv.classList.add("ugly-button");
  803. publicDiv.onclick = function() { public(true, true) }
  804. var publicTxt = document.createTextNode("Public");
  805. publicDiv.appendChild(publicTxt);
  806. buttonContainer.appendChild(publicDiv);
  807.  
  808. // one more button to toggle the visibility of the other buttons
  809. nextLocationX = BTNS_END_X - BTN_SPACER_X;
  810. var buttonsOn = false;
  811. var togglerDiv = document.createElement("div");
  812. togglerDiv.id = PRE_ELEMENT_ID + "-toggler";
  813. togglerDiv.style = ELEM_POS + ELEM_ON + "top:" + BTNS_TOP_0 + "px;left:calc(" + nextLocationX + "px + var(" + CSS_VARIABLE_X_DISPLACEMENT + "));"; // normally BTNS_TOP_1, but had to be changed to work with mppclone
  814. togglerDiv.classList.add("ugly-button");
  815. togglerDiv.onclick = function() {
  816. if (buttonsOn) { // if on, then turn off, else turn on
  817. playDiv.style.display =
  818. stopDiv.style.display =
  819. repeatDiv.style.display =
  820. songDiv.style.display =
  821. pauseDiv.style.display =
  822. resumeDiv.style.display =
  823. sustainDiv.style.display =
  824. publicDiv.style.display = "none";
  825. buttonsOn = false;
  826. } else {
  827. playDiv.style.display =
  828. stopDiv.style.display =
  829. repeatDiv.style.display =
  830. songDiv.style.display =
  831. pauseDiv.style.display =
  832. resumeDiv.style.display =
  833. sustainDiv.style.display =
  834. publicDiv.style.display = "block";
  835. buttonsOn = true;
  836. }
  837. }
  838. var togglerTxt = document.createTextNode(NAME);
  839. togglerDiv.appendChild(togglerTxt);
  840. buttonContainer.appendChild(togglerDiv);
  841. }
  842.  
  843. // Sends back the current time in the song against total time
  844. var getSongTimesFormatted = function(elapsed, duration) {
  845. return '[' + elapsed + " / " + duration + ']';
  846. }
  847.  
  848. // Shows limited message for user
  849. var playerLimited = function(username) {
  850. // displays message with their name about being limited
  851. mppChatSend(PRE_LIMITED + " You must of done something to earn this " + quoteString(username) + " as you are no longer allowed to use the bot");
  852. }
  853.  
  854. // When there is an incorrect command, show this error
  855. var cmdNotFound = function(cmd) {
  856. var error = PRE_ERROR + " Invalid command, " + quoteString(cmd) + " doesn't exist";
  857. if (publicOption) mppChatSend(error);
  858. else console.log(error);
  859. }
  860.  
  861. // Commands
  862. var help = function(command, userId, yourId) {
  863. var isOwner = MPP.client.isOwner();
  864. if (!exists(command) || command == "") {
  865. var publicCommands = formattedCommands(BOT_COMMANDS, LIST_BULLET + PREFIX, true);
  866. mppChatSend(PRE_HELP + " Commands: " + formattedCommands(BASE_COMMANDS, LIST_BULLET + PREFIX, true)
  867. + (publicOption ? ' ' + publicCommands : '')
  868. + (userId == yourId ? " | Bot Owner Commands: " + (publicOption ? '' : publicCommands + ' ') + formattedCommands(BOT_OWNER_COMMANDS, LIST_BULLET + PREFIX, true) : ''));
  869. } else {
  870. var valid = null;
  871. var commandIndex = null;
  872. var commandArray = null;
  873. command = command.toLowerCase();
  874. // check commands arrays
  875. var i;
  876. for(i = 0; i < BASE_COMMANDS.length; i++) {
  877. if (BASE_COMMANDS[i][0].indexOf(command) == 0) {
  878. valid = command;
  879. commandArray = BASE_COMMANDS;
  880. commandIndex = i;
  881. }
  882. }
  883. var j;
  884. for(j = 0; j < BOT_COMMANDS.length; j++) {
  885. if (BOT_COMMANDS[j][0].indexOf(command) == 0) {
  886. valid = command;
  887. commandArray = BOT_COMMANDS;
  888. commandIndex = j;
  889. }
  890. }
  891. var k;
  892. for(k = 0; k < BOT_OWNER_COMMANDS.length; k++) {
  893. if (BOT_OWNER_COMMANDS[k][0].indexOf(command) == 0) {
  894. valid = command;
  895. commandArray = BOT_OWNER_COMMANDS;
  896. commandIndex = k;
  897. }
  898. }
  899. // display info on command if it exists
  900. if (exists(valid)) mppChatSend(PRE_HELP + ' ' + formatCommandInfo(commandArray, commandIndex),);
  901. else cmdNotFound(command);
  902. }
  903. }
  904. var about = function() {
  905. mppChatSend(PRE_ABOUT + ' ' + BOT_DESCRIPTION + ' ' + BOT_AUTHOR + ' ' + BOT_NAMESPACE);
  906. }
  907. var link = function() {
  908. mppChatSend(PRE_LINK + " You can download this bot from " + DOWNLOAD_URL);
  909. }
  910. var feedback = function() {
  911. mppChatSend(PRE_FEEDBACK + " Please go to " + FEEDBACK_URL + " in order to submit feedback.");
  912. }
  913. var ping = function() {
  914. // get a response back in milliseconds
  915. pinging = true;
  916. pingTime = Date.now();
  917. mppChatSend(PRE_PING);
  918. setTimeout(function() {
  919. if (pinging) mppChatSend("Pong! [within 1 second]");
  920. pinging = false;
  921. }, SECOND);
  922. }
  923. var play = function(url) {
  924. var error = PRE_ERROR + " (play)";
  925. // URL needs to be entered to play a song
  926. if (!exists(url) || url == "") {
  927. stopLoadingMusic();
  928. mppChatSend(error + " No MIDI url entered... " + WHERE_TO_FIND_MIDIS);
  929. } else {
  930. // downloads file if possible and then plays it if it's a MIDI
  931. urlToBlob(url, function(blob) {
  932. if (blob == null) mppChatSend(error + " Invalid URL, this is not a MIDI file, or the file requires a manual download from " + quoteString(' ' + url + ' ') + "... " + WHERE_TO_FIND_MIDIS);
  933. else if (isMidi(blob) || isOctetStream(blob)) {
  934. // check and limit file size, mainly to prevent browser tab crashing (not enough RAM to load) and deter black midi
  935. if (blob.size <= MIDI_FILE_SIZE_LIMIT_BYTES) {
  936. fileOrBlobToBase64(blob, function(base64data) {
  937. // play song only if we got data
  938. if (exists(base64data)) {
  939. if (isOctetStream(blob)) { // when download with CORS, need to replace mimetype, but it doesn't guarantee it's a MIDI file
  940. base64data = base64data.replace("application/octet-stream", "audio/midi");
  941. }
  942. playURL(url, base64data);
  943. } else mppChatSend(error + " Unexpected result, MIDI file couldn't load... " + WHERE_TO_FIND_MIDIS);
  944. });
  945. } else mppChatSend(error + " The file choosen, \"" + decodeURIComponent(url.substring(url.lastIndexOf('/') + 1)) + "\", is too big (larger than " + MIDI_FILE_SIZE_LIMIT_BYTES + " bytes), please choose a file with a smaller size");
  946. } else mppChatSend(error + " Invalid URL, this is not a MIDI file... " + WHERE_TO_FIND_MIDIS);
  947. });
  948. }
  949. }
  950. var stop = function() {
  951. // stops the current song
  952. if (ended) mppChatSend(PRE_STOP + ' ' + NO_SONG);
  953. else {
  954. stopSong();
  955. paused = false;
  956. mppChatSend(PRE_STOP + " Stopped playing " + quoteString(currentSongName));
  957. currentFileLocation = currentSongName = null;
  958. }
  959. }
  960. var pause = function() {
  961. // pauses the current song
  962. if (ended) mppChatSend(PRE_PAUSE + ' ' + NO_SONG);
  963. else {
  964. var title = PRE_PAUSE + ' ' + getSongTimesFormatted(currentSongElapsedFormatted, currentSongDurationFormatted);
  965. if (paused) mppChatSend(title + " The song is already paused");
  966. else {
  967. Player.pause();
  968. paused = true;
  969. mppChatSend(title + " Paused " + quoteString(currentSongName));
  970. }
  971. }
  972. }
  973. var resume = function() {
  974. // resumes the current song
  975. if (ended) mppChatSend(PRE_RESUME + ' ' + NO_SONG);
  976. else {
  977. var title = PRE_RESUME + ' ' + getSongTimesFormatted(currentSongElapsedFormatted, currentSongDurationFormatted);
  978. if (paused) {
  979. Player.play();
  980. paused = false;
  981. mppChatSend(title + " Resumed " + quoteString(currentSongName));
  982. } else mppChatSend(title + " The song is already playing");
  983. }
  984. }
  985. var song = function() {
  986. // shows current song playing
  987. if (exists(currentSongName) && currentSongName != "") {
  988. mppChatSend(PRE_SONG + ' ' + getSongTimesFormatted(currentSongElapsedFormatted, currentSongDurationFormatted)
  989. + " Currently " + (paused ? "paused on" : "playing") + ' ' + quoteString(currentSongName));
  990. } else mppChatSend(PRE_SONG + ' ' + NO_SONG);
  991. }
  992. var repeat = function() {
  993. // turns on or off repeat
  994. repeatOption = !repeatOption;
  995.  
  996. mppChatSend(PRE_REPEAT + " Repeat set to " + (repeatOption ? "" : "not") + " repeating");
  997. }
  998. var sustain = function() {
  999. // turns on or off sustain
  1000. sustainOption = !sustainOption;
  1001.  
  1002. mppChatSend(PRE_SUSTAIN + " Sustain set to " + (sustainOption ? "MIDI controlled" : "MPP controlled"));
  1003. }
  1004. var loading = function(userId, yourId) {
  1005. // only let the bot owner set if loading music should be on or not
  1006. if (userId != yourId) return;
  1007. loadingOption = !loadingOption;
  1008. mppChatSend(PRE_LOAD_MUSIC + " The MIDI loading progress is now set to " + (loadingOption ? "audio" : "text"));
  1009. }
  1010. var public = function(userId, yourId) {
  1011. // only let the bot owner set if public bot commands should be on or not
  1012. if (userId != yourId) return;
  1013. publicOption = !publicOption;
  1014. mppChatSend(PRE_PUBLIC + " Public bot commands were turned " + (publicOption ? "on" : "off"));
  1015. }
  1016.  
  1017. // =============================================== MAIN
  1018.  
  1019. Player.on('fileLoaded', function() {
  1020. // Do something when file is loaded
  1021. stopLoadingMusic();
  1022. });
  1023. MPP.client.on('a', function (msg) {
  1024. // if user switches to VPN, these need to update
  1025. var yourParticipant = MPP.client.getOwnParticipant();
  1026. var yourId = yourParticipant._id;
  1027. var yourUsername = yourParticipant.name;
  1028. // get the message as string
  1029. var input = msg.a.trim();
  1030. var participant = msg.p;
  1031. var username = participant.name;
  1032. var userId = participant._id;
  1033.  
  1034. // check if ping
  1035. if (userId == yourId && pinging && input == PRE_PING) {
  1036. pinging = false;
  1037. pingTime = Date.now() - pingTime;
  1038. mppChatSend("Pong! [" + pingTime + "ms]", 0 );
  1039. }
  1040.  
  1041. // make sure the start of the input matches prefix
  1042. if (input.startsWith(PREFIX)) {
  1043. // don't allow banned or limited users to use the bot
  1044. var bannedPlayers = BANNED_PLAYERS.length;
  1045. if (bannedPlayers > 0) {
  1046. var i;
  1047. for(i = 0; i < BANNED_PLAYERS.length; ++i) {
  1048. if (BANNED_PLAYERS[i] == userId) {
  1049. playerLimited(username);
  1050. return;
  1051. }
  1052. }
  1053. }
  1054. var limitedPlayers = LIMITED_PLAYERS.length;
  1055. if (limitedPlayers > 0) {
  1056. var j;
  1057. for(j = 0; j < LIMITED_PLAYERS.length; ++j) {
  1058. if (LIMITED_PLAYERS[j] == userId) {
  1059. playerLimited(username);
  1060. return;
  1061. }
  1062. }
  1063. }
  1064. // evaluate input into command and possible arguments
  1065. var message = input.substring(PREFIX_LENGTH).trim();
  1066. var hasArgs = message.indexOf(' ');
  1067. var command = (hasArgs != -1) ? message.substring(0, hasArgs) : message;
  1068. var argumentsString = (hasArgs != -1) ? message.substring(hasArgs + 1).trim() : null;
  1069. // look through commands
  1070. var isBotOwner = userId == yourId;
  1071. var preventsPlaying = MPP.client.preventsPlaying();
  1072. switch (command.toLowerCase()) {
  1073. case "help": case "h": if ((isBotOwner || publicOption) && !preventsPlaying) help(argumentsString, userId, yourId); break;
  1074. case "about": case "ab": if ((isBotOwner || publicOption) && !preventsPlaying) about(); break;
  1075. case "link": case "li": if ((isBotOwner || publicOption) && !preventsPlaying) link(); break;
  1076. case "feedback": case "fb": if (isBotOwner || publicOption) feedback(); break;
  1077. case "ping": case "pi": if (isBotOwner || publicOption) ping(); break;
  1078. case "play": case "p": if ((isBotOwner || publicOption) && !preventsPlaying) play(argumentsString); break;
  1079. case "stop": case "s": if ((isBotOwner || publicOption) && !preventsPlaying) stop(); break;
  1080. case "pause": case "pa": if ((isBotOwner || publicOption) && !preventsPlaying) pause(); break;
  1081. case "resume": case "r": if ((isBotOwner || publicOption) && !preventsPlaying) resume(); break;
  1082. case "song": case "so": if ((isBotOwner || publicOption) && !preventsPlaying) song(); break;
  1083. case "repeat": case "re": if ((isBotOwner || publicOption) && !preventsPlaying) repeat(); break;
  1084. case "sustain": case "ss": if ((isBotOwner || publicOption) && !preventsPlaying) sustain(); break;
  1085. case "loading": case "lo": loading(userId, yourId); break;
  1086. case BOT_ACTIVATOR: public(userId, yourId); break;
  1087. }
  1088. }
  1089. });
  1090. MPP.client.on("ch", function(msg) {
  1091. // set new chat delay based on room ownership after changing rooms
  1092. if (!MPP.client.isOwner()) chatDelay = SLOW_CHAT_DELAY;
  1093. else chatDelay = CHAT_DELAY;
  1094. // update current room info
  1095. var newRoom = MPP.client.channel._id;
  1096. if (currentRoom != newRoom) {
  1097. currentRoom = MPP.client.channel._id;
  1098. // stop any songs that might have been playing before changing rooms
  1099. if (currentRoom.toUpperCase().indexOf(BOT_KEYWORD) == -1) stopSong();
  1100. }
  1101. });
  1102. MPP.client.on('p', function(msg) {
  1103. var userId = msg._id;
  1104. // kick ban all the banned players
  1105. var bannedPlayers = BANNED_PLAYERS.length;
  1106. if (bannedPlayers > 0) {
  1107. var i;
  1108. for(i = 0; i < BANNED_PLAYERS.length; ++i) {
  1109. var bannedPlayer = BANNED_PLAYERS[i];
  1110. if (userId == bannedPlayer) MPP.client.sendArray([{m: "kickban", _id: bannedPlayer, ms: 3600000}]);
  1111. }
  1112. }
  1113. });
  1114.  
  1115. // =============================================== INTERVALS
  1116.  
  1117. // Stuff that needs to be done by intervals (e.g. repeat)
  1118. var repeatingTasks = setInterval(function() {
  1119. if (MPP.client.preventsPlaying()) return;
  1120. // do repeat
  1121. if (repeatOption && ended && !stopped && exists(currentSongName) && exists(currentSongData)) {
  1122. ended = false;
  1123. // nice delay before playing song again
  1124. setTimeout(function() {Player.play()}, REPEAT_DELAY);
  1125. }
  1126. }, 1);
  1127. var dynamicButtonDisplacement = setInterval(function() {
  1128. // required when "Room Settings" button shows up
  1129. mppRoomSettingsBtn = document.getElementById(MPP_ROOM_SETTINGS_ID);
  1130. xDisplacement = getComputedStyle(document.documentElement).getPropertyValue(CSS_VARIABLE_X_DISPLACEMENT);
  1131. // if "Room Settings" button exists and is visible, enable displacement, else revert only when not already changed
  1132. if (xDisplacement == "0px" &&
  1133. (mppRoomSettingsBtn &&
  1134. (!mppRoomSettingsBtn.style ||
  1135. (!mppRoomSettingsBtn.style.display ||
  1136. (mppRoomSettingsBtn.style.display == "block"))))) {
  1137. document.documentElement.style.setProperty(CSS_VARIABLE_X_DISPLACEMENT, BTN_SPACER_X + "px");
  1138. } else if (xDisplacement != "0px" &&
  1139. (!mppRoomSettingsBtn ||
  1140. (mppRoomSettingsBtn.style &&
  1141. mppRoomSettingsBtn.style.display &&
  1142. mppRoomSettingsBtn.style.display != "block"))) {
  1143. document.documentElement.style.setProperty(CSS_VARIABLE_X_DISPLACEMENT, "0px");
  1144. }
  1145. }, TENTH_OF_SECOND);
  1146. var slowRepeatingTasks = setInterval(function() {
  1147. // do background tab fix
  1148. if (!pageVisible) {
  1149. var note = MPP.piano.keys["a-1"].note;
  1150. var participantId = MPP.client.getOwnParticipant().id;
  1151. MPP.piano.audio.play(note, 0.01, 0, participantId);
  1152. MPP.piano.audio.stop(note, 0, participantId);
  1153. }
  1154. }, SECOND);
  1155.  
  1156. // Automatically turns off the sound warning (mainly for autoplay)
  1157. var clearSoundWarning = setInterval(function() {
  1158. var playButton = document.querySelector("#sound-warning button");
  1159. if (exists(playButton)) {
  1160. clearInterval(clearSoundWarning);
  1161. playButton.click();
  1162. // wait for the client to come online
  1163. var waitForMPP = setInterval(function() {
  1164. if (exists(MPP) && exists(MPP.client) && exists(MPP.client.channel) && exists(MPP.client.channel._id) && MPP.client.channel._id != "") {
  1165. clearInterval(waitForMPP);
  1166.  
  1167. currentRoom = MPP.client.channel._id;
  1168. if (currentRoom.toUpperCase().indexOf(BOT_KEYWORD) >= 0) {
  1169. loadingOption = publicOption = true;
  1170. }
  1171. createButtons();
  1172. console.log(PRE_MSG + " Online!");
  1173. }
  1174. }, TENTH_OF_SECOND);
  1175. }
  1176. }, TENTH_OF_SECOND);