MIDI Player Bot

Plays MIDI files!

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址