├── .dockerignore ├── .env.example ├── .eslintrc.js ├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── .node-version ├── .vscode ├── settings.json └── tasks.json ├── Dockerfile ├── LICENSE ├── README.md ├── home ├── .config │ └── SuperCollider │ │ └── startup.scd ├── app │ ├── index.js │ ├── package-lock.json │ ├── package.json │ ├── package.yaml │ ├── parseMessage.js │ ├── stack.yaml │ ├── streamer.js │ └── tidal.js ├── jack-audio │ ├── .dockerignore │ ├── .gitignore │ ├── binding.gyp │ ├── package.json │ └── src │ │ └── jack-audio.cc └── supervisor.conf ├── nice-to-pin.md ├── package-lock.json └── package.json /.dockerignore: -------------------------------------------------------------------------------- 1 | /home/app/node_modules/ -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | TIDALBOT_TOKEN= -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "node": true, 4 | "es6": true 5 | }, 6 | 7 | "parserOptions": { 8 | "ecmaVersion": 2017 9 | }, 10 | 11 | "extends": "eslint:recommended", 12 | 13 | "rules": { 14 | // basics 15 | "indent": [ "error", 2 ], // it forces 2 spaces indentation 16 | "linebreak-style": [ "error", "linux" ], // CRLF is evil 17 | "quotes": [ "error", "single" ], // quotes must be single 18 | "eqeqeq": [ "error", "always" ], // fuck you, `==` 19 | 20 | // variables 21 | "no-unused-vars": [ "off" ], // unused vars are okay 22 | "no-undef": [ "warn" ], // draws yellow line below undefined vars 23 | 24 | // omittables 25 | "semi": [ "error", "always" ], // semicolon is required 26 | "curly": [ "error" ], // it kills `if (foo) bar++;` 27 | "arrow-parens": [ "error", "always" ], // it kills `arg => { func(); }` 28 | 29 | // force spacing (I prefer super sparse code!) 30 | "array-bracket-spacing": [ "error", "always" ], // it kills `[val1, val2]` 31 | "arrow-spacing": [ "error", { "before": true, "after": true } ], // it kills `( arg )=>{ func(); }` 32 | "block-spacing": [ "error", "always" ], // it kills `if ( cond ) {func();}` 33 | "comma-spacing": [ "error", { "before": false, "after": true } ], // it kills `func( arg1,arg2 )` 34 | "computed-property-spacing": [ "error", "always" ], // it kills `arr[i]` 35 | "key-spacing": [ "error", { "beforeColon": false, "afterColon": true } ], // it kills `{ key:val }` 36 | "keyword-spacing": [ "error", { "before": true, "after": true } ], // it kills `}else{` 37 | "object-curly-spacing": [ "error", "always" ], // it kills `{key: val}` 38 | "semi-spacing": [ "error", { "before": false, "after": true } ], // it kills `func1();func2();` 39 | "space-before-blocks": [ "error", "always" ], // it kills `if (cond){` 40 | "space-in-parens": [ "error", "always" ], // it kills `func(arg)` 41 | "space-infix-ops": [ "error" ], // it kills val1+val2 42 | "space-unary-ops": [ "error", { "words": true, "nonwords": false, "overrides": { "++": true, "--": true } } ], // it kills `val++` 43 | "spaced-comment": [ "error", "always" ], // it kills `//this is comment` 44 | 45 | // ban spacing 46 | "no-trailing-spaces": [ "error" ], // no-trailing-spaces. yea. 47 | "no-whitespace-before-property": [ "error" ], // it kills `obj .key` 48 | 49 | // others 50 | "no-eval": [ "warn" ], // wow, are you really going to use `eval()`? are you mad lol 51 | "no-implied-eval": [ "warn" ], // ok don't 52 | "no-console": [ "off" ], // here is nodejs, console.log is innocent 53 | } 54 | }; -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | push: 4 | branches: 5 | - master 6 | env: 7 | DOCKER_IMAGE: ${{ secrets.DOCKER_USERNAME }}/tidal-bot 8 | COMPOSE_SERVICE: tidal-bot 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v1 14 | 15 | - name: Set up QEMU 16 | uses: docker/setup-qemu-action@v1 17 | 18 | - name: Set up Docker Buildx 19 | id: buildx 20 | uses: docker/setup-buildx-action@v1 21 | 22 | - name: Build Docker Images 23 | run: | 24 | docker login \ 25 | -u ${{ secrets.DOCKER_USERNAME }} \ 26 | -p ${{ secrets.DOCKER_TOKEN }} 27 | docker buildx build \ 28 | --cache-from=${DOCKER_IMAGE}:cache \ 29 | --cache-to=${DOCKER_IMAGE}:cache \ 30 | --platform linux/amd64 \ 31 | -t $DOCKER_IMAGE:latest \ 32 | --push \ 33 | . 34 | - name: Notify on Discord (Failure) 35 | if: failure() 36 | env: 37 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} 38 | uses: Ilshidur/action-discord@master 39 | with: 40 | args: | 41 | 🚨 **Something went wrong while deploying** 🚨 42 | 43 | - name: Notify on Discord 44 | env: 45 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} 46 | uses: Ilshidur/action-discord@master 47 | with: 48 | args: | 49 | Deployed successfully 50 | 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /.env* 3 | !/.env.example 4 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 14.8.0 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "vector": "cpp", 4 | "xstring": "cpp", 5 | "xutility": "cpp" 6 | } 7 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "build", 9 | "problemMatcher": [] 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM phusion/baseimage:master 2 | 3 | # == define ENV_VARS =========================================================== 4 | ENV LANG C.UTF-8 5 | ENV HOME /root 6 | 7 | # == install some packs ======================================================== 8 | # Ref: https://github.com/ikag/docker-images/blob/master/supercollider/Dockerfile 9 | # Ref: https://github.com/lvm/tida1vm/blob/0.9/Dockerfile 10 | # Ref: https://github.com/maxhawkins/sc_radio/blob/master/Dockerfile 11 | RUN apt-get update 12 | RUN apt-get install -y --no-install-recommends \ 13 | supervisor \ 14 | autoconf \ 15 | automake \ 16 | libtool \ 17 | build-essential \ 18 | libsndfile1-dev \ 19 | libasound2-dev \ 20 | libavahi-client-dev \ 21 | libgmp3-dev \ 22 | libicu-dev \ 23 | libreadline6-dev \ 24 | libfftw3-dev \ 25 | libxt-dev \ 26 | libudev-dev \ 27 | git \ 28 | wget \ 29 | ca-certificates \ 30 | cmake \ 31 | jackd2 \ 32 | libjack-jackd2-dev \ 33 | ffmpeg \ 34 | haskell-mode \ 35 | zlib1g-dev \ 36 | liblo7 37 | 38 | # == deal with nodejs ========================================================== 39 | RUN curl -sL https://deb.nodesource.com/setup_14.x | bash - 40 | RUN apt-get install -y nodejs 41 | RUN npm install -g node-gyp 42 | 43 | # == install stack ============================================================= 44 | RUN curl -sL https://get.haskellstack.org/ | bash - 45 | RUN stack update 46 | 47 | # == bye apt =================================================================== 48 | RUN apt-get clean 49 | RUN rm -rf /var/lib/apt/lists/* 50 | 51 | # == build SuperCollider ======================================================= 52 | # Ref: https://github.com/ikag/docker-images/blob/master/supercollider/Dockerfile 53 | # Ref: https://github.com/supercollider/supercollider/wiki/Installing-SuperCollider-from-source-on-Ubuntu 54 | # Ref: https://github.com/supercollider/sc3-plugins 55 | WORKDIR $HOME 56 | # This does not work since github stopped support for git:// protocols 57 | # and the tag Version-3.12.2 relies on git:// in .gitmodules (shoutouts to feedbacker) 58 | # RUN git clone --depth=1 -b Version-3.12.2 --recursive https://github.com/supercollider/supercollider.git 59 | RUN git clone --depth=1 -b 3.12 --recursive https://github.com/supercollider/supercollider.git 60 | WORKDIR $HOME/supercollider 61 | RUN git submodule update --init 62 | RUN mkdir -p $HOME/supercollider/build 63 | WORKDIR $HOME/supercollider/build 64 | RUN cmake -DCMAKE_BUILD_TYPE="Release" -DBUILD_TESTING=OFF -DSUPERNOVA=OFF -DNATIVE=OFF -DSC_WII=OFF -DSC_QT=OFF -DSC_ED=OFF -DSC_EL=OFF -DSC_VIM=OFF .. 65 | RUN make -j4 && make install 66 | RUN ldconfig 67 | WORKDIR $HOME/supercollider 68 | RUN cp SCClassLibrary/Common/Core/Model.sc /usr/local/share/SuperCollider/SCClassLibrary/Common/Core/ 69 | RUN rm -f /usr/local/share/SuperCollider/SCClassLibrary/deprecated/3.7/deprecated-3.7.sc 70 | 71 | WORKDIR $HOME 72 | RUN git clone --recursive https://github.com/supercollider/sc3-plugins.git 73 | WORKDIR $HOME/sc3-plugins 74 | RUN git submodule update --init 75 | RUN mkdir -p $HOME/sc3-plugins/build 76 | WORKDIR $HOME/sc3-plugins/build 77 | RUN cmake -DCMAKE_BUILD_TYPE="Release" -DSC_PATH=$HOME/supercollider .. 78 | RUN cmake --build . --config Release --target install 79 | WORKDIR $HOME 80 | RUN rm -rf sc3-plugins supercollider 81 | 82 | # == pull SuperDirt and its samples ============================================ 83 | RUN echo 'include( "https://github.com/0b5vr/Dirt-Samples" );' | sclang 84 | RUN echo 'include( "SuperDirt" );' | sclang 85 | 86 | # == setup node app ============================================================ 87 | WORKDIR $HOME/app 88 | ADD ./home/app/package.json $HOME/app/package.json 89 | ADD ./home/app/package-lock.json $HOME/app/package-lock.json 90 | RUN npm i 91 | 92 | # == setup stack, install Tidal ================================================ 93 | WORKDIR $HOME/app 94 | ADD ./home/app/package.yaml $HOME/app/package.yaml 95 | ADD ./home/app/stack.yaml $HOME/app/stack.yaml 96 | RUN stack setup 97 | RUN stack install tidal-1.7.3 98 | # you also have to change package.yaml and stack.yaml when you bump the version of tidal! 99 | 100 | # == download BootTidal ======================================================== 101 | RUN curl -sL https://raw.githubusercontent.com/tidalcycles/Tidal/f49966f910a83f6507b8d426d46baf5c0678a903/BootTidal.hs > $HOME/app/BootTidal.hs 102 | 103 | # == send some files =========================================================== 104 | ADD ./home $HOME 105 | 106 | # == build jack-audio ========================================================== 107 | WORKDIR $HOME/jack-audio 108 | RUN npm i 109 | RUN node-gyp configure 110 | RUN node-gyp build 111 | RUN mv ./build/Release/jack-audio.node $HOME/app 112 | WORKDIR $HOME 113 | RUN rm -rf jack-audio 114 | 115 | # == I think it's done ========================================================= 116 | CMD supervisord -c supervisor.conf 117 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018-2022 0b5vr 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TidalBot 2 | 3 | Play with TidalCycles in Discord voice chat!! 4 | 5 | ## Installation 6 | 7 | ### Prerequisite 8 | 9 | [Docker](https://hub.docker.com/search/?type=edition&offering=community) 10 | 11 | ### Setup your discord app token 12 | 13 | - Go to https://discordapp.com/developers/applications/ 14 | - Retrieve your bot token 15 | - Paste the token in `.env` : 16 | ``` 17 | TIDALBOT_TOKEN= 18 | ``` 19 | 20 | ### Build and run 21 | 22 | - Enter these commands: 23 | ```sh 24 | docker build -t tidal-bot . 25 | docker run -it --env-file .env --rm tidal-bot 26 | ``` 27 | 28 | ## How to use 29 | 30 | - Invite the bot: 31 | `https://discord.com/oauth2/authorize?client_id=&scope=bot&permissions=36703232` 32 | 33 | - Join in any VC 34 | - Mention TidalBot along with your code: 35 | > @TidalBot 36 | > ``` 37 | > d1 38 | > $ sound "bd bd" 39 | > ``` 40 | - Now TidalBot joins in your VC automatically and plays cool music for you! 41 | 42 | ### Special commands 43 | 44 | - If you want to let TidalBot leave your VC, type this: 45 | > @TidalBot bye 46 | - If you want to see SuperCollider's log, type this: 47 | > @TidalBot sc-log 48 | 49 | ## nice-to-pin.md 50 | 51 | You might want to pin the content of [nice-to-pin.md](./nice-to-pin.md) on your Discord channel. 52 | 53 | ## License 54 | 55 | MIT 56 | -------------------------------------------------------------------------------- /home/.config/SuperCollider/startup.scd: -------------------------------------------------------------------------------- 1 | "SC_JACK_DEFAULT_OUTPUTS".setenv( 2 | "node:inL," 3 | "node:inR," 4 | ); 5 | 6 | ServerQuit.add( { 7 | 'FAIL: scsynth quit'.postln; 8 | 1.exit(); 9 | }, Server.default ); 10 | 11 | s.options.numBuffers = 1024 * 32; 12 | s.options.memSize = 8192 * 16; 13 | s.options.maxNodes = 1024 * 32; 14 | s.options.numOutputBusChannels = 2; 15 | s.options.numInputBusChannels = 2; 16 | 17 | s.waitForBoot { 18 | ~dirt = SuperDirt( 2, s ); 19 | s.sync; 20 | ~dirt.start( 57120, [ 0, 0 ] ); 21 | s.sync; 22 | 23 | ~dirt.loadSoundFiles; 24 | s.sync; 25 | } -------------------------------------------------------------------------------- /home/app/index.js: -------------------------------------------------------------------------------- 1 | const path = require( 'path' ); 2 | const Streamer = require( './streamer' ); 3 | const Tidal = require( './tidal' ); 4 | const parseMessage = require( './parseMessage' ); 5 | 6 | // == setup discord ================================================================================ 7 | const Discord = require( 'discord.js' ); 8 | const client = new Discord.Client( { 9 | messageCacheLifetime: 300, 10 | messageSweepInterval: 30, 11 | } ); 12 | 13 | client.on( 'ready', () => { 14 | console.log( 'Discord bot is working!' ); 15 | client.user.setPresence( { 16 | status: 'idle' 17 | } ); 18 | } ); 19 | 20 | client.on( 'error', ( error ) => { 21 | console.error( 'An error occurred while starting Discord bot:' ); 22 | console.error( error ); 23 | 24 | client.destroy(); 25 | process.exit(); 26 | } ); 27 | 28 | let lastTextChannel = null; 29 | let currentConnection = null; 30 | 31 | // == setup streamer =============================================================================== 32 | const streamer = new Streamer(); 33 | const jackClientName = 'node'; 34 | 35 | // == setup tidal ================================================================================== 36 | const tidal = new Tidal(); 37 | const bootTidalPath = path.resolve( __dirname, 'BootTidal.hs' ) ; 38 | 39 | // == log handler ================================================================================== 40 | tidal.on( 'log', ( msg ) => { 41 | if ( lastTextChannel ) { 42 | lastTextChannel.send( `\`\`\`\n${msg}\n\`\`\`` ); 43 | } 44 | } ); 45 | 46 | // == uh =========================================================================================== 47 | /** 48 | * @param {Discord.Message} msg 49 | */ 50 | const messageHandler = async ( msg ) => { 51 | const mentioned = msg.mentions.users.some( ( u ) => u.id === client.user.id ); 52 | if ( !mentioned ) { return; } 53 | 54 | // remove previous reactions 55 | await Promise.all( 56 | msg.reactions.cache.map( async ( reaction ) => { 57 | if ( reaction.users.cache.has( client.user.id ) ) { 58 | await reaction.users.remove( client.user.id ); 59 | } 60 | } ) 61 | ); 62 | 63 | const str = msg.toString() 64 | .replace( `<@${ client.user.id }>`, '' ) 65 | .trim(); 66 | 67 | if ( str.startsWith( 'bye' ) ) { 68 | if ( !currentConnection ) { 69 | msg.reply( '\n🤔 I don\'t think I\'m in any VC right now' ); 70 | msg.react( '🤔' ); 71 | return; 72 | } 73 | 74 | lastTextChannel = null; 75 | 76 | tidal.hush(); 77 | 78 | currentConnection.disconnect(); 79 | currentConnection = null; 80 | client.user.setPresence( { 81 | status: 'idle' 82 | } ); 83 | 84 | msg.reply( '👋 Bye' ); 85 | msg.react( '👋' ); 86 | return; 87 | } 88 | 89 | if ( str.startsWith( 'sc-log' ) ) { 90 | msg.channel.send( 91 | `🌀 SuperCollider stdout / stderr:\n\`\`\`\n${ tidal.getScLog() }\n\`\`\`` 92 | ); 93 | msg.react( '🌀' ); 94 | return; 95 | } 96 | 97 | const code = parseMessage( str ); 98 | 99 | console.log( `${ msg.author.tag }: ${ code }` ); 100 | 101 | // temp: to prevent dangerous things 102 | if ( code.includes( 'import' ) ) { 103 | msg.reply( '\n🚨 <@232233105040211969>' ); 104 | msg.react( '🚨' ); 105 | return; 106 | } 107 | 108 | // it cannot be in two servers at once 109 | const guild = msg.guild; 110 | if ( currentConnection && currentConnection.channel.guild !== msg.guild ) { 111 | msg.reply( '\n🙇 I\'m currently on an another server!' ); 112 | msg.react( '🙇' ); 113 | return; 114 | } 115 | 116 | if ( !currentConnection ) { 117 | const user = msg.author; 118 | 119 | let nope = true; 120 | guild.channels.cache.map( ( ch ) => { 121 | const isValidVC = ch.speakable && ch.joinable && ch.members.some( ( u ) => u.id === user.id ); 122 | if ( !isValidVC ) { return; } 123 | 124 | nope = false; 125 | ch.join().then( ( conn ) => { 126 | currentConnection = conn; 127 | msg.reply( `\n🛰 I joined to ${currentConnection.channel} !` ); 128 | client.user.setPresence( { 129 | game: { name: 'TidalCycles' }, 130 | status: 'online' 131 | } ); 132 | 133 | currentConnection.on( 'disconnect', () => { 134 | tidal.stop(); 135 | streamer.stop(); 136 | } ); 137 | 138 | // == stream audio ======================================================================= 139 | streamer.start( jackClientName ); 140 | tidal.start( bootTidalPath ); 141 | currentConnection.play( 142 | streamer.stream, 143 | { type: 'converted', volume: false, highWaterMark: 1 }, 144 | ); 145 | 146 | // == when all members left... =========================================================== 147 | const update = () => { 148 | const isAnyoneThere = ch.members.some( 149 | ( u ) => u.id !== client.user.id 150 | ); 151 | 152 | if ( !isAnyoneThere ) { 153 | if ( lastTextChannel ) { 154 | lastTextChannel.send( '👋 All users left, bye' ); 155 | lastTextChannel = null; 156 | } 157 | 158 | tidal.hush(); 159 | 160 | currentConnection.disconnect(); 161 | currentConnection = null; 162 | client.user.setPresence( { 163 | status: 'idle' 164 | } ); 165 | } 166 | 167 | if ( currentConnection ) { 168 | setTimeout( update, 5000 ); 169 | } 170 | }; 171 | update(); 172 | } ); 173 | } ); 174 | 175 | if ( nope ) { 176 | msg.reply( '\n🤔 You seem not to be in any VC???' ); 177 | msg.react( '🤔' ); 178 | return; 179 | } 180 | } 181 | 182 | lastTextChannel = msg.channel; 183 | 184 | tidal.evaluate( code ); 185 | msg.react( '✅' ); 186 | }; 187 | 188 | client.on( 'message', ( msg ) => messageHandler( msg ) ); 189 | client.on( 'messageUpdate', ( _, msg ) => messageHandler( msg ) ); 190 | 191 | client.login( process.env.TIDALBOT_TOKEN ); 192 | process.on( 'SIGTERM', () => { 193 | console.log( 'SIGTERM received' ); 194 | 195 | tidal.stop(); 196 | streamer.stop(); 197 | 198 | client.destroy(); 199 | process.exit(); 200 | } ); 201 | -------------------------------------------------------------------------------- /home/app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tidal-bot", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@discordjs/collection": { 8 | "version": "0.1.6", 9 | "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz", 10 | "integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ==" 11 | }, 12 | "@discordjs/form-data": { 13 | "version": "3.0.1", 14 | "resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz", 15 | "integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==", 16 | "requires": { 17 | "asynckit": "^0.4.0", 18 | "combined-stream": "^1.0.8", 19 | "mime-types": "^2.1.12" 20 | } 21 | }, 22 | "@discordjs/node-pre-gyp": { 23 | "version": "0.3.2", 24 | "resolved": "https://registry.npmjs.org/@discordjs/node-pre-gyp/-/node-pre-gyp-0.3.2.tgz", 25 | "integrity": "sha512-NqRvPz0X+/3h+6ClElrSfvsD5XEG9ljYzXhzyo81DslVkVKzmmxX9FLs3MUr9qI7p53DG1eYru633qosrOqMyA==", 26 | "requires": { 27 | "detect-libc": "^1.0.3", 28 | "http-proxy-agent": "^4.0.1", 29 | "make-dir": "^3.1.0", 30 | "node-fetch": "^2.6.1", 31 | "nopt": "^5.0.0", 32 | "npmlog": "^4.1.2", 33 | "rimraf": "^3.0.2", 34 | "semver": "^7.3.4", 35 | "tar": "^6.1.0" 36 | }, 37 | "dependencies": { 38 | "node-fetch": { 39 | "version": "2.6.1", 40 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", 41 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" 42 | } 43 | } 44 | }, 45 | "@discordjs/opus": { 46 | "version": "0.5.0", 47 | "resolved": "https://registry.npmjs.org/@discordjs/opus/-/opus-0.5.0.tgz", 48 | "integrity": "sha512-s3XUJ7dYV+UvYUiqkgs7sq8JKWMhQrHcwDECP2SKdxtL9h9qGa4mr0IR6XnZw+G/Sogx8c+HRS7+wEspkqk3zA==", 49 | "requires": { 50 | "@discordjs/node-pre-gyp": "^0.3.2", 51 | "node-addon-api": "^3.1.0" 52 | } 53 | }, 54 | "@tootallnate/once": { 55 | "version": "1.1.2", 56 | "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", 57 | "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==" 58 | }, 59 | "abbrev": { 60 | "version": "1.1.1", 61 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 62 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" 63 | }, 64 | "abort-controller": { 65 | "version": "3.0.0", 66 | "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", 67 | "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", 68 | "requires": { 69 | "event-target-shim": "^5.0.0" 70 | } 71 | }, 72 | "agent-base": { 73 | "version": "6.0.2", 74 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", 75 | "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", 76 | "requires": { 77 | "debug": "4" 78 | } 79 | }, 80 | "ansi-regex": { 81 | "version": "2.1.1", 82 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 83 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 84 | }, 85 | "aproba": { 86 | "version": "1.2.0", 87 | "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", 88 | "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" 89 | }, 90 | "are-we-there-yet": { 91 | "version": "1.1.5", 92 | "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", 93 | "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", 94 | "requires": { 95 | "delegates": "^1.0.0", 96 | "readable-stream": "^2.0.6" 97 | } 98 | }, 99 | "asynckit": { 100 | "version": "0.4.0", 101 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 102 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 103 | }, 104 | "balanced-match": { 105 | "version": "1.0.2", 106 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 107 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 108 | }, 109 | "brace-expansion": { 110 | "version": "1.1.11", 111 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 112 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 113 | "requires": { 114 | "balanced-match": "^1.0.0", 115 | "concat-map": "0.0.1" 116 | } 117 | }, 118 | "chownr": { 119 | "version": "2.0.0", 120 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", 121 | "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" 122 | }, 123 | "code-point-at": { 124 | "version": "1.1.0", 125 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 126 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" 127 | }, 128 | "combined-stream": { 129 | "version": "1.0.8", 130 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 131 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 132 | "requires": { 133 | "delayed-stream": "~1.0.0" 134 | } 135 | }, 136 | "concat-map": { 137 | "version": "0.0.1", 138 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 139 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 140 | }, 141 | "console-control-strings": { 142 | "version": "1.1.0", 143 | "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", 144 | "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" 145 | }, 146 | "core-util-is": { 147 | "version": "1.0.2", 148 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 149 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 150 | }, 151 | "debug": { 152 | "version": "4.3.1", 153 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", 154 | "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", 155 | "requires": { 156 | "ms": "2.1.2" 157 | } 158 | }, 159 | "delayed-stream": { 160 | "version": "1.0.0", 161 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 162 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 163 | }, 164 | "delegates": { 165 | "version": "1.0.0", 166 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", 167 | "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" 168 | }, 169 | "detect-libc": { 170 | "version": "1.0.3", 171 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", 172 | "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" 173 | }, 174 | "discord.js": { 175 | "version": "12.5.3", 176 | "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.3.tgz", 177 | "integrity": "sha512-D3nkOa/pCkNyn6jLZnAiJApw2N9XrIsXUAdThf01i7yrEuqUmDGc7/CexVWwEcgbQR97XQ+mcnqJpmJ/92B4Aw==", 178 | "requires": { 179 | "@discordjs/collection": "^0.1.6", 180 | "@discordjs/form-data": "^3.0.1", 181 | "abort-controller": "^3.0.0", 182 | "node-fetch": "^2.6.1", 183 | "prism-media": "^1.2.9", 184 | "setimmediate": "^1.0.5", 185 | "tweetnacl": "^1.0.3", 186 | "ws": "^7.4.4" 187 | } 188 | }, 189 | "event-target-shim": { 190 | "version": "5.0.1", 191 | "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", 192 | "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" 193 | }, 194 | "fs-minipass": { 195 | "version": "2.1.0", 196 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", 197 | "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", 198 | "requires": { 199 | "minipass": "^3.0.0" 200 | } 201 | }, 202 | "fs.realpath": { 203 | "version": "1.0.0", 204 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 205 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 206 | }, 207 | "gauge": { 208 | "version": "2.7.4", 209 | "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", 210 | "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", 211 | "requires": { 212 | "aproba": "^1.0.3", 213 | "console-control-strings": "^1.0.0", 214 | "has-unicode": "^2.0.0", 215 | "object-assign": "^4.1.0", 216 | "signal-exit": "^3.0.0", 217 | "string-width": "^1.0.1", 218 | "strip-ansi": "^3.0.1", 219 | "wide-align": "^1.1.0" 220 | } 221 | }, 222 | "glob": { 223 | "version": "7.1.6", 224 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 225 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 226 | "requires": { 227 | "fs.realpath": "^1.0.0", 228 | "inflight": "^1.0.4", 229 | "inherits": "2", 230 | "minimatch": "^3.0.4", 231 | "once": "^1.3.0", 232 | "path-is-absolute": "^1.0.0" 233 | } 234 | }, 235 | "has-unicode": { 236 | "version": "2.0.1", 237 | "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", 238 | "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" 239 | }, 240 | "http-proxy-agent": { 241 | "version": "4.0.1", 242 | "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", 243 | "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", 244 | "requires": { 245 | "@tootallnate/once": "1", 246 | "agent-base": "6", 247 | "debug": "4" 248 | } 249 | }, 250 | "inflight": { 251 | "version": "1.0.6", 252 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 253 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 254 | "requires": { 255 | "once": "^1.3.0", 256 | "wrappy": "1" 257 | } 258 | }, 259 | "inherits": { 260 | "version": "2.0.4", 261 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 262 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 263 | }, 264 | "is-fullwidth-code-point": { 265 | "version": "1.0.0", 266 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 267 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 268 | "requires": { 269 | "number-is-nan": "^1.0.0" 270 | } 271 | }, 272 | "isarray": { 273 | "version": "1.0.0", 274 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 275 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 276 | }, 277 | "lru-cache": { 278 | "version": "6.0.0", 279 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 280 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 281 | "requires": { 282 | "yallist": "^4.0.0" 283 | } 284 | }, 285 | "make-dir": { 286 | "version": "3.1.0", 287 | "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", 288 | "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", 289 | "requires": { 290 | "semver": "^6.0.0" 291 | }, 292 | "dependencies": { 293 | "semver": { 294 | "version": "6.3.0", 295 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 296 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" 297 | } 298 | } 299 | }, 300 | "mime-db": { 301 | "version": "1.47.0", 302 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", 303 | "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==" 304 | }, 305 | "mime-types": { 306 | "version": "2.1.30", 307 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", 308 | "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", 309 | "requires": { 310 | "mime-db": "1.47.0" 311 | } 312 | }, 313 | "minimatch": { 314 | "version": "3.0.4", 315 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 316 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 317 | "requires": { 318 | "brace-expansion": "^1.1.7" 319 | } 320 | }, 321 | "minipass": { 322 | "version": "3.1.3", 323 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", 324 | "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", 325 | "requires": { 326 | "yallist": "^4.0.0" 327 | } 328 | }, 329 | "minizlib": { 330 | "version": "2.1.2", 331 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", 332 | "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", 333 | "requires": { 334 | "minipass": "^3.0.0", 335 | "yallist": "^4.0.0" 336 | } 337 | }, 338 | "mkdirp": { 339 | "version": "1.0.4", 340 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 341 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" 342 | }, 343 | "ms": { 344 | "version": "2.1.2", 345 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 346 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 347 | }, 348 | "node-addon-api": { 349 | "version": "3.1.0", 350 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.1.0.tgz", 351 | "integrity": "sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw==" 352 | }, 353 | "node-fetch": { 354 | "version": "2.6.1", 355 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", 356 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" 357 | }, 358 | "nopt": { 359 | "version": "5.0.0", 360 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", 361 | "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", 362 | "requires": { 363 | "abbrev": "1" 364 | } 365 | }, 366 | "npmlog": { 367 | "version": "4.1.2", 368 | "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", 369 | "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", 370 | "requires": { 371 | "are-we-there-yet": "~1.1.2", 372 | "console-control-strings": "~1.1.0", 373 | "gauge": "~2.7.3", 374 | "set-blocking": "~2.0.0" 375 | } 376 | }, 377 | "number-is-nan": { 378 | "version": "1.0.1", 379 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 380 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" 381 | }, 382 | "object-assign": { 383 | "version": "4.1.1", 384 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 385 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 386 | }, 387 | "once": { 388 | "version": "1.4.0", 389 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 390 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 391 | "requires": { 392 | "wrappy": "1" 393 | } 394 | }, 395 | "path-is-absolute": { 396 | "version": "1.0.1", 397 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 398 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 399 | }, 400 | "prism-media": { 401 | "version": "1.2.9", 402 | "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.9.tgz", 403 | "integrity": "sha512-UHCYuqHipbTR1ZsXr5eg4JUmHER8Ss4YEb9Azn+9zzJ7/jlTtD1h0lc4g6tNx3eMlB8Mp6bfll0LPMAV4R6r3Q==" 404 | }, 405 | "process-nextick-args": { 406 | "version": "2.0.1", 407 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 408 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 409 | }, 410 | "readable-stream": { 411 | "version": "2.3.7", 412 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 413 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 414 | "requires": { 415 | "core-util-is": "~1.0.0", 416 | "inherits": "~2.0.3", 417 | "isarray": "~1.0.0", 418 | "process-nextick-args": "~2.0.0", 419 | "safe-buffer": "~5.1.1", 420 | "string_decoder": "~1.1.1", 421 | "util-deprecate": "~1.0.1" 422 | } 423 | }, 424 | "rimraf": { 425 | "version": "3.0.2", 426 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 427 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 428 | "requires": { 429 | "glob": "^7.1.3" 430 | } 431 | }, 432 | "safe-buffer": { 433 | "version": "5.1.2", 434 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 435 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 436 | }, 437 | "semver": { 438 | "version": "7.3.5", 439 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", 440 | "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", 441 | "requires": { 442 | "lru-cache": "^6.0.0" 443 | } 444 | }, 445 | "set-blocking": { 446 | "version": "2.0.0", 447 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 448 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 449 | }, 450 | "setimmediate": { 451 | "version": "1.0.5", 452 | "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", 453 | "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" 454 | }, 455 | "signal-exit": { 456 | "version": "3.0.3", 457 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", 458 | "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" 459 | }, 460 | "string-width": { 461 | "version": "1.0.2", 462 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 463 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 464 | "requires": { 465 | "code-point-at": "^1.0.0", 466 | "is-fullwidth-code-point": "^1.0.0", 467 | "strip-ansi": "^3.0.0" 468 | } 469 | }, 470 | "string_decoder": { 471 | "version": "1.1.1", 472 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 473 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 474 | "requires": { 475 | "safe-buffer": "~5.1.0" 476 | } 477 | }, 478 | "strip-ansi": { 479 | "version": "3.0.1", 480 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 481 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 482 | "requires": { 483 | "ansi-regex": "^2.0.0" 484 | } 485 | }, 486 | "tar": { 487 | "version": "6.1.0", 488 | "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz", 489 | "integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==", 490 | "requires": { 491 | "chownr": "^2.0.0", 492 | "fs-minipass": "^2.0.0", 493 | "minipass": "^3.0.0", 494 | "minizlib": "^2.1.1", 495 | "mkdirp": "^1.0.3", 496 | "yallist": "^4.0.0" 497 | } 498 | }, 499 | "tweetnacl": { 500 | "version": "1.0.3", 501 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", 502 | "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" 503 | }, 504 | "util-deprecate": { 505 | "version": "1.0.2", 506 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 507 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 508 | }, 509 | "wide-align": { 510 | "version": "1.1.3", 511 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", 512 | "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", 513 | "requires": { 514 | "string-width": "^1.0.2 || 2" 515 | } 516 | }, 517 | "wrappy": { 518 | "version": "1.0.2", 519 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 520 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 521 | }, 522 | "ws": { 523 | "version": "7.4.4", 524 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", 525 | "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==" 526 | }, 527 | "yallist": { 528 | "version": "4.0.0", 529 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 530 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 531 | } 532 | } 533 | } 534 | -------------------------------------------------------------------------------- /home/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tidal-bot", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "dependencies": { 6 | "@discordjs/opus": "^0.5.0", 7 | "discord.js": "^12.5.3" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /home/app/package.yaml: -------------------------------------------------------------------------------- 1 | name: app 2 | 3 | dependencies: 4 | - tidal == 1.7.3 5 | -------------------------------------------------------------------------------- /home/app/parseMessage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Parse messages and extract code. 3 | * 4 | * @param {string} msg a raw string 5 | * @returns {string} code 6 | */ 7 | function parseMessage( str ) { 8 | const matchTripleBackquoteHs = str.match( /```hs\s*([\S\s]+?)\s*```/m ); 9 | if ( matchTripleBackquoteHs ) { 10 | return matchTripleBackquoteHs[ 1 ]; 11 | } 12 | 13 | const matchTripleBackquote = str.match( /```\s*([\S\s]+?)\s*```/m ); 14 | if ( matchTripleBackquote ) { 15 | return matchTripleBackquote[ 1 ]; 16 | } 17 | 18 | const matchSingleBackquote = str.match( /`\s*([\S\s]+?)\s*`/m ); 19 | if ( matchSingleBackquote ) { 20 | return matchSingleBackquote[ 1 ]; 21 | } 22 | 23 | return str; 24 | } 25 | 26 | module.exports = parseMessage; 27 | -------------------------------------------------------------------------------- /home/app/stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-17.9 2 | 3 | extra-deps: 4 | - tidal-1.7.3 5 | -------------------------------------------------------------------------------- /home/app/streamer.js: -------------------------------------------------------------------------------- 1 | const { Readable } = require( 'stream' ); 2 | const jack = require( './jack-audio' ); 3 | 4 | const BUFFER_SIZE = Math.pow( 2, 16 ); 5 | const CHUNK_SIZE = 256; 6 | 7 | const Streamer = class { 8 | constructor() { 9 | // == setup jack =============================================================================== 10 | this.iRead = 0; 11 | this.iWrite = 0; 12 | this.queueSize = 0; 13 | this.buffer = new Int16Array( BUFFER_SIZE ); 14 | 15 | const streamer = this; 16 | 17 | this.stream = new Readable( { 18 | read( size ) { 19 | for ( let i = 0; i < size / 2 / CHUNK_SIZE; i ++ ) { 20 | if ( CHUNK_SIZE < streamer.queueSize ) { 21 | this.push( Buffer.from( streamer.buffer.buffer, streamer.iRead * 2, CHUNK_SIZE * 2 ) ); 22 | 23 | streamer.iRead = ( streamer.iRead + CHUNK_SIZE ) % BUFFER_SIZE; 24 | streamer.queueSize -= CHUNK_SIZE; 25 | 26 | // fast forward by 2x 27 | if ( BUFFER_SIZE * 0.75 < streamer.queueSize ) { 28 | streamer.iRead = ( streamer.iRead + CHUNK_SIZE ) % BUFFER_SIZE; 29 | streamer.queueSize -= CHUNK_SIZE; 30 | } 31 | } else { 32 | this.push( Buffer.alloc( CHUNK_SIZE * 2 ) ); 33 | } 34 | } 35 | } 36 | } ); 37 | 38 | jack.bind( ( nFrames, buffer ) => { 39 | for ( let i = 0; i < nFrames; i ++ ) { 40 | if ( BUFFER_SIZE <= this.queueSize ) { break; } 41 | this.buffer[ this.iWrite + 0 ] = parseInt( buffer[ 0 ][ i ] * 32767 ); 42 | this.buffer[ this.iWrite + 1 ] = parseInt( buffer[ 1 ][ i ] * 32767 ); 43 | this.iWrite = ( this.iWrite + 2 ) % BUFFER_SIZE; 44 | this.queueSize += 2; 45 | } 46 | } ); 47 | } 48 | 49 | start( clientName ) { 50 | jack.start( clientName ); 51 | } 52 | 53 | stop() { 54 | jack.close(); 55 | 56 | this.iRead = 0; 57 | this.iWrite = 0; 58 | this.queueSize = 0; 59 | } 60 | } 61 | 62 | module.exports = Streamer; 63 | -------------------------------------------------------------------------------- /home/app/tidal.js: -------------------------------------------------------------------------------- 1 | const cp = require( 'child_process' ); 2 | 3 | const Tidal = class { 4 | constructor() { 5 | this.listeners = {}; // e.g.: { "stdout": [ func, func ], "stderr": [ func ] } 6 | 7 | this.evalQueue = []; // codes being sent before init 8 | 9 | this.isReady = false; 10 | 11 | // == chunky tidal stdout ====================================================================== 12 | { 13 | let str = ''; 14 | let date = 0; 15 | 16 | const append = ( msg ) => { 17 | str += msg; 18 | str = str.replace( /Prelude Sound\.Tidal\.Context\| System.IO\| /g, '' ); 19 | 20 | date = Date.now(); 21 | }; 22 | 23 | this.on( 'ghci-stdout', append ); 24 | this.on( 'ghci-stderr', append ); 25 | 26 | const update = () => { 27 | if ( str !== '' && date < Date.now() - 400 ) { 28 | this.emit( 'log', str ); 29 | str = ''; 30 | } 31 | setTimeout( update, 100 ); 32 | }; 33 | update(); 34 | } 35 | 36 | // == sc stdout logger ========================================================================= 37 | this.scLog = ''; 38 | { 39 | const append = ( msg ) => { 40 | this.scLog += msg; 41 | 42 | const lines = this.scLog.split( '\n' ); 43 | if ( 10 < lines.length ) { 44 | this.scLog = lines.splice( lines.length - 10 ).join( '\n' ) 45 | } 46 | 47 | if ( 1000 < this.scLog.length ) { 48 | this.scLog = '...' + this.scLog.substring( this.scLog.length - 1000 ); 49 | } 50 | }; 51 | 52 | this.on( 'sc-stdout', append ); 53 | this.on( 'sc-stderr', append ); 54 | } 55 | } 56 | 57 | start( bootTidalPath ) { 58 | // == setup sclang ============================================================================= 59 | const cpSc = cp.spawn( 'sclang' ); 60 | 61 | cpSc.stderr.on( 'data', ( data ) => { 62 | process.stderr.write( data ); 63 | this.emit( 'sc-stderr', data.toString( 'utf8' ) ); 64 | } ); 65 | 66 | cpSc.stdout.on( 'data', ( data ) => { 67 | process.stdout.write( data ); 68 | this.emit( 'sc-stdout', data.toString( 'utf8' ) ); 69 | } ); 70 | 71 | this.cpSc = cpSc; 72 | 73 | // == setup ghci =============================================================================== 74 | const cpGhci = cp.spawn( 75 | 'stack', 76 | [ 'exec', '--package', 'tidal', '--', 'ghci' ], 77 | { cwd: __dirname } 78 | ); 79 | 80 | cpGhci.stderr.on( 'data', ( data ) => { 81 | process.stderr.write( data ); 82 | this.emit( 'ghci-stderr', data.toString( 'utf8' ) ); 83 | } ); 84 | 85 | cpGhci.stdout.on( 'data', ( data ) => { 86 | process.stdout.write( data ); 87 | this.emit( 'ghci-stdout', data.toString( 'utf8' ) ); 88 | } ); 89 | 90 | this.cpGhci = cpGhci; 91 | 92 | // == ready! =================================================================================== 93 | this.isReady = true; 94 | this.emit( 'ready' ); 95 | 96 | // == execute BootTidal ======================================================================== 97 | this.sendLine( `:script ${bootTidalPath}` ); 98 | 99 | // == evaluate evalQueue ======================================================================= 100 | this.evalQueue.forEach( ( code ) => this.evaluate( code ) ); 101 | this.evalQueue = []; 102 | } 103 | 104 | stop() { 105 | this.isReady = false; 106 | 107 | this.cpGhci.stdin.write( ':quit\n' ); 108 | this.cpGhci = null; 109 | 110 | this.cpSc.stdin.write( '0.exit\n' ); 111 | this.cpSc = null; 112 | 113 | this.emit( 'stop' ); 114 | } 115 | 116 | kill() { 117 | this.isReady = false; 118 | 119 | this.cpSc.kill(); 120 | this.cpSc = null; 121 | 122 | this.cpGhci.kill(); 123 | this.cpGhci = null; 124 | 125 | this.emit( 'stop' ); 126 | } 127 | 128 | sendLine( line ) { 129 | if ( !this.isReady ) { 130 | console.warn( 'Tidal: ignoring sendLine because it is not ready' ); 131 | return; 132 | } 133 | 134 | this.cpGhci.stdin.write( line ); 135 | this.cpGhci.stdin.write( '\n' ); 136 | } 137 | 138 | evaluate( code ) { 139 | if ( !this.isReady ) { 140 | this.evalQueue.push( code ); 141 | return; 142 | } 143 | 144 | this.sendLine( ':{' ); 145 | code.split( '\n' ).map( ( line ) => this.sendLine( line ) ); 146 | this.sendLine( ':}' ); 147 | } 148 | 149 | hush() { 150 | if ( !this.isReady ) { 151 | console.warn( 'Tidal: ignoring hush because it is not ready' ); 152 | return; 153 | } 154 | 155 | this.evaluate( 'hush' ); 156 | this.emit( 'hush' ); 157 | } 158 | 159 | getScLog() { 160 | return this.scLog; 161 | } 162 | 163 | emit( name, ...val ) { 164 | if ( !this.listeners[ name ] ) { return; } 165 | 166 | this.listeners[ name ].map( ( func ) => func( val ) ); 167 | } 168 | 169 | on( name, func ) { 170 | if ( !this.listeners[ name ] ) { 171 | this.listeners[ name ] = []; 172 | } 173 | 174 | this.listeners[ name ].push( func ); 175 | } 176 | 177 | off( name, func ) { 178 | if ( !this.listeners[ name ] ) { return; } 179 | 180 | const index = this.listeners[ name ].indexOf( func ); 181 | if ( index !== -1 ) { 182 | this.listeners[ name ].splice( index, 1 ); 183 | } 184 | } 185 | }; 186 | 187 | module.exports = Tidal; 188 | -------------------------------------------------------------------------------- /home/jack-audio/.dockerignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | build 3 | node_modules -------------------------------------------------------------------------------- /home/jack-audio/.gitignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /home/jack-audio/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "jack-audio", 5 | "sources": [ "src/jack-audio.cc" ], 6 | "include_dirs": [ " 3 | #include 4 | 5 | // == definitions ============================================================== 6 | #define STR_SIZE 256 7 | #define DEFAULT_CLIENT_NAME "jack_audio" 8 | 9 | // == globals ================================================================== 10 | char gJackClientName[ STR_SIZE ]; 11 | jack_port_t *gJackPortInL = NULL; 12 | jack_port_t *gJackPortInR = NULL; 13 | jack_client_t *gJackClient = NULL; 14 | Nan::Callback gOnProcess; 15 | 16 | typedef struct processData { 17 | jack_default_audio_sample_t *inL, *inR; 18 | jack_nframes_t nframes; 19 | } ProcessData; 20 | 21 | /** 22 | * why 23 | */ 24 | void h( uv_work_t* req ) {} 25 | 26 | /** 27 | * um 28 | */ 29 | void postProcess( uv_work_t* req ) { 30 | v8::Isolate *isolate = v8::Isolate::GetCurrent(); 31 | v8::HandleScope scope(isolate); 32 | 33 | ProcessData *data = static_cast( req->data ); 34 | jack_default_audio_sample_t *inL = data->inL; 35 | jack_default_audio_sample_t *inR = data->inR; 36 | jack_nframes_t nframes = data->nframes; 37 | 38 | v8::Local sendL = Nan::New( nframes ); 39 | v8::Local sendR = Nan::New( nframes ); 40 | for ( int i = 0; i < (int)nframes; i ++ ) { 41 | Nan::Set( sendL, i, Nan::New( inL[ i ] ) ); 42 | Nan::Set( sendR, i, Nan::New( inR[ i ] ) ); 43 | } 44 | 45 | v8::Local send = Nan::New(); 46 | Nan::Set( send, 0, sendL ); 47 | Nan::Set( send, 1, sendR ); 48 | 49 | v8::Local argv[ 2 ] = { Nan::New( nframes ), send }; 50 | Nan::Call( gOnProcess, 2, argv ); 51 | 52 | delete data; 53 | delete req; 54 | } 55 | 56 | /** 57 | * It will be applied to jack process callback 58 | */ 59 | int jackProcess( jack_nframes_t nframes, void *arg ) 60 | { 61 | if ( gOnProcess.IsEmpty() ) { 62 | return 0; 63 | } 64 | 65 | uv_work_t *req = new uv_work_t; 66 | ProcessData *data = new ProcessData; 67 | 68 | data->inL = (jack_default_audio_sample_t *)jack_port_get_buffer( gJackPortInL, nframes ); 69 | data->inR = (jack_default_audio_sample_t *)jack_port_get_buffer( gJackPortInR, nframes ); 70 | data->nframes = nframes; 71 | 72 | req->data = data; 73 | 74 | uv_queue_work( uv_default_loop(), req, h, reinterpret_cast(postProcess) ); 75 | 76 | return 0; 77 | } 78 | 79 | /** 80 | * stab stab stab 81 | */ 82 | void kill() 83 | { 84 | gJackPortInL = NULL; 85 | gJackPortInR = NULL; 86 | 87 | if ( gJackClient != NULL ) { 88 | jack_client_close( gJackClient ); 89 | gJackClient = NULL; 90 | } 91 | } 92 | 93 | /** 94 | * It will be applied to jack shutdown callback 95 | */ 96 | void jackShutdown( void *arg ) 97 | { 98 | kill(); 99 | } 100 | 101 | /** 102 | * Starts jack client. 103 | * @param {string} client Client name 104 | */ 105 | NAN_METHOD( nodeStart ) 106 | { 107 | // argument check 108 | if ( info.Length() < 1 ) { 109 | Nan::ThrowTypeError( "1 argument is required" ); 110 | return; 111 | } 112 | 113 | if ( !info[ 0 ]->IsString() ) { 114 | Nan::ThrowTypeError( "Client name (arg1) must be string" ); 115 | return; 116 | } 117 | 118 | jack_status_t status; 119 | 120 | // read client name given as argument 121 | v8::Local name = Nan::To( info[ 0 ] ).ToLocalChecked(); 122 | if ( STR_SIZE <= name->Length() ) { 123 | Nan::ThrowError( "Client name is too loooooooong" ); 124 | } 125 | Nan::DecodeWrite( gJackClientName, name->Length(), name ); 126 | 127 | // open client 128 | gJackClient = jack_client_open( gJackClientName, JackNullOption, &status, NULL ); 129 | if ( gJackClient == NULL ) { 130 | if ( status & JackServerFailed ) { 131 | Nan::ThrowError( "Unable to connect to JACK server" ); 132 | } else { 133 | Nan::ThrowError( "jack_client_open() failed" ); 134 | } 135 | return; 136 | } 137 | 138 | if ( status & JackNameNotUnique ) { 139 | Nan::ThrowError( "Client name must be unique" ); 140 | kill(); 141 | return; 142 | } 143 | 144 | // setup callback function 145 | jack_set_process_callback( gJackClient, jackProcess, 0 ); 146 | jack_on_shutdown( gJackClient, jackShutdown, 0 ); 147 | 148 | // register ports 149 | gJackPortInL = jack_port_register( gJackClient, "inL", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 ); 150 | gJackPortInR = jack_port_register( gJackClient, "inR", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 ); 151 | 152 | if ( ( gJackPortInL == NULL ) || ( gJackPortInR == NULL ) ) { 153 | Nan::ThrowError( "There is something wrong with JACK ports" ); 154 | kill(); 155 | return; 156 | } 157 | 158 | // activate client 159 | if ( jack_activate( gJackClient ) ) { 160 | Nan::ThrowError( "Something weird happened when attempt to activate the client" ); 161 | return; 162 | } 163 | } 164 | 165 | /** 166 | * Closes jack client. 167 | */ 168 | NAN_METHOD( nodeClose ) 169 | { 170 | kill(); 171 | } 172 | 173 | /** 174 | * Bind callback method. 175 | */ 176 | NAN_METHOD( nodeBind ) 177 | { 178 | // argument check 179 | if ( info.Length() < 1 ) { 180 | Nan::ThrowTypeError( "1 argument is required" ); 181 | return; 182 | } 183 | 184 | if ( !info[ 0 ]->IsFunction() ) { 185 | Nan::ThrowTypeError( "Binding function (arg1) must be function" ); 186 | return; 187 | } 188 | 189 | // bind function 190 | gOnProcess.Reset( Nan::To( info[ 0 ] ).ToLocalChecked() ); 191 | } 192 | 193 | // == bind methods and done ==================================================== 194 | NAN_MODULE_INIT( init ) 195 | { 196 | Nan::SetMethod( target, "start", nodeStart ); 197 | Nan::SetMethod( target, "close", nodeClose ); 198 | Nan::SetMethod( target, "bind", nodeBind ); 199 | } 200 | 201 | NODE_MODULE( addon, init ) -------------------------------------------------------------------------------- /home/supervisor.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | stdout_logfile=/dev/stdout 4 | stdout_logfile_maxbytes=0 5 | stderr_logfile=/dev/stderr 6 | stderr_logfile_maxbytes=0 7 | 8 | [program:jackd] 9 | command=jackd -r -p 32 -d dummy -r 48000 10 | stdout_logfile=/dev/stdout 11 | stdout_logfile_maxbytes=0 12 | stderr_logfile=/dev/stderr 13 | stderr_logfile_maxbytes=0 14 | 15 | [program:node] 16 | directory=/root/app 17 | command=node index.js 18 | startsecs=3 19 | stdout_logfile=/dev/stdout 20 | stdout_logfile_maxbytes=0 21 | stderr_logfile=/dev/stderr 22 | stderr_logfile_maxbytes=0 23 | -------------------------------------------------------------------------------- /nice-to-pin.md: -------------------------------------------------------------------------------- 1 | **Resources** 2 | 3 | TidalCycles official documentation: 4 | This workshop will make you understand quickly: 5 | 6 | tado's performance logs are gold vain: 7 | 8 | You can use these samples in TidalBot: 9 | 10 | ------ 11 | 12 | **Basic expressions** 13 | 14 | Each lines would be executed separately 15 | 16 | ```hs 17 | d1 $ sound "bd" -- hit bassdrum once per cycle 18 | d1 $ sound "bd bd bd bd" -- four on the floor 19 | d1 silence -- stop the pattern on d1 20 | 21 | setcps (120/60/4) -- set cps to 0.5 (equivalent of 120BPM) 22 | 23 | solo $ d1 -- solo d1 24 | unsolo $ d1 -- unsolo d1 25 | hush -- stop all patterns 26 | ``` 27 | 28 | ------ 29 | 30 | **Special commands** 31 | 32 | `@TidalBot bye`: :wave: Make them disconnect from the VC 33 | `@TidalBot sc-log`: :cyclone: Output SuperCollider log 34 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tidal-bot", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@discordjs/collection": { 8 | "version": "0.1.6", 9 | "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz", 10 | "integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ==", 11 | "dev": true 12 | }, 13 | "@discordjs/form-data": { 14 | "version": "3.0.1", 15 | "resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz", 16 | "integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==", 17 | "dev": true, 18 | "requires": { 19 | "asynckit": "^0.4.0", 20 | "combined-stream": "^1.0.8", 21 | "mime-types": "^2.1.12" 22 | } 23 | }, 24 | "@types/node": { 25 | "version": "14.0.27", 26 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.27.tgz", 27 | "integrity": "sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g==", 28 | "dev": true 29 | }, 30 | "abort-controller": { 31 | "version": "3.0.0", 32 | "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", 33 | "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", 34 | "dev": true, 35 | "requires": { 36 | "event-target-shim": "^5.0.0" 37 | } 38 | }, 39 | "asynckit": { 40 | "version": "0.4.0", 41 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 42 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", 43 | "dev": true 44 | }, 45 | "combined-stream": { 46 | "version": "1.0.8", 47 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 48 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 49 | "dev": true, 50 | "requires": { 51 | "delayed-stream": "~1.0.0" 52 | } 53 | }, 54 | "delayed-stream": { 55 | "version": "1.0.0", 56 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 57 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", 58 | "dev": true 59 | }, 60 | "discord.js": { 61 | "version": "12.3.1", 62 | "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.3.1.tgz", 63 | "integrity": "sha512-mSFyV/mbvzH12UXdS4zadmeUf8IMQOo/YdunubG1wWt1xjWvtaJz/s9CGsFD2B5pTw1W/LXxxUbrQjIZ/xlUdw==", 64 | "dev": true, 65 | "requires": { 66 | "@discordjs/collection": "^0.1.6", 67 | "@discordjs/form-data": "^3.0.1", 68 | "abort-controller": "^3.0.0", 69 | "node-fetch": "^2.6.0", 70 | "prism-media": "^1.2.2", 71 | "setimmediate": "^1.0.5", 72 | "tweetnacl": "^1.0.3", 73 | "ws": "^7.3.1" 74 | } 75 | }, 76 | "event-target-shim": { 77 | "version": "5.0.1", 78 | "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", 79 | "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", 80 | "dev": true 81 | }, 82 | "mime-db": { 83 | "version": "1.44.0", 84 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 85 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", 86 | "dev": true 87 | }, 88 | "mime-types": { 89 | "version": "2.1.27", 90 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 91 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 92 | "dev": true, 93 | "requires": { 94 | "mime-db": "1.44.0" 95 | } 96 | }, 97 | "node-fetch": { 98 | "version": "2.6.0", 99 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", 100 | "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", 101 | "dev": true 102 | }, 103 | "prism-media": { 104 | "version": "1.2.2", 105 | "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.2.tgz", 106 | "integrity": "sha512-I+nkWY212lJ500jLe4tN9tWO7nRiBAVdMv76P9kffZjYhw20raMlW1HSSvS+MLXC9MmbNZCazMrAr+5jEEgTuw==", 107 | "dev": true 108 | }, 109 | "setimmediate": { 110 | "version": "1.0.5", 111 | "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", 112 | "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", 113 | "dev": true 114 | }, 115 | "tweetnacl": { 116 | "version": "1.0.3", 117 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", 118 | "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", 119 | "dev": true 120 | }, 121 | "ws": { 122 | "version": "7.3.1", 123 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", 124 | "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==", 125 | "dev": true 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tidal-bot", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "build": "docker build -t 0b5vr/tidal-bot .", 6 | "cleanbuild": "docker build --no-cache -t 0b5vr/tidal-bot .", 7 | "start": "docker run -it --env-file .env --rm 0b5vr/tidal-bot", 8 | "shell": "docker run -it --env-file .env --entrypoint=bash 0b5vr/tidal-bot" 9 | }, 10 | "devDependencies": { 11 | "@types/node": "^14.0.27", 12 | "discord.js": "^12.3.1" 13 | } 14 | } 15 | --------------------------------------------------------------------------------