MIDI Player Bot

Plays MIDI files!

目前为 2022-09-10 提交的版本,查看 最新版本

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