├── .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 |
--------------------------------------------------------------------------------