├── .gitignore ├── LICENSE ├── README.md ├── debug ├── amqAwesomeplete.js ├── test.html └── test.js ├── deprecated ├── amqBugFixFor16.2.user.js ├── amqBugFixFor17.0.user.js ├── amqInviteURL.user.js ├── amqShowPingPong.user.js └── amqSoundEffects.user.js ├── design ├── .keep ├── amqBackground.user.js ├── amqSimpleBackground.css └── replaceRules.js ├── encoding ├── .keep ├── amqfunc │ ├── README.md │ └── amqfunc.py └── autoencoder │ ├── README.md │ ├── autoconvert.config │ ├── autoconvert.py │ ├── autorescheck.py │ ├── catbox.config │ ├── catbox.py │ ├── interface.config │ └── interface.py ├── gameplay ├── .keep ├── amqAnswerTimesUtility.user.js ├── amqAutocomplete.user.js ├── amqAvatarCount.user.js ├── amqChat.user.js ├── amqExpandLibrary.user.js ├── amqExpandQuickFix.user.js ├── amqGetOriginalNameUtility.user.js ├── amqLevelGuard.user.js ├── amqNoDropdown.user.js ├── amqNotificationSounds.user.js ├── amqPlayerAnswerTimeDisplay.user.js ├── amqReplay.user.js ├── amqShowOriginalName.user.js ├── amqShowOriginalOrAlt.user.js ├── amqSongArtistMode.user.js ├── amqSpecialCharacters.user.js ├── commonUtilities.js └── simpleLogger.js ├── programs ├── .keep ├── discord-emote-submission │ ├── catbox.config │ ├── catbox.py │ ├── main.py │ └── readme.md └── old-expand-but-better │ ├── .gitignore │ ├── README.md │ ├── autoconvert.config │ ├── autoconvert.py │ ├── automator.config │ ├── automator.py │ ├── catbox.config │ ├── catbox.py │ └── find_entry.py └── unofficial-api └── node ├── .gitignore ├── amq-api.js ├── commands ├── avatar.js ├── battleRoyal.js ├── commands.js ├── expand.js ├── exports.js ├── lobby.js ├── patreon.js ├── quiz.js ├── roomBrowser.js ├── settings.js ├── social.js └── tutorial.js ├── events.js ├── examples ├── allEvents.js ├── onlineplayerCount.js └── playerProfile.js ├── package.json ├── structures └── lobbySettings.js └── tests └── lobbySettings.js /.gitignore: -------------------------------------------------------------------------------- 1 | # .gitignore for the AMQ-Scripts project 2 | 3 | ##IDE / Dev env files## 4 | ###Komodo IDE/Edit### 5 | *.komodoproject 6 | .komodotools 7 | 8 | ###Notepad++### 9 | *.bak 10 | 11 | ##Python files## 12 | __pycache__/ 13 | __pycache__/* 14 | *.pyc 15 | 16 | ##auto-encoder output## 17 | outputfiles/ 18 | outputfiles/* 19 | *.log 20 | *.webm 21 | *.mp3 22 | *.mkv 23 | *.mka 24 | *dummy* 25 | currentnumber.txt 26 | *.bat 27 | *.vs 28 | debug/animes.js 29 | 30 | ##Feel free to add anything I missed## 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 amq-script-project 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AMQ Scripts 2 | 3 | This is a catch-all repository for any scripts that improve the quality of game play/life in [Anime Music Quiz](https://animemusicquiz.com/). 4 | 5 | 6 | # Usage 7 | 8 | Most scripts can be used by importing them to a browser extension such as [TamperMonkey](https://www.tampermonkey.net/) for .js files, and [Stylus](https://add0n.com/stylus.html) for .css files. More information can be found in specific projects' folders. 9 | 10 | `design` - scripts which change the design/layout of AMQ, generally for aesthetic purposes. 11 | 12 | `encoding` - scripts which aid the process of encoding new content to be added to AMQ (Expand). 13 | 14 | `gameplay` - scripts which changes the game play of AMQ, such as improving autocomplete, or adding new interactable panels. 15 | 16 | `programs` - scripts which automate some function of AMQ, including upload bots. 17 | 18 | # Contributing 19 | 20 | If you would like to contribute to this repository, feel free to submit a Pull Request with your additions or edits. Please make sure your code is human readable before submission. If you'd like to be a part of the project, join us below! 21 | 22 | ## License 23 | 24 | All code in this repository is licensed under the MIT License, details of which are available [here](https://choosealicense.com/licenses/mit/). 25 | 26 | ## Getting Help/Join Us 27 | 28 | Join us in the AMQ discord here: https://discord.gg/w4a3sbP 29 | -------------------------------------------------------------------------------- /debug/amqAwesomeplete.js: -------------------------------------------------------------------------------- 1 | isNode = typeof window === 'undefined'; 2 | 3 | if (isNode) Awesomplete = require('Awesomplete') 4 | 5 | 'use strict'; 6 | /*exported AmqAwesomeplete*/ 7 | 8 | if (isNode || $ == undefined) $ = (v) => v 9 | 10 | function AmqAwesomeplete(input, o, scrollable) { 11 | o.filter = (text, input) => { 12 | return RegExp(input.trim(), "i").test(text); 13 | }; 14 | Awesomplete.call(this, input, o); 15 | this.searchId = 0; 16 | this.currentSubList = null; 17 | 18 | this.letterLists = {}; 19 | o.list.forEach(inputEntry => { 20 | let handledChars = {}; 21 | let label; 22 | if (o.data) { 23 | label = o.data(inputEntry).label; 24 | } else { 25 | label = inputEntry; 26 | } 27 | label.split('').forEach(char => { 28 | let lowerChar = char.toLowerCase(); 29 | if (!handledChars[lowerChar]) { 30 | let altChar; 31 | if (!/[a-bA-Z0-9-]/.test(lowerChar)) { 32 | if (/[ōóòöôøΦ]/.test(lowerChar)) { 33 | altChar = 'o'; 34 | } else if (/[ä@âàáạåæā]/.test(lowerChar)) { 35 | altChar = 'a'; 36 | } else if (char === 'č') { 37 | altChar = 'c'; 38 | } else if (/[éêëèæ]/.test(lowerChar)) { 39 | altChar = 'e'; 40 | } else if (char === '’') { 41 | altChar = '\''; 42 | } else if (char === 'ñ') { 43 | altChar = 'n'; 44 | } else if (char === 'í') { 45 | altChar = 'i'; 46 | } else if (char === '×') { 47 | altChar = 'x'; 48 | } else if (char === 'ß') { 49 | altChar = 'b'; 50 | } 51 | } 52 | if (!this.letterLists[lowerChar]) { 53 | this.letterLists[lowerChar] = []; 54 | } 55 | this.letterLists[lowerChar].push(inputEntry); 56 | handledChars[lowerChar] = true; 57 | if (altChar && !handledChars[altChar]) { 58 | if (!this.letterLists[altChar]) { 59 | this.letterLists[altChar] = []; 60 | } 61 | this.letterLists[altChar].push(inputEntry); 62 | handledChars[altChar] = true; 63 | } 64 | } 65 | }); 66 | }); 67 | 68 | this.currentQuery = ""; 69 | this.$ul = $(this.ul); 70 | 71 | if (scrollable) { 72 | let $input = $(input); 73 | let $awesompleteList = $input.parent().find('ul'); 74 | $awesompleteList.perfectScrollbar({ 75 | suppressScrollX: true 76 | }); 77 | 78 | $input.on('awesomplete-open', () => { 79 | $awesompleteList.perfectScrollbar('update'); 80 | $awesompleteList[0].scrollTop = 0; 81 | }); 82 | } 83 | 84 | this.item = function (text, input, item_id) { 85 | var html = input.trim() === "" ? text : text.replace(RegExp(input.trim(), "gi"), "$&"); 86 | return this.create("li", { 87 | innerHTML: html, 88 | "role": "option", 89 | "aria-selected": "false", 90 | "id": "awesomplete_list_" + this.count + "_item_" + item_id 91 | }); 92 | }; 93 | } 94 | 95 | 96 | AmqAwesomeplete.prototype = Object.create(Awesomplete.prototype); 97 | AmqAwesomeplete.prototype.constructor = AmqAwesomeplete; 98 | 99 | AmqAwesomeplete.prototype.create = (tag, o) => { 100 | if (isNode) return o; 101 | var element = document.createElement(tag); 102 | 103 | for (var i in o) { 104 | var val = o[i]; 105 | 106 | if (i === "inside") { 107 | $(val).appendChild(element); 108 | } 109 | else if (i === "around") { 110 | var ref = $(val); 111 | ref.parentNode.insertBefore(element, ref); 112 | element.appendChild(ref); 113 | 114 | if (ref.getAttribute("autofocus") != null) { 115 | ref.focus(); 116 | } 117 | } 118 | else if (i in element) { 119 | element[i] = val; 120 | } 121 | else { 122 | element.setAttribute(i, val); 123 | } 124 | } 125 | 126 | return element; 127 | } 128 | 129 | 130 | AmqAwesomeplete.prototype.evaluate = function () { 131 | var me = this; 132 | let unescapedValue = this.input.value; 133 | var value = createAnimeSearchRegexQuery(unescapedValue); 134 | let inputList; 135 | let response = new Promise((resolve, reject) => { 136 | 137 | if (this.currentQuery && new RegExp(this.currentQuery, 'i').test(unescapedValue) && this.currentSubList) { 138 | inputList = this.currentSubList; 139 | } else if (unescapedValue.length > 0) { 140 | let letterList = this.letterLists[unescapedValue[0].toLowerCase()]; 141 | if (letterList) { 142 | inputList = letterList; 143 | } else { 144 | inputList = []; 145 | } 146 | this.currentSubList = inputList; 147 | } else { 148 | inputList = this._list; 149 | this.currentSubList = null; 150 | } 151 | 152 | this.currentQuery = value; 153 | 154 | if (value.length >= this.minChars && inputList.length > 0) { 155 | this.searchId++; 156 | var currentSearchId = this.searchId; 157 | this.index = -1; 158 | 159 | var suggestions = []; 160 | let selectedItems = []; 161 | let unique = new Set(); 162 | 163 | let handlePassedSuggestions = function (me) { 164 | if (this.sort !== false) { 165 | this.suggestions = this.suggestions.sort(this.sort); 166 | } 167 | this.currentSubList = selectedItems; 168 | this.suggestions = this.suggestions.slice(0, this.maxItems); 169 | 170 | resolve(this.suggestions); 171 | 172 | if (!isNode) { 173 | this.$ul.children('li').remove(); 174 | 175 | for (let i = this.suggestions.length - 1; i >= 0; i--) { 176 | let text = this.suggestions[i]; 177 | me.ul.insertBefore(me.item(text, value, i), me.ul.firstChild); 178 | } 179 | 180 | if (this.ul.children.length === 0) { 181 | 182 | this.status.textContent = "No results found"; 183 | 184 | this.close({ reason: "nomatches" }); 185 | 186 | } else { 187 | this.open(); 188 | this.status.textContent = this.ul.children.length + " results found"; 189 | } 190 | } 191 | 192 | }.bind(this); 193 | 194 | let timeoutLoop = function (index, me, handlePassedSuggestions, currentSearchId) { 195 | if (currentSearchId !== this.searchId) { 196 | return; 197 | } 198 | 199 | if (index < inputList.length) { 200 | for (let i = index; i < inputList.length && i < index + 1000; i++) { 201 | let item = inputList[i]; 202 | let suggestion = new Suggestion(me.data(item, value)); 203 | if (me.filter(suggestion, value) && !unique.has(suggestion.value)) { 204 | selectedItems.push(item); 205 | suggestions.push(suggestion); 206 | unique.add(suggestion.value); 207 | } 208 | } 209 | setTimeout(function () { 210 | timeoutLoop(index + 1000, me, handlePassedSuggestions, currentSearchId); 211 | }.bind(this), 10); 212 | } else { 213 | this.suggestions = suggestions; 214 | handlePassedSuggestions(me); 215 | } 216 | }.bind(this); 217 | 218 | timeoutLoop(0, me, handlePassedSuggestions, currentSearchId); 219 | } 220 | else { 221 | resolve([]) 222 | } 223 | }); 224 | 225 | return response; 226 | }; 227 | 228 | AmqAwesomeplete.prototype.hide = function () { 229 | this.close(); 230 | this.searchId++; 231 | $("#qpAnswerInputLoadingContainer").addClass("hide"); 232 | }; 233 | 234 | function Suggestion(data) { 235 | var o = Array.isArray(data) 236 | ? { label: data[0], value: data[1] } 237 | : typeof data === "object" && "label" in data && "value" in data ? data : { label: data, value: data }; 238 | 239 | this.label = o.label || o.value; 240 | this.value = o.value; 241 | } 242 | Object.defineProperty(Suggestion.prototype = Object.create(String.prototype), "length", { 243 | get: function () { return this.label.length; } 244 | }); 245 | Suggestion.prototype.toString = Suggestion.prototype.valueOf = function () { 246 | return "" + this.label; 247 | }; 248 | 249 | AmqAwesomeplete.prototype.goto = function (i) { 250 | //Handle case where perfectscollbar have added scroll containers 251 | let childrenCount = this.$ul.children().length; 252 | if (i >= childrenCount - 2 && this.$ul.children('div').length) { 253 | if (this.index === 0) { 254 | i = childrenCount - 3; 255 | } else { 256 | i = 0; 257 | } 258 | 259 | } 260 | Awesomplete.prototype.goto.call(this, i); 261 | }; 262 | 263 | 264 | const ANIME_REGEX_REPLACE_RULES = [ 265 | { 266 | input: 'ou', 267 | replace: '(ou|ō)' 268 | }, 269 | { 270 | input: 'oo', 271 | replace: '(oo|ō)' 272 | }, 273 | { 274 | input: 'o', 275 | replace: '[oōóòöôøΦ]' 276 | }, 277 | { 278 | input: 'u', 279 | replace: '([uūûúùüǖ]|uu)' 280 | }, 281 | { 282 | input: 'a', 283 | replace: '[aä@âàáạåæā]' 284 | }, 285 | { 286 | input: 'c', 287 | replace: '[cč]' 288 | }, 289 | { 290 | input: ' ', 291 | replace: '( ?[★☆\\/\\*=\\+·♥∽・〜†×♪→␣:;~\\-?,.!@_]+ ?| )' 292 | }, 293 | { 294 | input: 'e', 295 | replace: '[eéêëèæē]' 296 | }, 297 | { 298 | input: '\'', 299 | replace: '[\'’]' 300 | }, 301 | { 302 | input: 'n', 303 | replace: '[nñ]' 304 | }, 305 | { 306 | input: '2', 307 | replace: '[2²]' 308 | }, 309 | { 310 | input: 'i', 311 | replace: '[ií]' 312 | }, 313 | { 314 | input: '3', 315 | replace: '[3³]' 316 | }, 317 | { 318 | input: 'x', 319 | replace: '[x×]' 320 | }, 321 | { 322 | input: 'b', 323 | replace: '[bß]' 324 | } 325 | ]; 326 | 327 | function createAnimeSearchRegexQuery(query) { 328 | let escapedValue = escapeRegExp(query); 329 | ANIME_REGEX_REPLACE_RULES.forEach(rule => { 330 | escapedValue = escapedValue.replace(new RegExp(rule.input, 'gi'), rule.replace); 331 | }); 332 | return escapedValue; 333 | } 334 | 335 | 336 | class QuizAnswerInput { 337 | bindListener() {} 338 | } 339 | 340 | Listener = QuizAnswerInput 341 | 342 | if (isNode) module.exports = {AmqAwesomeplete, createAnimeSearchRegexQuery} -------------------------------------------------------------------------------- /debug/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 |
19 |
20 | 21 | 22 | 23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 |
32 | 33 | 34 | 68 | 69 | 111 | 112 | -------------------------------------------------------------------------------- /debug/test.js: -------------------------------------------------------------------------------- 1 | let AmqAwesomeplete = require('./amqAwesomeplete').AmqAwesomeplete; 2 | let FilterManager = require('../gameplay/amqAutocomplete.user.js').FilterManager; 3 | let animes = require('./animes.js').animes; 4 | let awesomeplete = require('Awesomplete') 5 | 6 | function noop() {} 7 | 8 | global.document = {createElement: (a) => ({setAttribute: noop, addEventListener:noop})} 9 | awesomeplete.$.create = () => ({addEventListener:noop}) 10 | 11 | 12 | function ms(t) { 13 | return (t[1] / 1000000) + 'ms'; 14 | } 15 | 16 | function hrtime(arg) { 17 | return process.hrtime(arg); 18 | } 19 | 20 | 21 | (async function() { 22 | let longAnimeList = animes.concat(animes, animes, animes, animes); 23 | 24 | for (let list of [animes]) { 25 | 26 | let t = hrtime() 27 | amq = new AmqAwesomeplete({setAttribute: noop, getAttribute: noop, addEventListener: noop, hasAttribute: noop}, {list: list, minChars: 1, maxItems: 25}) 28 | console.info("amq setup " + ms(hrtime(t))) 29 | t = hrtime() 30 | filterManager = new FilterManager(list, amq.maxItems) 31 | console.info("my setup " + ms(hrtime(t))) 32 | 33 | let searches = ["shingeki", "art", "ouooouooou", "ōōōōō", "ooo", "Angel Beats"]; 34 | 35 | console.log(list.length, amq.maxItems, filterManager.list.length) 36 | 37 | for (let s of searches) { 38 | amq.input.value = ''; 39 | for (let c of s.split('')) { 40 | amq.input.value += c; 41 | try { 42 | let t = hrtime() 43 | let results = await amq.evaluate() 44 | let t2 = hrtime(t) 45 | let t3 = hrtime() 46 | let results2 = filterManager.filterBy(amq.input.value) 47 | let t4 = hrtime(t3) 48 | console.info(amq.input.value + ' amq ' + ms(t2) + ' mine ' + ms(t4)) 49 | 50 | if (results.length != results2.length && (amq.input.value != 'ōō' && amq.input.value != 'ooo' && s != "Angel Beats")) console.log(results.length, results2.length, amq.input.value, results, results2) 51 | } catch (ex) {console.log(ex)} 52 | } 53 | } 54 | } 55 | })() 56 | 57 | -------------------------------------------------------------------------------- /deprecated/amqBugFixFor16.2.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name bugfix on 0.16.2 3 | // @namespace http://tampermonkey.net/ 4 | // @version 1.0 5 | // @downloadURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqBugFixFor16.2.user.js 6 | // @updateURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqBugFixFor16.2.user.js 7 | // @description Fixes opening new profiles after gaccha update 8 | // @author Zolhungaj 9 | // @match https://animemusicquiz.com/* 10 | // @grant none 11 | // @copyright MIT License 12 | // ==/UserScript== 13 | 14 | playerProfileController.displayProfile2 = playerProfileController.displayProfile 15 | 16 | playerProfileController.displayProfile = (payload, offset, closeHandler, offline, inGame) => { 17 | if(!payload.profileEmoteId){ 18 | payload.avatarProfileImage = true 19 | } 20 | playerProfileController.displayProfile2(payload, offset, closeHandler, offline, inGame); 21 | } 22 | -------------------------------------------------------------------------------- /deprecated/amqBugFixFor17.0.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name bugfix on 0.17.0 3 | // @namespace http://tampermonkey.net/ 4 | // @version 1.0 5 | // @downloadURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqBugFixFor17.0.user.js 6 | // @updateURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqBugFixFor17.0.user.js 7 | // @description Fixes opening profiles 8 | // @author Zolhungaj 9 | // @match https://animemusicquiz.com/* 10 | // @grant none 11 | // @copyright MIT License 12 | // ==/UserScript== 13 | quiz.setupQuizInner = quiz.setupQuiz 14 | quiz.setupQuiz = (players, isSpectator, quizState, settings, isHost, groupSlotMap, soloMode) => { 15 | quiz.setupQuizInner(players, isSpectator, quizState, settings, isHost, groupSlotMap, soloMode) 16 | Object.values(quiz.players).forEach(player => {player.avatarSlot.$nameContainerOuter = player.avatarSlot.$bottomContainer}) 17 | } 18 | -------------------------------------------------------------------------------- /deprecated/amqInviteURL.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name AMQ Invite URL 3 | // @namespace http://tampermonkey.net/ 4 | // @version 1.0 5 | // @description Let's you send a link to your friends to join a room if they also have this script 6 | // @author Zolhungaj 7 | // @match https://animemusicquiz.com/* 8 | // @downloadURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqInviteURL.user.js 9 | // @updateURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqInviteURL.user.js 10 | // @grant GM.setValue 11 | // @grant GM.getValue 12 | // @grant GM.deleteValue 13 | // ==/UserScript== 14 | "use strict" 15 | const saveSpaceKey = "amqInviteURL" 16 | const roomIDKey = "roomID" 17 | const roomPasswordKey = "roomPassword" 18 | const tileID = "amqInviteTile" 19 | 20 | const PASSWORD_MAX_LENGTH = 50 21 | 22 | const inviteTypeEnum = { 23 | ROOM_INVITE: 1, 24 | ROOM_INVITE_SPECTATOR_ONLY: 2, 25 | ROOM_INVITE_PLAYER_ONLY: 3, 26 | ROOM_SHOWCASE: 4 27 | } 28 | 29 | 30 | const getInvite = () => { 31 | const temp = document.URL.split("?") 32 | if(temp.length < 2){ 33 | return 34 | } 35 | const searchQuery = "?" + temp[1] 36 | const params = new URLSearchParams(searchQuery) 37 | if(params.has(saveSpaceKey)){ 38 | const inviteType = Number.parseInt(params.get(saveSpaceKey)) 39 | const roomPassword = params.get(roomPasswordKey) 40 | ?.substring(0,PASSWORD_MAX_LENGTH) //prevents injection of excessive amounts of data 41 | const roomID = Number.parseInt(params.get(roomIDKey)) 42 | GM.setValue(saveSpaceKey, inviteType) 43 | GM.setValue(roomIDKey, roomID) 44 | if(roomPassword){ 45 | GM.setValue(roomPasswordKey, roomPassword) 46 | } 47 | } 48 | } 49 | 50 | getInvite() 51 | 52 | if (typeof(Listener) === "undefined") { 53 | return 54 | } 55 | 56 | const checkInvite = async () => { 57 | const inviteType = await GM.getValue(saveSpaceKey, 0) 58 | if(inviteType){ 59 | const roomID = await GM.getValue(roomIDKey) 60 | const roomPassword = await GM.getValue(roomPasswordKey) 61 | clear() 62 | spawnInviteModal(roomID, roomPassword, inviteType) 63 | } 64 | } 65 | 66 | 67 | 68 | 69 | 70 | class RoomBrowserSurrogate{ 71 | constructor(){ 72 | this.activeRooms = [] 73 | new Listener("New Rooms", (rooms) => { 74 | rooms.forEach(room => { 75 | this.activeRooms[room.id] = room 76 | }) 77 | 78 | }).bindListener() 79 | } 80 | 81 | removeRoomTile(tileId){ 82 | delete this.activeRooms[tileId] 83 | } 84 | 85 | appendRoomTile(tileHtml){ 86 | let newChild = document.createRange().createContextualFragment(tileHtml) 87 | document.getElementById(tileID).appendChild(newChild) 88 | } 89 | 90 | } 91 | 92 | const rbSurrogate = new RoomBrowserSurrogate() 93 | 94 | const spawnInviteModal = async (roomID, roomPassword, inviteType) => { 95 | const ROOM_TILE_TEMPLATE = document.getElementById("rbRoomTileTemplate").innerHTML 96 | 97 | let room = rbSurrogate.activeRooms[roomID] 98 | 99 | if(!room){ 100 | await new Promise((resolve, reject) => { 101 | 102 | const a = new Listener("New Rooms", (rooms) => { 103 | a.unbindListener() 104 | resolve() 105 | }) 106 | a.bindListener() 107 | socket.sendCommand({ 108 | type: "roombrowser", 109 | command: "get rooms" 110 | }) 111 | }) 112 | room = rbSurrogate.activeRooms[roomID] 113 | if(!room){ 114 | console.error(`roomID ${roomID} does not correspond to an existing room`) 115 | return 116 | } 117 | } 118 | const spectateOnly = (result) => { 119 | if(result.dismiss){} 120 | else{ 121 | roomBrowser.fireSpectateGame(roomID, roomPassword) 122 | } 123 | } 124 | 125 | const joinOnly = (result) => { 126 | if(result.dismiss){} 127 | else{ 128 | roomBrowser.fireJoinLobby(roomID, roomPassword) 129 | } 130 | } 131 | 132 | const showCase = (result) => { 133 | 134 | } 135 | 136 | const joinOrSpectate = (result) => { 137 | if(result.dismiss === "close"){} 138 | else if (result.dismiss === "cancel") { 139 | roomBrowser.fireSpectateGame(roomID, roomPassword) 140 | }else{ 141 | roomBrowser.fireJoinLobby(roomID, roomPassword) 142 | } 143 | } 144 | 145 | let thenFunction = (result) => {} 146 | 147 | const swalObject = { 148 | title: "Received invite ", 149 | html : `
`, 150 | showCancelButton: true, 151 | showCloseButton: true, 152 | } 153 | 154 | switch(inviteType){ 155 | case inviteTypeEnum.ROOM_INVITE: 156 | thenFunction = joinOrSpectate 157 | swalObject.title += "to join" 158 | swalObject.confirmButtonText = "Join" 159 | swalObject.cancelButtonText = "Spectate" 160 | break 161 | case inviteTypeEnum.ROOM_INVITE_SPECTATOR_ONLY: 162 | thenFunction = spectateOnly 163 | swalObject.title += "to spectate" 164 | swalObject.confirmButtonText = "Spectate" 165 | swalObject.cancelButtonText = "Cancel" 166 | break 167 | case inviteTypeEnum.ROOM_INVITE_PLAYER_ONLY: 168 | thenFunction = joinOnly 169 | swalObject.title += "to play" 170 | swalObject.confirmButtonText = "Join" 171 | swalObject.cancelButtonText = "Cancel" 172 | break 173 | case inviteTypeEnum.ROOM_SHOWCASE: 174 | thenFunction = showCase 175 | swalObject.title = "Room showcase" 176 | swalObject.showCancelButton = false 177 | swalObject.confirmButtonText = "Close" 178 | break 179 | default: 180 | swalObject.title = "INVALID INVITE TYPE" 181 | } 182 | 183 | 184 | swal(swalObject).then( 185 | thenFunction, 186 | () => {} //catch any rejection 187 | ); 188 | 189 | new RoomTile(room.settings, room.host, room.hostAvatar, room.id, room.numberOfPlayers, room.numberOfSpectators, room.players, room.inLobby, rbSurrogate, room.songLeft, false, $("#rbRoomHider")) 190 | document.getElementById(tileID).getElementsByClassName("hidden")[0].classList.remove("hidden") 191 | } 192 | 193 | const clear = () => { 194 | [saveSpaceKey, roomIDKey, roomPasswordKey] 195 | .forEach(e => {GM.deleteValue(e)}) 196 | } 197 | 198 | const createInviteURL = (roomID, password=null, inviteType=1) => { 199 | if(!Number.isInteger(roomID)){ 200 | return "" 201 | } 202 | const baseURL = "https://animemusicquiz.com/?" 203 | let searchParams = `${saveSpaceKey}=${inviteType}&${roomIDKey}=${roomID}` 204 | if(password){ 205 | password = encodeURIComponent(password) 206 | searchParams += `&${roomPasswordKey}=${password}` 207 | } 208 | return baseURL + searchParams 209 | } 210 | 211 | const wait = (timeMs) => { 212 | return new Promise((resolve, reject) => { 213 | setTimeout(() => { resolve() }, timeMs) 214 | }) 215 | } 216 | 217 | let blocked = false 218 | 219 | setInterval(async () => { 220 | if(!setupDocumentDone){ 221 | return 222 | } 223 | if(blocked){ 224 | return 225 | } 226 | blocked = true 227 | await checkInvite() 228 | blocked = false 229 | }, 1000); 230 | 231 | const style = document.createElement("style") 232 | document.head.append(style) 233 | const styleSheet = style.sheet 234 | 235 | styleSheet.insertRule(` 236 | #swal2-content > #amqInviteTile { 237 | color: #d9d9d9; 238 | } 239 | `) 240 | 241 | //add button to invite spectators while in game 242 | function addButtons() { //stolen from https://github.com/YokipiPublic/AMQ/blob/master/FTFRemoteUpdate.user.js 243 | const shareButton = $(`
`) 244 | .click(function () { 245 | swal({ 246 | title: "Invite URL", 247 | html : `${createInviteURL(lobby.gameId, lobby.settings.password, inviteTypeEnum.ROOM_INVITE_SPECTATOR_ONLY)}`, 248 | }) 249 | }) 250 | .popover({ 251 | placement: "bottom", 252 | content: "Get invite link", 253 | trigger: "hover" 254 | }); 255 | 256 | let oldWidth = $("#qpOptionContainer").width(); 257 | $("#qpOptionContainer").width(oldWidth + 35); 258 | $("#qpOptionContainer > div").append(shareButton); 259 | 260 | const lobbyButton = $(` 261 |
262 |

263 |
264 | `) 265 | .click(function () { 266 | swal({ 267 | title: "Invite URL", 268 | html : `${createInviteURL(lobby.gameId, lobby.settings.password, inviteTypeEnum.ROOM_INVITE)}`, 269 | }) 270 | }) 271 | .popover({ 272 | placement: "bottom", 273 | content: "Get invite link", 274 | trigger: "hover" 275 | }); 276 | $("#lobbyPage > .topMenuBar").append(lobbyButton) 277 | styleSheet.insertRule(` 278 | #shareButtonLobby{ 279 | right: calc(89% - 150px); 280 | padding-left: 0.2%; 281 | width: 3.2%; 282 | } 283 | `) 284 | } 285 | addButtons() -------------------------------------------------------------------------------- /deprecated/amqShowPingPong.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name AMQ Show Ping-Pong 3 | // @version 1.0.1 4 | // @description Shows ping together with online player count 5 | // @author Zolhungaj 6 | // @match https://animemusicquiz.com/* 7 | // @grant none 8 | // @updateURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqShowPingPong.user.js 9 | // @downloadURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqShowPingPong.user.js 10 | // ==/UserScript== 11 | 12 | // Wait until the LOADING... screen is hidden and load script 13 | if (typeof Listener === "undefined") return; 14 | let loadInterval = setInterval(() => { 15 | if ($("#loadingScreen").hasClass("hidden")) { 16 | clearInterval(loadInterval); 17 | setup(); 18 | } 19 | }, 500); 20 | 21 | let setup = () => { 22 | const container = document.getElementById("mmSocialButtonOnlineContainer"); 23 | const pingContainer = document.createElement("div"); 24 | pingContainer.className = "mmSocialButtonOnlineCountContainer"; 25 | 26 | const counter = document.createElement("h4"); 27 | counter.id = "ping"; 28 | const initialPingDisplay = document.createTextNode("-"); 29 | counter.appendChild(initialPingDisplay); 30 | 31 | const description = document.createElement("h5"); 32 | const descriptionText = document.createTextNode("Ping"); 33 | description.appendChild(descriptionText); 34 | 35 | pingContainer 36 | .append(counter, description); 37 | 38 | container.appendChild(pingContainer); 39 | 40 | const counterList = container.getElementsByClassName("mmSocialButtonOnlineCountContainer"); 41 | 42 | const width = 100 / counterList.length; 43 | 44 | for(const counterElement of counterList){ 45 | counterElement.setAttribute("style", `width:${width}%`); 46 | } 47 | container.setAttribute("style", "right:3%"); 48 | socket._socket.on("pong", time => document.getElementById("ping").innerText = time); 49 | } 50 | -------------------------------------------------------------------------------- /deprecated/amqSoundEffects.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name AMQ Sound Effects 3 | // @namespace http://tampermonkey.net/ 4 | // @version 1.2.0 5 | // @description Makes the game a lot noisier 6 | // @author Zolhungaj 7 | // @match https://animemusicquiz.com/* 8 | // @grant none 9 | // @downloadURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqSoundEffects.user.js 10 | // @updateURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqSoundEffects.user.js 11 | // @copyright MIT license 12 | // ==/UserScript== 13 | 14 | // based on https://stackoverflow.com/questions/879152/how-do-i-make-javascript-beep 15 | // audio created with audacity, converted to mp3 with ffmpeg, and converted to base64 with https://base64.guru/converter/encode/audio 16 | 17 | if (!window.GameChat) return; 18 | function settings_changed() { 19 | var snd = new Audio("data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjM1LjEwNAAAAAAAAAAAAAAA//NwwAAAAAAAAAAAAEluZm8AAAAPAAAASwAACF4AGBwfIiIlKCsrLjE1NTg7Pj5BREdHSk5RUVRXWlpdYGNjZ2ptbXBzdnZ5fICAg4aJiYyPkpKVmJycn6Klpairrq6xtbi4u77BwcTHysrO0dTU19rd3eDj5+fq7fDw8/b5+fz/AAAAAExhdmM1OC42NgAAAAAAAAAAAAAAACQCgAAAAAAAAAhevwSUfwAAAAAAAAAAAAAAAAD/8xDEAALAAoxBQAAAs7/g/B8H/jwfyhwJYv/zEsQCA1gGrAGBEABLNCifrd/eo13f/QoMi//zEMQCA1C+vFHAEAAPp/9DCP3VBht8+uqJ//MQxAEDCALAaAAAACEP/Cbef7uZAS10VQD/8xDEAQL4P08AAEYiqqqoAAkoDNA5yNYf7v/zEMQCA1g+7UAAxgQADG8ChiEGX//xkZUW//MQxAEC+D8J4AIGCagkCgBzSWxpxibOKDj/8xDEAgKYLwVgAkQBUQC+cgp9Mv5VaBsGT//zEMQEAuAu4AADDADsGSUDWP/+tTAFSoKh//MSxAUDEDLQYDgKIDGBvURib/pVt8BaZxFF//MQxAYEOEbUABgGIMQFgXvv/KAQ5/8uJQL/8xDEAgNYFugIEMYACIMcUOGP/+q3/lk1Xv/zEMQBAmg2+UAQRgAMDQ0CAQgpASqoMVLq//MQxAQC4A7wAChGAChhv//rT/lagDgEGIL/8xDEBQLoDvUAEAQAyJRJ7fYgxZQwIx7hdv/zEMQGAsAS6AAoQACb////68VBiqCOjRwa//MQxAgC2DbcIDjGBKpNEyoBpecAVGUPGDH/8xLECQMIEvZAEAIAiKLk1QTDAsRmXh4EWGv/8xDECgLIDtGASEYAyqoAxCgKCt50+WXq8f/zEMQLAugOzaA4xAAdsFTalCiWN////o1q//MQxAwC2Ca8AEhKABIw5AOEawkF/b6VVYj/8xDEDQLIIsDgMIQA4DDBfijPnvmPLhADgP/zEMQOArgixEBBxABoCPRb//kqVWiQFHep//MQxBACYBbI4DCEAGxqwTOCEWpEMCf/////8xDEEwHQBzaAAIYDZ6U1Cg3gqwIVY7Ifrv/zEsQYA1gutAB5RAAfNqBBSgArFiQtGixCh//zEMQYA5gSwOAwRADIP5C/F7E0yEjmpRiQ//MQxBYE6L69gEBGPaBlb67/X97QNo4a9/n/8xDEDwT4sryoKATke20SGC4FxtK/KgL7l//zEMQIBLiywYAIRDxxFmF0f6BTVH/q1VAA//MQxAIC6AMu4ABEApj/gJ+DDRYW30IDCJj/8xDEAwKQAsmgCARMH19NUu1X5Or3CUAD/v/zEMQFAugDBHgABALR16nfoJTNR5//+xyw//MSxAYCmLa4IABMysYV/mWqkAGP/hnWZXw7//MQxAkCOLaoEBAEPApiRyz3939bpH/h1QD/8xDEDQLgEpQgSEQAjAhUG0Pp//r2/1IXgP/zEMQOAugWmFAQhAD/////1vKJWfkyFSf8//MQxA8DGAbUGABEAOYAYRuXkSRJ1goYUYX/8xDEDwOhTtwIAJNiEoH7qYkGjY1QjImmUP/zEMQNA/FPFBgAk2KN3r0ICQoAG3/KuGC///MQxAoCuJsomABGxioD0QgD//z+3xOT/oT/8xLEDAMIAwx4AAQC1QAAeIgACqv3c5i3AH7/8xDEDQLAbyMAAEZmHgADOIP/ihBpEgeIcP/zEMQPAug3LmAABAcfTiBQSCo4PCMZUoph//MQxBACEDNCgABGA9QSnvkqdIAoHB3Rx4L/8xDEFALQMvFAOsYBqjFoAwKQ8IaCYcTcmv/zEMQVAjAzAeA6xAAyjYAYWg9wSxL6Vrp4//MQxBkC4D79gBDEBFFwix4l0Fw3Zt9FLcf/8xDEGgLgNwGgEEQgneADPc2GrvIc49W0Af/zEMQbAug66GA4hASFVeuPpurbMDU1Vnqa//MSxBwDEC8qYAhKAgAM56QY95RDsJJyYCQm//MQxB0CoGr4YCgEgoME9YUV53lF5So0Vnn/8xDEHwNoM0sACEQCAIAMc1WiTDTf0wA3hf/zEMQeA1AzPmAIRAIAWBmuGhqh7zPuAWSA//MQxB0DOC87AAhGBknJDpONQhidqgIzasD/8xDEHQNALuJAOYQAWBNj1x2qKpYABNkonP/zEMQdAsgy1UBSRgHYG5qHLgArNoFbKHz0//MQxB4CkBr2gBCEARMQdWqjmLNI8S11LPj/8xLEIAKwNrgAe8wAjq////66EAuCBFhpTP//8xDEIwLQOtXgWYQB////////5CejhbYIpv/zEMQkA6AuxAFPAAC64AAcQACo5A2m0f/m//MQxCIFMSbkoYUQAH6qlw4iACJKyIHf/+//8xDEGgOgGxZZwxAC/2DKAAtAA///+K/8hf/zEMQYA2ADDfgABAI2tEUACf//+IXf+DRU//MQxBcDOAbweAAEADoqOmP/+r7f8qd6jVX/8xDEFwK4AuxgAARyAMMARAfr+z///TyKe//zEsQZAsgKkABIAABf2IDaQQX7bsB+JD/gn//zEMQbAugGxZlAEALnzP80fCMJ+gUGDOvf//MQxBwFOG68AYUoAPULAuZVJ/yESZ3/+sX/8xDEFALQBsChwAAAEhXFulIEsvW3/91oof/zEMQVAqCuxCAIBIlpQXeGg4gbE3/UGnJC//MQxBcCwK64AADESQqXDAShqViX89AtaiT/8xDEGQKoPrAAEAYEO25IRBI876xKRUxBTf/zEMQbAug+qCAQBARFMy4xMDBVVVVVVVVV//MSxBwC8BJ4ABgGAFVVVVVVVVVVVVVVVVVV"); 20 | snd.play(); 21 | } 22 | function mention() { 23 | var snd = new Audio("data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjM1LjEwNAAAAAAAAAAAAAAA//NwwAAAAAAAAAAAAEluZm8AAAAPAAAADgAAAiQAYWFhYWFhYW1tbW1tbW15eXl5eXl5hoaGhoaGhpKSkpKSkpKenp6enp6eqqqqqqqqqra2tra2tra2wsLCwsLCws/Pz8/Pz8/b29vb29vb5+fn5+fn5/Pz8/Pz8/P/////////AAAAAExhdmM1OC42NgAAAAAAAAAAAAAAACQEQAAAAAAAAAIkpJQFdgAAAAAAAAAAAAAAAAD/8xDEAAM4xmwBQCgB//U4mH3EADA4v/MMEP/zEsQAA3jCwAmAUAAgf//6EGhHQoAo6s5FUP/zEMQAAvDG2BnAUAACgf/+dMJgFxDp/6Ew//MQxAAC2M7IAAYTIP5vXGZgF5tABTKJSYD/8xDEAAL4xuXgCBZ0A3A/zEZfMlYHSIU0mP/zEMQAAzgu6eAJTAAX/DMN1UgAE1//6Hg0//MQxAACsKrmAAgEjAm+BZvkKcgL6MghMQT/8xDEAAH4rtAACCI4zX62PgUAPKKYgpqKAP/zEMQAAqCq1UAIJI0Szr99fUaAnwho5MQQ//MSxAACyKcJ+AgSkgBaBcB/7txt39H0piCA//MQxAACmKcF+AgKcmBQBwBX+L+oG/0JiCD/8xDEAAM4rrwBQlAADO43/////UiEsA0LLv/zEMQAA0CaoCmCKAACQAK3///+qfiIFOeP//MQxAAAAANIAcAAAExBTUUzLjEwMAAAAAA="); 24 | snd.play(); 25 | } 26 | 27 | function dm() { 28 | var snd = new Audio("data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjM1LjEwNAAAAAAAAAAAAAAA//NwwAAAAAAAAAAAAEluZm8AAAAPAAAAEgAAAo0AUVFRUVFcXFxcXFxmZmZmZnBwcHBwcHp6enp6hISEhISEj4+Pj4+ZmZmZmZmjo6Ojo66urq6urri4uLi4uMLCwsLCzMzMzMzM1tbW1tbh4eHh4eHr6+vr6/X19fX19f//////AAAAAExhdmM1OC42NgAAAAAAAAAAAAAAACQDzAAAAAAAAAKNBvqRDQAAAAAAAAAAAAAAAAD/8xDEAAMoUngBQgAA//qc7hAMDAgIaw/ggP/zEsQAA0i25AGBOAD//7MaTaFX75uIG5vLgP/zEMQAAwiW3EHKOAAwP//4vDY47/0JAicA//MQxAAC0KbsYABEoRQNC2If/wonDnCC0wD/8xDEAAMIntGAAFqBADMI+ZBrdH//UEkKgP/zEMQAAsimzAADVKCVhZLPRv/nngNiYqYA//MQxAACsKbcIAIOpkM0hKBUrX/8JxJZMQT/8xDEAAKYosgAAJqghBAUEC3/WvgnJumIIP/zEMQAAoCe3GAAChMACRkh//roAQTBTEFN//MSxAADCKLuQABEgQABkAveCeP/8G+MyyYA//MQxAAC4J7uYACKiAABwOTDSmG/61fANMD/8xDEAALAosxgAIypAA8M4Qj//wvuIzNMQf/zEMQAAtiiyeABUIwAAW+g8YBP/+9AUqmA//MQxAACkJ7AYAFOoSALgwSDP/0zgmxTEED/8xDEAAKQnqQAAY6gl1gwFDv+qGsPi1MQQP/zEMQAAtimkCACkKgKmnuCCd//alAAn0mA//MQxAACmKJ0AACSqJgYkMP/6lLlgSNJiCD/8xLEAAGwAkSAAEYCAAmv//xWZTEFNRQAAAA="); 29 | snd.play(); 30 | } 31 | 32 | 33 | new Listener("Room Settings Changed", (changes) => { settings_changed()}).bindListener() 34 | new Listener("Game Chat Message", function (payload) { 35 | if (!socialTab.isBlocked(payload.sender)) { 36 | //this.chatMessage(payload.sender, payload.message, payload.emojis, payload.badges, payload.messageId, payload.atEveryone); 37 | if (gameChat.atSelfRegex.test(payload.message) || payload.atEveryone) { 38 | mention(); 39 | } 40 | } 41 | }).bindListener() 42 | new Listener("chat message", function (payload) { dm() }).bindListener(); 43 | -------------------------------------------------------------------------------- /design/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amq-script-project/AMQ-Scripts/69ad64684ca6af2e69cbb5f2169f98305f0c7f1f/design/.keep -------------------------------------------------------------------------------- /design/amqBackground.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name AMQ Background script 3 | // @namespace http://tampermonkey.net/ 4 | // @version 3.9 5 | // @description Adds multiple custom background to amq or even a video. Tried to include as many selectors as possible, so remove the ones where you prefer to have original background 6 | // @author Juvian 7 | // @match https://animemusicquiz.com/* 8 | // @grant none 9 | // @require https://gist.githubusercontent.com/arantius/3123124/raw/grant-none-shim.js 10 | // ==/UserScript== 11 | 12 | 13 | let timer = (secs) => setInterval(changeBackground, secs * 1000); 14 | 15 | let onMusicChange = () => { 16 | new Listener("play next song", function (data) { 17 | changeBackground(); 18 | }).bindListener(); 19 | }; 20 | 21 | let onManualChange = (key) => { 22 | document.addEventListener ("keydown", function (zEvent) { 23 | if (zEvent.ctrlKey && zEvent.key.toLowerCase() === key.toLowerCase()) { 24 | changeBackground(); 25 | zEvent.preventDefault(); 26 | } 27 | }); 28 | } 29 | 30 | let defaultOpacity = 0.5; 31 | 32 | let options = { 33 | images: [ 34 | "https://w.wallhaven.cc/full/wy/wallhaven-wye6g6.jpg", 35 | "https://w.wallhaven.cc/full/lm/wallhaven-lmwegp.png" 36 | ], 37 | imageChangePolicy: onManualChange("b"), //options: timer(3) = every 3 seconds, onMusicChange() or onManualChange("n") = when pressing ctrl + your key, 38 | video: { 39 | url: "https://desktophut-com.cdn.vidyome.com/videos/12-2018/kStWC5u7eyifonqthwFl.mp4", //other good ones: "https://desktophut-com.cdn.vidyome.com/videos/04-2019/mySuBqs1CcijooCKJsOq.mp4", "https://www.desktophut.com/wp-content/uploads/2021/12/Anime-Ganyu-Girl-And-Rainy-Night-4K-Live-Wallpaper.mp4" 40 | enabled: false, // no images with this 41 | filter: "none" // could be blur(3px) or "none" to deactivate 42 | }, 43 | transparent: [ 44 | { 45 | selector: "div.lobbyAvatarImgContainer", 46 | description: "dots in game lobby", 47 | opacity: 0.7 48 | }, 49 | { 50 | selector: "#mpDriveStatsContainer>.col-xs-6 .floatingContainer", 51 | description: "avatar drive entries", 52 | opacity: defaultOpacity, 53 | css: `.mpDriveEntryName::after { 54 | width: 0px; 55 | } 56 | .mpDriveEntry:nth-child(2n) { 57 | background-color: rgba(27, 27, 27, 0.6) !important; 58 | } 59 | ` 60 | }, 61 | { 62 | selector: "#mpAvatarDriveContainer", 63 | description: "dots in game lobby", 64 | opacity: defaultOpacity 65 | }, 66 | { 67 | selector: ".qpAvatarImgContainer", 68 | description: "backgound of avatar image in quiz", 69 | css: `.qpAvatarImgContainer { 70 | box-shadow:none; 71 | }` 72 | }, 73 | { 74 | selector: "#gameChatPage > .col-xs-9", 75 | description: "quiz main screen" 76 | }, 77 | { 78 | selector: "#gameChatContainer, .gcInputContainer, .gcList > li:nth-child(2n)", 79 | description: "quiz chat", 80 | opacity: defaultOpacity 81 | }, 82 | { 83 | selector: ".rbRoom, .rbrRoomImageContainer", 84 | description: "rooms to choose", 85 | opacity: defaultOpacity, 86 | css: `.rbrRoomImageContainer { 87 | background-color: transparent !important; 88 | }` 89 | }, 90 | { 91 | selector: "#mainMenuSocailButton", 92 | description: "friends/social button (bottom left)" 93 | }, 94 | { 95 | selector: "#avatarUserImgContainer", 96 | description: "avatar background (bottom right)" 97 | }, 98 | { 99 | selector: ".topMenuBar", 100 | description: "top menu" 101 | }, 102 | { 103 | selector: ".awSkinPreviewButtom, .awSkinPreview", 104 | description: "unlock/change avatar preview" 105 | }, 106 | { 107 | selector: "#footerMenuBarBackground, #rightMenuBarPartContainer::before, [id='3YearCelebrationContainer'], #xpBarOuter", 108 | description: "bottom menu", 109 | opacity: 0.3 110 | }, 111 | { 112 | selector: "#mpPlayButton", 113 | description: "play button main screen", 114 | opacity: defaultOpacity 115 | }, 116 | { 117 | selector: "#mpExpandButton", 118 | description: "expand button main screen", 119 | opacity: defaultOpacity 120 | }, 121 | { 122 | selector: "#mpRankedButton", 123 | description: "expand button main screen", 124 | opacity: defaultOpacity 125 | }, 126 | { 127 | selector: "#mpLeaderboardButton", 128 | description: "expand button main screen", 129 | opacity: defaultOpacity 130 | }, 131 | { 132 | selector: "#mpAvatarDriveContainer, #mpAvatarDriveHeaderShadowHider .floatingContainer", 133 | description: "avatar drive container main screen", 134 | opacity: defaultOpacity 135 | }, 136 | { 137 | selector: "#mpDriveDonationContainer .button", 138 | description: "avatar drive donate/faq buttons", 139 | enabled: true 140 | }, 141 | { 142 | selector: "#mpDriveStatsContainer *", 143 | description: "avatar drive entries", 144 | enabled: false 145 | }, 146 | { 147 | selector: "#mpNewsContainer, #mpNewsTitleShadowHider div", 148 | description: "news main menu", 149 | opacity: defaultOpacity 150 | }, 151 | { 152 | selector: "#mpNewSocailTab .leftRightButtonTop, #mpPatreonContainer, .startPageSocailIcon", 153 | description: "main menu black backgrounds near news", 154 | opacity: defaultOpacity 155 | }, 156 | { 157 | selector: "#rbMajorFilters", 158 | description: "game room top middle filters", 159 | enabled: false 160 | }, 161 | { 162 | selector: "#roomBrowserHostButton", 163 | description: "host room button", 164 | enabled: false 165 | }, 166 | { 167 | selector: "#topMenuBack", 168 | description: "top back button", 169 | enabled: false 170 | }, 171 | { 172 | selector: "#qpAnimeContainer div:first-child .qpSideContainer", 173 | description: "standings menu in quiz", 174 | opacity: defaultOpacity 175 | }, 176 | { 177 | selector: ".qpAvatarInfoContainer > div, .qpAvatarAnswerContainer", 178 | description: "name/guess near avatar image in quiz", 179 | opacity: defaultOpacity, 180 | css: `.qpAvatarInfoContainer > div { 181 | box-shadow:none; 182 | }` 183 | }, 184 | { 185 | selector: "#qpInfoHider, .col-xs-6 + .col-xs-3 .container.qpSideContainer.floatingContainer, .col-xs-3 .qpSingleRateContainer", 186 | description: "song info in quiz", 187 | opacity: defaultOpacity 188 | }, 189 | { 190 | selector: "#qpAnimeNameHider, .qpAnimeNameContainer, #qpCounter", 191 | description: "anime name answer top menu in quiz", 192 | opacity: defaultOpacity 193 | }, 194 | { 195 | selector: "#qpVideoHider, #qpVideoOverflowContainer", 196 | description: "video counter/sound only background", 197 | opacity: defaultOpacity 198 | }, 199 | { 200 | selector: "#socailTabFooter > .selected, #socialTab", 201 | description: "friends online menu", 202 | opacity: defaultOpacity 203 | }, 204 | { 205 | selector: ".lobbyAvatarTextContainer", 206 | description: "username/level text lobby", 207 | opacity: defaultOpacity 208 | }, 209 | { 210 | selector: "#startPageCenter", 211 | description: "login screen", 212 | opacity: defaultOpacity 213 | }, 214 | { 215 | selector: "#startPageLogoContainer", 216 | description: "login screen logo", 217 | opacity: defaultOpacity 218 | } 219 | ] 220 | } 221 | 222 | let transparents = options.transparent.filter(opt => opt.enabled !== false); 223 | 224 | function changeBackground() { 225 | this.index = ((this.index || 0) + 1) % options.images.length; 226 | document.documentElement.style.setProperty('--url', `url("${options.images[this.index]}")`); 227 | } 228 | 229 | let template = $(`
`); 230 | 231 | 232 | if (options.video.enabled) { 233 | template.append(``); 234 | options.images = [""]; 235 | } 236 | 237 | $("#mainContainer").append(template); 238 | 239 | function extend (func, method, ext) { 240 | let old = (func.fn ? func.fn : func.prototype)[method]; 241 | func.prototype[method] = function () { 242 | let result = old.apply(this, Array.from(arguments)); 243 | ext.apply(this, Array.from(arguments)); 244 | return result; 245 | } 246 | } 247 | 248 | let loggedIn = window.QuizInfoContainer != null; 249 | 250 | if (loggedIn) {//we are logged in 251 | extend(QuizInfoContainer, "showContent", function () { 252 | $("#qpInfoHider").prevAll().css("visibility", "visible"); 253 | $("#qpAnimeNameContainer").css("visibility", "visible"); 254 | }); 255 | 256 | extend(QuizInfoContainer, "hideContent", function () { 257 | $("#qpInfoHider").prevAll().css("visibility", "hidden"); 258 | $("#qpAnimeNameContainer").css("visibility", "hidden"); 259 | }); 260 | 261 | extend(VideoOverlay, "show", function () { 262 | this.$hider.siblings().not('#qpVideoMainTextOverlay').css("visibility", "hidden"); 263 | }); 264 | 265 | extend(VideoOverlay, "hide", function () { 266 | this.$hider.siblings().css("visibility", "visible"); 267 | }); 268 | 269 | 270 | extend(VideoOverlay, "showWaitingBuffering", function () { 271 | this.$bufferingScreen.siblings().css("visibility", "hidden"); 272 | }); 273 | 274 | extend(VideoOverlay, "hideWaitingBuffering", function () { 275 | this.$bufferingScreen.siblings().css("visibility", "visible"); 276 | }); 277 | 278 | extend(StoreWindow, "toggle", function () { 279 | if (this.open) { 280 | $("#custom-background").css("z-index", 10); 281 | $("#storeWindow").css("z-index", 11); 282 | } else { 283 | $("#custom-background").css("z-index", -1); 284 | $("#storeWindow").css("z-index", -1); 285 | } 286 | }); 287 | 288 | 289 | 290 | let loadingScreenStateChange = function () { 291 | if ($(this).attr("id") == "loadingScreen") { 292 | if ($(this).hasClass("hidden")) { 293 | $("#custom-background").css("z-index", -1); 294 | } else { 295 | $("#custom-background").css("z-index", 10); 296 | } 297 | } 298 | } 299 | 300 | extend($, "addClass", loadingScreenStateChange); 301 | extend($, "removeClass", loadingScreenStateChange); 302 | } 303 | 304 | GM_addStyle(` 305 | 306 | :root { 307 | --url: url("${options.images[0]}"); 308 | } 309 | 310 | ${transparents.map(obj => ` 311 | ${obj.selector} { 312 | background-color: rgba(${obj.color || "27, 27, 27"}, ${obj.opacity || 0}) !important; 313 | background-image: none !important; 314 | } 315 | ${obj.css || ''} 316 | `).join('\n')} 317 | 318 | 319 | .leftShadowBorder, #currencyContainer, #menuBarOptionContainer, #awContentRow .rightShadowBorder { 320 | box-shadow:none; 321 | } 322 | 323 | #socialTab:not(.open), #optionsContainer:not(.open) { 324 | display:none; 325 | } 326 | 327 | #mainMenuSocailButton, #avatarUserImgContainer { 328 | border:none !important; 329 | } 330 | 331 | #optionsContainer li { 332 | background-color:#424242 !important; 333 | } 334 | 335 | #rbMajorFilters { 336 | background-color: #1b1b1b; 337 | padding-left: 10px; 338 | } 339 | 340 | #custom-background { 341 | position: absolute; 342 | left: 0%; 343 | top: 0%; 344 | /* The following will size the video to fit the full container. Not necessary, just nice.*/ 345 | min-width: 100%; 346 | min-height: 100%; 347 | /* 348 | left: 50%; 349 | top: 50%; 350 | -webkit-transform: translate(-50%,-50%); 351 | -moz-transform: translate(-50%,-50%); 352 | -ms-transform: translate(-50%,-50%); 353 | transform: translate(-50%,-50%);*/ 354 | z-index: ${loggedIn ? 5 : -1}; 355 | filter: ${options.video.filter}; 356 | will-change: contents; 357 | background-image: var(--url) !important; 358 | background-size: 100% auto !important; 359 | background-attachment: fixed !important; 360 | background-position: 0px !important; 361 | } 362 | 363 | #custom-background video { 364 | width: 100%; 365 | height: 100%; 366 | object-fit: cover; 367 | position: absolute; 368 | } 369 | 370 | #mainContainer > *, #awMainView, #storeWindow, #startPage, #loadingScreen { 371 | background: none; 372 | } 373 | 374 | `); 375 | 376 | -------------------------------------------------------------------------------- /design/amqSimpleBackground.css: -------------------------------------------------------------------------------- 1 | /* 2 | AMQ Simple Background Stylesheet. 3 | Simply change the URL in quotes to your desired background, and save the stylesheet. 4 | If you are using Stylish/Stylus, you need to set it to "Applies to" -> "URLs on the domain" -> animemusicquiz.com 5 | 6 | If you'd like the chat area to have a separate background, you can change it from 7 | rgba(0,0,0,0.5); to url("abc.zzz/background.png"); 8 | */ 9 | 10 | :root { 11 | /* Change the URL in quotes to whatever you want the bg to be. */ 12 | --bg: url("https://files.catbox.moe/0fg6l8.jpg"); 13 | /* Optional: if you want a different background for chat. Phone wallpapers tend to work best. */ 14 | --chatbg: rgba(0,0,0,0.5); 15 | } 16 | 17 | #gameContainer { 18 | background-image: var(--bg); 19 | background-size: 100% auto; 20 | } 21 | 22 | #startPage { 23 | background-image: var(--bg); 24 | background-size: 100% auto; 25 | } 26 | 27 | #awMainView { 28 | background-image: var(--bg); 29 | background-size: 100% auto; 30 | } 31 | 32 | #loadingScreen { 33 | background-image: none; 34 | background-color: transparent; 35 | } 36 | 37 | #gameChatPage > .col-xs-9 { 38 | background-image: none; 39 | background-color: transparent; 40 | } 41 | 42 | #gameChatContainer { 43 | background: var(--chatbg); 44 | } 45 | 46 | .leftShadowBorder { 47 | box-shadow: none; 48 | -webkit-box-shadow: none; 49 | } 50 | 51 | .gcList > li:nth-child(2n) { 52 | background: none; 53 | } 54 | 55 | .gcInputContainer { 56 | background: none; 57 | } 58 | 59 | #gcEmojiPickerButton > i { 60 | color: #d9d9d9; 61 | } -------------------------------------------------------------------------------- /design/replaceRules.js: -------------------------------------------------------------------------------- 1 | function replaceRule(match, replace) { 2 | let newRules = []; 3 | Array.from(document.styleSheets).forEach(s => { 4 | Array.from(s.cssRules || s.rules).map((rule, idx) => ({rule, idx})).reverse().forEach(r => { 5 | if (r.rule.cssText.includes(match)) { 6 | newRules.push(r.rule.cssText.split(match).join(replace)); 7 | s.deleteRule(r.idx) 8 | } 9 | }) 10 | }); 11 | newRules.forEach(r => document.styleSheets[0].insertRule(r, 0)) 12 | } 13 | 14 | replaceRule("rgb(66, 66, 66)", "rgb(27, 27, 27)") -------------------------------------------------------------------------------- /encoding/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amq-script-project/AMQ-Scripts/69ad64684ca6af2e69cbb5f2169f98305f0c7f1f/encoding/.keep -------------------------------------------------------------------------------- /encoding/amqfunc/README.md: -------------------------------------------------------------------------------- 1 | # AMQfunc 2 | A 'func' with general purpose, quick-'n-dirty functions for filtering videos in VapourSynth for AMQ. 3 | 4 | Please check the docstrings in the .py for more information. 5 | 6 | # Dependencies 7 | *Please ensure you also meet the requirements for each individual dependency* 8 | * [Vapoursynth](http://www.vapoursynth.com/) 9 | * [lvsfunc](https://github.com/LightArrowsEXE/lvsfunc) 10 | * [fvsfunc](https://github.com/Irrational-Encoding-Wizardry/fvsfunc) 11 | * [kagefunc](https://github.com/Irrational-Encoding-Wizardry/kagefunc) 12 | * [nnedi3_rpow2](https://github.com/darealshinji/vapoursynth-plugins/blob/master/scripts/nnedi3_rpow2.py) 13 | * [vsTAAmbk](https://github.com/HomeOfVapourSynthEvolution/vsTAAmbk) 14 | * [f3kdb](https://f3kdb.readthedocs.io/en/latest/) 15 | * [vsutil](https://github.com/Irrational-Encoding-Wizardry/vsutil) -------------------------------------------------------------------------------- /encoding/amqfunc/amqfunc.py: -------------------------------------------------------------------------------- 1 | """ 2 | Various functions for filtering videos specifically for AMQ using Vapoursynth. 3 | Please make sure you install the required dependancies. 4 | """ 5 | 6 | import vapoursynth as vs 7 | import lvsfunc as lvf 8 | import fvsfunc as fvf 9 | from nnedi3_rpow2 import nnedi3_rpow2 10 | import kagefunc as kgf 11 | import vsutil 12 | import vsTAAmbk as taa 13 | core = vs.core 14 | 15 | def quick_import(file: str, force_lsmas=False, resample=True): 16 | """ 17 | A function to quickly import and resample a file. 18 | 19 | If the file does not have a chroma subsampling of 4:2:0, it'll automatically be converted to such. 20 | """ 21 | src = lvf.src(file, force_lsmas=force_lsmas) 22 | depth = vsutil.get_depth(src) 23 | 24 | if vsutil.get_subsampling != '420': 25 | src = multi_resample(src, depth=depth) 26 | 27 | if resample: 28 | return fvf.Depth(src, 16) 29 | else: 30 | return src 31 | 32 | 33 | def quick_scale(clip: vs.VideoNode, height=480): 34 | """ 35 | A function for quickly scaling to a specific resolution. 36 | """ 37 | width = vsutil.get_w(height, aspect_ratio=clip.width / clip.height) 38 | 39 | if height not in [480, 576, 720]: 40 | raise ValueError('quick_scale: Please use a AMQ-compliant resolution!') 41 | 42 | if height > clip.height: 43 | return nnedi3_rpow2(clip).resize.Spline36(width, height) 44 | elif height < clip.height: 45 | return core.resize.Spline36(clip, width, height) 46 | else: 47 | return clip 48 | 49 | 50 | def quick_output(clip: vs.VideoNode, bitdepth=8): 51 | """ 52 | A function for quickly preparing the clip for the output node. 53 | """ 54 | if vsutil.get_depth != bitdepth: 55 | return fvf.Depth(clip, bitdepth) 56 | 57 | 58 | def general_filtering(clip: vs.VideoNode, mode='low', denoise=True, deband=True, grain=True): 59 | """ 60 | Generic filterchain. You can set the overall strenght by changing the mode. 61 | 62 | Modes: 63 | - lower 64 | - low 65 | - medium 66 | - high 67 | - higher 68 | """ 69 | 70 | # There's probably way nicer ways to do this 71 | modes = { 72 | 'lower': (0.1, 2, 10, 16, 0.1), 73 | 'low': (0.4, 3, 14, 24, 0.2), 74 | 'medium': (0.6, 4, 16, 32, 0.3), 75 | 'high': (1.0, 6, 18, 40, 0.8), 76 | 'higher': (2.0, 10, 23, 64, 2.0) 77 | } 78 | try: 79 | h, sigma, range, y, grain_strength = modes[mode] 80 | except KeyError: 81 | raise ValueError("general_filtering: Unknown mode!") 82 | 83 | filtered = clip 84 | 85 | if denoise: 86 | filtered = lvf.quick_denoise(filtered, h=h, sigma=sigma) 87 | 88 | if deband: 89 | cb = cr = y - 8 90 | filtered = core.f3kdb.Deband(filtered, range=range, y=y, cb=cb, cr=cr, grainy=0, grainc=0, output_depth=16) 91 | 92 | if grain: 93 | filtered = kgf.adaptive_grain(filtered, strength=grain_strength) 94 | 95 | return filtered 96 | 97 | 98 | def general_antialiasing(src, strength='weak'): 99 | """ 100 | A function for generic antialiasing (AA). Uses either Nnedi3 or Eedi3. 101 | 102 | strength: 103 | - weak (Nnedi3) 104 | - strong (Eedi3) 105 | - stronger (Eedi3SangNom) 106 | """ 107 | strengths = { 108 | 'weak' : 'Nnedi3', 109 | 'strong' : 'Eedi3', 110 | 'stronger' : 'Eedi3SangNom' 111 | } 112 | try: 113 | return taa.TAAmbk(src, aatype=strengths[strength]) 114 | except KeyError: 115 | raise ValueError("general_antialiasing: Unknown strength!") 116 | 117 | 118 | # Helper Functions: 119 | def multi_resample(clip, depth=None): 120 | format = { 121 | 10: vs.YUV420P10, 122 | 12: vs.YUV420P12, 123 | 16: vs.YUV420P16, # You never know~ 124 | } 125 | return core.resize.Spline36(clip, format=format.get(depth, vs.YUV420P8)) 126 | 127 | # PS: I hate everything I've written in here 128 | -------------------------------------------------------------------------------- /encoding/autoencoder/README.md: -------------------------------------------------------------------------------- 1 | # AMQ-autoconverter 2 | A tool that autonomously converts videos for Anime Music Quiz's Expand Library. 3 | 4 | requires that mediainfo and ffmpeg are in the PATH-variable. 5 | 6 | to use start "interface.py" 7 | 8 | this is afaik Windows only. 9 | 10 | Requires Python 3+ with the requests library 11 | -------------------------------------------------------------------------------- /encoding/autoencoder/autoconvert.config: -------------------------------------------------------------------------------- 1 | ffmpeg_path= 2 | mediainfo_path= 3 | mkclean_path= 4 | output_folder_path=output/ 5 | logfile_path=autoconvert.log 6 | max_mean_volume = -16.0 7 | max_peak_volume = -1.0 8 | enable_av1=False 9 | enable_mkclean=False 10 | -------------------------------------------------------------------------------- /encoding/autoencoder/autorescheck.py: -------------------------------------------------------------------------------- 1 | """ 2 | Simple piece of code that checks the current video quality 3 | and then returns the best AMQ option. 4 | 5 | Author: FokjeM / RivenSkaye 6 | Zolhungaj, upgrade for the addition of a config file 7 | """ 8 | import os 9 | import sys 10 | import re 11 | 12 | with open(os.path.dirname(sys.argv[0]) + os.sep + "autoconvert.config") as file: 13 | match = re.search("mediainfo_path" + r"\s?=[ \t\r\f\v]*(.+)$", file.read(), re.I | re.M) 14 | if match is None: 15 | print("ERROR %s missing in config file" % keyword) 16 | input() 17 | exit(0) 18 | else: 19 | mediainfo = match.group(1) 20 | 21 | 22 | def autorescheck(inputfile): 23 | print("Using automatic determination for video resolution!") 24 | command = '%s "%s" --output=Video;%%Height%%' % (mediainfo, inputfile) 25 | process = os.popen(command) 26 | height = int(process.read()) 27 | if height <= 480: # 480 or worse, should be encoded as AMQ 480p 28 | return "9" 29 | elif height < 720: # This falls under the 576p category, because we know it's more than 480p 30 | return "8" 31 | else: # It's 720p or better. Someone has quality content! 32 | return "7" 33 | -------------------------------------------------------------------------------- /encoding/autoencoder/catbox.config: -------------------------------------------------------------------------------- 1 | userhash= 2 | -------------------------------------------------------------------------------- /encoding/autoencoder/catbox.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import datetime 3 | import os 4 | import re 5 | import sys 6 | 7 | def upload(file): 8 | host = "https://catbox.moe/user/api.php" 9 | origname = file 10 | if(re.match(r"^.*\.webm$", file)): 11 | mime_type = "video/webm" 12 | ext = ".webm" 13 | elif(re.match(r"^.*\.mp3$", file)): 14 | mime_type = "audio/mpeg" 15 | ext = ".mp3" 16 | else: 17 | return None 18 | if userhash: 19 | payload = {'reqtype': 'fileupload', 'userhash': userhash} 20 | else: 21 | payload = {'reqtype': 'fileupload'} 22 | timestamp = str(int(datetime.datetime.now().timestamp())) 23 | file = "temp" + timestamp + ext 24 | os.rename(origname, file) # fixes special character errors 25 | f = open(file, 'rb') 26 | files = {'fileToUpload': (file, f, mime_type)} 27 | response = requests.post(host, data=payload, files=files) 28 | f.close() 29 | os.rename(file, origname) 30 | if response.ok: 31 | print("upload success: %s" % response.text) 32 | return response.text 33 | else: 34 | print("upload failed: %s" % response.text) 35 | return None 36 | 37 | 38 | def upload_from_url(url): 39 | print("mirroring %s to catbox" % url) 40 | host = "https://catbox.moe/user/api.php" 41 | if userhash: 42 | payload = {"reqtype": "urlupload", "userhash": userhash, "url": url} 43 | else: 44 | payload = {"reqtype": "urlupload", "url": url} 45 | response = requests.post(host, data=payload) 46 | if response.ok: 47 | print("mirror success: %s" % response.text) 48 | try: 49 | caturl = response.text 50 | source_extension = re.match(r".*\.(\w+)$", url).group(1) 51 | cat_extension = re.match(r".*\.(\w+)$", caturl).group(1) 52 | if cat_extension != source_extension: 53 | f = open("catfail.txt", "a", encoding="utf-8") 54 | f.write("%s -> %s\n" % (url, caturl)) 55 | f.close() 56 | print("%s -> %s" % (url, caturl)) 57 | except Exception: 58 | pass 59 | return response.text 60 | else: 61 | print("mirror failed: %s" % response.text) 62 | return None 63 | 64 | 65 | userhash = None 66 | try: 67 | with open(sys.path[0] + os.sep + "catbox.config") as file: 68 | match = re.search("userhash" + r"\s?=[ \t\r\f\v]*(.+)$", file.read(), re.I | re.M) 69 | if match is None: 70 | print("catbox.py: no userhash present") 71 | else: 72 | userhash = match.group(1) 73 | except Exception: 74 | print("catbox.py: no config file present") 75 | 76 | if __name__ == "__main__": 77 | f = input("select file to upload") 78 | print(upload(f)) 79 | -------------------------------------------------------------------------------- /encoding/autoencoder/interface.config: -------------------------------------------------------------------------------- 1 | upload=True 2 | -------------------------------------------------------------------------------- /encoding/autoencoder/interface.py: -------------------------------------------------------------------------------- 1 | """" 2 | The part of the program the user should be using. 3 | Presents them with a CLI that asks for file info. 4 | 5 | Tries to check what kind of file the user is offering 6 | so the user doesn't have to do anything. 7 | 8 | Authors: Zolhungaj 9 | FokjeM / RivenSkaye (minor tweaks) 10 | """ 11 | 12 | from autoconvert import autoconvert 13 | from autorescheck import autorescheck 14 | import os 15 | import re 16 | import sys 17 | 18 | upload = False 19 | try: 20 | with open(sys.path[0] + os.sep + "interface.config") as file: 21 | match = re.search("upload" + r"\s?=[ \t\r\f\v]*(true|false)$", file.read(), re.I | re.M) 22 | if match is None: 23 | print("interface.py: upload defaults to FALSE") 24 | else: 25 | if match.group(1).lower() == "true": 26 | upload = True 27 | except Exception: 28 | print("interface.py: no config file present") 29 | if upload: 30 | import catbox 31 | 32 | print("Welcome to the autoconverter interface") 33 | while True: 34 | while True: 35 | filename = input("Please type the path/url to the file, or drag and drop the file into the window:\n\t") 36 | if filename.startswith('"') and filename.endswith('"'): 37 | filename = filename[1:-1] 38 | if not os.path.isfile(filename): 39 | if re.match("https?://.*", filename): 40 | # assume webpage is live and is a file 41 | break 42 | print("ERROR: That file does not exist.") 43 | else: 44 | break 45 | while True: 46 | target = input( 47 | """Please choose target resolution: 48 | 0: mp3 49 | 1: 480p 50 | 2: 720p 51 | 3: Source [default] (slow) 52 | 4: All (Source, 720p, 480p, mp3) 53 | 5: All-AMQ submit order (720p, Source, 480p, mp3) 54 | 6: unscaled (like 720p but for esoteric sizes) 55 | 7: AMQ-720 source is 720 or higher (720p, mp3, 480p) 56 | 8: AMQ-576 source is 576 or at least higher than 480 (unscaled, 480p, mp3) 57 | 9: AMQ-480 source is 480 or lower (unscaled, mp3) 58 | Leave blank for auto-determination mode, we'll figure out the details for you. 59 | \t""") 60 | choice = {"0" : 0, "mp3" : 0, "1" : 480, "480p" : 480, "2" : 720, "720p" : 720, "3" : -1, "source" : -1, "4" : -2, "all" : -2, "all-amq" : -2, "5" : -3, "amq-720" : -3, "6" : -4, "unscaled" : -4, "7" : -5, "amq" : -5, "8" : -6, "amq-576" : -6, "9" : -7, "amq-480" : -7} 61 | target = target.lower() 62 | crf = -1 # default value 63 | try: 64 | targetResolution = choice[target] 65 | # Any illegal value causes auto-determination to run. 66 | except KeyError: 67 | targetResolution = choice[autorescheck(filename)] 68 | # range(a, b) is inclusive a and exclusive b! 69 | if (targetResolution in range(-3, 0)): 70 | while True: 71 | try: 72 | string = input( 73 | "Optional: enter your desired crf [0-63]" + 74 | ", default=16\n\t") 75 | if string == "": 76 | crf = 16 77 | break 78 | else: 79 | crf = int(string) 80 | except ValueError as e: 81 | print("That appears to not be a valid integer") 82 | continue 83 | if crf < 0 or crf > 63: 84 | print("%d is not in the range [0-63]") 85 | continue 86 | break 87 | break 88 | animeTitle = input( 89 | "Please enter anime name, this will be used to name the file\n\t") 90 | songType = input("Optional: enter song type\n\t") 91 | songTitle = input("Optional: enter song title\n\t") 92 | songArtist = input("Optional: enter song artist\n\t") 93 | while True: 94 | start = input("Optional: enter start time override\n\t") 95 | if start == "": 96 | start = 0.0 97 | else: 98 | try: 99 | start = float(start) 100 | except ValueError: 101 | print("Invalid value") 102 | continue 103 | if start < 0.0: 104 | print("Invalid value") 105 | continue 106 | break 107 | while True: 108 | end = input("Optional: enter end time override\n\t") 109 | if end == "": 110 | end = 0.0 111 | else: 112 | try: 113 | end = float(end) 114 | except ValueError: 115 | print("Invalid value") 116 | continue 117 | if end < 0.0: 118 | print("Invalid value") 119 | continue 120 | break 121 | try: 122 | res = [] 123 | if targetResolution == -2: 124 | sourcefile = autoconvert(filename, -1, animeTitle, 125 | songType, songTitle, songArtist, 126 | start, end, crf) 127 | res.append(sourcefile) 128 | if start != 0.0 or end != 0.0: 129 | res.append(autoconvert(sourcefile, 0, animeTitle, 130 | songType, songTitle, songArtist, 0.0, 0.0, -1)) 131 | else: 132 | res.append(autoconvert(filename, 0, animeTitle, 133 | songType, songTitle, songArtist, start, end, -1)) 134 | res.append(autoconvert(filename, 480, animeTitle, 135 | songType, songTitle, songArtist, start, end, -1)) 136 | res.append(autoconvert(filename, 720, animeTitle, 137 | songType, songTitle, songArtist, start, end, -1)) 138 | elif targetResolution == -3: 139 | res.append(autoconvert(filename, 720, animeTitle, 140 | songType, songTitle, songArtist, start, end, -1)) 141 | sourcefile = autoconvert(filename, -1, animeTitle, 142 | songType, songTitle, songArtist, 143 | start, end, crf) 144 | res.append(sourcefile) 145 | if start != 0.0 or end != 0.0: 146 | res.append(autoconvert(sourcefile, 0, animeTitle, 147 | songType, songTitle, songArtist, 0.0, 0.0, -1)) 148 | else: 149 | res.append(autoconvert(filename, 0, animeTitle, 150 | songType, songTitle, songArtist, start, end, -1)) 151 | res.append(autoconvert(filename, 480, animeTitle, 152 | songType, songTitle, songArtist, start, end, -1)) 153 | elif targetResolution == -4: # unscaled 154 | res.append(autoconvert(filename, -2, animeTitle, 155 | songType, songTitle, songArtist, start, end, -1)) 156 | elif targetResolution == -5: # amq-720 157 | sourcefile = autoconvert(filename, 720, animeTitle, 158 | songType, songTitle, songArtist, start, end, -1) 159 | res.append(sourcefile) 160 | if start != 0.0 or end != 0.0: 161 | res.append(autoconvert(sourcefile, 0, animeTitle, 162 | songType, songTitle, songArtist, 0.0, 0.0, -1)) 163 | else: 164 | res.append(autoconvert(filename, 0, animeTitle, 165 | songType, songTitle, songArtist, start, end, -1)) 166 | res.append(autoconvert(filename, 480, animeTitle, 167 | songType, songTitle, songArtist, start, end, -1)) 168 | elif targetResolution == -6: # amq-576 169 | sourcefile = autoconvert(filename, -2, animeTitle, 170 | songType, songTitle, songArtist, start, end, -1) 171 | res.append(sourcefile) 172 | if start != 0.0 or end != 0.0: 173 | res.append(autoconvert(sourcefile, 0, animeTitle, 174 | songType, songTitle, songArtist, 0.0, 0.0, -1)) 175 | else: 176 | res.append(autoconvert(filename, 0, animeTitle, 177 | songType, songTitle, songArtist, start, end, -1)) 178 | res.append(autoconvert(filename, 480, animeTitle, 179 | songType, songTitle, songArtist, start, end, -1)) 180 | elif targetResolution == -7: # amq-480 181 | sourcefile = autoconvert(filename, -2, animeTitle, 182 | songType, songTitle, songArtist, start, end, -1) 183 | res.append(sourcefile) 184 | if start != 0.0 or end != 0.0: 185 | res.append(autoconvert(sourcefile, 0, animeTitle, 186 | songType, songTitle, songArtist, 0.0, 0.0, -1)) 187 | else: 188 | res.append(autoconvert(filename, 0, animeTitle, 189 | songType, songTitle, songArtist, start, end, -1)) 190 | else: 191 | res.append(autoconvert(filename, targetResolution, animeTitle, 192 | songType, songTitle, songArtist, start, end, crf)) 193 | print("Job completed") 194 | except Exception as e: 195 | print("During execution, an exception occured:%s" % str(e)) 196 | print("") 197 | if upload: 198 | links = [] 199 | for file in res: 200 | try: 201 | links.append(catbox.upload(file)) 202 | except Exception as e: 203 | print("Could not upload %s: %s" % (file, str(e))) 204 | print(links) 205 | cont = input("Would you like to continue?(y/[n])").lower() 206 | if cont == "": 207 | break 208 | elif cont[0] == "y": 209 | continue 210 | else: 211 | break 212 | -------------------------------------------------------------------------------- /gameplay/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amq-script-project/AMQ-Scripts/69ad64684ca6af2e69cbb5f2169f98305f0c7f1f/gameplay/.keep -------------------------------------------------------------------------------- /gameplay/amqAnswerTimesUtility.user.js: -------------------------------------------------------------------------------- 1 | // AMQ Player Answer Times Utility 2 | // version 1.4 3 | // this script is fetched automatically, do not attempt to add it to tampermonkey 4 | 5 | const amqAnswerTimesUtility = new function() { 6 | "use strict" 7 | this.songStartTime = 0 8 | this.playerTimes = [] 9 | if (typeof(Listener) === "undefined") { 10 | return 11 | } 12 | new Listener("play next song", () => { 13 | this.songStartTime = Date.now() 14 | this.playerTimes = [] 15 | }).bindListener() 16 | 17 | new Listener("player answered", (data) => { 18 | const time = Date.now() - this.songStartTime 19 | data.forEach(gamePlayerId => { 20 | this.playerTimes[gamePlayerId] = time 21 | }) 22 | }).bindListener() 23 | 24 | new Listener("Join Game", (data) => { 25 | const quizState = data.quizState 26 | if (quizState) { 27 | this.songStartTime = Date.now() - quizState.songTimer * 1000 28 | } 29 | }).bindListener() 30 | 31 | new Listener("Spectate Game", (data) => { 32 | const quizState = data.quizState 33 | if (quizState) { 34 | this.songStartTime = Date.now() - quizState.songTimer * 1000 35 | } 36 | }).bindListener() 37 | }() 38 | -------------------------------------------------------------------------------- /gameplay/amqAvatarCount.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name AMQ Avatar Count 3 | // @namespace http://tampermonkey.net/ 4 | // @version 1.1 5 | // @description Shows your avatar count in the avatar screen 6 | // @author Zolhungaj 7 | // @match https://animemusicquiz.com/* 8 | // @grant none 9 | // @downloadURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqAvatarCount.user.js 10 | // @updateURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqAvatarCount.user.js 11 | // @require https://github.com/joske2865/AMQ-Scripts/raw/master/common/amqScriptInfo.js 12 | // @copyright MIT license 13 | // ==/UserScript== 14 | 15 | if (typeof Listener === "undefined") return; 16 | const version = "1.1"; 17 | 18 | const injectNumbers = () => { 19 | const total = storeWindow.topBar.characters.reduce((acc, val) => acc + val.avatars.reduce((acc, val) => acc + val.colors.length, 0), 0) 20 | const current = Object.values(storeWindow.avatarUnlockCount).reduce((acc, val) => acc+ val,0) 21 | $("#swNoSelectionContainer").find("h2").html(`Nothing Selected
Select Above
${current}/${total} unlocked`) 22 | } 23 | let isFirst = true 24 | 25 | storeWindow.toggleOld = storeWindow.toggle 26 | storeWindow.toggle = function(){ 27 | storeWindow.toggleOld() 28 | injectNumbers() 29 | if(isFirst){ 30 | isFirst = false 31 | storeWindow.avatarColumn.newUnlockOld = storeWindow.avatarColumn.newUnlock 32 | storeWindow.avatarColumn.newUnlock = function(){ 33 | storeWindow.avatarColumn.newUnlockOld() 34 | injectNumbers() 35 | } 36 | } 37 | } 38 | 39 | AMQ_addScriptData({ 40 | name: "AMQ Avatar Count", 41 | author: "Zolhungaj", 42 | version: version, 43 | link: "https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqAvatarCount.user.js", 44 | description: `

Shows your avatar count in the avatar screen

` 45 | }) 46 | -------------------------------------------------------------------------------- /gameplay/amqChat.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name AMQ Chat Improvement 3 | // @namespace http://tampermonkey.net/ 4 | // @version 1.0 5 | // @description Makes chat a lot cooler 6 | // @author Zolhungaj 7 | // @match https://animemusicquiz.com/* 8 | // @grant none 9 | // @copyright MIT license 10 | // ==/UserScript== 11 | 12 | if (!window.GameChat) return; 13 | 14 | GameChat.prototype.insertMsg = function (msg) { 15 | if(msg instanceof Object){ 16 | let sum = 0; 17 | let usr = ""; 18 | let self = this; 19 | this.$chatMessageContainer.find('li').each(function () { 20 | let $entry = $(this); 21 | if($entry.find('.gcMessage').length == 0){ 22 | return; 23 | } 24 | if($entry.find('.gcMessage')[0].innerHTML.trim().toLowerCase() === msg.find('.gcMessage')[0].innerHTML.trim().toLowerCase()){ 25 | msg.find('.gcMessage')[0].innerHTML = $entry.find('.gcMessage')[0].innerHTML; 26 | let username = $entry.find('.gcUserName').text(); 27 | let pattern2 = /\w+/; 28 | usr = pattern2.exec(username)[0]; 29 | //if(usr === msg.find('.gcUserName').text()){ 30 | // sum--; //this was silly, anyone else but the original can spam the count up so why not? 31 | //} 32 | let pattern = /\+\d+/; 33 | let result = pattern.exec(username); 34 | if(result){ 35 | sum += + result; 36 | } 37 | sum++; 38 | $entry.remove(); 39 | self.currentMessageCount--; 40 | } 41 | }); 42 | if(sum){ 43 | msg.find('.gcUserName').text(usr + " +" + sum + " (" + msg.find('.gcUserName').text()+ ")"); 44 | } 45 | } 46 | let atBottom = this.$chatMessageContainer.scrollTop() + this.$chatMessageContainer.innerHeight() >= this.$chatMessageContainer[0].scrollHeight - 100; 47 | this.$chatMessageContainer.append(msg); 48 | if (atBottom) { 49 | this.$chatMessageContainer.scrollTop(this.$chatMessageContainer.prop("scrollHeight")); 50 | } 51 | this.$SCROLLABLE_CONTAINERS.perfectScrollbar('update'); 52 | }; 53 | -------------------------------------------------------------------------------- /gameplay/amqExpandLibrary.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name AMQ Expand Library 3 | // @namespace http://tampermonkey.net/ 4 | // @version 1.2 5 | // @description Makes it more ugly and efficient 6 | // @author Juvian 7 | // @match https://animemusicquiz.com/* 8 | // @grant none 9 | // @downloadURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqExpandLibrary.user.js 10 | // @updateURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqExpandLibrary.user.js 11 | // @require https://github.com/joske2865/AMQ-Scripts/raw/master/common/amqScriptInfo.js 12 | // ==/UserScript== 13 | 14 | if (!window.ExpandQuestionList) return; 15 | 16 | const version = "1.2"; 17 | 18 | ExpandQuestionList.prototype.updateQuestionList = function (questions) { 19 | this.clear(); 20 | this.animeEntries = questions.map((entry) => { return new ExpandQuestionListEntry(entry, this); }); 21 | 22 | let entries = new WeakMap(); 23 | 24 | this.animeEntries.forEach(function(entry) { 25 | entry.$songContainer.detach(); 26 | entries.set(entry.$animeEntry[0], entry); 27 | }) 28 | 29 | this._$questionList.off().on("click", ".elQuestionAnime", (ev) => { 30 | let entry = entries.get(ev.currentTarget); 31 | if (entry.open) { 32 | $(ev.currentTarget).parent().append(entry.$songContainer); 33 | } else { 34 | entry.$songContainer.detach(); 35 | } 36 | }); 37 | 38 | this._$questionList.append( 39 | this.animeEntries 40 | .sort((a, b) => { return a.name.localeCompare(b.name); }) 41 | .map(entry => entry.$body) 42 | ) 43 | .append(this._LIST_FILLER_HTML) 44 | .prepend(this._LIST_FILLER_HTML); 45 | 46 | this.topShownQuestionIndex = 0; 47 | this._$questionList.perfectScrollbar("destroy"); 48 | this._$questionList.attr("style", "overflow-y: scroll !important"); 49 | this._QUERY_UPDATE_CHUNK_SiZE = 200; 50 | }; 51 | 52 | ExpandQuestionList.prototype.updateScrollLayout = function () {} 53 | 54 | AMQ_addScriptData({ 55 | name: "Expand Library", 56 | author: "Juvian", 57 | version: version, 58 | link: "https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqExpandLibrary.user.js", 59 | description: ` 60 |

Makes the expand library more ugly and efficient

61 | ` 62 | }); 63 | 64 | AMQ_addStyle(` 65 | .elQuestion.open .elQuestionSongContainer { 66 | display: block; 67 | } 68 | 69 | .elQuestionSongContainer { 70 | display: none; 71 | height: auto !important; 72 | } 73 | `); 74 | -------------------------------------------------------------------------------- /gameplay/amqExpandQuickFix.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name AMQ Expand No Name Bug Patch 3 | // @namespace http://tampermonkey.net/ 4 | // @version 1.1 5 | // @description makes the error where an entry has no name less intrusive 6 | // @author Zolhungaj 7 | // @match https://animemusicquiz.com/* 8 | // @grant none 9 | // @copyright MIT license 10 | // ==/UserScript== 11 | 12 | ExpandQuestionList.prototype.updateQuestionListOld = ExpandQuestionList.prototype.updateQuestionList 13 | const newfun = function(questions){ 14 | questions.forEach(question => { 15 | if(question.name === null){ 16 | question.name = "___" + "ANNID" + question.annId + "_null" 17 | } 18 | }) 19 | this.updateQuestionListOld(questions) 20 | } 21 | ExpandQuestionList.prototype.updateQuestionList = newfun 22 | 23 | -------------------------------------------------------------------------------- /gameplay/amqGetOriginalNameUtility.user.js: -------------------------------------------------------------------------------- 1 | // AMQ Get Original Name Utility 2 | // version 1.2 3 | // this script is fetched automatically, do not attempt to add it to tampermonkey 4 | 5 | const amqGetOriginalNameUtility = new function() { 6 | "use strict" 7 | this._nameToOriginalNameMap = {} 8 | this._profileLock = 0 9 | this._lockoutTime = 1000 10 | this._timeout = 10000 11 | this.getOriginalName = () => Promise.reject("getOriginalName was unable to be defined") 12 | this._wait = (timeMs) => { 13 | return new Promise((resolve, reject) => { 14 | setTimeout(() => { resolve() }, timeMs) 15 | }) 16 | } 17 | this._updateName = async (oldName, newName) => { 18 | delete this._nameToOriginalNameMap[oldName] 19 | await this.getOriginalName(newName) //to ensure integrity 20 | } 21 | 22 | if(typeof(Listener) === "undefined" || typeof(socket) === "undefined"){ 23 | return 24 | } 25 | this.getOriginalName = async (name, timeout=this._timeout) => { 26 | if(this._nameToOriginalNameMap[name]){ 27 | return this._nameToOriginalNameMap[name] 28 | }else{ 29 | const now = Date.now() 30 | if(now < this._profileLock){ 31 | this._profileLock = Math.max(this._profileLock + this._lockoutTime, now + this._lockoutTime) //adds additional time for next to wait, cumulative but always at least lockoutTime seconds 32 | await this._wait(this._profileLock - this._lockoutTime - now) 33 | }else{ 34 | this._profileLock = now + this._lockoutTime 35 | } 36 | return await new Promise((resolve, reject) => { 37 | let timeoutError 38 | const profileListener = new Listener("player profile", (payload) => { 39 | if(payload.name !== name){ 40 | //in theory this shouldn't happen without incredibly slow internet or another more agressive script 41 | }else{ 42 | this._nameToOriginalNameMap[payload.name] = payload.originalName 43 | profileListener.unbindListener() 44 | clearTimeout(timeoutError) 45 | resolve(payload.originalName) 46 | } 47 | }) 48 | profileListener.bindListener() 49 | socket.sendCommand({ 50 | type: 'social', 51 | command: 'player profile', 52 | data: { 53 | name: name 54 | } 55 | }); 56 | timeoutError = setTimeout(() => { 57 | profileListener.unbindListener() 58 | reject(`getOriginalName: timeout for "${name}"`) 59 | }, timeout) 60 | }) 61 | } 62 | } 63 | new Listener("player name change", ({oldName, newName}) => { 64 | this._updateName(oldName,newName) 65 | }).bindListener() 66 | 67 | new Listener("spectator name change", ({oldName, newName}) => { 68 | this._updateName(oldName,newName) 69 | }).bindListener() 70 | 71 | new Listener("friend name change", ({oldName, newName}) => { 72 | this._updateName(oldName,newName) 73 | }).bindListener() 74 | 75 | new Listener("all player name change", ({oldName, newName}) => { 76 | this._updateName(oldName,newName) 77 | }).bindListener() 78 | 79 | new Listener("player profile", (payload) => { 80 | //this just passively collects names in case the user has another userscript doing this stuff, to lessen the load on the server 81 | 82 | //allBadges, empty array unless own profile 83 | //avatar. 84 | // avatarName 85 | // colorName 86 | // optionActive 87 | // optionName 88 | // outfitName 89 | //avatarProfileImage //1 or null 90 | //badges[ 91 | // fileName 92 | // id 93 | // name 94 | // slot 95 | // special 96 | // type 97 | // unlockDescription 98 | //creationDate. 99 | // adminView : boolean 100 | // hidden : boolean 101 | // value : string/null 102 | //guessPercent. 103 | // hidden : boolean 104 | // value : number/null 105 | //level : int 106 | //name : string 107 | //originalName : string 108 | //profileEmoteId : int/null 109 | //songCount. 110 | // hidden : boolean 111 | // value : int/null 112 | this._nameToOriginalNameMap[payload.name] = payload.originalName 113 | 114 | console.log(payload) 115 | }).bindListener() 116 | }() 117 | 118 | const getOriginalName = amqGetOriginalNameUtility.getOriginalName -------------------------------------------------------------------------------- /gameplay/amqLevelGuard.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name AMQ Level Guard 3 | // @version 1.2 4 | // @description Introduces ability to limit level of players 5 | // @author Zolhungaj 6 | // @match https://animemusicquiz.com/* 7 | // @grant none 8 | // @require https://github.com/joske2865/AMQ-Scripts/raw/master/common/amqScriptInfo.js 9 | // @updateURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqLevelGuard.user.js 10 | // @downloadURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqLevelGuard.user.js 11 | // ==/UserScript== 12 | 13 | // Wait until the LOADING... screen is hidden and load script 14 | if (typeof Listener === "undefined") return; 15 | let loadInterval = setInterval(() => { 16 | if ($("#loadingScreen").hasClass("hidden")) { 17 | clearInterval(loadInterval); 18 | setup(); 19 | } 20 | }, 500); 21 | 22 | const version = "1.2" 23 | let maxLevel = -1 // -1: disabled, 0: kick everyone who aren't guest, 1+: kick everyone above that level 24 | let minLevel = 0 // 0: disabled, 1: kick guests, 2+: kick everyone below that level 25 | let instaKick = false // false: move offending players to spectator, true: kick offending players AND spectators 26 | const guestRegex = /^Guest-\d{5}$/ 27 | const userScriptName = "LevelGuard" 28 | const grudges = {} 29 | const grudgeLimit = 3 30 | const command = "/levelguard" 31 | 32 | function setup() { 33 | 34 | new Listener("Host Game", () => { 35 | reset() 36 | }).bindListener() 37 | 38 | new Listener("Spectate Game", () => { 39 | reset() 40 | }).bindListener() 41 | 42 | new Listener("Join Game", () => { 43 | reset() 44 | }).bindListener() 45 | 46 | new Listener("game chat update", ({messages}) => { 47 | messages.forEach(({sender, message}) => { 48 | if (sender === selfName && message.startsWith(command)) { 49 | parseCommand(message) 50 | } 51 | }) 52 | }).bindListener() 53 | 54 | new Listener("New Player", ({name, level}) => { 55 | if(isOn()){ 56 | judgePlayer(name, level) 57 | } 58 | }).bindListener() 59 | 60 | new Listener("Spectator Change To Player", ({name, level}) => { 61 | if(isOn()){ 62 | judgePlayer(name, level) 63 | } 64 | }).bindListener() 65 | 66 | new Listener("New Spectator", ({name}) => { 67 | if(!lobby.isHost || !isOn()){ 68 | return //ignore when not host or when disabled 69 | } 70 | judgeSpectator(name) 71 | }).bindListener() 72 | 73 | AMQ_addScriptData({ 74 | name: "Level Guard", 75 | author: "Zolhungaj", 76 | version: version, 77 | link: "https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqLevelGuard.user.js", 78 | description: ` 79 |

Adds level discrimination to AMQ, a feature to limit highest and lowest level, plus limit game to only guests or only registered players

80 |

Usage:"/levelguard a b c" a and b are required, c is optional

81 |

a is the lowest allowed level (1 to ban guests only), 0 to disable

82 |

b is the highest allowed level, 0 to ban everything but guests, -1 to disable

83 |

c is the instakick toggle, set to 1 to instantly kick offending players and spectators. 0(default) to only move player to spectator

84 | ` 85 | }) 86 | } 87 | 88 | const reset = () => { 89 | maxLevel = -1 90 | minLevel = 0 91 | instaKick = false 92 | } 93 | 94 | const isOn = () => { 95 | return maxLevel !== -1 || minLevel !== 0 96 | } 97 | 98 | const parseCommand = (message) => { 99 | const args = message 100 | .split(/\s+/) 101 | .slice(1) 102 | if(args[0] === "off"){ 103 | reset() 104 | printSuccessToChat(`${userScriptName} disabled`) 105 | } 106 | if(args.length < 2){ 107 | printErrorToChat(`Error, 2 required arguments, got ${args.length}. Usage: ${command} [min level] [max level] <instant kick>`) 108 | return 109 | } 110 | const minLevelArg = Number.parseInt(args[0]) 111 | const maxLevelArg = Number.parseInt(args[1]) 112 | const instaKickArg = Boolean(args[2]) 113 | 114 | if(isNaN(minLevelArg)){ 115 | printErrorToChat("Error, minLevel must be a whole number") 116 | }else if(isNaN(maxLevelArg)){ 117 | printErrorToChat("Error, maxLevel must be a whole number") 118 | }else if(minLevelArg < 0){ 119 | printErrorToChat("Error, minLevel must be in the range [0,∞)") 120 | }else if(maxLevelArg < -1){ 121 | printErrorToChat("Error, maxLevel must be in the range [-1,∞)") 122 | }else if(maxLevelArg === 0 && minLevelArg > 0){ 123 | printErrorToChat("Error, no players can join with these settings") 124 | }else{ 125 | minLevel = minLevelArg 126 | maxLevel = maxLevelArg 127 | instaKick = instaKickArg 128 | if(minLevel === 0 && maxLevel === -1){ 129 | printSuccessToChat(`${userScriptName} disabled`) 130 | }else{ 131 | let successMessage = `${userScriptName} activated:` 132 | if(minLevel > 0){ 133 | successMessage += " guests disabled," 134 | if(minLevel > 1){ 135 | successMessage += ` minimum level:${minLevel},` 136 | } 137 | } 138 | if(maxLevel === 0){ 139 | successMessage += ` registered users disabled,` 140 | }else if(maxLevel > 0){ 141 | successMessage += ` maximum level:${maxLevel},` 142 | } 143 | successMessage += " offenders are " + (instaKick ? "kicked!" : "moved to spectator.") 144 | 145 | printSuccessToChat(successMessage) 146 | activate() 147 | } 148 | } 149 | } 150 | 151 | const printErrorToChat = (message) => { 152 | printToChat(`${message}`) 153 | } 154 | const printSuccessToChat = (message) => { 155 | printToChat(`${message}`) 156 | } 157 | 158 | const printToChat = (message) => { 159 | gameChat.systemMessage(message) 160 | } 161 | 162 | const activate = () => { 163 | Object.keys(lobby.players) 164 | .map(num => lobby.players[num]) 165 | .forEach(({_name, level}) => judgePlayer(_name, level)) 166 | 167 | gameChat.spectators.forEach(({name}) => judgeSpectator(name)) 168 | } 169 | 170 | const judgeSpectator = (playerName) => { 171 | if(!instaKick || playerName === selfName){ 172 | return // do not react to spectators unless they can be kicked, ignore self for practical reasons 173 | } 174 | const profileListener = new Listener("player profile", ({name, level}) => { 175 | if(playerName === name){ 176 | if(!isAllowedLevel(level, playerName)){ 177 | kick(playerName) 178 | } 179 | profileListener.unbindListener() 180 | } 181 | }) 182 | profileListener.bindListener() 183 | socket.sendCommand({ 184 | type: 'social', 185 | command: 'player profile', 186 | data: { 187 | name: playerName 188 | } 189 | }) 190 | } 191 | 192 | const judgePlayer = (playerName, level) => { 193 | if(!isAllowedLevel(level, playerName)){ 194 | if(playerName === selfName){ 195 | lobby.changeToSpectator(playerName) 196 | printErrorToChat("You are outside the set limits and thus not allowed to play :)") 197 | }else if(instaKick){ 198 | kick(playerName) 199 | }else{ 200 | grudges[playerName] = (grudges[playerName] ?? 0) + 1 201 | if(grudges[playerName] > grudgeLimit){ 202 | kick(playerName) 203 | }else{ 204 | lobby.changeToSpectator(playerName) 205 | } 206 | } 207 | } 208 | } 209 | 210 | 211 | const kick = (playerName) => { 212 | socket.sendCommand({ 213 | type: 'lobby', 214 | command: 'kick player', 215 | data: { 216 | playerName: playerName 217 | } 218 | }) 219 | } 220 | 221 | function isAllowedLevel(level, username) { 222 | if(maxLevel !== -1){ 223 | if(maxLevel === 0){ 224 | if(!guestRegex.test(username)){ 225 | return false 226 | } 227 | }else{ 228 | if(level > maxLevel){ 229 | return false 230 | } 231 | } 232 | } 233 | 234 | if(minLevel !== 0){ 235 | if(minLevel === 1){ 236 | if(guestRegex.test(username)){ 237 | return false 238 | } 239 | }else{ 240 | if(level < minLevel){ 241 | return false 242 | } 243 | } 244 | } 245 | return true 246 | } 247 | -------------------------------------------------------------------------------- /gameplay/amqNoDropdown.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name AMQ No Dropdown 3 | // @namespace http://tampermonkey.net/ 4 | // @version 0.2 5 | // @description Disables dropdown for that extra taste of rage. Use ctrl + b to enable/disable dropdown 6 | // @author Juvian 7 | // @match https://animemusicquiz.com/* 8 | // @grant none 9 | // @require https://github.com/joske2865/AMQ-Scripts/raw/master/common/amqScriptInfo.js 10 | // @downloadURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqNoDropdown.user.js 11 | // @updateURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqNoDropdown.user.js 12 | // ==/UserScript== 13 | 14 | if (!window.AutoCompleteController) return; 15 | 16 | const version = 0.2; 17 | let enabled = false; 18 | let realList; 19 | 20 | function changeEnabled() { 21 | enabled = !enabled; 22 | let controller = window?.quiz?.answerInput?.autoCompleteController; 23 | 24 | if (controller) { 25 | controller.newList(); 26 | } 27 | } 28 | 29 | let oldNewList = AutoCompleteController.prototype.newList; 30 | 31 | AutoCompleteController.prototype.newList = function () { 32 | if (this.list.length) realList = this.list; 33 | if (!realList) return; 34 | this.list = enabled ? realList : []; 35 | oldNewList.apply(this, Array.from(arguments)); 36 | } 37 | 38 | document.addEventListener ("keydown", function (zEvent) { 39 | if (zEvent.ctrlKey && zEvent.key.toLowerCase() === 'b') { 40 | changeEnabled(); 41 | } 42 | }); 43 | 44 | AMQ_addScriptData({ 45 | name: "AMQ No Dropdown", 46 | author: "Juvian", 47 | version: version, 48 | link: "https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqNoDropdown.user.js", 49 | description: ` 50 |

Disables dropdown for that extra taste of rage. Use ctrl + b to enable/disable dropdown

51 | ` 52 | }); -------------------------------------------------------------------------------- /gameplay/amqPlayerAnswerTimeDisplay.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name AMQ Player Answer Time Display 3 | // @namespace http://tampermonkey.net/ 4 | // @version 1.12 5 | // @description Makes you able to see how quickly people answered 6 | // @author Zolhungaj 7 | // @match https://animemusicquiz.com/* 8 | // @grant none 9 | // @downloadURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqPlayerAnswerTimeDisplay.user.js 10 | // @updateURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqPlayerAnswerTimeDisplay.user.js 11 | // @require https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqAnswerTimesUtility.user.js 12 | // @require https://github.com/joske2865/AMQ-Scripts/raw/master/common/amqScriptInfo.js 13 | // @copyright MIT license 14 | // ==/UserScript== 15 | 16 | if (typeof Listener === "undefined") return 17 | 18 | const version = "1.12" 19 | let ignoredPlayerIds = [] 20 | 21 | const ignorePlayersRegular = (players) => { 22 | ignoredPlayerIds = [] 23 | const self = players.find(player => player.name === selfName) 24 | if (self?.teamNumber) { 25 | const teamMates = players.filter(player => player.teamNumber === self.teamNumber) 26 | if (teamMates.length > 1) { 27 | ignoredPlayerIds = teamMates.map(player => player.gamePlayerId) 28 | } 29 | } 30 | } 31 | 32 | const ignorePlayersNexus = () => { 33 | ignoredPlayerIds = [1, 2, 3, 4, 5, 6, 7, 8] 34 | } 35 | 36 | new Listener("Game Starting", (data) => { 37 | ignorePlayersRegular(data.players) 38 | }).bindListener() 39 | 40 | new Listener("Join Game", (data) => { 41 | if (data.quizState) { 42 | ignorePlayersRegular(data.quizState.players) 43 | } 44 | }).bindListener() 45 | 46 | new Listener("Spectate Game", () => { 47 | ignoredPlayerIds = [] 48 | }).bindListener() 49 | 50 | new Listener("player late join", () => { 51 | setTimeout(() => { 52 | ignorePlayersRegular(Object.values(quiz.players)) 53 | }, 0) 54 | }).bindListener() 55 | 56 | new Listener("nexus enemy encounter", () => { 57 | ignorePlayersNexus() 58 | }).bindListener() 59 | 60 | new Listener("nexus map rejoin", () => { 61 | ignorePlayersNexus() 62 | }).bindListener() 63 | 64 | new Listener("player answered", (data) => { 65 | data.filter(id => !ignoredPlayerIds.includes(id)).forEach(id => { 66 | let player = quiz.players?.[id] 67 | if (player) { //prevent errors from hidden players 68 | player.answer = amqAnswerTimesUtility.playerTimes[id] + "ms" 69 | } 70 | }) 71 | }).bindListener() 72 | 73 | new Listener("answer results", (data) => { 74 | if (data.lateJoinPlayers) { 75 | setTimeout(() => { 76 | ignorePlayersRegular(Object.values(quiz.players)) 77 | }, 0) 78 | } 79 | }).bindListener() 80 | 81 | quiz._playerAnswerListner = new Listener("player answers", (data) => { 82 | data.answers.forEach((answer) => { 83 | const quizPlayer = quiz.players[answer.gamePlayerId] 84 | let answerText = answer.answer 85 | if (amqAnswerTimesUtility.playerTimes[answer.gamePlayerId] !== undefined) { 86 | answerText += " (" + amqAnswerTimesUtility.playerTimes[answer.gamePlayerId] + "ms)" 87 | } 88 | quizPlayer.answer = answerText 89 | quizPlayer.unknownAnswerNumber = answer.answerNumber 90 | quizPlayer.toggleTeamAnswerSharing(false) 91 | }) 92 | 93 | if (!quiz.isSpectator) { 94 | quiz.answerInput.showSubmitedAnswer() 95 | quiz.answerInput.resetAnswerState() 96 | if (quiz.hintGameMode) { 97 | quiz.hintController.hide() 98 | } 99 | } 100 | 101 | quiz.videoTimerBar.updateState(data.progressBarState) 102 | quizVideoController.checkForBufferingIssue() 103 | }) 104 | 105 | AMQ_addScriptData({ 106 | name: "Player Answer Time Display", 107 | author: "Zolhungaj", 108 | version: version, 109 | link: "https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqPlayerAnswerTimeDisplay.user.js", 110 | description: ` 111 |

Makes you able to see how quickly people answered

112 |

(# ms) will be appended to all players' answers in their answer boxes

113 | ` 114 | }) 115 | -------------------------------------------------------------------------------- /gameplay/amqReplay.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name AMQ Replay 3 | // @namespace http://tampermonkey.net/ 4 | // @version 1.2 5 | // @description Lets you record game events and then replay them 6 | // @author Juvian 7 | // @match https://animemusicquiz.com/* 8 | // @grant none 9 | // @require https://github.com/joske2865/AMQ-Scripts/raw/master/common/amqScriptInfo.js 10 | // ==/UserScript== 11 | 12 | /* 13 | Usage 14 | 1) Login to amq 15 | 2) run let recorder = new ReplayStorage(); recorder.start(); 16 | 3) join a room, spectate a game, host one or whatever 17 | 4) stop recording 18 | 5) recorder.stop(); localStorage.recording = recorder.export(); 19 | 6) emulate a stored game from main screen 20 | 7) run if (window.emulator === undefined) emulator = new ServerEmulator(); emulator.start(JSON.parse(localStorage.recording)); 21 | 22 | To only replay songs which you failed, replace 7) with: if (window.emulator === undefined) emulator = new ServerEmulator(); emulator.start(new GameParser(JSON.parse(localStorage.recording)).onlyFailedSongs()); 23 | */ 24 | 25 | const version = "1.2"; 26 | 27 | //dont store these in replay 28 | const globalIgnore = new Set(["online player count change", "Room Change", "New Rooms", "ping", "pong", "start game", "get all song names"]); 29 | 30 | class ReplayStorage { 31 | constructor() { 32 | this.listener = socket._socket.on("command", this.onCommand.bind(this)); // intercept all commands 33 | this.messages = []; 34 | this.running = false; 35 | } 36 | 37 | onCommand({ command, data }) { 38 | if (this.running && this.shouldStore(command)) { 39 | let toSave = { command, time: new Date().getTime(), data }; 40 | this.messages.push(JSON.parse(JSON.stringify(toSave))); 41 | } 42 | 43 | if (command == "get all song names") { // this makes replays too big, just store in localStorage (requesting while viewing replay is not possible, amq doesnt give you list if not in a real game) 44 | localStorage[command] = JSON.stringify(data); 45 | } 46 | } 47 | 48 | shouldStore(command) { 49 | return !globalIgnore.has(command); 50 | } 51 | 52 | start() { 53 | this.running = true; 54 | this.messages.push({ event: "record", time: new Date().getTime() }) 55 | } 56 | 57 | stop() { 58 | this.running = false; 59 | this.messages.push({ event: "stop", time: new Date().getTime() }) 60 | } 61 | 62 | export() { 63 | return JSON.stringify(this.messages); 64 | } 65 | } 66 | 67 | class ServerEmulator { 68 | constructor() { 69 | if (!ServerEmulator.oldEmit) { 70 | ServerEmulator.oldEmit = io.Socket.prototype.emit; 71 | ServerEmulator.oldGetVideoUrl = MoeVideoPlayer.prototype.getVideoUrl; 72 | } 73 | 74 | this.running = false; 75 | this.logging = true; 76 | } 77 | 78 | emit(command, data) { 79 | this.log("emit", command, data); 80 | 81 | if (this.running && command == 'command' && this.shouldMock(data.command, data.data)) { // dont bother server in our fake game 82 | this.handle(data.command, data.data); 83 | this.log("mocked"); 84 | } else { 85 | ServerEmulator.oldEmit.call(socket._socket, command, data); 86 | } 87 | } 88 | 89 | handle(command, data) { 90 | if (command == "get all song names") { 91 | this.fire(command, JSON.parse(localStorage[command])) 92 | } 93 | } 94 | 95 | shouldMock(command, data) { 96 | return !globalIgnore.has(command) || command == "get all song names"; 97 | } 98 | 99 | serverEmit(command, data) { 100 | this.log(command, data); 101 | 102 | if (command == "Spectate Game") { 103 | roomBrowser.fireSpectateGame(); 104 | } else if (command == "Host Game") { 105 | lobby.setupLobby(data, false); 106 | viewChanger.changeView("lobby"); 107 | } else if (command == "get all song names") { 108 | quiz.answerInput.updateAutocomplete(); 109 | return; 110 | } else if (command == "Join Game") { 111 | roomBrowser.joinLobby(data, false); 112 | } else if (command == "quiz next video info") { // need to replace song url to real one because amq one will stop working after a while 113 | const song = this.parser.songs.find(s => s.info.data == data); 114 | if (song) data.videoInfo.videoMap = song.result.data.songInfo.urlMap; 115 | } 116 | 117 | this.fire(command, data); 118 | 119 | } 120 | 121 | start(messages) { 122 | io.Socket.prototype.emit = this.emit.bind(this); 123 | MoeVideoPlayer.prototype.getVideoUrl = this.getVideoUrl; 124 | this.messages = messages; 125 | this.running = true; 126 | this.current = 0; 127 | this.parser = new GameParser(messages); 128 | this.replayMessage(); 129 | } 130 | 131 | getVideoUrl() { 132 | return this.getNextVideoId(); 133 | } 134 | 135 | stop() { 136 | this.running = false; 137 | if (this.messageTimeout) clearInterval(this.messageTimeout); 138 | io.Socket.prototype.emit = ServerEmulator.oldEmit; 139 | MoeVideoPlayer.prototype.getVideoUrl = ServerEmulator.oldGetVideoUrl; 140 | } 141 | 142 | fire(command, data) { 143 | (socket.listners[command] || []).slice(0).forEach(l => l.fire(data)); 144 | } 145 | 146 | replayMessage() { 147 | if (this.current < this.messages.length) { 148 | const message = this.messages[this.current]; 149 | let diff = this.current + 1 < this.messages.length ? this.messages[this.current + 1].time - this.messages[this.current].time : null; 150 | 151 | this.current++; 152 | if (message.event == "record") { 153 | diff = 0; 154 | } else if (message.event == "stop") { 155 | diff = 0; 156 | } else { 157 | this.serverEmit(message.command, message.data); 158 | } 159 | 160 | if (diff != null) this.messageTimeout = setTimeout(this.replayMessage.bind(this), diff); 161 | } else { 162 | this.stop(); 163 | this.done(); 164 | } 165 | } 166 | 167 | done() { 168 | this.log("replay done"); 169 | } 170 | 171 | log() { 172 | if (this.logging) console.log.apply(null, Array.from(arguments)); 173 | } 174 | } 175 | 176 | class GameParser { 177 | constructor(messages) { 178 | this.messages = messages; 179 | this.parse(); 180 | } 181 | 182 | parse() { 183 | this.players = {}; 184 | this.songs = []; 185 | 186 | let currentSong; 187 | 188 | for (const message of this.messages) { 189 | const idx = message.command == 'quiz next video info' ? (currentSong == null ? 0 : currentSong + 1) : currentSong; 190 | 191 | if (message.command == 'quiz end result') { 192 | break; 193 | } else if (message.command == 'Game Starting') { 194 | this.gameStart = message.data; 195 | for (const player of message.data.players) { 196 | this.players[player.name] = player; 197 | } 198 | } else if (message.command == 'quiz next video info') { 199 | this.songs[idx] = {info: message}; 200 | } else if (message.command == 'quiz ready') { 201 | this.numberOfSongs = message.data.numberOfSongs; 202 | } else if (message.command == 'play next song') { 203 | currentSong = message.data.songNumber - 1; 204 | this.songs[currentSong].play = message; 205 | } else if (message.command == 'answer results') { 206 | this.songs[currentSong].result = message; 207 | } 208 | 209 | if (idx == null) continue; 210 | 211 | this.songs[idx].messages = this.songs[idx].messages || []; 212 | this.songs[idx].messages.push(message); 213 | } 214 | } 215 | 216 | onlyFailedSongs(playerName) { 217 | playerName = playerName || selfName; 218 | 219 | const failedSongs = this.songs.filter(s => s.result.data.players.some(p => p.gamePlayerId == this.players[playerName].gamePlayerId && !p.correct)); 220 | 221 | return this.filterBySongs(failedSongs); 222 | } 223 | 224 | filterBySongs(songs) { 225 | const messages = []; 226 | const references = this.songReferences(songs); 227 | const allReferences = this.songReferences(this.songs); 228 | 229 | for (const message of this.messages) { 230 | if (allReferences.has(message) && !references.has(message)) { 231 | message.time = messages[messages.length - 1].time + 1; 232 | } 233 | messages.push(message); 234 | } 235 | 236 | return messages; 237 | } 238 | 239 | 240 | songReferences(songs) { 241 | const references = new Set(); 242 | 243 | for (const song of songs) { 244 | for (const message of song.messages) references.add(message); 245 | } 246 | 247 | return references; 248 | } 249 | } 250 | 251 | AMQ_addScriptData({ 252 | name: "Replay", 253 | author: "Juvian", 254 | version: version, 255 | link: "https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqReplay.user.js", 256 | description: ` 257 | 266 | ` 267 | }) 268 | 269 | window.ReplayStorage = ReplayStorage; 270 | window.ServerEmulator = ServerEmulator; 271 | window.GameParser = GameParser; -------------------------------------------------------------------------------- /gameplay/amqShowOriginalName.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name AMQ Show Original Name 3 | // @namespace http://tampermonkey.net/ 4 | // @version 1.6 5 | // @description Makes you able to see the original names of players 6 | // @author Zolhungaj 7 | // @match https://animemusicquiz.com/* 8 | // @grant none 9 | // @downloadURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqShowOriginalName.user.js 10 | // @updateURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqShowOriginalName.user.js 11 | // @require https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqGetOriginalNameUtility.user.js 12 | // @require https://github.com/joske2865/AMQ-Scripts/raw/master/common/amqScriptInfo.js 13 | // @copyright MIT license 14 | // ==/UserScript== 15 | 16 | if (typeof Listener === "undefined") return 17 | 18 | const version = "1.6" 19 | let enableOriginalName = true 20 | let alwaysShowOriginalName = false 21 | 22 | const generateNameString = (nickName, originalName, hasNewLine=false) => { 23 | //this could be turned into a one-liner, but it would be a very ugly one-liner 24 | if(!enableOriginalName){ 25 | return nickName 26 | }else if(alwaysShowOriginalName || nickName !== originalName){ 27 | return nickName + (hasNewLine?"\n":"") + "(" + originalName + ")" 28 | }else{ 29 | return nickName 30 | } 31 | } 32 | 33 | gameChat._newSpectatorListner = new Listener( 34 | "New Spectator", 35 | async function (spectator) { 36 | spectator.originalName = spectator.name //fallback 37 | try{ 38 | spectator.originalName = await getOriginalName(spectator.name, 2000) 39 | }catch(error){ 40 | console.error(`unable to resolve new spectator "${spectator.name}" due to error >${error}`) 41 | } 42 | const that = gameChat 43 | that.addSpectator(spectator) 44 | if (that.displayJoinLeaveMessages) { 45 | that.systemMessage(generateNameString(spectator.name, spectator.originalName) + " has started spectating.", "") 46 | } 47 | } 48 | ) 49 | gameChat._forceSpectatorListner = new Listener( 50 | "Player Changed To Spectator", 51 | async function (payload) { 52 | const that = gameChat 53 | const playerName = payload.spectatorDescription.name 54 | let originalName = playerName 55 | try{ 56 | originalName = await getOriginalName(payload.spectatorDescription.name, 2000) 57 | }catch (error){ 58 | console.error(`unable to resolve player who changed to spectator "${playerName}" due to error >${error}`) 59 | } 60 | payload.spectatorDescription.originalName = originalName 61 | that.addSpectator(payload.spectatorDescription, payload.isHost) 62 | if (that.displayJoinLeaveMessages) { 63 | that.systemMessage(generateNameString(playerName, originalName) + " changed to spectator", "") 64 | } 65 | if (selfName === playerName) { 66 | that.setSpectatorButtonState(false) 67 | that.setQueueButtonState(true) 68 | } 69 | } 70 | ) 71 | 72 | lobby.oldAddPlayer = lobby.addPlayer 73 | lobby.addPlayer = function(player, teamFullMap){ 74 | const newPlayer = this.oldAddPlayer(player, teamFullMap) 75 | getOriginalName(newPlayer.name, 2000).then( 76 | (originalName) => { 77 | newPlayer.lobbySlot.originalName = originalName 78 | const that = newPlayer.lobbySlot 79 | Object.defineProperty(that, "name", { 80 | get: function() { return that._name }, 81 | set: function(newName){ 82 | that._name = newName; 83 | that.$NAME_CONTAINER.text(generateNameString(newName, that.originalName)) 84 | if (!that.isSelf) { 85 | setTimeout(() => { 86 | that.setupAvatarOptions() 87 | }, 1) 88 | } 89 | } 90 | }) 91 | that.name = that.name 92 | } 93 | ).catch(e => console.error(`unable to resolve player "${newPlayer.name}" due to error >${e}`)) 94 | return newPlayer 95 | } 96 | 97 | lobby._newPlayerListner = new Listener( 98 | "New Player", 99 | async function (player) { 100 | const that = lobby 101 | const newPlayer = that.addPlayer(player) 102 | try { 103 | player.originalName = await getOriginalName(player.name, 2000) 104 | }catch (error){ 105 | console.error(`unable to resolve player "${player.name}" due to error >${error}`) 106 | player.originalName = player.name 107 | } 108 | newPlayer.originalName = player.originalName 109 | if (that.displayJoinLeaveMessages) { 110 | gameChat.systemMessage(generateNameString(newPlayer.name, newPlayer.originalName) + " joined the room.", "") 111 | } 112 | } 113 | ); 114 | 115 | lobby._spectatorChangeToPlayer = new Listener( 116 | "Spectator Change To Player", 117 | async function (player) { 118 | const that = lobby 119 | player.originalName = player.name //fallback 120 | const newPlayer = that.addPlayer(player) 121 | try{ 122 | player.originalName = await getOriginalName(player.name, 2000) 123 | }catch(error){ 124 | console.error(`unable to resolve spectator who changed to player "${player.name}" due to error >${error}`) 125 | } 126 | newPlayer.originalName = player.originalName 127 | if (that.displayJoinLeaveMessages) { 128 | gameChat.systemMessage(generateNameString(newPlayer.name, newPlayer.originalName) + " changed to player.", "") 129 | } 130 | if (player.name === selfName) { 131 | that.isSpectator = false; 132 | that.updateMainButton(); 133 | gameChat.toggleShowTeamChatSwitch(that.numberOfTeams > 0 && that.isHost); 134 | } 135 | } 136 | ) 137 | 138 | quiz.oldSetupQuiz = quiz.setupQuiz 139 | quiz.setupQuiz = function(players, isSpectator, quizState, settings, isHost, groupSlotMap, soloMode, teamAnswers, selfAnswer,champGame, enemies, avatarAssets){ 140 | const that = quiz 141 | that.oldSetupQuiz(players, isSpectator, quizState, settings, isHost, groupSlotMap, soloMode, teamAnswers, selfAnswer,champGame, enemies, avatarAssets) 142 | players.forEach((player) => { 143 | const thePlayer = this.players[player.gamePlayerId] 144 | getOriginalName(thePlayer.name, 2000).then( 145 | (originalName) => { 146 | thePlayer.originalName = originalName 147 | const that = thePlayer 148 | Object.defineProperty(that, "name", { 149 | get: function() { return that._name }, 150 | set: function(newName){ 151 | that._name = newName 152 | that.avatarSlot.name = generateNameString(newName, that.originalName, true) 153 | that.avatarSlot.updateSize(that.avatarSlot.currentMaxWidth, that.avatarSlot.currentMaxHeight) 154 | } 155 | }) 156 | that.name = that.name 157 | } 158 | ).catch(e => console.error(`unable to resolve player "${thePlayer.name}" due to error >${e}`)) 159 | }) 160 | } 161 | 162 | 163 | GameChat.prototype.oldAddSpectator = GameChat.prototype.addSpectator; 164 | GameChat.prototype.addSpectator = function (spectator, isHost) { 165 | this.oldAddSpectator(spectator, isHost) 166 | const name = spectator.name 167 | const item = $("#gcSpectatorItem-" + name) 168 | const nameField = item.find("h3") 169 | const adjustName = (originalName) =>{ 170 | nameField.find("span").text(generateNameString(name, originalName)) 171 | 172 | //stolen code start 173 | let nameWidth = nameField.innerWidth(); 174 | if (!nameWidth) { 175 | //handle case where spectatorlist is not displayed (nameWidth 0) 176 | $("#measureBox").append(item.clone()); 177 | nameWidth = $("#measureBox").find("h3").innerWidth(); 178 | $("#measureBox").html(""); 179 | item.find(".gcSpectatorIconContainer").css("width", "calc(100% - 15px - " + nameWidth + "px)"); 180 | } else { 181 | this.updateSpectatorNameFontSize(name); 182 | } 183 | //stolen code end 184 | } 185 | if(!spectator.originalName){ 186 | spectator.originalName = getOriginalName(name, 2000).then(adjustName).catch(e => console.error(`unable to resolve spectator "${name}" due to error >${e}`)) 187 | }else{ 188 | adjustName(spectator.originalName) 189 | } 190 | } 191 | 192 | GameChat.prototype.oldAddPlayerToQueue = GameChat.prototype.addPlayerToQueue 193 | GameChat.prototype.addPlayerToQueue = function (name) { 194 | this.oldAddPlayerToQueue(name) 195 | getOriginalName(name, 2000) 196 | .then( 197 | (originalName) => { 198 | this.queueMap[name].find("h3").text(generateNameString(name, originalName)) 199 | }) 200 | .catch(e => console.error(`unable to resolve queued player "${name}" due to error >${e}`)) 201 | } 202 | 203 | AMQ_addScriptData({ 204 | name: "Show Original Name", 205 | author: "Zolhungaj", 206 | version: version, 207 | link: "https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqShowOriginalName.user.js", 208 | description: ` 209 |

Makes you able to see the original names of players

210 | ` 211 | }) 212 | -------------------------------------------------------------------------------- /gameplay/amqShowOriginalOrAlt.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name AMQ Show Alt or Original Name 3 | // @namespace http://tampermonkey.net/ 4 | // @version 1.4 5 | // @description Makes you able to see the original name (or alt name) of a player, incompatible with Original Name 6 | // @author Zolhungaj 7 | // @match https://animemusicquiz.com/* 8 | // @downloadURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqShowOriginalOrAlt.user.js 9 | // @updateURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqShowOriginalOrAlt.user.js 10 | // @require https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqGetOriginalNameUtility.user.js 11 | // @require https://github.com/joske2865/AMQ-Scripts/raw/master/common/amqScriptInfo.js 12 | // @copyright MIT license 13 | // @connect api.zolhungaj.tech 14 | // @grant GM_xmlhttpRequest 15 | // ==/UserScript== 16 | 17 | if (typeof Listener === "undefined") return; 18 | 19 | const version = "1.4" 20 | const username = "" 21 | const password = "" 22 | const isAdmin = false 23 | 24 | let alwaysShowOriginalName = false 25 | const enableOriginalName = true 26 | 27 | const getOriginalNameOrAlt = async (nickname, timeout) => { 28 | let originalName = await getOriginalName(nickname, timeout) 29 | let altName = await getAltName(originalName) 30 | return altName !== "" ? `[${altName}]` : originalName 31 | } 32 | 33 | const getAltName = (originalName) => { 34 | return new Promise((resolve, reject) => { 35 | const settings = { 36 | url: `https://api.zolhungaj.tech/amq/alts/${originalName}?fields=mainName,isUnsure${isAdmin ? "&admin=true" : ""}`, 37 | method: "GET", 38 | timeout: 5000, 39 | user: username, 40 | password: password, 41 | onload: (response) => { 42 | if(response.status === 404){ 43 | resolve("") 44 | }else if(response.status === 200){ 45 | const obj = JSON.parse(response.responseText) 46 | const mainName = obj["mainName"] 47 | const isUnsure = obj["isUnsure"] 48 | if(isUnsure){ 49 | resolve(mainName + "?") 50 | }else{ 51 | resolve(mainName) 52 | } 53 | }else{ 54 | reject(response) 55 | } 56 | } 57 | }; 58 | GM_xmlhttpRequest(settings) 59 | }) 60 | } 61 | 62 | const generateNameString = (nickName, originalName, hasNewLine=false) => { 63 | //this could be turned into a one-liner, but it would be a very ugly one-liner 64 | if(!enableOriginalName){ 65 | return nickName 66 | }else if(alwaysShowOriginalName || nickName !== originalName){ 67 | return nickName + (hasNewLine?"\n":"") + "(" + originalName + ")" 68 | }else{ 69 | return nickName 70 | } 71 | } 72 | 73 | gameChat._newSpectatorListner = new Listener( 74 | "New Spectator", 75 | async function (spectator) { 76 | spectator.originalName = spectator.name //fallback 77 | try{ 78 | spectator.originalName = await getOriginalNameOrAlt(spectator.name, 2000) 79 | }catch(error){ 80 | console.error(`unable to resolve new spectator "${spectator.name}" due to error >${error}`) 81 | } 82 | const that = gameChat 83 | that.addSpectator(spectator) 84 | if (that.displayJoinLeaveMessages) { 85 | that.systemMessage(generateNameString(spectator.name, spectator.originalName) + " has started spectating.", "") 86 | } 87 | } 88 | ) 89 | gameChat._forceSpectatorListner = new Listener( 90 | "Player Changed To Spectator", 91 | async function (payload) { 92 | const that = gameChat 93 | const playerName = payload.spectatorDescription.name 94 | let originalName = playerName 95 | try{ 96 | originalName = await getOriginalNameOrAlt(payload.spectatorDescription.name, 2000) 97 | }catch (error){ 98 | console.error(`unable to resolve player who changed to spectator "${playerName}" due to error >${error}`) 99 | } 100 | payload.spectatorDescription.originalName = originalName 101 | that.addSpectator(payload.spectatorDescription, payload.isHost) 102 | if (that.displayJoinLeaveMessages) { 103 | that.systemMessage(generateNameString(playerName, originalName) + " changed to spectator", "") 104 | } 105 | if (selfName === playerName) { 106 | that.setSpectatorButtonState(false) 107 | that.setQueueButtonState(true) 108 | } 109 | } 110 | ) 111 | 112 | lobby.oldAddPlayer = lobby.addPlayer 113 | lobby.addPlayer = function(player, teamFullMap){ 114 | const newPlayer = this.oldAddPlayer(player, teamFullMap) 115 | getOriginalNameOrAlt(newPlayer.name, 2000).then( 116 | (originalName) => { 117 | newPlayer.lobbySlot.originalName = originalName 118 | const that = newPlayer.lobbySlot 119 | Object.defineProperty(that, "name", { 120 | get: function() { return that._name }, 121 | set: function(newName){ 122 | that._name = newName; 123 | that.$NAME_CONTAINER.text(generateNameString(newName, that.originalName)) 124 | if (!that.isSelf) { 125 | setTimeout(() => { 126 | that.setupAvatarOptions() 127 | }, 1) 128 | } 129 | } 130 | }) 131 | that.name = that.name 132 | } 133 | ).catch(e => console.error(`unable to resolve player "${newPlayer.name}" due to error >${e}`)) 134 | return newPlayer 135 | } 136 | 137 | lobby._newPlayerListner = new Listener( 138 | "New Player", 139 | async function (player) { 140 | const that = lobby 141 | const newPlayer = that.addPlayer(player) 142 | try { 143 | player.originalName = await getOriginalNameOrAlt(player.name, 2000) 144 | }catch (error){ 145 | console.error(`unable to resolve player "${player.name}" due to error >${error}`) 146 | player.originalName = player.name 147 | } 148 | newPlayer.originalName = player.originalName 149 | if (that.displayJoinLeaveMessages) { 150 | gameChat.systemMessage(generateNameString(newPlayer.name, newPlayer.originalName) + " joined the room.", "") 151 | } 152 | } 153 | ); 154 | 155 | lobby._spectatorChangeToPlayer = new Listener( 156 | "Spectator Change To Player", 157 | async function (player) { 158 | const that = lobby 159 | player.originalName = player.name //fallback 160 | const newPlayer = that.addPlayer(player) 161 | try{ 162 | player.originalName = await getOriginalNameOrAlt(player.name, 2000) 163 | }catch(error){ 164 | console.error(`unable to resolve spectator who changed to player "${player.name}" due to error >${error}`) 165 | } 166 | newPlayer.originalName = player.originalName 167 | if (that.displayJoinLeaveMessages) { 168 | gameChat.systemMessage(generateNameString(newPlayer.name, newPlayer.originalName) + " changed to player.", "") 169 | } 170 | if (player.name === selfName) { 171 | that.isSpectator = false; 172 | that.updateMainButton(); 173 | gameChat.toggleShowTeamChatSwitch(that.numberOfTeams > 0 && that.isHost); 174 | } 175 | } 176 | ) 177 | 178 | quiz.oldSetupQuiz = quiz.setupQuiz 179 | quiz.setupQuiz = function(players, isSpectator, quizState, settings, isHost, groupSlotMap, soloMode, teamAnswers, selfAnswer,champGame, enemies, avatarAssets){ 180 | const that = quiz 181 | that.oldSetupQuiz(players, isSpectator, quizState, settings, isHost, groupSlotMap, soloMode, teamAnswers, selfAnswer,champGame, enemies, avatarAssets) 182 | players.forEach((player) => { 183 | const thePlayer = this.players[player.gamePlayerId] 184 | getOriginalNameOrAlt(thePlayer.name, 2000).then( 185 | (originalName) => { 186 | thePlayer.originalName = originalName 187 | const that = thePlayer 188 | Object.defineProperty(that, "name", { 189 | get: function() { return that._name }, 190 | set: function(newName){ 191 | that._name = newName 192 | that.avatarSlot.name = generateNameString(newName, that.originalName, true) 193 | that.avatarSlot.updateSize(that.avatarSlot.currentMaxWidth, that.avatarSlot.currentMaxHeight) 194 | } 195 | }) 196 | that.name = that.name 197 | } 198 | ).catch(e => console.error(`unable to resolve player "${thePlayer.name}" due to error >${e}`)) 199 | }) 200 | } 201 | 202 | 203 | GameChat.prototype.oldAddSpectator = GameChat.prototype.addSpectator; 204 | GameChat.prototype.addSpectator = function (spectator, isHost) { 205 | this.oldAddSpectator(spectator, isHost) 206 | const name = spectator.name 207 | const item = $("#gcSpectatorItem-" + name) 208 | const nameField = item.find("h3") 209 | const adjustName = (originalName) =>{ 210 | nameField.find("span").text(generateNameString(name, originalName)) 211 | 212 | //stolen code start 213 | let nameWidth = nameField.innerWidth(); 214 | if (!nameWidth) { 215 | //handle case where spectatorlist is not displayed (nameWidth 0) 216 | $("#measureBox").append(item.clone()); 217 | nameWidth = $("#measureBox").find("h3").innerWidth(); 218 | $("#measureBox").html(""); 219 | item.find(".gcSpectatorIconContainer").css("width", "calc(100% - 15px - " + nameWidth + "px)"); 220 | } else { 221 | this.updateSpectatorNameFontSize(name); 222 | } 223 | //stolen code end 224 | } 225 | if(!spectator.originalName){ 226 | spectator.originalName = getOriginalNameOrAlt(name, 2000).then(adjustName).catch(e => console.error(`unable to resolve spectator "${name}" due to error >${e}`)) 227 | }else{ 228 | adjustName(spectator.originalName) 229 | } 230 | } 231 | 232 | GameChat.prototype.oldAddPlayerToQueue = GameChat.prototype.addPlayerToQueue 233 | GameChat.prototype.addPlayerToQueue = function (name) { 234 | this.oldAddPlayerToQueue(name) 235 | getOriginalNameOrAlt(name, 2000) 236 | .then( 237 | (originalName) => { 238 | this.queueMap[name].find("h3").text(generateNameString(name, originalName)) 239 | }) 240 | .catch(e => console.error(`unable to resolve queued player "${name}" due to error >${e}`)) 241 | } 242 | 243 | AMQ_addScriptData({ 244 | name: "Show Alt or Original Name", 245 | author: "Zolhungaj", 246 | version: version, 247 | link: "https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqShowOriginalOrAlt.user.js", 248 | description: ` 249 |

Makes you able to see the original name (or alt name) of a player, incompatible with Original Name

250 | ` 251 | }) 252 | -------------------------------------------------------------------------------- /gameplay/amqSpecialCharacters.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name AMQ special character inclusion 3 | // @namespace http://tampermonkey.net/ 4 | // @version 1.0 5 | // @description Gives shortkeys for special characters 6 | // @author Zolhungaj 7 | // @match https://animemusicquiz.com/* 8 | // @downloadURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqSpecialCharacters.user.js 9 | // @updateURL https://github.com/amq-script-project/AMQ-Scripts/raw/master/gameplay/amqSpecialCharacters.user.js 10 | // @grant none 11 | // @copyright MIT license 12 | // ==/UserScript== 13 | const letterMap = { 14 | A:"Ā", a:"ā", 15 | I:"Ī", i:"ī", 16 | U:"Ū", u:"ū", 17 | E:"Ē", e:"ē", 18 | O:"Ō", o:"ō" 19 | } 20 | 21 | document.addEventListener("keydown", function(event) { 22 | const key = event.key 23 | const element = event.target 24 | if (!element || (element.tagName != "INPUT" && element.tagName != "TEXTAREA")) { 25 | return 26 | } 27 | if (event.ctrlKey && letterMap[key]) { 28 | event.preventDefault(); 29 | const replacement = letterMap[key] 30 | let value = element.value 31 | const startPos = element.selectionStart // these are the start and end of a markup selection 32 | const endPos = element.selectionEnd // if these are equal they give the position of the cursor, 33 | // the position is given as how many characters are to the left of the cursor 34 | value = value.slice(0,startPos) + replacement + value.slice(endPos) 35 | element.value = value 36 | element.setSelectionRange(startPos+1, startPos+1) 37 | element.dispatchEvent(new InputEvent("input")) 38 | } 39 | }, false); -------------------------------------------------------------------------------- /gameplay/commonUtilities.js: -------------------------------------------------------------------------------- 1 | //Listener manipulation 2 | //NOTE: make sure replacement functions are arrow functions or use .bind(something) if they use the "this" keyword 3 | 4 | const listeners = new function(){ 5 | "use strict" 6 | this.notInitialized = () => {throw "commonUtilities.listeners was unable to initialize"} 7 | this.create = this.notInitialized 8 | this.on = this.notInitialized 9 | this.once = this.notInitialized 10 | 11 | this.override = (listener, newFunction) => { 12 | //this function overrides the callback function 13 | listener.callback = newFunction 14 | } 15 | this.append = (listener, newFunction) => { 16 | //this appends a function to be executed after the original callback 17 | const oldFire = listener.fire.bind(listener) 18 | listener.fire = (payload) => { 19 | oldFire(payload) 20 | newFunction(payload) 21 | } 22 | } 23 | 24 | this.prepend = (listener, newFunction) => { 25 | //this prepends a function to be executed before the original callback 26 | const oldFire = listener.fire.bind(listener) 27 | listener.fire = (payload) => { 28 | newFunction(payload) 29 | oldFire(payload) 30 | } 31 | } 32 | 33 | this.destroy = (listener) => { 34 | //completely disables a listener 35 | listener.unbindListener() 36 | listener.fire = () => {} 37 | } 38 | if(typeof Listener === "undefined"){ 39 | return 40 | } 41 | 42 | this.create = (command, callback) => { 43 | //creates a new listener 44 | return new Listener(command, callback) 45 | } 46 | this.on = (command, callback) => { 47 | //creates a new listener and immediately binds it 48 | const listener = this.create(command, callback) 49 | listener.bindListener() 50 | return listener 51 | } 52 | this.once = (command, callback) => { 53 | //creates a new listener, binds it and then when it fires it immediately unbinds 54 | const listener = this.on(command, callback) 55 | this.prepend(listener, () => this.destroy(listener)) 56 | return listener 57 | } 58 | }() 59 | 60 | const chat = new function(){ 61 | "use strict" 62 | this.notInitialized = () => {throw "commonUtilities.chat was unable to initialize"} 63 | this.sendMessageRaw = this.notInitialized 64 | this.sendMessageSafe = this.notInitialized 65 | this.sendMessage = this.notInitialized 66 | this.onMessage = this.notInitialized 67 | this.onceMessage = this.notInitialized 68 | this.escapeRegex = (string) => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') //for use with onMessage and onceMessage for literal strings 69 | 70 | if(typeof socket === "undefined" || typeof gameChat === "undefined" || typeof Listener === "undefined"){ 71 | return 72 | } 73 | this.MAX_MESSAGE_LENGTH = gameChat.MAX_MESSAGE_LENGTH 74 | this.sendMessageRaw = async (msg, teamMessage=false) => { 75 | socket.sendCommand({ 76 | type: "lobby", 77 | command: "game chat message", 78 | data: { 79 | msg, 80 | teamMessage, 81 | }, 82 | }) 83 | return new Promise((resolve, reject) => { 84 | let rejectMessage = "timeout" 85 | if(msg.length > this.MAX_MESSAGE_LENGTH){ 86 | rejectMessage ="message was too long" //we know the server won't accept a message that is too long, but maybe someday it will 87 | } 88 | let timeout 89 | const listener = this.onMessage(() => { 90 | clearTimeout(timeout) 91 | listeners.destroy(listener) 92 | resolve() 93 | }, `^${this.escapeRegex(msg)}$`, selfName) 94 | timeout = setTimeout(() => { 95 | listeners.destroy(listener) 96 | reject(rejectMessage) 97 | }, 2000) 98 | }) 99 | } 100 | this.sendMessage = async (msg, teamMessage=false) => { 101 | //this is for sending a single message short message that always fit the rules 102 | if(msg.length > this.MAX_MESSAGE_LENGTH){ 103 | throw `chat.sendMessage: message cannot be longer than ${this.MAX_MESSAGE_LENGTH}` 104 | } 105 | return this.sendMessageRaw(msg, teamMessage) 106 | } 107 | this.sendMessageSafe = async (msg, teamMessage=false) => { 108 | //this will send a series of messages, this particular segment is borrowed from my other project at https://raw.githubusercontent.com/Zolhungaj/Athena/5015ac8003de87c399b0cdc33b34e3554f8429c5/chatMonitor.js 109 | const words = msg.split(" ") 110 | let currentMessage = "" 111 | if (words[0].length > this.MAX_MESSAGE_LENGTH) { 112 | words.splice(0,1,words[0].slice(0,this.MAX_MESSAGE_LENGTH), words[0].slice(this.MAX_MESSAGE_LENGTH)) 113 | } 114 | currentMessage = words[0] //this is to avoid all messages starting with a space 115 | for(let i = 1; i < words.length; i++){ 116 | if(words[i].length > this.MAX_MESSAGE_LENGTH){ 117 | const slicepoint = this.MAX_MESSAGE_LENGTH - currentMessage.length - 1 118 | words.splice(i,1,words[i].slice(0,slicepoint), words[i].slice(slicepoint)) 119 | } 120 | if(currentMessage.length + 1 + words[i].length > this.MAX_MESSAGE_LENGTH){ 121 | await this.sendMessage(currentMessage, teamMessage) 122 | currentMessage = words[i] 123 | }else{ 124 | currentMessage += " " + words[i] 125 | } 126 | } 127 | } 128 | this.onMessage = (callback, messageRegex="", senderName="") => { 129 | //triggers a function when something matches a regex, set messageRegex to "" and senderName to "" to always trigger 130 | messageRegex = new RegExp(messageRegex) 131 | return listeners.on("game chat update", ({messages}) => { 132 | for(const {sender, message} of messages){ 133 | if(messageContent.test(message) && (!senderName || senderName === sender)){ 134 | callback(message, sender) 135 | } 136 | } 137 | }) 138 | } 139 | this.onceMessage = (callback, messageRegex="", senderName="") => { 140 | //triggers a function once when something matches a regex with the correct sender 141 | messageRegex = new RegExp(messageRegex) 142 | const listener = listeners.on("game chat update", ({messages}) => { 143 | for(const {sender, message} of messages){ 144 | if(messageContent.test(message) && (!senderName || senderName === sender)){ 145 | callback(message, sender) 146 | listeners.destroy(listener) 147 | break 148 | } 149 | } 150 | }) 151 | return listener 152 | } 153 | }() 154 | -------------------------------------------------------------------------------- /gameplay/simpleLogger.js: -------------------------------------------------------------------------------- 1 | class SimpleLogger { 2 | #marker 3 | #logLevel 4 | 5 | get logLevel(){ 6 | return this.#logLevel 7 | } 8 | set logLevel(value){ 9 | if(SimpleLogger.VALID_LOG_LEVELS.includes(value)){ 10 | this.#logLevel = value 11 | } 12 | } 13 | constructor(title = "", defaultLogLevel = SimpleLogger.LOG_LEVEL.DEFAULT){ 14 | this.#marker = title 15 | this.#logLevel = defaultLogLevel 16 | } 17 | 18 | static LOG_LEVEL = { 19 | ALL: 0, 20 | FINE : 1, 21 | DEBUG : 2, 22 | INFO : 3, 23 | WARN : 4, 24 | DEFAULT : 5, 25 | ERROR : 6, 26 | FATAL : 7 27 | } 28 | 29 | static VALID_LOG_LEVELS = Object.keys(SimpleLogger.LOG_LEVEL).map((key) => SimpleLogger.LOG_LEVEL[key]) 30 | 31 | /** 32 | * outputs a debug log 33 | * @param {string} title 34 | * @param {string} message 35 | */ 36 | debug = (title= "", message= "") => { 37 | if(this.#shouldLog(SimpleLogger.LOG_LEVEL.DEBUG)){ 38 | this.#log("DEBUG: " + title, message, "simpleLogDebug", "aqua", "aquamarine") 39 | console.debug(this.#marker, title, message) 40 | } 41 | } 42 | 43 | /** 44 | * Outputs an info log 45 | * @param {string} title 46 | * @param {string} message 47 | */ 48 | info = (title= "", message= "") => { 49 | if(this.#shouldLog(SimpleLogger.LOG_LEVEL.INFO)){ 50 | this.#log("INFO: " + title, message, "simpleLogInfo", "lawngreen", "lightgreen") 51 | console.info(this.#marker, title, message) 52 | } 53 | } 54 | 55 | /** 56 | * Outputs a warning log 57 | * @param {string} title 58 | * @param {string} message 59 | */ 60 | warn = (title= "", message= "") => { 61 | if(this.#shouldLog(SimpleLogger.LOG_LEVEL.WARN)){ 62 | this.#log("WARNING: " + title, message, "simpleLogWarn", "gold", "yellow") 63 | console.warn(this.#marker, title, message) 64 | } 65 | } 66 | 67 | /** 68 | * Outputs an error message 69 | * @param {string} title 70 | * @param {string} message 71 | */ 72 | error = (title= "", message = "") => { 73 | if(this.#shouldLog(SimpleLogger.LOG_LEVEL.ERROR)){ 74 | this.#log("ERROR: " + title, message, "simpleLogError", "darkred", "red") 75 | console.error(this.#marker, title, message) 76 | } 77 | } 78 | 79 | /** 80 | * Outputs an error message, and a stack trace into the console 81 | * @param {string} title 82 | * @param {string} message 83 | */ 84 | fatal = (title, message = "") => { 85 | if(this.#shouldLog(SimpleLogger.LOG_LEVEL.FATAL)){ 86 | this.#log("FATAL: " + title, message, "simpleLogFatal", "maroon", "crimson") 87 | console.group(this.#marker) 88 | console.error(title, message) 89 | console.trace() 90 | console.groupEnd() 91 | } 92 | } 93 | 94 | /** 95 | * @param {number} logLevel 96 | * @return {boolean} should log 97 | */ 98 | #shouldLog = (logLevel) => { 99 | return this.#logLevel <= logLevel 100 | } 101 | 102 | /** 103 | * @param {string} title 104 | * @param {string} message 105 | * @param {string} className a legal HTML className 106 | * @param {string} titleColour a legal HTML colour for the title 107 | * @param {string} messageColour a legal HTML colour for the message 108 | */ 109 | #log = (title, message, className, titleColour, messageColour) => { 110 | title = `${this.#marker}: ${title}` 111 | title = this.#escapeMessage(title) 112 | message = this.#escapeMessage(message) 113 | const titleFormatted = `${title}` 114 | let messageFormatted 115 | if(message === ""){ 116 | messageFormatted = "" 117 | }else{ 118 | messageFormatted = `${message}` 119 | } 120 | this.#printToChat(titleFormatted, messageFormatted) 121 | } 122 | 123 | /** 124 | * @param {string} message 125 | * @returns {string} the escaped message 126 | */ 127 | #escapeMessage = (message) => { 128 | return message 129 | .replaceAll("&", "&") 130 | .replaceAll("<", "<") 131 | .replaceAll(">", ">") 132 | } 133 | 134 | /** 135 | * @param {string} title 136 | * @param {string} message 137 | */ 138 | #printToChat = (title, message) => { 139 | window.gameChat.systemMessage(title, message) 140 | } 141 | } 142 | window.SimpleLogger = SimpleLogger -------------------------------------------------------------------------------- /programs/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amq-script-project/AMQ-Scripts/69ad64684ca6af2e69cbb5f2169f98305f0c7f1f/programs/.keep -------------------------------------------------------------------------------- /programs/discord-emote-submission/catbox.config: -------------------------------------------------------------------------------- 1 | userhash= 2 | -------------------------------------------------------------------------------- /programs/discord-emote-submission/catbox.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import datetime 3 | import os 4 | import re 5 | 6 | def upload(file): 7 | origname = file 8 | if(re.match(r"^.*\.png$", file)): 9 | mime_type = "image/png" 10 | ext = ".png" 11 | elif(re.match(r"^.*\.jpe?g$", file)): 12 | mime_type = "image/jpeg" 13 | ext = ".jpg" 14 | elif(re.match(r"^.*\.gif$", file)): 15 | mime_type = "image/gif" 16 | ext = ".gif" 17 | else: 18 | return None 19 | if userhash: 20 | payload = {'reqtype': 'fileupload', 'userhash': userhash} 21 | else: 22 | payload = {'reqtype': 'fileupload'} 23 | timestamp = str(int(datetime.datetime.now().timestamp())) 24 | file = "temp" + timestamp + ext 25 | os.rename(origname, file) # fixes special character errors 26 | f = open(file, 'rb') 27 | files = {'fileToUpload': (file, f, mime_type)} 28 | response = requests.post(host, data=payload, files=files) 29 | f.close() 30 | os.rename(file, origname) 31 | if response.ok: 32 | print("upload success: %s" % response.text) 33 | return response.text 34 | else: 35 | print("upload failed: %s" % response.text) 36 | return None 37 | 38 | 39 | def upload_from_url(url): 40 | print("mirroring %s to catbox" % url) 41 | if userhash: 42 | payload = {"reqtype": "urlupload", "userhash": userhash, "url": url} 43 | else: 44 | payload = {"reqtype": "urlupload", "url": url} 45 | response = requests.post(host, data=payload) 46 | if response.ok: 47 | print("mirror success: %s" % response.text) 48 | return response.text 49 | else: 50 | print("mirror failed: %s" % response.text) 51 | return None 52 | 53 | 54 | def create_album(file_list, title, description): 55 | files = "" 56 | for f in file_list: 57 | files += re.search(r".*/(.*)$", f).group(1)+ " " 58 | payload = {"reqtype": "createalbum", "userhash": userhash, "title":title, "desc":description, "files": files} 59 | response = requests.post(host, data=payload) 60 | if response.ok: 61 | print("album success: %s" % response.text) 62 | return response.text 63 | else: 64 | print("album failed: %s" % response.text) 65 | return None 66 | 67 | 68 | userhash = None 69 | host = "https://catbox.moe/user/api.php" 70 | try: 71 | with open("catbox.config") as file: 72 | match = re.search("userhash" + r"\s?=[ \t\r\f\v]*(.+)$", file.read(), re.I | re.M) 73 | if match is None: 74 | print("catbox.py: no userhash present") 75 | else: 76 | userhash = match.group(1) 77 | except Exception: 78 | print("catbox.py: no config file present") 79 | 80 | if not userhash: 81 | exit(0) 82 | 83 | if __name__ == "__main__": 84 | f = input("select file to upload") 85 | print(upload(f)) 86 | -------------------------------------------------------------------------------- /programs/discord-emote-submission/main.py: -------------------------------------------------------------------------------- 1 | from tkinter.filedialog import askopenfilenames 2 | from catbox import upload, create_album, upload_from_url 3 | import re 4 | import os 5 | 6 | 7 | file_list = [] 8 | small_file_list = [] 9 | while(True): 10 | selection = input("""what wanna do? 11 | 1: upload more files from computer 12 | 2: upload files from urls 13 | 3: make album 14 | 4: quit 15 | """) 16 | if selection == "1": 17 | filenames = askopenfilenames() 18 | for f in filenames: 19 | ret = upload(f) 20 | if ret is not None: 21 | file_list.append(ret) 22 | prefix, suffix = re.match(r"(.+)\.(.+)", f).groups() 23 | scale = 32 24 | outputfile = "%s%dx%d.%s" % (prefix, scale, scale, suffix) 25 | command = 'ffmpeg -y -i "%s" ' % f 26 | command += '-vf "scale=iw*sar*min(%d/(iw*sar)\,%d/ih):ih*min(%d/(iw*sar)\,%d/ih)' % ((scale,)*4) # width:height 27 | # ripped from https://superuser.com/questions/891145/ffmpeg-upscale-and-letterbox-a-video#891478 28 | command += ', pad=%d:%d:-1:-1:000000@0.0" ' % ((scale,)*2) # -1 makes ffmpeg autimatically centre the image, 000000@0.0 is fully transparent black 29 | command += '"%s"' % outputfile 30 | os.system("start /wait /MIN cmd /c %s" % command) 31 | cb = upload(outputfile) 32 | small_file_list.append(cb) 33 | else: 34 | print("failed to upload %s" % f) 35 | elif selection == "2": 36 | urls = input("Gib urls, separate them with space\n") 37 | for url in re.match(r"(?:(.*?)\s)*").groups(): 38 | ret = upload_from_url(url) 39 | if ret is not None: 40 | file_list.append(ret) 41 | prefix, suffix = re.match(r"https://files.catbox.moe/(.+)\.(.+)", ret).groups() 42 | scale = 32 43 | outputfile = "%s%dx%d.%s" % (prefix, scale, scale, suffix) 44 | command = 'ffmpeg -y -i "%s" ' % f 45 | command += '-vf "scale=iw*sar*min(%d/(iw*sar)\,%d/ih):ih*min(%d/(iw*sar)\,%d/ih)' % (scale,)*4 # width:height 46 | # ripped from https://superuser.com/questions/891145/ffmpeg-upscale-and-letterbox-a-video#891478 47 | command += ', pad=%d:%d:-1:-1:000000@0.0" ' % (scale,)*2 # -1 makes ffmpeg autimatically centre the image, 000000@0.0 is fully transparent black 48 | command += '"%s"' % outputfile 49 | os.system("start /wait /MIN cmd /c %s" % command) 50 | cb = upload(outputfile) 51 | small_file_list.append(cb) 52 | elif selection == "3": 53 | title = input("provide title\n") 54 | description = input("explain what's so great about your album\n") 55 | album = create_album(file_list, title, description) 56 | print(album) 57 | for link in small_file_list: 58 | print(link) 59 | file_list = [] 60 | elif selection == "4": 61 | break 62 | -------------------------------------------------------------------------------- /programs/discord-emote-submission/readme.md: -------------------------------------------------------------------------------- 1 | A lazy way to submit a bunch of emotes 2 | -------------------------------------------------------------------------------- /programs/old-expand-but-better/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !*.py 3 | !*.config 4 | !*README* 5 | !.gitignore 6 | *.webm 7 | *.mp3 8 | *.mkv 9 | *.pyc 10 | *.log 11 | *.mka 12 | geckodriver.exe 13 | *dummy* 14 | currentnumber.txt 15 | *.bat 16 | -------------------------------------------------------------------------------- /programs/old-expand-but-better/README.md: -------------------------------------------------------------------------------- 1 | # AMQ-automator 2 | A tool that finds entries matching a particular upload pattern and prompts the user for a video source 3 | 4 | modules 5 | automator - core module, the one usually ran 6 | autoconvert - conversion backbone 7 | catbox - catbox upload handler 8 | find_entry - database search 9 | 10 | automator.config defintion{ 11 | amq-username 12 | amq-password 13 | anilist-username 14 | kitsu-username 15 | seek op in ed 720 480 mp3 random 16 | geckodriver_location 17 | }[ 18 | 1st line is the amq username used to log in 19 | 2nd line is the amq password used to log in 20 | 3rd line is anilist username (leave blank for none) 21 | 4th line is kitsu username (leave blank for none) 22 | 5th line is the mode parameter, valid content{ 23 | "seek": the mode seek, the automator will search for entries matching the other parameters and prompt the user for a good source 24 | 25 | "op": limit the search to openings 26 | "ed": limit the search to endings 27 | "in": limit the search to inserts 28 | "op ed in": can be combined in any way, omitting them is the same as including all of them 29 | 30 | "720": limit the search to songs which have 720p uploaded 31 | "480": limit the search to songs which have 480p uploaded 32 | "mp3": limit the search to songs which have mp3 uploaded 33 | "720 480 mp3": can be combined in any way, vomiting any of them will limit to songs which do not have them uploaded 34 | 35 | "random": scrambles the order, songs will still be grouped by anime. 36 | } 37 | 6th line is the path to geckodriver 38 | ] 39 | -------------------------------------------------------------------------------- /programs/old-expand-but-better/autoconvert.config: -------------------------------------------------------------------------------- 1 | ffmpeg_path=ffmpeg 2 | mediainfo_path=mediainfo 3 | output_folder_path= 4 | logfile_path= 5 | max_mean_volume = -16.0 6 | max_peak_volume = -1.0 7 | 8 | enable_mkclean=False 9 | mkclean_path= 10 | enable_av1=False 11 | -------------------------------------------------------------------------------- /programs/old-expand-but-better/automator.config: -------------------------------------------------------------------------------- 1 | amq-username 2 | amq-password 3 | anilist-username 4 | kitsu-username 5 | seek 6 | geckodriver_location 7 | -------------------------------------------------------------------------------- /programs/old-expand-but-better/automator.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module automates the process of uploading on Anime Music Quiz. 3 | Dependencies: 4 | mediainfo 5 | ffmpeg 6 | selenium 7 | Firefox 8 | geckodriver 9 | """ 10 | import datetime 11 | import time 12 | import re 13 | import os 14 | import random 15 | import _thread 16 | import subprocess 17 | import json 18 | import sqlite3 19 | 20 | import requests 21 | from selenium import webdriver 22 | from selenium.webdriver.common.keys import Keys 23 | from selenium.webdriver.common.action_chains import ActionChains 24 | 25 | from autoconvert import autoconvert as autoc 26 | import catbox 27 | 28 | def init_database(conn): 29 | conn.execute("""CREATE TABLE IF NOT EXISTS upload( 30 | source TEXT, 31 | resolution INTEGER, 32 | anime TEXT, 33 | title TEXT, 34 | type TEXT, 35 | artist TEXT, 36 | filename TEXT, 37 | url TEXT 38 | );""") 39 | conn.execute("""CREATE TABLE IF NOT EXISTS catbox( 40 | source TEXT NOT NULL, 41 | resolution INTEGER NOT NULL, 42 | filename TEXT, 43 | url TEXT NOT NULL 44 | );""") 45 | conn.commit() 46 | 47 | 48 | def save_upload(conn, source, res, anime, title, type, artist, url, 49 | filename=None): 50 | conn.execute("""INSERT INTO upload VALUES( 51 | ?, 52 | ?, 53 | ?, 54 | ?, 55 | ?, 56 | ?, 57 | ?, 58 | ? 59 | )""", (source, res, anime, title, type, artist, filename, url,)) 60 | conn.commit() 61 | 62 | 63 | def save_catbox(conn, source, res, url, filename=None): 64 | conn.execute("""INSERT INTO catbox VALUES( 65 | ?, 66 | ?, 67 | ?, 68 | ? 69 | )""", (source, res, filename, url,)) 70 | conn.commit() 71 | 72 | 73 | def already_uploaded_catbox(conn, source, res): 74 | c = conn.cursor() 75 | c.execute(""" 76 | SELECT url 77 | FROM catbox 78 | WHERE source=(?) 79 | AND resolution = ?""", (source, res,)) 80 | result = c.fetchone() 81 | if result is None: 82 | return None 83 | else: 84 | return result[0] 85 | 86 | 87 | def already_submitted(conn, source, res, anime, title, type, artist): 88 | c = conn.cursor() 89 | c.execute(""" 90 | SELECT url 91 | FROM upload 92 | WHERE source=(?) 93 | AND resolution = ? 94 | AND anime = ? 95 | AND title = ? 96 | AND type = ? 97 | AND artist = ?""", (source, res, anime, title, type, artist,)) 98 | result = c.fetchone() 99 | if result is None: 100 | return None 101 | else: 102 | return result[0] 103 | 104 | 105 | def autoconvert(file, res, anime, type, title, artist): 106 | try: 107 | return autoc(file, res, anime, type, title, artist) 108 | except Exception: 109 | return None 110 | 111 | 112 | def log(message): 113 | """ 114 | logs events to the logfile 115 | """ 116 | # write timestamp message newline to file 117 | timestamp = datetime.datetime.fromtimestamp( 118 | datetime.datetime.now().timestamp()).isoformat() 119 | msg = "[%s]: %s\n" % (timestamp, message) 120 | file = open(logfile, "a", encoding="utf-8") 121 | file.write(msg) 122 | file.close() 123 | print(msg) 124 | 125 | 126 | def enter_and_submit(driver, link): 127 | input_field = driver.find_element_by_id("elQuestionInput") 128 | button = driver.find_element_by_id("elQuestionSubmitButton") 129 | warning_triangle = driver.find_element_by_id("elQuestionInputWarning") 130 | spinner = driver.find_element_by_id("elQuestionInputSpinner") 131 | input_field = driver.find_element_by_id("elQuestionInput") 132 | for i in range(50): 133 | input_field.send_keys(Keys.BACKSPACE) 134 | input_field.send_keys(link) 135 | time.sleep(1) 136 | for i in range(120): 137 | if "hide" in spinner.get_attribute("class"): 138 | break 139 | if i == 60: 140 | input_field.send_keys("a") 141 | time.sleep(0.5) 142 | input_field.send_keys(Keys.BACKSPACE) 143 | time.sleep(1) 144 | else: 145 | for i in range(len(link)+5): 146 | input_field.send_keys(Keys.BACKSPACE) 147 | return False 148 | if "hide" not in warning_triangle.get_attribute("class"): 149 | for i in range(len(link)+5): 150 | input_field.send_keys(Keys.BACKSPACE) 151 | return False 152 | #input("Confirm\n") 153 | button.click() 154 | time.sleep(1) 155 | return True 156 | 157 | 158 | def update_anime_lists(driver, anilist="", kitsu=""): 159 | driver.execute_script('document.getElementById("mpNewsContainer").innerHTML = "Updating AniList...";') 160 | status = driver.find_element_by_id("mpNewsContainer") 161 | driver.execute_script("""new Listener("anime list update result", function (result) { 162 | if (result.success) { 163 | document.getElementById("mpNewsContainer").innerHTML = "Updated Successful: " + result.message; 164 | } else { 165 | document.getElementById("mpNewsContainer").innerHTML = "Update Unsuccessful: " + result.message; 166 | } 167 | }).bindListener()""") 168 | driver.execute_script(""" 169 | socket.sendCommand({ 170 | type: "library", 171 | command: "update anime list", 172 | data: { 173 | newUsername: arguments[0], 174 | listType: 'ANILIST' 175 | } 176 | });""", anilist) 177 | while True: 178 | if status.text != "Updating AniList...": 179 | break 180 | time.sleep(0.5) 181 | driver.execute_script('document.getElementById("mpNewsContainer").innerHTML = "Updating Kitsu...";') 182 | driver.execute_script(""" 183 | socket.sendCommand({ 184 | type: "library", 185 | command: "update anime list", 186 | data: { 187 | newUsername: arguments[0], 188 | listType: 'KITSU' 189 | } 190 | });""", kitsu) 191 | while True: 192 | if status.text != "Updating Kitsu...": 193 | break 194 | time.sleep(0.5) 195 | 196 | 197 | def load_song_info(driver, song_info, animeId, animeName): 198 | print(song_info_pretty(animeId, animeName, song_info)) 199 | driver.execute_script('expandLibrary.songOpened(new ExpandQuestionSongEntry(eval( "(" + arguments[0] + ")" ), arguments[1], arguments[2]))', json.dumps(song_info), animeId, animeName) 200 | 201 | 202 | def get_question_list(driver): 203 | driver.execute_script('document.getElementById("mpNewsContainer").innerHTML = "Loading Expand...";') 204 | script ="""new Listener("expandLibrary questions", function (payload) { 205 | expandLibrary.tackyVariable = (JSON.stringify(payload.questions)); 206 | document.getElementById("mpNewsContainer").innerHTML = "Expand Loaded!" 207 | }).bindListener(); 208 | socket.sendCommand({ 209 | type: "library", 210 | command: "expandLibrary questions" 211 | });""" 212 | driver.execute_script(script) 213 | status = driver.find_element_by_id("mpNewsContainer") 214 | while True: 215 | if status.text != "Loading Expand...": 216 | break 217 | time.sleep(0.5) 218 | time.sleep(3) 219 | pure_string = driver.execute_script('return expandLibrary.tackyVariable') 220 | driver.execute_script('expandLibrary.tackyVariable = ""') 221 | ret = json.loads(pure_string) 222 | driver.execute_script('document.getElementById("mpNewsContainer").innerHTML = "";') 223 | return ret 224 | 225 | 226 | def update_question_list(driver, new_list): 227 | driver.execute_script('expandLibrary.questionListHandler.update_question_list(arguments[0])',json.dumps(new_list)) 228 | 229 | 230 | def open_expand_library(driver): 231 | driver.execute_script(""" document.getElementById("mpNewsContainer").innerHTML = '' """) 232 | ActionChains(driver).move_to_element(driver.find_element_by_id("myfunnyvalentine")).click().perform() 233 | driver.execute_script(""" document.getElementById("mpNewsContainer").innerHTML = '' """) 234 | script = """expandLibrary.openView = function (callback) { 235 | if (xpBar.level < 5) { 236 | displayMessage("Level 5+ required", "To use the Expand Library function, you must be at least level 5"); 237 | viewChanger.changeView("main"); 238 | } else { 239 | this.open = true; 240 | //Add question listener 241 | this._newAnswerListener.bindListener(); 242 | this.$view.removeClass("hidden"); 243 | expandLibrary.afkKicker.setInExpandLibrary(true); 244 | callback(); 245 | this.questionListHandler.updateQuestionList([{"annId":-1,"name":"AUTOMATION","songs":[{"annSongId":-11,"name":"BOT","type":1,"number":1,"artist":"CONTROL","examples":{"720":"https://files.catbox.moe/ll526i.webm","mp3":"https://files.catbox.moe/q2hkle.mp3"},"versions":{"open":{"catbox":{"480":3,"720":1,"mp3":1}},"closed":{"animethemes":{"resolution":null,"status":3},"openingsmoe":{"resolution":null,"status":3}}}}]}]); 246 | } 247 | };""" 248 | driver.execute_script(script) 249 | driver.execute_script('viewChanger.changeView("expandLibrary");') 250 | 251 | 252 | logfile = "AMQ-automator.log" 253 | outputFolder = "../OUTPUT/" # path to output folder 254 | lasttimeout = None 255 | 256 | 257 | def main(): 258 | """ 259 | the main function, where the magic happens 260 | """ 261 | log("AMQ-automator started") 262 | with open("automator.config") as file: 263 | data = file.readlines() 264 | username = data[0][:-1] 265 | password = data[1][:-1] 266 | anilist = data[2][:-1] 267 | kitsu = data[3][:-1] 268 | mode = data[4][:-1].lower() 269 | geckodriver_location = data[5][:-1] 270 | enables = ["720" in mode, "480" in mode, "mp3" in mode] 271 | types = ["op" in mode, "ed" in mode, "in" in mode] 272 | if types == [False, False, False]: 273 | types = [True, True, True] 274 | random_order = "random" in mode 275 | if mode.startswith("convert"): 276 | print("convert mode is disabled") 277 | return 278 | handle_song = convert_song 279 | if enables == [False, False, False]: 280 | enables = [True, True, True] 281 | elif mode.startswith("seek"): 282 | handle_song = find_song_to_upload 283 | else: 284 | handle_song = find_song_to_upload 285 | return 286 | 287 | driver = webdriver.Firefox(executable_path=geckodriver_location) 288 | driver.get('https://animemusicquiz.com') 289 | driver.find_element_by_id("loginUsername").send_keys(username) 290 | driver.find_element_by_id("loginPassword").send_keys(password) 291 | driver.find_element_by_id("loginButton").click() 292 | conn = sqlite3.connect("automator.db") 293 | init_database(conn) 294 | time.sleep(5) 295 | update_anime_lists(driver, anilist, kitsu) 296 | questions = get_question_list(driver) 297 | open_expand_library(driver) 298 | if random_order: 299 | questions = random.sample(questions, len(questions)) 300 | for question in questions: 301 | annId = question["annId"] 302 | name = question["name"] 303 | songs = question["songs"] 304 | if random_order: 305 | songs = random.sample(songs, len(songs)) 306 | for song in songs: 307 | handle_song(driver, conn, annId,name, song, enables, types) 308 | driver.execute_script("options.logout();") 309 | driver.close() 310 | 311 | 312 | def find_song_to_upload(driver, conn, annId, anime, song, exists=[False,False,False], types=[True,True,True]): 313 | source_720 = song["examples"].get("720", None) 314 | source_480 = song["examples"].get("480", None) 315 | source_mp3 = song["examples"].get("mp3", None) 316 | if (source_720 and not exists[0]) or (source_480 and not exists[1]) or (source_mp3 and not exists[2]) or (not source_720 and exists[0]) or (not source_480 and exists[1]) or (not source_mp3 and exists[2]): 317 | return 318 | open_mapping = [False,False,False,True] 319 | catbox_status_720_open = open_mapping[song["versions"]["open"]["catbox"]["720"]] 320 | catbox_status_480_open = open_mapping[song["versions"]["open"]["catbox"]["480"]] 321 | catbox_status_mp3_open = open_mapping[song["versions"]["open"]["catbox"]["mp3"]] 322 | if not (catbox_status_720_open or catbox_status_480_open or catbox_status_mp3_open): 323 | return 324 | title = song["name"] 325 | artist = song["artist"] 326 | type = ["UN", "OP", "ED", "IN"][song["type"]] 327 | if type != "UN" and not types[song["type"]-1]: 328 | return 329 | if type != "IN": 330 | type += str(song["number"]) 331 | load_song_info(driver, song, annId, anime) 332 | source = input("enter filename for a suitable source:\n") 333 | if source == "": 334 | return 335 | video_720 = autoconvert(source, 720, anime, type, title, artist) 336 | if not video_720: 337 | video_720 = autoconvert(source, -2, anime, type, title, artist) 338 | if not video_720: 339 | return 340 | video_480 = autoconvert(source, 480, anime, type, title, artist) 341 | video_mp3 = autoconvert(source, 0, anime, type, title, artist) 342 | if video_720: 343 | cblink = catbox.upload(video_720) 344 | if cblink is not None: 345 | sourceold = source 346 | source = cblink 347 | save_catbox(conn, sourceold, 720, cblink, video_720) 348 | ret = enter_and_submit(driver, cblink) 349 | if ret: 350 | save_upload(conn, sourceold, 720, anime, title, type, artist, cblink, video_720) 351 | else: 352 | return 353 | if video_480: 354 | cblink = catbox.upload(video_480) 355 | if cblink is not None: 356 | save_catbox(conn, source, 480, cblink, video_480) 357 | ret = enter_and_submit(driver, cblink) 358 | if ret: 359 | save_upload(conn, source, 480, anime, title, type, artist, cblink, video_480) 360 | if video_mp3: 361 | cblink = catbox.upload(video_mp3) 362 | if cblink is not None: 363 | save_catbox(conn, source, 0, cblink, video_mp3) 364 | ret = enter_and_submit(driver, cblink) 365 | if ret: 366 | save_upload(conn, source, 0, anime, title, type, artist, cblink, video_mp3) 367 | 368 | 369 | def song_info_pretty(annId, anime, song, reason=None): 370 | ret = "" 371 | ret += "**ANNID**: %d\n" % annId 372 | ret += "**AnnSongId**: %d\n" % song["annSongId"] 373 | ret += "**Anime**: '%s'\n" % anime 374 | ret += "**Title**: '%s'\n" % song["name"] 375 | ret += "**Artist**: '%s'\n" % song["artist"] 376 | ret += "**Type**: %s" % ["UN","OP","ED","IN"][song["type"]] 377 | ret += "%d" % song["number"] if song["type"] != 3 else "" 378 | ret += "\n" 379 | return ret 380 | 381 | 382 | if __name__ == "__main__": 383 | main() 384 | -------------------------------------------------------------------------------- /programs/old-expand-but-better/catbox.config: -------------------------------------------------------------------------------- 1 | userhash= 2 | -------------------------------------------------------------------------------- /programs/old-expand-but-better/catbox.py: -------------------------------------------------------------------------------- 1 | """ 2 | this module uploads files (webm/mp3) to catbox.moe 3 | """ 4 | import datetime 5 | import os 6 | import re 7 | 8 | import requests 9 | 10 | def upload(file): 11 | origname = file 12 | if(re.match(r"^.*\.webm$", file)): 13 | mime_type = "video/webm" 14 | ext = ".webm" 15 | elif(re.match(r"^.*\.mp3$", file)): 16 | mime_type = "audio/mpeg" 17 | ext = ".mp3" 18 | else: 19 | return None 20 | if userhash: 21 | payload = {'reqtype': 'fileupload', 'userhash': userhash} 22 | else: 23 | payload = {'reqtype': 'fileupload'} 24 | timestamp = str(int(datetime.datetime.now().timestamp())) 25 | file = "temp" + timestamp + ext 26 | os.rename(origname, file) # fixes special character errors 27 | f = open(file, 'rb') 28 | files = {'fileToUpload': (file, f, mime_type)} 29 | response = requests.post(host, data=payload, files=files) 30 | f.close() 31 | os.rename(file, origname) 32 | if response.ok: 33 | print("upload success: %s" % response.text) 34 | return response.text 35 | else: 36 | print("upload failed: %s" % response.text) 37 | return None 38 | 39 | 40 | def upload_from_url(url): 41 | print("mirroring %s to catbox" % url) 42 | if userhash: 43 | payload = {"reqtype": "urlupload", "userhash": userhash, "url": url} 44 | else: 45 | payload = {"reqtype": "urlupload", "url": url} 46 | response = requests.post(host, data=payload) 47 | if response.ok: 48 | print("mirror success: %s" % response.text) 49 | try: 50 | caturl = response.text 51 | source_extension = re.match(r".*\.(\w+)$", url).group(1) 52 | cat_extension = re.match(r".*\.(\w+)$", caturl).group(1) 53 | if cat_extension != source_extension: 54 | f = open("catfail.txt", "a", encoding="utf-8") 55 | f.write("%s -> %s\n" % (url, caturl)) 56 | f.close() 57 | print("%s -> %s" % (url, caturl)) 58 | except Exception: 59 | pass 60 | return response.text 61 | else: 62 | print("mirror failed: %s" % response.text) 63 | return None 64 | 65 | 66 | userhash = None 67 | host = "https://catbox.moe/user/api.php" 68 | try: 69 | with open("catbox.config") as file: 70 | match = re.search("userhash" + r"\s?=[ \t\r\f\v]*(.+)$", file.read(), re.I | re.M) 71 | if match is None: 72 | print("catbox.py: no userhash present") 73 | else: 74 | userhash = match.group(1) 75 | except Exception: 76 | print("catbox.py: no config file present") 77 | 78 | if __name__ == "__main__": 79 | f = input("select file to upload") 80 | print(upload(f)) 81 | -------------------------------------------------------------------------------- /programs/old-expand-but-better/find_entry.py: -------------------------------------------------------------------------------- 1 | """ 2 | this module searches through the database of uploaded songs and tries to print a match 3 | """ 4 | 5 | import sqlite3 6 | conn = sqlite3.connect("automator.db") 7 | url = "!" 8 | url = input("input url\n") 9 | while(url != ""): 10 | print() 11 | res = conn.execute("""SELECT 12 | source, 13 | anime, 14 | title, 15 | type, 16 | artist 17 | from upload 18 | WHERE url = ? 19 | ;""", (url,)) 20 | box = res.fetchone() 21 | if box is None: 22 | print("no result") 23 | else: 24 | while box is not None: 25 | print("source url: <" + box[0] + ">") 26 | print("anime: " + box[1]) 27 | print("song title: " + box[2]) 28 | print("type: " + box[3]) 29 | print("artist: " + box[4]) 30 | print() 31 | box = res.fetchone() 32 | url = input("input url\n") 33 | -------------------------------------------------------------------------------- /unofficial-api/node/.gitignore: -------------------------------------------------------------------------------- 1 | data.json 2 | package-lock.json 3 | node_modules -------------------------------------------------------------------------------- /unofficial-api/node/amq-api.js: -------------------------------------------------------------------------------- 1 | const io = require('socket.io-client'); 2 | const request = require("request-promise"); 3 | const fs = require("fs").promises; 4 | const commands = require('./commands/exports'); 5 | const EVENTS = require('./events'); 6 | 7 | const URL = { 8 | signIn: 'https://animemusicquiz.com/signIn', 9 | getToken: 'https://animemusicquiz.com/socketToken', 10 | socket: 'https://socket.animemusicquiz.com' 11 | } 12 | 13 | function sleep(ms) { 14 | return new Promise(resolve => setTimeout(resolve, ms)); 15 | } 16 | 17 | async function getToken(user, pass, path = 'data.json') { 18 | try { 19 | data = await fs.readFile(path, 'utf-8').then(f => JSON.parse(f)); 20 | } catch (e) { 21 | data = {} 22 | } 23 | 24 | if (!data.cookie) { 25 | data.cookie = (await request.post(URL.signIn, {form: {username: user, password: pass}, resolveWithFullResponse: true})).headers['set-cookie'] 26 | } 27 | 28 | data.token = await request.get(URL.getToken, {headers: {'Cookie': data.cookie}}) 29 | 30 | await fs.writeFile(path, JSON.stringify(data)); 31 | 32 | return JSON.parse(data.token); 33 | } 34 | 35 | class Listener { 36 | constructor(eventName, callback, value, socket) { 37 | this.eventName = eventName; 38 | this.callback = callback; 39 | this.socket = socket; 40 | this.value = value; 41 | } 42 | 43 | destroy() { 44 | this.socket.destroy(this); 45 | } 46 | } 47 | 48 | 49 | class SocketWrapper { 50 | constructor() { 51 | this.listeners = {}; 52 | this.dependencies = { 53 | room: 0, 54 | leaderboards: 0, 55 | expand: 0, 56 | online: 0 57 | } 58 | 59 | this.social = new commands.Social(this); 60 | this.roomBrowser = new commands.RoomBrowser(this); 61 | this.expand = new commands.Expand(this); 62 | this.avatar = new commands.Avatar(this); 63 | this.quiz = new commands.Quiz(this); 64 | this.lobby = new commands.Lobby(this); 65 | this.battleRoyal = new commands.BattleRoyal(this); 66 | this.settings = new commands.Settings(this); 67 | this.tutorial = new commands.Tutorial(this); 68 | this.patreon = new commands.Patreon(this); 69 | this.debug = false; 70 | this.timeout = 5000; 71 | } 72 | 73 | connect(token) { 74 | return new Promise((resolve, reject) => { 75 | this.socket = io.connect(URL.socket + ":" + token.port , { 76 | reconnection: true, 77 | reconnectionDelay: 1000, 78 | reconnectionDelayMax: 2000, 79 | reconnectionAttempts: 3, 80 | query: { 81 | token: token.token 82 | } 83 | }); 84 | this.socket.on('sessionId', (sessionId) => { 85 | this.sessionId = sessionId; 86 | resolve(this); 87 | }); 88 | 89 | this.socket.on("disconnect", () => { 90 | console.log("Disconnected from server", "Attempting to reconnect."); 91 | }); 92 | 93 | this.socket.on('reconnect', () => { 94 | console.log("reconnect: " + this.sessionId, "Successfully Reconnected"); 95 | }); 96 | 97 | this.socket.on('reconnect_failed', () => { 98 | console.log("Unable to Reconnect", "Unable to reconnect to the server"); 99 | }); 100 | 101 | this.socket.on('reconnect_attempt', () => { 102 | console.log("Attempting to reconnect: " + this.sessionId); 103 | this.socket.io.opts.query = { 104 | session: this.sessionId 105 | }; 106 | }); 107 | 108 | this.socket.on("error", console.log) 109 | this.socket.on("connect_error", console.log) 110 | 111 | this.socket.on('command', this._processCommand.bind(this)); 112 | }) 113 | } 114 | 115 | disconnect() { 116 | this.socket.disconnect() 117 | } 118 | 119 | on(eventName, callback, value) { 120 | if (!this.listeners[eventName]) this.listeners[eventName] = []; 121 | let listener = new Listener(eventName, callback, value, this); 122 | this.listeners[eventName].push(listener) 123 | this._checkDependencies(eventName); 124 | return listener; 125 | } 126 | 127 | once(eventName, value) { 128 | return new Promise((resolve, reject) => { 129 | this.on(eventName, (data, listener) => { 130 | listener.destroy(); 131 | resolve(data); 132 | }, value) 133 | 134 | setTimeout(() => reject('timeout'), this.timeout); 135 | }) 136 | } 137 | 138 | destroy(listener) { 139 | let arr = this.listeners[listener.eventName]; 140 | let index = arr.indexOf(listener); 141 | 142 | if (index != -1) { 143 | this.listeners[listener.eventName].splice(index, 1); 144 | } 145 | 146 | this._removeDependencies(listener.eventName); 147 | } 148 | 149 | _sendCommand(data, responseEvent, value) { 150 | let promise = this.once(responseEvent, value); 151 | this.socket.emit("command", data); 152 | return promise; 153 | } 154 | 155 | _processCommand(data) { 156 | if (this.debug) console.log(data); 157 | (this.listeners[data.command] || []).concat(this.listeners[EVENTS.ALL] || []).forEach(listener => listener.callback(data.data, listener, data)); 158 | 159 | } 160 | 161 | _startLeaderBoardListeners() { 162 | this.socket.emit("command", {"type":"social","command":"get leaderboard level entries"}); 163 | } 164 | 165 | _stopLeaderBoardListeners() { 166 | this.socket.emit("command", {"type":"social","command":"stop leaderboard listning"}); 167 | 168 | } 169 | 170 | _startRoomListeners() { 171 | this.socket.emit("command", {"type":"roombrowser","command":"get rooms"}); 172 | } 173 | 174 | _stopRoomListeners() { 175 | this.socket.emit("command", {"type":"roombrowser","command":"remove roombrowser listners"}); 176 | } 177 | 178 | _closeExpand() { 179 | this.socket.emit("command", {"type":"library","command":"expandLibrary closed"}); 180 | } 181 | 182 | _stopTrackingOnlineUsers() { 183 | this.socket.emit("command", {"type":"social","command":"stop tracking online users"}); 184 | } 185 | 186 | _checkDependencies(eventName) { 187 | if (eventName == EVENTS.LEADERBOARD) { 188 | if (this.dependencies.leaderboards++ == 0) this._startLeaderBoardListeners(); 189 | } else if (eventName == EVENTS.ROOM_CHANGE || eventName == EVENTS.NEW_ROOMS) { 190 | if (this.dependencies.rooms++ == 0) this._startRoomListeners(); 191 | } else if (eventName == EVENTS.EXPAND_QUESTIONS) { 192 | this.dependencies.expand++; 193 | } else if (eventName == EVENTS.ALL_ONLINE_USERS) { 194 | this.dependencies.online++; 195 | } 196 | } 197 | 198 | _removeDependencies(eventName) { 199 | if (eventName == EVENTS.LEADERBOARD) { 200 | if (--this.dependencies.leaderboards == 0) this._stopLeaderBoardListeners(); 201 | } else if (eventName == EVENTS.ROOM_CHANGE || eventName == EVENTS.NEW_ROOMS) { 202 | if (--this.dependencies.rooms == 0) this._stopLeaderBoardListeners(); 203 | } else if (eventName == EVENTS.EXPAND_QUESTIONS) { 204 | if (--this.dependencies.expand == 0) this._closeExpand(); 205 | } else if (eventName == EVENTS.ALL_ONLINE_USERS) { 206 | if (--this.dependencies.online == 0) this._stopTrackingOnlineUsers(); 207 | } 208 | } 209 | 210 | } 211 | 212 | 213 | module.exports = {SocketWrapper, getToken, EVENTS, sleep} -------------------------------------------------------------------------------- /unofficial-api/node/commands/avatar.js: -------------------------------------------------------------------------------- 1 | const Commands = require('./commands'); 2 | const EVENTS = require('../events'); 3 | 4 | class Avatar extends Commands { 5 | standings() { 6 | return this._sendCommand({type:"avatardrive",command:"get avatar drive standings"}, EVENTS.AVATAR_DRIVE_STANDINGS); 7 | } 8 | 9 | change(avatarId, colorId, optionActive, backgroundAvatarId=avatarId, backgroundColorId=colorId) { 10 | const data = {avatar: {avatarId, colorId, optionActive}, background: {avatarId: backgroundAvatarId, colorId: backgroundColorId}} 11 | return this._sendCommand({type:"avatar",command:"use avatar", data}, EVENTS.USE_AVATAR); 12 | } 13 | 14 | addFavorite(avatarId, colorId, optionActive, backgroundAvatarId=avatarId, backgroundColorId=colorId) { 15 | const data = {avatar: {avatarId, colorId, optionActive}, background: {avatarId: backgroundAvatarId, colorId: backgroundColorId}} 16 | this._sendCommand({type:"avatar",command:"new favorite avatar", data}); 17 | } 18 | 19 | removeFavorite(favoriteId) { 20 | this._sendCommand({type:"avatar",command:"remove favorite avatar", data: {favoriteId}}); 21 | } 22 | 23 | unlock(avatarId, colorId) { 24 | return this._sendCommand({type:"avatar",command:"unlock avatar", data: {avatarId, colorId}}, EVENTS.UNLOCK_AVATAR); 25 | } 26 | 27 | patreonUnlock(avatarId) { 28 | this._sendCommand({type:"patreon",command:"unlock buyable avatar", data: {avatarId}}); 29 | } 30 | 31 | ticketRoll(amount) { 32 | this._sendCommand({type:"patreon",command:"ticket roll", data: {amount}}); 33 | } 34 | 35 | outfits(avatarId) { 36 | return this._sendCommand({type:"avatar",command:"get outfit designs", data: {avatarId}}, EVENTS.OUTFIT_DESIGNS); 37 | } 38 | } 39 | 40 | module.exports = Avatar; -------------------------------------------------------------------------------- /unofficial-api/node/commands/battleRoyal.js: -------------------------------------------------------------------------------- 1 | const Commands = require('./commands'); 2 | const EVENTS = require('../events'); 3 | 4 | class BattleRotal extends Commands { 5 | returnToMap() { 6 | return this._sendCommand({type:"quiz",command:"battle royal return map"}, EVENTS.BATTLE_ROYAL_RETURN_MAP); 7 | } 8 | 9 | executeMove(x, y, deltaTime) { 10 | this._sendCommand({type:"quiz",command:"battle royal position", data: {x, y, deltaTime}}); 11 | } 12 | 13 | selectTile(x, y) { 14 | this._sendCommand({type:"quiz",command:"tile selected", data: {x, y}}); 15 | } 16 | 17 | selectObject(x, y) { 18 | this._sendCommand({type:"quiz",command:"object selected", data: {x, y}}); 19 | } 20 | 21 | selectEntry(x, y, id) { 22 | this._sendCommand({type:"quiz",command:"container entry selected", data: {x, y, id}}); 23 | } 24 | 25 | changeTile(direction) { 26 | this._sendCommand({type:"quiz",command:"change tile", data: {direction}}); 27 | } 28 | 29 | dropEntry(id) { 30 | this._sendCommand({type:"quiz",command:"drop entry"}, {id}); 31 | } 32 | } 33 | 34 | module.exports = BattleRotal; -------------------------------------------------------------------------------- /unofficial-api/node/commands/commands.js: -------------------------------------------------------------------------------- 1 | class Commands { 2 | constructor(socketWrapper) { 3 | this.socketWrapper = socketWrapper; 4 | } 5 | 6 | _sendCommand(...args) { 7 | if (args.length == 1) return this.socketWrapper.socket.emit("command", args[0]); 8 | return this.socketWrapper._sendCommand(...args); 9 | } 10 | 11 | once(...args) { 12 | return this.socketWrapper.once(...args); 13 | } 14 | } 15 | 16 | module.exports = Commands; -------------------------------------------------------------------------------- /unofficial-api/node/commands/expand.js: -------------------------------------------------------------------------------- 1 | const Commands = require('./commands'); 2 | const EVENTS = require('../events'); 3 | 4 | class Expand extends Commands { 5 | songs() { 6 | return this._sendCommand({type:"library",command:"expandLibrary questions"}, EVENTS.EXPAND_QUESTIONS); 7 | } 8 | 9 | submitAnswer(annId, annSongId, url, resolution) { 10 | return this._sendCommand({type:"library",command:"expandLibrary answer", data: {annId, annSongId, url, resolution}}, EVENTS.EXPAND_ANSWER); 11 | } 12 | } 13 | 14 | 15 | module.exports = Expand; -------------------------------------------------------------------------------- /unofficial-api/node/commands/exports.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Quiz: require('./quiz'), 3 | Social: require('./social'), 4 | Avatar: require('./avatar'), 5 | BattleRoyal: require('./battleRoyal'), 6 | Expand: require('./expand'), 7 | Lobby: require('./lobby'), 8 | RoomBrowser: require('./roomBrowser'), 9 | Settings: require('./settings'), 10 | Tutorial: require('./tutorial'), 11 | Patreon: require('./patreon') 12 | } -------------------------------------------------------------------------------- /unofficial-api/node/commands/lobby.js: -------------------------------------------------------------------------------- 1 | const Commands = require('./commands'); 2 | const EVENTS = require('../events'); 3 | 4 | class Lobby extends Commands { 5 | flag(type, targetName, messageId) { 6 | //type: 1=spam, 2=spoiling/hinting, 3=offsensive message 7 | this._sendCommand({type:"lobby",command:"instant mod flag", data: {type, targetName, messageId}}); 8 | } 9 | 10 | kick(playerName) { 11 | this._sendCommand({type:"lobby",command:"kick player", data: {playerName}}); 12 | } 13 | 14 | leaveQueue() { 15 | return this._sendCommand({type:"lobby",command:"leave game queue"}, EVENTS.LEAVE_GAME_QUEUE); 16 | } 17 | 18 | joinQueue() { 19 | return this._sendCommand({type:"lobby",command:"join game queue"}, EVENTS.JOIN_GAME_QUEUE); 20 | } 21 | 22 | leaveTeam() { 23 | this._sendCommand({type:"lobby",command:"leave team"}); 24 | } 25 | 26 | joinTeam(teamNumber) { 27 | this._sendCommand({type:"lobby",command:"join team", data: {teamNumber}}); 28 | } 29 | 30 | shuffleTeams() { 31 | this._sendCommand({type:"lobby",command:"shuffle teams"}); 32 | } 33 | 34 | leaveGame() { 35 | this._sendCommand({type:"lobby",command:"leave game"}); 36 | } 37 | 38 | hostAfk() { 39 | this._sendCommand({type:"lobby",command:"host afk"}); 40 | } 41 | 42 | changeSettings(data) { 43 | this._sendCommand({type:"lobby",command:"change game settings", data}); 44 | } 45 | 46 | changeToSpectator(playerName) { 47 | this._sendCommand({type:"lobby",command:"change player to spectator", data: {playerName}}); 48 | } 49 | 50 | promoteHost(playerName) { 51 | this._sendCommand({type:"lobby",command:"promote host", data: {playerName}}); 52 | } 53 | 54 | changeToPlayer() { 55 | this._sendCommand({type:"lobby",command:"change to player"}); 56 | } 57 | 58 | start() { 59 | this._sendCommand({type:"lobby",command:"start game"}); 60 | } 61 | 62 | setReady(ready) { 63 | this._sendCommand({type:"lobby",command:"set ready", data: {ready}}); 64 | } 65 | } 66 | 67 | module.exports = Lobby; -------------------------------------------------------------------------------- /unofficial-api/node/commands/patreon.js: -------------------------------------------------------------------------------- 1 | const Commands = require('./commands'); 2 | const EVENTS = require('../events'); 3 | 4 | class Patreon extends Commands { 5 | deleteEmoji(emojiId) { 6 | this._sendCommand({type:"patreon",command:"delete emoji", data: {emojiId}}) 7 | } 8 | 9 | freeAvatarDonation(donationType, description, avatarSelected, anon, value) { 10 | //donationType: 1 = existing, 2 = new, 3 = no avatar, just drive 11 | //description, short description of a new avatar, ignored for 1 and 3 12 | //avatarSelected, selected avatar for 1 or backup for 2 13 | //anon: boolean, should name be anonymous? 14 | //value: size of donation 15 | return this._sendCommand({type:"avatardrive",command:"free avatar donation", data: {donationType, description, avatarSelected, anon, value}}, EVENTS.FREE_AVATAR_DONATION) 16 | } 17 | 18 | unlink() { 19 | this._sendCommand({type:"patreon",command:"unlink patreon"}) 20 | } 21 | 22 | requestUpdate() { 23 | this._sendCommand({type:"patreon",command:"request update"}) 24 | } 25 | } 26 | 27 | module.exports = Patreon; -------------------------------------------------------------------------------- /unofficial-api/node/commands/quiz.js: -------------------------------------------------------------------------------- 1 | const Commands = require('./commands'); 2 | const EVENTS = require('../events'); 3 | 4 | class Quiz extends Commands { 5 | 6 | constructor(socketWrapper) { 7 | super(socketWrapper); 8 | this.chat = new QuizChat(socketWrapper); 9 | this.settings = new QuizSettings(socketWrapper); 10 | this.autocomplete = new AutoComplete(socketWrapper); 11 | } 12 | 13 | leaveGame() { 14 | this._sendCommand({type:"lobby",command:"leave game"}); 15 | } 16 | 17 | videoReady(songId) { 18 | this._sendCommand({type:"quiz",command:"video ready", data: {songId}}); 19 | } 20 | 21 | returnToLobby() { 22 | this._sendCommand({type:"quiz",command:"start return lobby vote"}); 23 | } 24 | 25 | submitAnswer(answer, isPlaying, volumeAtMax) { 26 | this._sendCommand({type:"quiz",command:"quiz answer", data: {answer, isPlaying, volumeAtMax}}); 27 | } 28 | 29 | songFeedback(feedbackType, resolution, host, songId, adminReport=false) { 30 | //feedbacktype: 0=none, 1=like, 2=dislike, 3=report 31 | const data = {feedbackType, resolution, host, songId, adminReport} 32 | this._sendCommand({type:"quiz",command:"song feedback", data}); 33 | } 34 | 35 | videoHiddenFeedback(hidden) { 36 | this._sendCommand({type:"quiz",command:"video hidden feedback", data: {hidden}}); 37 | } 38 | 39 | videoError(songId, host, resolution) { 40 | this._sendCommand({type:"quiz",command:"video error", data: {songId, host, resolution}}); 41 | } 42 | 43 | skip(skipVote) { 44 | this._sendCommand({type:"quiz",command:"skip vote", data: {skipVote}}); 45 | } 46 | 47 | voteReturn(accept) { 48 | this._sendCommand({type:"quiz",command:"return lobby vote", data: {accept}}); 49 | } 50 | 51 | pause() { 52 | this._sendCommand({type:"quiz",command:"quiz pause"}); 53 | } 54 | 55 | unpause() { 56 | this._sendCommand({type:"quiz",command:"quiz unpause"}); 57 | } 58 | } 59 | 60 | class QuizChat extends Commands { 61 | send(msg, teamMessage=false) { 62 | this._sendCommand({type:"lobby",command:"game chat message", data: {msg: msg, teamMessage: teamMessage}}); 63 | } 64 | 65 | ban(msg) { 66 | this._sendCommand({type:"lobby",command:"game chat message ban", data: {msg}}); 67 | } 68 | 69 | clearBans() { 70 | this._sendCommand({type:"lobby",command:"game chat clear message ban"}); 71 | } 72 | } 73 | 74 | class QuizSettings extends Commands { 75 | save(name, settingString) { 76 | return this._sendCommand({type:"settings",command:"save quiz settings", data: {name, settingString}}, EVENTS.SAVE_QUIZ_SETTINGS); 77 | } 78 | 79 | delete(id) { 80 | this._sendCommand({type:"settings",command:"delete quiz settings", data: {id}}); 81 | } 82 | } 83 | 84 | class AutoComplete extends Commands { 85 | songs() { 86 | return this._sendCommand({type:"quiz",command:"get all song names"}, EVENTS.GET_SONG_NAMES); 87 | } 88 | 89 | updateSongs(currentVersion) { 90 | return this._sendCommand({type:"quiz",command:"update all song names", data: {currentVersion}}, EVENTS.UPDATE_SONG_NAMES); 91 | } 92 | } 93 | 94 | module.exports = Quiz; 95 | -------------------------------------------------------------------------------- /unofficial-api/node/commands/roomBrowser.js: -------------------------------------------------------------------------------- 1 | const Commands = require('./commands'); 2 | const EVENTS = require('../events'); 3 | 4 | class RoomBrowser extends Commands { 5 | host(settings) { 6 | let command = settings.gameMode === "Solo" ? "host solo room" : "host room" 7 | return this._sendCommand({type:"roombrowser",command, data: settings}, EVENTS.HOST_GAME) 8 | } 9 | 10 | join(gameId, password) { 11 | return this._sendCommand({type:"roombrowser",command:"join game", data: {gameId, password}}, EVENTS.JOIN_GAME); 12 | } 13 | 14 | spectate(gameId, password, gameInvite) { 15 | return this._sendCommand({type:"roombrowser",command:"spectate game", data: {gameId, password, gameInvite}}, EVENTS.SPECTATE_GAME); 16 | } 17 | 18 | rooms() { 19 | return this.once(EVENTS.NEW_ROOMS); 20 | } 21 | 22 | joinRanked() { 23 | this._sendCommand({type:"roombrowser",command:"join ranked game"}) 24 | } 25 | } 26 | 27 | module.exports = RoomBrowser; -------------------------------------------------------------------------------- /unofficial-api/node/commands/settings.js: -------------------------------------------------------------------------------- 1 | const Commands = require('./commands'); 2 | 3 | class Settings extends Commands { 4 | _updateAnimeList(newUsername, listType) { 5 | this._sendCommand({type:"library",command:"update anime list", data: {newUsername, listType}}) 6 | } 7 | 8 | updateMyAnimeList(newUsername) { 9 | this._updateAnimeList(newUsername, 'MAL') 10 | } 11 | 12 | updateAniList(newUsername) { 13 | this._updateAnimeList(newUsername, 'ANILIST') 14 | } 15 | 16 | updateKitsu(newUsername) { 17 | this._updateAnimeList(newUsername, 'KITSU') 18 | } 19 | 20 | updateShareMal(shareMal) { 21 | this._sendCommand({type:"settings",command:"update share mal", data: {shareMal}}) 22 | } 23 | 24 | updateShareScore(shareScore) { 25 | this._sendCommand({type:"settings",command:"update share score", data: {shareScore}}) 26 | } 27 | 28 | _updateListInclusion(listStatus, on) { 29 | this._sendCommand({type:"settings",command:"update use list entry " + listStatus, data: {on}}) 30 | } 31 | 32 | updateIncludeWatching(on) { 33 | this._updateListInclusion("watching", on) 34 | } 35 | 36 | updateIncludeCompleted(on) { 37 | this._updateListInclusion("completed", on) 38 | } 39 | 40 | updateIncludeOnHold(on) { 41 | this._updateListInclusion("on hold", on) 42 | } 43 | 44 | updateIncludeDropped(on) { 45 | this._updateListInclusion("dropped", on) 46 | } 47 | 48 | updateIncludePlanning(on) { 49 | this._updateListInclusion("planning", on) 50 | } 51 | 52 | updateAutoSubmit(autoSubmit) { 53 | this._sendCommand({type:"settings",command:"update auto submit", data: {autoSubmit}}) 54 | } 55 | 56 | updateAutoSkipGuess(autoVote) { 57 | this._sendCommand({type:"settings",command:"update auto vote skip guess", data: {autoVote}}) 58 | } 59 | 60 | updateAutoSkipReplay(autoVote) { 61 | this._sendCommand({type:"settings",command:"update auto vote skip replay", data: {autoVote}}) 62 | } 63 | 64 | updateDisableEmojis(disable) { 65 | this._sendCommand({type:"settings",command:"update disable emojis", data: {disable}}) 66 | } 67 | 68 | updateUseRomajiNames(use) { 69 | this._sendCommand({type:"settings",command:"update use romaji names", data: {use}}) 70 | } 71 | 72 | updateAutoSwitchFavoritedAvatars(switchState) { 73 | this._sendCommand({type:"settings",command:"update auto switch avatars", data: {switchState}}) 74 | } 75 | 76 | sendGameState(inGame, inExpand, inMain) { 77 | this._sendCommand({type:"settings",command:"game state", data: {inGame, inExpand, inMain}}) 78 | } 79 | 80 | guestRegistration(username, password, email, country) { 81 | this._sendCommand({type:"settings",command:"guest registration", data: {username, password, email, country}}) 82 | } 83 | } 84 | 85 | module.exports = Settings; -------------------------------------------------------------------------------- /unofficial-api/node/commands/social.js: -------------------------------------------------------------------------------- 1 | const Commands = require('./commands'); 2 | const EVENTS = require('../events'); 3 | 4 | class Social extends Commands { 5 | 6 | constructor(socketWrapper) { 7 | super(socketWrapper); 8 | this.profile = new Profile(socketWrapper); 9 | } 10 | 11 | leaderboards() { 12 | return this.once(EVENTS.LEADERBOARD); 13 | } 14 | 15 | onlineUsers() { 16 | return this._sendCommand({type:"social",command:"get online users"}, EVENTS.ALL_ONLINE_USERS) 17 | } 18 | 19 | answerFriendRequest(target, accept) { 20 | this._sendCommand({type:"social",command:"friend request response", data: {target, accept}}) 21 | } 22 | 23 | message(target, message) { 24 | this._sendCommand({type:"social",command:"chat message", data: {target, message}}) 25 | } 26 | 27 | openChat(target) { 28 | this._sendCommand({type:"social",command:"opened chat", data: {target}}) 29 | } 30 | 31 | closeChat(target) { 32 | this._sendCommand({type:"social",command:"closed chat", data: {target}}) 33 | } 34 | 35 | invite(target) { 36 | this._sendCommand({type:"social",command:"invite to game", data: {target}}) 37 | } 38 | 39 | addFriend(target) { 40 | this._sendCommand({type:"social",command:"friend request", data: {target}}) 41 | } 42 | 43 | removeFriend(target) { 44 | this._sendCommand({type:"social",command:"remove friend", data: {target}}) 45 | } 46 | 47 | block(target) { 48 | this._sendCommand({type:"social",command:"block player", data: {target}}) 49 | } 50 | 51 | unblock(target) { 52 | this._sendCommand({type:"social",command:"unblock player", data: {target}}) 53 | } 54 | 55 | report(reportType, reportReason, target) { 56 | this._sendCommand({type:"social",command:"report player", data: {reportType, reportReason, target}}) 57 | } 58 | 59 | modStrike(strikeType, reason, target) { 60 | this._sendCommand({type:"social",command:"mod strike", data: {strikeType, reason, target}}) 61 | } 62 | 63 | unlockEmote(emoteId) { 64 | this._sendCommand({type:"avatar",command:"unlock emote", data: {emoteId}}) 65 | } 66 | } 67 | 68 | 69 | class Profile extends Commands { 70 | get(name) { 71 | return this._sendCommand({type:"social",command:"player profile",data:{name: name}}, EVENTS.PLAYER_PROFILE, name) 72 | } 73 | 74 | setImage(avatarImage=undefined, emoteId=undefined) { 75 | this._sendCommand({type:"social",command:"player profile set image", data: {avatarImage, emoteId}}) 76 | } 77 | 78 | showBadge(slotNumber, badgeId) { 79 | this._sendCommand({type:"social",command:"player profile show badge", data: {slotNumber, badgeId}}) 80 | } 81 | 82 | clearBadge(slotNumber) { 83 | this._sendCommand({type:"social",command:"player profile clear badge", data: {slotNumber}}) 84 | } 85 | 86 | setChatBadge(badgeId) { 87 | this._sendCommand({type:"social",command:"player profile set chat badge", data: {badgeId}}) 88 | } 89 | 90 | clearChatBadge(badgeId) { 91 | this._sendCommand({type:"social",command:"player profile clear chat badge", data: {badgeId}}) 92 | } 93 | 94 | toggle(fieldName) { 95 | this._sendCommand({type:"social",command:"player profile toggle hide", data: {fieldName}}) 96 | } 97 | 98 | setList(listId) { 99 | this._sendCommand({type:"social",command:"player profile set list", data: {listId}}) 100 | } 101 | } 102 | 103 | module.exports = Social; -------------------------------------------------------------------------------- /unofficial-api/node/commands/tutorial.js: -------------------------------------------------------------------------------- 1 | const Commands = require('./commands'); 2 | 3 | class Tutorial extends Commands { 4 | skip() { 5 | this._sendCommand({type:"tutorial",command:"tutorial skipped", data: settings}) 6 | } 7 | 8 | complete() { 9 | this._sendCommand({type:"tutorial",command:"completed tutorial", data: settings}) 10 | } 11 | } 12 | 13 | module.exports = Tutorial; -------------------------------------------------------------------------------- /unofficial-api/node/events.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NEW_ROOMS: "New Rooms", 3 | ROOM_CHANGE: "Room Change", 4 | PLAYER_COUNT: "online player count change", 5 | LOGIN_COMPLETE: "login complete", 6 | EXPAND_QUESTIONS: "expandLibrary questions", 7 | PLAYER_PROFILE: "player profile", 8 | LEADERBOARD: "all leaderboard level entries", 9 | SELF_LEADERBOARDS: "self leaderboard level entries", 10 | TOP_LEADERBOARDS: "top leaderboard level entries", 11 | FRIENDS_LEADERBOARDS: "friends leaderboard level entries", 12 | HOST_GAME: "Host Game", 13 | JOIN_GAME: "Join Game", 14 | SPECTATE_GAME: "Spectate Game", 15 | AVATAR_DRIVE_STANDINGS: "avatar drive standings", 16 | PATREON_CHANGED: "patreon changed", 17 | PATREON_DESYNCED: "patreon descyned", //sic 18 | USE_AVATAR: "use avatar", 19 | OUTFIT_DESIGNS: "get outfit designs", 20 | LEAVE_GAME_QUEUE: "leave game queue", 21 | JOIN_GAME_QUEUE: "join game queue", 22 | GET_SONG_NAMES: "get all song names", 23 | UPDATE_SONG_NAMES: "update all song names", 24 | SAVE_QUIZ_SETTINGS: "save quiz settings", 25 | ALL_ONLINE_USERS: "all online users", 26 | ANSWER_RESULTS: "answer results", 27 | GAME_STARTING: "Game Starting", 28 | QUIZ_NEXT_VIDEO_INFO: "quiz next video info", 29 | QUIZ_END_RESULT: "quiz end result", 30 | PLAY_NEXT_SONG: "play next song", 31 | NEW_DONATION: "new donation", 32 | AVATAR_DRIVE_CHANGES: "avatar drive changes", 33 | FREE_AVATAR_DONATION: "free avatar donation", 34 | UNLOCK_AVATAR: "unlock avatar", 35 | LOCK_AVATAR: "lock avatar", 36 | NEW_FAVORITE_AVATAR: "new favorite avatar", 37 | REMOVE_FAVORITE_AVATAR: "remove favorite avatar", 38 | EMOTE_UNLOCKED: "emote unlocked", 39 | EMOTE_LOCKED: "emote locked", 40 | TICKET_ROLL_RESULT: "ticket roll result", 41 | TICKET_ROLL_ERROR: "ticket roll error", 42 | EXPAND_SONG_ANSWERED: "expandLibrary song answered", 43 | EXPAND_ANSWER: "expandLibrary answer", 44 | BATTLE_ROYAL_READY: "battle royal ready", 45 | BATTLE_ROYAL_SPAWN: "battle royal spawn", 46 | BATTLE_ROYAL_OBJECT_DESPAWN: "battle royal object despawn", 47 | BATTLE_ROYAL_ENTRY_COLLECTED: "new collected name entry", 48 | BATTLE_ROYAL_DATA_STORE_CONTENT: "battle royal data store content", 49 | BATTLE_ROYAL_CONTAINER_ENTRY_DESPAWN: "container entry despawn", 50 | BATTLE_ROYAL_SPAWN_PLAYER: "battle royal spawn player", 51 | BATTLE_ROYAL_NEW_PLAYER_POSITION: "battle royal new player position", 52 | BATTLE_ROYAL_PLAYER_DESPAWN: "battle royal despawn player", 53 | BATTLE_ROYAL_PHASE_OVER: "battle royal phase over", 54 | BATTLE_ROYAL_CORRECT_POSITION: "battle royale correct posistion", 55 | BATTLE_ROYAL_TILE_COUNT: "battle royal tile count", 56 | BATTLE_ROYAL_TILE_SPECTATOR_COUNT: "tile spectator count change", 57 | BATTLE_ROYAL_RETURN_MAP: "battle royal return map", 58 | BATTLE_ROYAL_DROP_NAME_ENTRY: "drop name entry", 59 | BATTLE_ROYAL_OBJECT_SPAWN: "battle royal object spawn", 60 | PLAYER_NAME_CHANGE: "player name change", 61 | SPECTATOR_NAME_CHANGE: "spectator name change", 62 | PLAYER_LEFT: "Player Left", 63 | GAME_CHAT_MESSAGE: "Game Chat Message", //legacy, replaced by "game chat update" 64 | //GAME_CHAT_BUBBLE: "game chat buble", //sic, legacy, replaced by "game chat update" 65 | GAME_CHAT_UPDATE: "game chat update", 66 | NEW_SPECTATOR: "New Spectator", 67 | SPECTATOR_LEFT: "Spectator Left", 68 | SPECTATOR_CHANGED_TO_PLAYER: "Spectator Change To Player", 69 | CHANGED_TO_PLAYER: "Change To Player", 70 | KICKED_FROM_GAME: "Kicked From Game", 71 | PLAYER_CHANGED_TO_SPECTATOR: "Player Changed To Spectator", 72 | ROOM_SETTINGS_CHANGED: "Room Settings Changed", 73 | NEW_PLAYER_IN_GAME_QUEUE: "new player in game queue", 74 | PLAYER_LEFT_QUEUE: "player left queue", 75 | HOST_PROMOTION: "Host Promotion", 76 | DELETE_PLAYER_MESSAGES: "delete player messages", 77 | DELETE_CHAT_MESSAGES: "delete chat message", 78 | NEW_PLAYER: "New Player", 79 | JOIN_TEAM: "join team", 80 | SHUFFLE_TEAMS: "shuffle teams", 81 | PLAYER_READY_CHANGE: "Player Ready Change", 82 | AVATAR_CHANGE: "avatar change", 83 | GAME_CLOSED: "game closed", 84 | QUIZ_NO_SONGS: "Quiz no songs", 85 | QUIZ_OVER: "quiz over", 86 | QUIZ_SEND_FEEDBACK: "send feedback", 87 | QUIZ_REJOINING_PLAYER: "Rejoining Player", 88 | QUIZ_READY: "quiz ready", 89 | QUIZ_PLAYER_ANSWERS: "player answers", 90 | QUIZ_WAITING_BUFFERING: "quiz waiting buffering", 91 | QUIZ_XP_CREDIT_GAIN: "quiz xp credit gain", 92 | QUIZ_NO_PLAYERS: "quiz no players", 93 | QUIZ_PLAYER_ANSWERED: "player answered", 94 | QUIZ_TEAM_MEMBER_ANSWERED: "team member answer", 95 | QUIZ_OVERLAY_MESSAGE: "quiz overlay message", 96 | QUIZ_SKIP_MESSAGE: "quiz skip message", 97 | QUIZ_RETURN_LOBBY_VOTE_START: "return lobby vote start", 98 | QUIZ_GUESS_PHASE_OVER: "guess phase over", 99 | QUIZ_FATAL_ERROR: "quiz fatal error", 100 | QUIZ_ANSWER: "quiz answer", 101 | QUIZ_RETURN_LOBBY_VOTE_RESULT: "return lobby vote result", 102 | QUIZ_SETTING_DELETED: "quiz setting deleted", 103 | QUIZ_SAVE_SETTINGS: "save quiz settings", 104 | QUIZ_PAUSE_TRIGGERED: "quiz pause triggered", 105 | QUIZ_UNPAUSE_TRIGGERED: "quiz unpause triggered", 106 | RANKED_STANDING_UPDATED: "ranked standing updated", 107 | RANKED_CHAMPIONS_UPDATED: "ranked champions updated", 108 | RANKED_GAME_STATE_CHANGE: "ranked game state change", 109 | COMPLETED_TUTORIAL: "completed tutorial", 110 | ONLINE_USER_CHANGE: "online user change", 111 | ALL_PLAYER_NAME_CHANGE: "all player name change", 112 | NEW_FRIEND_REQUEST_RECEIVED: "new friend request recived", 113 | GAME_INVITE: "game invite", 114 | NEW_CHAT_ALERT: "new chat alert", 115 | CHAT_MESSAGE: "chat message", 116 | SERVER_MESSAGE: "server message", 117 | PLAYER_ONLINE_CHANGE: "player online change", 118 | CHAT_MESSAGE_RESPONSE: "chat message response", 119 | NEW_FRIEND: "new friend", 120 | FRIEND_REMOVED: "friend removed", 121 | FRIEND_STATE_CHANGE: "friend state change", 122 | FRIEND_NAME_CHANGE: "friend name change", 123 | FRIEND_REQUEST: "friend request", 124 | XP_CREDIT_CHANGE: "xp credit change", 125 | TICKET_UPDATE: "ticket update", 126 | EMOJI_DELETED: "emoji deleted", 127 | EMOJI_APPROVED: "emoji approved", 128 | MAL_LAST_UPDATE: "malLastUpdate update", 129 | ANILIST_LAST_UPDATE: "aniListLastUpdate update", 130 | KITSU_LAST_UPDATE: "kitsuLastUpdate update", 131 | MAL_UPDATE_RESULT: "mal update result", 132 | ANILIST_UPDATE_RESULT: "anime list update result", 133 | SERVER_STATE_CHANGED: "server state change", 134 | GUEST_REGISTRATION: "guest registration", 135 | ACCOUNT_VERIFIED: "account verified", 136 | FORCED_LOGOFF: "force logoff", 137 | ALERT: "alert", 138 | UNKNOWN_ERROR: "unknown error", 139 | SERVER_RESTART: "server restart", 140 | POPOUT_MESSAGE: "popout message", 141 | RANKED_SCORE_UPDATE: "ranked score update", 142 | ALL: "*" 143 | } -------------------------------------------------------------------------------- /unofficial-api/node/examples/allEvents.js: -------------------------------------------------------------------------------- 1 | const {SocketWrapper, getToken, EVENTS, sleep} = require('../amq-api'); 2 | 3 | async function main() { 4 | let token = await getToken("juvian", "xxx", '../data.json'); 5 | let socket = new SocketWrapper() 6 | 7 | let listener = socket.on(EVENTS.ALL, (data, listener, fullData) => { 8 | console.log(data); 9 | }); 10 | 11 | await socket.connect(token); 12 | 13 | listener.destroy(); 14 | 15 | await sleep(1000); 16 | 17 | socket.disconnect(); 18 | } 19 | 20 | main(); 21 | -------------------------------------------------------------------------------- /unofficial-api/node/examples/onlineplayerCount.js: -------------------------------------------------------------------------------- 1 | const {SocketWrapper, getToken, EVENTS, sleep} = require('../amq-api'); 2 | 3 | async function main() { 4 | let token = await getToken("juvian", "xxx", '../data.json'); 5 | let socket = await new SocketWrapper().connect(token); 6 | 7 | socket.on(EVENTS.PLAYER_COUNT, (data) => { 8 | console.log("Player count updated!", data); 9 | }); 10 | 11 | await sleep(30000); 12 | 13 | socket.disconnect(); 14 | } 15 | 16 | main(); 17 | -------------------------------------------------------------------------------- /unofficial-api/node/examples/playerProfile.js: -------------------------------------------------------------------------------- 1 | const {SocketWrapper, getToken, EVENTS} = require('../amq-api'); 2 | 3 | async function main() { 4 | let token = await getToken("juvian", "xxx", '../data.json'); 5 | let socket = await new SocketWrapper().connect(token); 6 | 7 | let profile = await socket.social.profile.get('juvian'); 8 | console.log(profile); 9 | 10 | profile = await socket.social.profile.get('`notExisting``'); 11 | console.log(profile) 12 | 13 | socket.disconnect(); 14 | } 15 | 16 | main(); 17 | -------------------------------------------------------------------------------- /unofficial-api/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amq-api", 3 | "version": "1.0.0", 4 | "description": "Unofficial node api for animemusicquiz.com", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/amq-script-project/AMQ-Scripts/blob/master/unofficial-api/node" 8 | }, 9 | "author": "juvian", 10 | "license": "MIT", 11 | "dependencies": { 12 | "request-promise": "^4.2.6", 13 | "socket.io-client": "^4.7.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /unofficial-api/node/tests/lobbySettings.js: -------------------------------------------------------------------------------- 1 | let LobbySettings = require('../structures/lobbySettings') 2 | 3 | verboseTests = false 4 | test = (verbose=verboseTests) => { 5 | const oldVerbose = verboseTests 6 | const CONST_VALUES = LobbySettings.CONST_VALUES 7 | verboseTests = verbose 8 | const dummy = new LobbySettings() 9 | dummy.getSettings() //test that valid settings work, validate is part of getSettings 10 | new LobbySettings(dummy.getSettings(false)).getSettings() //test that valid override works 11 | 12 | verifyFailList(dummy.setRoomName, "", 4, "a".repeat(CONST_VALUES.ROOM_NAME_MAX_LENGTH + 1)) 13 | verifyFailList(dummy.setPassword, 4, "a".repeat(CONST_VALUES.PASSWORD_MAX_LENGTH + 1)) 14 | verifyFailList(dummy.setRoomSize, "4", CONST_VALUES.ROOM_SIZE_MIN-1, CONST_VALUES.ROOM_SIZE_MAX+1,8.5) 15 | verifyFailList(dummy.setTeamSize, "4", CONST_VALUES.TEAM_SIZE_MIN-1, CONST_VALUES.TEAM_SIZE_MAX+1,3.5) 16 | verifyFailList(dummy.setSongCount, "4", CONST_VALUES.SONG_COUNT_MIN-1, CONST_VALUES.SONG_COUNT_MAX+1,10.5) 17 | verifyFailList(dummy.enableSkipGuessing, 1, 0, "", undefined) 18 | verifyFailList(dummy.enableSkipReplay, 1, 0, "", undefined) 19 | verifyFailList(dummy.enableDuplicates, 1, 0, "", undefined) 20 | verifyFailList(dummy.enableQueueing, 1, 0, "", undefined) 21 | verifyFailList(dummy.enableLootDropping, 1, 0, "", undefined) 22 | verifyFailList(dummy.setSongSelection, 0, 4) 23 | verifyFailList(dummy.setSongSelectionAdvanced, [0,0,0], [-1,14,14], [0,-1,4], [4,4,-1], [-1,-1,-1]) 24 | verifyFailList(dummy.enableSongTypes, [false, false, false], [1, true, true], [true, 1, true], [true, true, 1]) 25 | verifyFailList(dummy.setSongTypeSelectionAdvanced, [0,0,0,0], [-1,14,14,15], [0,-1,4,4], [4,4,-1,0], [4,4,0,-1], [-1,-1,-1, -1], [undefined]) 26 | verifyFailList(dummy.setGuessTime, "4", CONST_VALUES.GUESS_TIME_MIN-1, CONST_VALUES.GUESS_TIME_MAX+1,10.5) 27 | verifyFailList(dummy.setGuessTimeAdvanced, [CONST_VALUES.GUESS_TIME_MIN,"4"], ["4",CONST_VALUES.GUESS_TIME_MAX], [CONST_VALUES.GUESS_TIME_MIN-1, CONST_VALUES.GUESS_TIME_MAX],[CONST_VALUES.GUESS_TIME_MIN, CONST_VALUES.GUESS_TIME_MAX+1], [CONST_VALUES.GUESS_TIME_MAX, CONST_VALUES.GUESS_TIME_MIN]) 28 | verifyFailList(dummy.enableRandomGuessTime, 1, 0, "", undefined) 29 | verifyFailList(dummy.setScoreType, 0, 4, "a", undefined) 30 | verifyFailList(dummy.setShowSelection, 0, 3, "a", undefined) 31 | verifyFailList(dummy.setGameMode, 0, 5, "a", undefined) 32 | verifyFailList(dummy.setInventorySize, "4", CONST_VALUES.INVENTORY_SIZE_MIN-1, CONST_VALUES.INVENTORY_SIZE_MAX+1,10.5) 33 | verifyFailList(dummy.setInventorySizeAdvanced, [CONST_VALUES.INVENTORY_SIZE_MIN,"4"], ["4",CONST_VALUES.INVENTORY_SIZE_MAX], [CONST_VALUES.INVENTORY_SIZE_MIN-1, CONST_VALUES.INVENTORY_SIZE_MAX],[CONST_VALUES.INVENTORY_SIZE_MIN, CONST_VALUES.INVENTORY_SIZE_MAX+1], [CONST_VALUES.INVENTORY_SIZE_MAX, CONST_VALUES.INVENTORY_SIZE_MIN]) 34 | verifyFailList(dummy.enableRandomInventorySize, 1, 0, "", undefined) 35 | verifyFailList(dummy.setLootingTime, "4", CONST_VALUES.LOOTING_TIME_MIN-1, CONST_VALUES.LOOTING_TIME_MAX+1,10.5) 36 | verifyFailList(dummy.setLootingTimeAdvanced, [CONST_VALUES.LOOTING_TIME_MIN,"4"], ["4",CONST_VALUES.LOOTING_TIME_MAX], [CONST_VALUES.LOOTING_TIME_MIN-1, CONST_VALUES.LOOTING_TIME_MAX],[CONST_VALUES.LOOTING_TIME_MIN, CONST_VALUES.LOOTING_TIME_MAX+1], [CONST_VALUES.LOOTING_TIME_MAX, CONST_VALUES.LOOTING_TIME_MIN]) 37 | verifyFailList(dummy.enableRandomLootingTime, 1, 0, "", undefined) 38 | verifyFailList(dummy.setLives, "4", CONST_VALUES.LIVES_MIN-1, CONST_VALUES.LIVES_MAX+1,4.5) 39 | verifyFailList(dummy.setSamplePoint, 0, 4, "a", undefined) 40 | verifyFailList(dummy.setSamplePointAdvanced, [CONST_VALUES.SAMPLE_POINT_MIN,"4"], ["4",CONST_VALUES.SAMPLE_POINT_MAX], [CONST_VALUES.SAMPLE_POINT_MIN-1, CONST_VALUES.SAMPLE_POINT_MAX],[CONST_VALUES.SAMPLE_POINT_MIN, CONST_VALUES.SAMPLE_POINT_MAX+1], [CONST_VALUES.SAMPLE_POINT_MAX, CONST_VALUES.SAMPLE_POINT_MIN]) 41 | verifyFailList(dummy.enableRandomSamplePoint, 1, 0, "", undefined) 42 | verifyFailList(dummy.setPlaybackSpeed, CONST_VALUES.PLAYBACK_SPEED_MIN-0.5, CONST_VALUES.PLAYBACK_SPEED_MAX+0.5, "4", undefined) 43 | verifyFailList(dummy.setPlaybackSpeedAdvanced, [false, false, false, false], [1, true, true, true], [true, 1, true, true], [true, true, 1, true], [true, true, true, 1]) 44 | verifyFailList(dummy.enableRandomPlaybackSpeed, 1, 0, "", undefined) 45 | verifyFailList(dummy.enableSongDifficulty, [false, false, false], [1, true, true], [true, 1, true], [true, true, 1]) 46 | verifyFailList(dummy.setSongDifficultyAdvanced, [CONST_VALUES.DIFFICULTY_MIN,"4"], ["4",CONST_VALUES.DIFFICULTY_MAX], [CONST_VALUES.DIFFICULTY_MIN-1, CONST_VALUES.DIFFICULTY_MAX],[CONST_VALUES.DIFFICULTY_MIN, CONST_VALUES.DIFFICULTY_MAX+1], [CONST_VALUES.DIFFICULTY_MAX, CONST_VALUES.DIFFICULTY_MIN]) 47 | verifyFailList(dummy.enableSongDifficultyAdvanced, 1, 0, "", undefined) 48 | verifyFailList(dummy.enableSongPopularity, [false, false, false], [1, true, true], [true, 1, true], [true, true, 1]) 49 | verifyFailList(dummy.setSongPopularityAdvanced, [CONST_VALUES.POPULARITY_MIN,"4"], ["4",CONST_VALUES.POPULARITY_MAX], [CONST_VALUES.POPULARITY_MIN-1, CONST_VALUES.POPULARITY_MAX],[CONST_VALUES.POPULARITY_MIN, CONST_VALUES.POPULARITY_MAX+1], [CONST_VALUES.POPULARITY_MAX, CONST_VALUES.POPULARITY_MIN]) 50 | verifyFailList(dummy.enableSongPopularityAdvanced, 1, 0, "", undefined) 51 | verifyFailList(dummy.setPlayerScore, [CONST_VALUES.PLAYER_SCORE_MIN,"4"], ["4",CONST_VALUES.PLAYER_SCORE_MAX], [CONST_VALUES.PLAYER_SCORE_MIN-1, CONST_VALUES.PLAYER_SCORE_MAX],[CONST_VALUES.PLAYER_SCORE_MIN, CONST_VALUES.PLAYER_SCORE_MAX+1], [CONST_VALUES.PLAYER_SCORE_MAX, CONST_VALUES.PLAYER_SCORE_MIN]) 52 | verifyFailList(dummy.enablePlayerScoreAdvanced, 1, 0, "", undefined) 53 | verifyFailList(dummy.setPlayerScoreAdvanced, [0, true], [1.5, true], [11, true]) 54 | verifyFailList(dummy.setAnimeScore, [CONST_VALUES.ANIME_SCORE_MIN,"4"], ["4",CONST_VALUES.ANIME_SCORE_MAX], [CONST_VALUES.ANIME_SCORE_MIN-1, CONST_VALUES.ANIME_SCORE_MAX],[CONST_VALUES.ANIME_SCORE_MIN, CONST_VALUES.ANIME_SCORE_MAX+1], [CONST_VALUES.ANIME_SCORE_MAX, CONST_VALUES.ANIME_SCORE_MIN]) 55 | verifyFailList(dummy.enableAnimeScoreAdvanced, 1, 0, "", undefined) 56 | verifyFailList(dummy.setAnimeScoreAdvanced, [1, true], [2.5, true], [11, true]) 57 | { //vintage 58 | dummy.resetVintage() 59 | dummy.addVintage(1999, 2001, 1, 3) 60 | dummy.addVintage(1944, 2020, 0, 3) 61 | dummy.addVintage(1955, 2002, 1, 2) 62 | verifyFailList(dummy.addVintage, [1999, 2001, 1, 3], [1944, 2020, 0, 3], [1955, 2002, 1, 2]) 63 | dummy.resetVintage() 64 | verifyFailList(dummy.setVintage, 65 | [CONST_VALUES.YEAR_MIN-1, CONST_VALUES.YEAR_MAX, CONST_VALUES.SEASON_MIN, CONST_VALUES.SEASON_MAX], 66 | [CONST_VALUES.YEAR_MIN, CONST_VALUES.YEAR_MAX+1, CONST_VALUES.SEASON_MIN, CONST_VALUES.SEASON_MAX], 67 | [CONST_VALUES.YEAR_MIN, CONST_VALUES.YEAR_MAX, CONST_VALUES.SEASON_MIN-1, CONST_VALUES.SEASON_MAX], 68 | [CONST_VALUES.YEAR_MIN, CONST_VALUES.YEAR_MAX, CONST_VALUES.SEASON_MIN, CONST_VALUES.SEASON_MAX+1], 69 | [CONST_VALUES.YEAR_MAX, CONST_VALUES.YEAR_MIN, CONST_VALUES.SEASON_MIN, CONST_VALUES.SEASON_MAX], 70 | [CONST_VALUES.YEAR_MIN, CONST_VALUES.YEAR_MAX, CONST_VALUES.SEASON_MAX, CONST_VALUES.SEASON_MIN], 71 | ) 72 | } 73 | verifyFailList(dummy.enableShowTypes, 74 | ["true",true,true,true,true], 75 | [true,"true",true,true,true], 76 | [true,true,"true",true,true], 77 | [true,true,true,"true",true], 78 | [true,true,true,true,"true"], 79 | [false,false,false,false,false], 80 | ) 81 | {//genres 82 | dummy.clearGenres() 83 | dummy.addGenre("5", 2) 84 | verifyFailList(dummy.removeGenre, 5, "4") 85 | verifyFailList(dummy.changeGenreState, [4, 2], ["4", 4], ["0", 0], ["0", "1"], ["0", 1]) 86 | verifyFailList(dummy.addGenre, ["5", 2], [0, 1], ["0", 0], ["0", 4]) 87 | } 88 | {//tags 89 | dummy.clearTags() 90 | dummy.addTag("5", 2) 91 | verifyFailList(dummy.removeTag, 5, "4") 92 | verifyFailList(dummy.changeTagState, [4, 2], ["4", 4], ["0", 0], ["0", "1"], ["0", 1]) 93 | verifyFailList(dummy.addTag, ["5", 2], [0, 1], ["0", 0], ["0", 4]) 94 | } 95 | //verifyFailList(dummy.enable, 1, 0, "", undefined) 96 | // verifyFailList(dummy. , ) 97 | //verifyFailList(dummy. , ) 98 | //verifyFailList(dummy. , ) 99 | console.log("All tests passed successfully") 100 | verboseTests = oldVerbose 101 | //exit() 102 | } 103 | 104 | verifyFailList = (func, ...listOfArgs) => { 105 | listOfArgs.forEach((arg) => { 106 | if(Array.isArray(arg)){ 107 | verifyFail(func, ...arg) 108 | }else{ 109 | verifyFail(func, arg) 110 | } 111 | }) 112 | if(verboseTests){ 113 | console.log(func.name, "SUCCESS", listOfArgs.length, "cases tested\n") 114 | } 115 | } 116 | 117 | verifyFail = (func, ...args) => { 118 | let success = false 119 | try{ 120 | //console.log(args, ...args) 121 | func(...args) 122 | if(verboseTests){ 123 | console.log(func.name, ...args, "SUCCEEDED, FAIL!") 124 | } 125 | success = true 126 | }catch(err){ 127 | if(verboseTests){ 128 | console.log(func.name, ...args, "FAILED, SUCCESS!", err) 129 | } 130 | success = false 131 | } 132 | if(success){ 133 | throw func.name + " succeeded with arguments [" + args.join(", ") + "]" 134 | } 135 | } 136 | 137 | test(true) --------------------------------------------------------------------------------