diff --git a/.circleci/config.yml b/.circleci/config.yml index 56dd97bb7..3a5e9ba6e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,5 +1,41 @@ version: 2 + jobs: + sentry-association: + docker: + - image: getsentry/sentry-cli + steps: + - checkout + - run: + name: Assosiate commits with Sentry + command: | + VERSION=$(sentry-cli releases propose-version) + sentry-cli releases new -p wildbeast $VERSION + sentry-cli releases set-commits --auto $VERSION + + crowdin-download: + working_directory: /tmp/project + docker: + - image: dougley/crowdin + steps: + - checkout + - run: + name: Request fresh build + command: crowdin download -b WildBeast + - persist_to_workspace: + root: /tmp/project + paths: + - src/languages + + crowdin-upload: + docker: + - image: dougley/crowdin + steps: + - checkout + - run: + name: Upload new strings + command: crowdin upload sources -b WildBeast + test: docker: - image: node:latest @@ -17,18 +53,18 @@ jobs: - run: name: Test command: npm test + build: docker: - image: docker:latest steps: - - run: - name: Git - command: apk add --update git - checkout - - setup_remote_docker + - attach_workspace: + at: /tmp/workspace - run: - name: Initialize submodule - command: git submodule update --init --remote + name: Merge translations + command: cp -rf /tmp/workspace/src/languages/ ~/project/src/ + - setup_remote_docker - run: name: Build Docker image command: if [ "$CIRCLE_BRANCH" = "master" ]; then docker build -t dougley/wildbeast:latest --build-arg buildno=$CIRCLE_BUILD_NUM --build-arg commitsha=$CIRCLE_SHA1 .; else docker build -t dougley/wildbeast:$CIRCLE_BRANCH --build-arg buildno=$CIRCLE_BUILD_NUM --build-arg commitsha=$CIRCLE_SHA1 .; fi @@ -37,19 +73,18 @@ jobs: command: | echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin if [ "$CIRCLE_BRANCH" = "master" ]; then docker push dougley/wildbeast:latest; else docker push dougley/wildbeast:$CIRCLE_BRANCH; fi + tagged-build: docker: - image: docker:latest steps: - - run: - name: Git - command: apk add --update git - checkout - - setup_remote_docker: - docker_layer_caching: true + - attach_workspace: + at: /tmp/workspace - run: - name: Initialize submodule - command: git submodule update --init --remote + name: Merge translations + command: cp -rf /tmp/workspace/src/languages/ ~/project/src/ + - setup_remote_docker - run: name: Build Docker image command: docker build -t dougley/wildbeast:$(git describe --abbrev=0 --tags) --build-arg buildno=$CIRCLE_BUILD_NUM --build-arg commitsha=$CIRCLE_SHA1 . @@ -58,6 +93,7 @@ jobs: command: | echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin docker push dougley/wildbeast:$(git describe --abbrev=0 --tags) + docs-build-test: docker: - image: anthonyz/python3-node8:latest @@ -75,6 +111,7 @@ jobs: - run: name: Build docs command: npm run build + docs-deploy: docker: - image: anthonyz/python3-node8:latest @@ -100,6 +137,7 @@ jobs: command: | git config user.email 9768134+Dougley@users.noreply.github.com python3 -m mkdocs gh-deploy + workflows: version: 2 docs: @@ -111,20 +149,41 @@ workflows: filters: branches: only: master - test_n_build: + docker-image: jobs: - - test + - test: + filters: + tags: + only: /v.+/ + - crowdin-download: + filters: + branches: + only: /master|experimental/ + - sentry-association: + requires: + - test + filters: + branches: + only: /master|experimental/ - build: requires: - test + - crowdin-download filters: branches: only: /master|experimental/ - tagged-build: requires: - test + - crowdin-download filters: branches: ignore: /.*/ tags: only: /v.+/ + crowdin: + jobs: + - crowdin-upload: + filters: + branches: + only: /master|experimental/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7001c41ad..26f84db46 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,6 @@ site # Command info dump commandInfo.json docs/js/dist/commands.js* + +# lokijs database +wildbeast.db diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 8b020985b..000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "src/languages"] - path = src/languages - url = https://github.com/TheSharks/WildBeast-Translations.git diff --git a/CHANGELOG.md b/CHANGELOG.md index 2011c8fd5..c78b94972 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # 6.0.0 ## Release phase +### 6.1.0 +Removed translation submodule, we now include the standard file by default. Translations are able to be downloaded from our crowdin project +CircleCI will now automatically include translations into new Docker images +For Docker images, uws is locked to v10.148.1 +Commands that call external APIs will now indicate errors +Music command have been updated to support lavalink v3 +Added optional embedded database powered by LokiJS +Added new ffmpeg based voice encoder +`++meme` will no longer use imgflip, memegen is used instead +`++settings` will error out if an unknown language is being set +Encoders and drivers are loaded on-demand instead of requiring them all +Elasticsearch logging will now log command arguments instead of the full message +Version checking will check for git commits instead of versions only, this falls back to original version check if git is not available + ### 6.0.0 **This is a breaking release, nothing is backwards compatible.** diff --git a/Dockerfile b/Dockerfile index a9a1799c1..4b03b95ed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ COPY . /opt/wildbeast WORKDIR /opt/wildbeast RUN npm i --production # Install optional native modules -RUN npm i zlib-sync uws https://github.com/discordapp/erlpack.git bufferutil sodium-native node-opus +RUN npm i zlib-sync uws@10.148.1 https://github.com/discordapp/erlpack.git bufferutil sodium-native node-opus # Switch to wildbeast user and run entrypoint USER wildbeast diff --git a/README.md b/README.md index 1dd5ccfe6..1584fe124 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,14 @@


- License - Version - Discord server + License + Version + Discord server

- - CircleCI - CircleCI + + CircleCI + CircleCI

diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 000000000..601e28b97 --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,10 @@ +"project_identifier" : "wildbeast" +"api_key_env": CROWDIN_API_KEY +"preserve_hierarchy": true + +files: [ + { + "source" : "/src/languages/en-EN.json", + "translation" : "/src/languages/%locale%.json", + } +] \ No newline at end of file diff --git a/docs/commands.md b/docs/commands.md index 49ac64ef2..4bf759d97 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -21,7 +21,6 @@ Additional command information: [Addendums](#addendums)
[Tag subcommands](#tag-subcommands)
[Settings subcommands](#settings-subcommands)
-[Available meme types](#available-meme-types) ## Commands @@ -30,8 +29,9 @@ Additional command information: ## Addendums 1. For the **colorrole** command, a hexadecimal value can be submitted in either **#FFFFFF** or **FFFFFF** format. -2. The **softban** command bans a user and then immediately unbans them, deleting their messages without barring future access to the server. -3. The **request** command supports playing from the following resources: YouTube, SoundCloud, Bandcamp, Twitch, Vimeo, Mixer and raw HTML audio. +2. You can get all the available meme types for the **meme** command by using the command **meme templates**. +3. The **softban** command bans a user and then immediately unbans them, deleting their messages without barring future access to the server. +4. The **request** command supports playing from the following resources: YouTube, SoundCloud, Bandcamp, Twitch, Vimeo, Mixer and raw HTML audio. ## Tag subcommands @@ -54,7 +54,7 @@ The tag command has the following subcommands. All subcommands inherit the permi The following settings can be edited with this command. All settings are server-specific and all subcommands inherit the permission level of the main command. !!! note - Languages are still WIP. Only the "en" language is supported at the moment. + Translation support is in beta, please see the [translation directory](https://github.com/TheSharks/WildBeast/tree/master/src/languages) for supported languages. | Name | Description | Usage | | ---- | ----------- | ----- | @@ -63,48 +63,3 @@ The following settings can be edited with this command. All settings are server- | welcome | Change the welcome message target. | settings welcome **<#channel/dm\>** | | welcomeMessage | Change the welcome message that is sent when a new member joins. | settings welcomeMessage **** | | reset | Reset a setting to its default value. | settings reset **** | - -## Available meme types - -The values in the **Name** column of the table below correspond to the relevant meme ID on https://api.imgflip.com/popular_meme_ids. - -| Name | ID | -| ---- | -- | -| brace | 61546 | -| mostinteresting | 61532 | -| fry | 61520 | -| onedoesnot | 61579 | -| yuno | 61527 | -| success | 61544 -| allthethings | 61533 | -| doge | 8072285 | -| drevil | 40945639 | -| skeptical | 101711 | -| notime | 442575 | -| yodawg | 101716 | -| ermahgerd | 101462 | -| hipsterariel | 86601 | -| imagination | 163573 | -| grumpycat | 405658 | -| morpheus | 100947 | -| 1stworldproblems | 61539 | -| facepalm | 1509839 | -| wtf | 245898 | -| batmanslaprobin | 438680 | -| takemymoney | 176908 | -| gollum | 681831 | -| grindmygears | 356615 | -| consuela | 160583 | -| ineedyouto | 89655 | -| chucknorrisapproves | 241304 | -| asianfather | 61559 | -| foreveralone | 61528 | -| grandmainternet | 61556 | -| zoidberg | 61573 | -| troll | 101484 | -| familyguybrian | 674967 | -| obama | 185239 | -| badluckbrian | 61585 | -| philosoraptor | 61516 | -| 3rdworldsuccess | 101287 | -| ancientaliens | 101470 | diff --git a/package-lock.json b/package-lock.json index a3c66693a..0c23a0fe3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "wildbeast", - "version": "6.0.0", + "version": "6.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -50,6 +50,7 @@ "version": "5.5.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, "requires": { "co": "^4.6.0", "fast-deep-equal": "^1.0.0", @@ -131,16 +132,6 @@ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, - "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", @@ -151,16 +142,6 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" - }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -202,31 +183,6 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "optional": true, - "requires": { - "tweetnacl": "^0.14.3" - }, - "dependencies": { - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true - } - } - }, - "boom": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", - "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", - "requires": { - "hoek": "4.x.x" - } - }, "brace-expansion": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", @@ -264,11 +220,6 @@ "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", "dev": true }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, "chalk": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", @@ -333,7 +284,8 @@ "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true }, "code-point-at": { "version": "1.1.0", @@ -429,24 +381,6 @@ "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=", "optional": true }, - "cryptiles": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", - "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", - "requires": { - "boom": "5.x.x" - }, - "dependencies": { - "boom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", - "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", - "requires": { - "hoek": "4.x.x" - } - } - } - }, "d": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", @@ -456,14 +390,6 @@ "es5-ext": "^0.10.9" } }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" - } - }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", @@ -554,15 +480,6 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz", "integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0=" }, - "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "optional": true, - "requires": { - "jsbn": "~0.1.0" - } - }, "elasticsearch": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/elasticsearch/-/elasticsearch-14.0.0.tgz", @@ -608,23 +525,21 @@ } }, "eris": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/eris/-/eris-0.8.5.tgz", - "integrity": "sha512-Jjqq+imrMisfmq8iPI5ZdqfmFxuDzB8NwlUIzsz6a1FWA6+W92UWQgpQOBXWtKYzfWjrYVwPAIv4E0vOsgpEtA==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/eris/-/eris-0.9.0.tgz", + "integrity": "sha512-U3/fv/wF2nA7Pc003v2cMXfTXvz8OySZYE21Zpx3ipm5sKbHWjcwDGdNXbp2Nb7fUip/IIjA/5540YDcqyQSvQ==", "requires": { "opusscript": "^0.0.4", "tweetnacl": "^1.0.0", - "ws": "^3.3.2" + "ws": "^6.0.0" }, "dependencies": { "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.0.0.tgz", + "integrity": "sha512-c2UlYcAZp1VS8AORtpq6y4RJIkJ9dQz18W32SpR/qXGfLDZ2jU4y4wKvvZwqbi7U6gxFQTeE+urMbXU/tsDy4w==", "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" + "async-limiter": "~1.0.0" } } } @@ -906,9 +821,9 @@ } }, "eslint-plugin-import": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.12.0.tgz", - "integrity": "sha1-2tMXgSktZmSyUxf9BJ0uKy8CIF0=", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz", + "integrity": "sha512-FpuRtniD/AY6sXByma2Wr0TXvXJ4nA/2/04VPlfpmUDPOpOY264x+ILiwnrk/k4RINgDAyFZByxqPUbSQ5YE7g==", "dev": true, "requires": { "contains-path": "^0.1.0", @@ -953,9 +868,9 @@ } }, "resolve": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", - "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", "dev": true, "requires": { "path-parse": "^1.0.5" @@ -984,9 +899,9 @@ } }, "eslint-plugin-promise": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-3.7.0.tgz", - "integrity": "sha512-2WO+ZFh7vxUKRfR0cOIMrWgYKdR6S1AlOezw6pC52B6oYpd5WFghN+QHxvrRdZMtbo8h3dfUZ2o1rWb0UPbKtg==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-3.8.0.tgz", + "integrity": "sha512-JiFL9UFR15NKpHyGii1ZcvmtIqa3UTwiDAGb8atSffe43qJ3+1czVGN6UtkklpcJ2DVnqvTMzEKRaJdBkAL2aQ==", "dev": true }, "eslint-plugin-react": { @@ -1120,20 +1035,17 @@ "tmp": "^0.0.33" } }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, "fast-deep-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", - "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=" + "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", + "dev": true }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true }, "fast-levenshtein": { "version": "2.0.6", @@ -1209,11 +1121,6 @@ "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", "dev": true }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, "form-data": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", @@ -1279,14 +1186,6 @@ "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", "dev": true }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" - } - }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", @@ -1343,20 +1242,6 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", - "requires": { - "ajv": "^5.1.0", - "har-schema": "^2.0.0" - } - }, "has": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", @@ -1385,32 +1270,16 @@ "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", "dev": true }, - "hawk": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", - "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", - "requires": { - "boom": "4.x.x", - "cryptiles": "3.x.x", - "hoek": "4.x.x", - "sntp": "2.x.x" - } - }, "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, - "hoek": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", - "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" - }, "hosted-git-info": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", - "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", "dev": true }, "hot-shots": { @@ -1419,16 +1288,6 @@ "integrity": "sha512-Wffw6kQzXI1a6ZBismu8ksuf36OiCOFwwyQb07pDSDd0t1haipYp9gDT+VYEIoINvjqerX1rA/q91HNdzclVPg==", "optional": true }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, "iconv-lite": { "version": "0.4.21", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.21.tgz", @@ -1444,14 +1303,6 @@ "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==", "dev": true }, - "imgflipper": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/imgflipper/-/imgflipper-1.0.1.tgz", - "integrity": "sha1-VA4KlsZM3Cs/KQzhGQVxWaajHh4=", - "requires": { - "request": "^2.63.0" - } - }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -1614,11 +1465,6 @@ "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", "dev": true }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -1630,11 +1476,6 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", @@ -1651,27 +1492,17 @@ "esprima": "^4.0.0" } }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true - }, "json-parse-better-errors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.1.tgz", "integrity": "sha512-xyQpxeWWMKyJps9CuGJYeng6ssI5bpqS9ltQpdVQ90t4ql6NdnxFKh95JcRt2cun/DjMVNrdjniLPuMA69xmCw==", "dev": true }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, "json-schema-traverse": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true }, "json-stable-stringify": { "version": "1.0.1", @@ -1688,11 +1519,6 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -1714,17 +1540,6 @@ "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", "dev": true }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, "jsx-ast-utils": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz", @@ -1807,9 +1622,9 @@ "optional": true }, "lokijs": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/lokijs/-/lokijs-1.5.4.tgz", - "integrity": "sha1-RiJZXGvxdykwZjqzrVDR9im2zDA=" + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/lokijs/-/lokijs-1.5.5.tgz", + "integrity": "sha1-HCH4KvdXkDf63nueSBNIXCNwi7Y=" }, "lru-cache": { "version": "4.1.2", @@ -1941,9 +1756,9 @@ } }, "moment": { - "version": "2.22.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.1.tgz", - "integrity": "sha512-shJkRTSebXvsVqk56I+lkb2latjBs8I+pc2TzWc545y2iFnSjm7Wg0QMh+ZWcdSLQyGEau5jI8ocnmkyTgr9YQ==" + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", + "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" }, "ms": { "version": "2.0.0", @@ -1989,11 +1804,6 @@ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" - }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2147,11 +1957,6 @@ "pify": "^2.0.0" } }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -2287,11 +2092,6 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, "qs": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", @@ -2401,42 +2201,6 @@ "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", "dev": true }, - "request": { - "version": "2.85.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", - "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.6.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.1", - "forever-agent": "~0.6.1", - "form-data": "~2.3.1", - "har-validator": "~5.0.3", - "hawk": "~6.0.2", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.17", - "oauth-sign": "~0.8.2", - "performance-now": "^2.1.0", - "qs": "~6.5.1", - "safe-buffer": "^5.1.1", - "stringstream": "~0.0.5", - "tough-cookie": "~2.3.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.1.0" - }, - "dependencies": { - "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" - } - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -2579,14 +2343,6 @@ "is-fullwidth-code-point": "^2.0.0" } }, - "sntp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", - "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", - "requires": { - "hoek": "4.x.x" - } - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2631,29 +2387,6 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, - "sshpk": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", - "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "tweetnacl": "~0.14.0" - }, - "dependencies": { - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true - } - } - }, "stack-trace": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", @@ -3054,11 +2787,6 @@ "safe-buffer": "~5.1.0" } }, - "stringstream": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", - "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==" - }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -3180,28 +2908,12 @@ "os-tmpdir": "~1.0.2" } }, - "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", - "requires": { - "punycode": "^1.4.1" - } - }, "trim": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=", "optional": true }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, "tweetnacl": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.0.tgz", @@ -3285,25 +2997,15 @@ "optional": true }, "validate-npm-package-license": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", - "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, "which": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", @@ -3356,9 +3058,9 @@ } }, "xml-js": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.3.tgz", - "integrity": "sha512-Bx2bIhLVxVTpauRZ06ZtNJZTrV8HephVRSipcoxjhXV9O2TGb04UC6L0sIKe4uD2dw+YIuwpWIohFpjw47Q01A==", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.7.tgz", + "integrity": "sha512-1hn0xwwfMcWywnJxqiOXiv+pZaOJyf/YWcUeqJICF0BFb+IOkRFSkKyeA0V62WqTHXNdBxNuCFHhS/w2DtYpoA==", "requires": { "sax": "^1.2.4" } diff --git a/package.json b/package.json index 4f6ea2c38..cd59291b5 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "private": true, "name": "wildbeast", - "version": "6.0.0", + "version": "6.1.0", "description": "Discord bot", "main": "index.js", "scripts": { "start": "node index.js", - "start-docs": "npm run generate-command-data && mkdocs serve", + "start-docs": "npm run build && mkdocs serve", "build": "npm run generate-command-data && npm run minify", "minify": "uglifyjs --compress --mangle --toplevel --output docs/js/dist/commands.js -- docs/js/dist/commands.js.temp", "test": "eslint . && mocha", @@ -33,9 +33,9 @@ "devDependencies": { "eslint": "^4.19.1", "eslint-config-standard": "^11.0.0", - "eslint-plugin-import": "^2.12.0", + "eslint-plugin-import": "^2.14.0", "eslint-plugin-node": "^5.2.1", - "eslint-plugin-promise": "^3.7.0", + "eslint-plugin-promise": "^3.8.0", "eslint-plugin-standard": "^3.1.0", "fs-extra": "^6.0.1", "mocha": "^5.2.0", @@ -46,18 +46,19 @@ "@thesharks/jagtag-js": "^1.0.3", "chalk": "^2.4.1", "dotenv": "^4.0.0", - "eris": "^0.8.5", - "imgflipper": "^1.0.1", - "moment": "^2.22.1", + "eris": "^0.9.0", + "moment": "^2.22.2", "superagent": "^3.8.3", "ws": "^4.1.0", - "xml-js": "^1.6.3" + "xml-js": "^1.6.7", + "youtube-dl": "^1.12.2" }, "optionalDependencies": { "arangojs": "^6.0.0", "elasticsearch": "^14.0.0", "eris-lavalink": "^1.0.0", "hot-shots": "^5.4.1", + "lokijs": "^1.5.5", "raven": "^2.4.0" } } diff --git a/src/commands/advice.js b/src/commands/advice.js index 987595f8d..11d622f17 100644 --- a/src/commands/advice.js +++ b/src/commands/advice.js @@ -21,6 +21,7 @@ module.exports = { const advice = JSON.parse(res.text) msg.channel.createMessage(advice.slip.advice) } else { + global.i18n.send('API_ERROR', msg.channel) global.logger.error(`REST call failed: ${err}, status code: ${res.status}`) } }) diff --git a/src/commands/catfact.js b/src/commands/catfact.js index 78d9b5170..e83e90a6d 100644 --- a/src/commands/catfact.js +++ b/src/commands/catfact.js @@ -14,6 +14,7 @@ module.exports = { if (!err && res.status === 200) { msg.channel.createMessage(res.body.fact) } else { + global.i18n.send('API_ERROR', msg.channel) global.logger.error(`REST call failed: ${err}`) } }) diff --git a/src/commands/dice.js b/src/commands/dice.js index f1181b391..5bf33e7a3 100644 --- a/src/commands/dice.js +++ b/src/commands/dice.js @@ -21,6 +21,7 @@ module.exports = { const roll = res.body msg.channel.createMessage(`<@${msg.author.id}>, Your ${roll.input} resulted in ${roll.result}${roll.details}`) } else { + global.i18n.send('API_ERROR', msg.channel) global.logger.error(`REST call failed: ${err}`) } }) diff --git a/src/commands/dogfact.js b/src/commands/dogfact.js index fd57a39fd..f1673c13b 100644 --- a/src/commands/dogfact.js +++ b/src/commands/dogfact.js @@ -14,6 +14,7 @@ module.exports = { if (!err && res.status === 200) { msg.channel.createMessage(res.body.facts[0]) } else { + global.i18n.send('API_ERROR', msg.channel) global.logger.error(`REST call failed: ${err}`) } }) diff --git a/src/commands/fancyinsult.js b/src/commands/fancyinsult.js index 3aa04aaae..7530e15e7 100644 --- a/src/commands/fancyinsult.js +++ b/src/commands/fancyinsult.js @@ -3,6 +3,7 @@ const request = require('superagent') module.exports = { meta: { help: 'Insult someone in a fancy manner.', + usage: '[name]', module: 'Fun', level: 0, timeout: 5, @@ -19,6 +20,7 @@ module.exports = { msg.channel.createMessage(suffix + ', ' + fancyinsult.insult) } } else { + global.i18n.send('API_ERROR', msg.channel) global.logger.error(`REST call failed: ${err}`) } }) diff --git a/src/commands/gif.js b/src/commands/gif.js index 7676a887f..b78caa172 100644 --- a/src/commands/gif.js +++ b/src/commands/gif.js @@ -3,6 +3,7 @@ const request = require('superagent') module.exports = { meta: { help: 'Search Giphy for a gif.', + usage: '', module: 'Fun', level: 0, timeout: 5 @@ -12,7 +13,7 @@ module.exports = { .get('http://api.giphy.com/v1/gifs/random') .set('api_key', 'dc6zaTOxFJmzC') .query({rating: msg.channel.nsfw === true ? 'r' : 'pg13', fmt: 'json'}) - .query(`tag=${suffix.split(' ').join('+')}`) + .query(`tag=${encodeURIComponent(suffix.split(' ').join('+'))}`) .then(res => { if (res.statusCode !== 200 || res.body.meta.status !== 200) return global.i18n.send('API_ERROR', msg.channel) if (res.body.data.id !== undefined) { diff --git a/src/commands/info.js b/src/commands/info.js index 0e2fd22cb..e960127f7 100644 --- a/src/commands/info.js +++ b/src/commands/info.js @@ -8,9 +8,8 @@ module.exports = { }, fn: async (msg) => { let bot = global.bot - let owner let user = await bot.getRESTUser('107904023901777920') - owner = `${user.username}#${user.discriminator}` + let owner = `${user.username}#${user.discriminator}` let fields = [{name: 'Servers Connected', value: '```\n' + bot.guilds.size + '```', inline: true}, {name: 'Users Known', value: '```\n' + bot.users.size + '```', inline: true}, {name: 'Channels Connected', value: '```\n' + Object.keys(bot.channelGuildMap).length + '```', inline: true}, diff --git a/src/commands/join-voice.js b/src/commands/join-voice.js index 92ceb02cb..36292f7e8 100644 --- a/src/commands/join-voice.js +++ b/src/commands/join-voice.js @@ -3,7 +3,7 @@ const url = require('url') module.exports = { meta: { help: 'Make the bot join a voice channel. Optionally supply a track to play on join.', - usage: '[track link]', + usage: '[song link or YouTube search query]', module: 'Music', level: 1, noDM: true, @@ -17,7 +17,8 @@ module.exports = { } else if (!msg.channel.guild.channels.find(c => c.id === msg.member.voiceState.channelID).permissionsOf(global.bot.user.id).has('voiceConnect') || !msg.channel.guild.channels.find(c => c.id === msg.member.voiceState.channelID).permissionsOf(global.bot.user.id).has('voiceSpeak')) { global.i18n.send('NO_VOICE_CONNECT_PERM', msg.channel, {channel: msg.channel.guild.channels.find(c => c.id === msg.member.voiceState.channelID).name}) } else if (global.bot.voiceConnections.get(msg.channel.guild.id)) { - global.i18n.send('VOICE_CONNECTED', msg.channel, {channel: msg.channel.guild.channels.find(c => c.id === global.bot.voiceConnections.get(msg.channel.guild.id).channelId).name}) + const channelID = global.bot.voiceConnections.get(msg.channel.guild.id).channelId === undefined ? global.bot.voiceConnections.get(msg.channel.guild.id).channelID : global.bot.voiceConnections.get(msg.channel.guild.id).channelId + global.i18n.send('VOICE_CONNECTED', msg.channel, {channel: msg.channel.guild.channels.find(c => c.id === channelID).name}) } else { if (suffix) { let link = url.parse(suffix) @@ -38,39 +39,41 @@ module.exports = { }) } } else { - resolveTracks(suffix).then(tracks => { - if (tracks.length === 0) { + resolveTracks(suffix).then(result => { + global.logger.trace(result) + if (result.length === 0) { global.i18n.send('LINK_NO_TRACK', msg.channel, {user: msg.author.username, url: suffix}) - } else if (tracks.length === 1) { - hhMMss(tracks[0].info.length / 1000).then(time => { - createPlayer(msg, tracks) + } else if (result.length === 1) { + hhMMss(result[0].info.length / 1000).then(time => { + createPlayer(msg, result) global.i18n.send('TRACK_ADDED', msg.channel, { - title: tracks[0].info.title, + title: result[0].info.title, duration: time, user: msg.author.username }) }) } else { - createPlayer(msg, tracks) - global.i18n.send('TRACKS_ADDED', msg.channel, {count: tracks.length, user: msg.author.username}) + createPlayer(msg, result) + global.i18n.send('TRACKS_ADDED', msg.channel, {count: result.length, user: msg.author.username}) } - }).catch(console.error) + }).catch(global.logger.error) } } else { - resolveTracks(encodeURI(`ytsearch:${suffix}`)).then(tracks => { - if (tracks.length === 0) { + resolveTracks(`ytsearch:${encodeURI(suffix)}`).then(result => { + global.logger.trace(result) + if (result.length === 0) { global.i18n.send('SEARCH_NO_TRACKS', msg.channel, {user: msg.author.mention}) } else { - hhMMss(tracks[0].info.length / 1000).then(time => { - createPlayer(msg, [tracks[0]]) + hhMMss(result[0].info.length / 1000).then(time => { + createPlayer(msg, [result[0]]) global.i18n.send('TRACK_ADDED', msg.channel, { - title: tracks[0].info.title, + title: result[0].info.title, duration: time, user: msg.author.username }) }) } - }).catch(console.error) + }).catch(global.logger.error) } } else { createPlayer(msg) diff --git a/src/commands/leave-voice.js b/src/commands/leave-voice.js index 15795a036..0921fd00f 100644 --- a/src/commands/leave-voice.js +++ b/src/commands/leave-voice.js @@ -1,4 +1,4 @@ -const {guildInfo} = require('../internal/encoder-selector.js') +const {leaveVoiceChannel} = require('../internal/encoder-selector.js') module.exports = { meta: { help: 'Make the bot leave the current voice channel.', @@ -9,9 +9,7 @@ module.exports = { }, fn: async (msg) => { if (global.bot.voiceConnections.get(msg.channel.guild.id)) { - global.i18n.send('VOICE_DISCONNECT', msg.channel, {channel: msg.channel.guild.channels.find(c => c.id === global.bot.voiceConnections.get(msg.channel.guild.id).channelId).name}) - global.bot.leaveVoiceChannel(global.bot.voiceConnections.get(msg.channel.guild.id).channelId) - guildInfo[msg.channel.guild.id] = undefined + await leaveVoiceChannel(msg) } else { global.i18n.send('VOICE_NOT_CONNECTED', msg.channel) } diff --git a/src/commands/leetspeak.js b/src/commands/leetspeak.js index efd1c8913..5517ef0dd 100644 --- a/src/commands/leetspeak.js +++ b/src/commands/leetspeak.js @@ -13,6 +13,7 @@ const map = { module.exports = { meta: { help: 'Encode a message into l337sp3@K.', + usage: '', module: 'Fun', level: 0, alias: ['leetspeek', 'leetspeech', 'leet'] diff --git a/src/commands/magic8ball.js b/src/commands/magic8ball.js index ac8042ddf..bd853f0e5 100644 --- a/src/commands/magic8ball.js +++ b/src/commands/magic8ball.js @@ -28,30 +28,12 @@ const answers = [ module.exports = { meta: { help: 'Ask the magic 8-ball for advice.', - usage: '', module: 'Fun', level: 0, timeout: 5, alias: ['8ball'] }, - fn: function (msg, suffix) { - if (!suffix) { - msg.channel.createMessage(`<@${msg.author.id}>, I mean I can shake this 8ball all I want but without a question it's kinda dumb.`) - return - } - msg.channel.createMessage('The Magic 8 Ball says:\n```' + answerShuffle(answers)[0] + '```') - - function answerShuffle (array) { - let rand - let index = -1 - let length = array.length - let result = Array(length) - while (++index < length) { - rand = Math.floor(Math.random() * (index + 1)) - result[index] = result[rand] - result[rand] = array[index] - } - return (result) - } + fn: function (msg) { + msg.channel.createMessage('The Magic 8 Ball says:\n```' + answers[Math.floor(Math.random() * answers.length)] + '```') } } diff --git a/src/commands/meme.js b/src/commands/meme.js index 44e4f0f10..66fc60a2a 100644 --- a/src/commands/meme.js +++ b/src/commands/meme.js @@ -1,76 +1,48 @@ -const meme = { - brace: 61546, - mostinteresting: 61532, - fry: 61520, - onedoesnot: 61579, - yuno: 61527, - success: 61544, - allthethings: 61533, - doge: 8072285, - drevil: 40945639, - skeptical: 101711, - notime: 442575, - yodawg: 101716, - ermahgerd: 101462, - hipsterariel: 86601, - imagination: 163573, - grumpycat: 405658, - morpheus: 100947, - '1stworldproblems': 61539, - facepalm: 1509839, - wtf: 245898, - batmanslaprobin: 438680, - takemymoney: 176908, - gollum: 681831, - grindmygears: 356615, - consuela: 160583, - ineedyouto: 89655, - chucknorrisapproves: 241304, - asianfather: 61559, - foreveralone: 61528, - grandmainternet: 61556, - zoidberg: 61573, - troll: 101484, - familyguybrian: 674967, - obama: 185239, - badluckbrian: 61585, - philosoraptor: 61516, - '3rdworldsuccess': 101287, - ancientaliens: 101470 -} +const SA = require('superagent') module.exports = { meta: { - name: 'meme', - help: 'Create a meme.', - usage: ' "upper text" "lower text" (Important: Include the quotes)', + help: 'Create memes and other reaction images.', + usage: ' "upper text" "lower text"', module: 'Fun', - level: 0, - timeout: 10, - alias: ['makeameme'], + timeout: 5, addons: [ - `\nAvailable meme types: ${Object.getOwnPropertyNames(meme).join(', ')}` - ] + 'Use `meme templates` to get all available templates' + ], + level: 0 }, - fn: function (msg, suffix) { - const tags = suffix.split('"') - const memetype = tags[0].split(' ')[0] - const Imgflipper = require('imgflipper') - const imgflipper = new Imgflipper(process.env.IMGFLIP_USERNAME, process.env.IMGFLIP_PASSWORD) - imgflipper.generateMeme(meme[memetype], tags[1] ? tags[1] : '', tags[3] ? tags[3] : '', (err, image) => { - if (err) { - msg.channel.createMessage(`<@${msg.author.id}>, Please try again, use \`help meme\` if you do not know how to use this command.`) + fn: async (msg, suffix) => { + if (suffix.toLowerCase() === 'templates') { + const data = await SA.get('https://memegen.link/api/templates/') + const names = Object.entries(data.body).map(x => /https:\/\/memegen\.link\/api\/templates\/(.+)/.exec(x)[1]) + msg.channel.createMessage('Sending you all available meme templates via DM') + const ctx = await msg.author.getDMChannel() + ctx.createMessage(`Available meme templates:\n\n${names.join(', ')}`) + } else { + const tags = suffix.split('"') + const memetype = tags[0].split(' ')[0] + const keywords = tags.slice(1).filter(x => x.trim().length > 0) + if (!memetype || keywords.length === 0) { + return global.i18n.send('PERMISSIONS_MALFORMED', msg.channel) } else { - const user = global.bot.user - if (msg.channel.guild) { - msg.channel.createMessage(image) - } else if (msg.channel.guild.members.get(user.id).permission.json.manageMessages) { - msg.delete() - msg.channel.createMessage(image) - } else { - msg.channel.createMessage(image) - } + return msg.channel.createMessage(`http://memegen.link/${memetype}/${translate(keywords[0])}/${translate(keywords[1])}.jpg`) } - }) + } + } +} + +function translate (string) { + const map = { + ' ': '_', + '-': '--', + '%': '~p', + '#': '~h', + '/': '~s', + '"': "''" + } + for (const y in map) { + string = string.replace(new RegExp(y, 'g'), map[y]) } + string = string.replace(/\?/g, '~q') + return string } diff --git a/src/commands/nowplaying.js b/src/commands/nowplaying.js index 23a291fd8..4b4b7689f 100644 --- a/src/commands/nowplaying.js +++ b/src/commands/nowplaying.js @@ -1,4 +1,4 @@ -const {guildInfo, hhMMss, getPlayer} = require('../internal/encoder-selector.js') +const {guildInfo, hhMMss, getTimestamp} = require('../internal/encoder-selector.js') module.exports = { meta: { help: 'Show the currently playing track.', @@ -15,7 +15,7 @@ module.exports = { duration: await hhMMss(guildInfo[msg.channel.guild.id].tracks[0].info.length / 1000), url: guildInfo[msg.channel.guild.id].tracks[0].info.uri, state: guildInfo[msg.channel.guild.id].paused === false ? ':arrow_forward:' : ':pause_button:', - progress: await progressBar(await getPlayer(msg.channel).then(p => p.getTimestamp()) / guildInfo[msg.channel.guild.id].tracks[0].info.length) + progress: await progressBar(await getTimestamp(msg.channel) / guildInfo[msg.channel.guild.id].tracks[0].info.length) }) } else { global.i18n.send('QUEUE_EMPTY', msg.channel) diff --git a/src/commands/randomdog.js b/src/commands/randomdog.js index 04608eece..104ce8a9b 100644 --- a/src/commands/randomdog.js +++ b/src/commands/randomdog.js @@ -14,6 +14,7 @@ module.exports = { if (!err && res.status === 200) { msg.channel.createMessage(res.body.url) } else { + global.i18n.send('API_ERROR', msg.channel) global.logger.error(`REST call failed: ${err}`) } }) diff --git a/src/commands/randommeme.js b/src/commands/randommeme.js index 9b52845bb..81c9ef2d5 100644 --- a/src/commands/randommeme.js +++ b/src/commands/randommeme.js @@ -14,6 +14,7 @@ module.exports = { if (!err && !result.body.data.error) { msg.channel.createMessage(result.body.data[Math.floor((Math.random() * 20) + 1)].link) } else { + global.i18n.send('API_ERROR', msg.channel) global.logger.error(`REST call failed: ${err}`) } }) diff --git a/src/commands/request.js b/src/commands/request.js index 43d6aab27..03452d493 100644 --- a/src/commands/request.js +++ b/src/commands/request.js @@ -3,7 +3,7 @@ const url = require('url') module.exports = { meta: { help: 'Add a track to the playback queue.', - usage: '', + usage: '', module: 'Music', level: 1, noDM: true, @@ -35,41 +35,41 @@ module.exports = { }) } } else { - resolveTracks(suffix).then(tracks => { - if (tracks.length === 0) { + resolveTracks(suffix).then(result => { + global.logger.trace(result) + if (result.length === 0) { global.i18n.send('LINK_NO_TRACK', msg.channel, {user: msg.author.username, url: suffix}) - } else if (tracks.length === 1) { - hhMMss(tracks[0].info.length / 1000).then(time => { - addTracks(msg, tracks) + } else if (result.length === 1) { + hhMMss(result[0].info.length / 1000).then(time => { + addTracks(msg, result) global.i18n.send('TRACK_ADDED', msg.channel, { - title: tracks[0].info.title, + title: result[0].info.title, duration: time, user: msg.author.username }) }) } else { - addTracks(msg, tracks) - global.i18n.send('TRACKS_ADDED', msg.channel, {count: tracks.length, user: msg.author.username}) + addTracks(msg, result) + global.i18n.send('TRACKS_ADDED', msg.channel, {count: result.length, user: msg.author.username}) } - }).catch(console.error) + }).catch(global.logger.error) } } else { - resolveTracks(`ytsearch:${encodeURI(suffix)}`).then(tracks => { - if (tracks.length === 0) { + resolveTracks(`ytsearch:${encodeURI(suffix)}`).then(result => { + global.logger.trace(result) + if (result.length === 0) { global.i18n.send('SEARCH_NO_TRACKS', msg.channel, {user: msg.author.mention}) } else { - hhMMss(tracks[0].info.length / 1000).then(time => { - addTracks(msg, [tracks[0]]) + hhMMss(result[0].info.length / 1000).then(time => { + addTracks(msg, [result[0]]) global.i18n.send('TRACK_ADDED', msg.channel, { - title: tracks[0].info.title, + title: result[0].info.title, duration: time, user: msg.author.username }) }) } - }).catch(err => { - console.error(err) - }) + }).catch(global.logger.error) } } } else { diff --git a/src/commands/settings.js b/src/commands/settings.js index b0a414dc8..b2bf47f52 100644 --- a/src/commands/settings.js +++ b/src/commands/settings.js @@ -51,12 +51,23 @@ module.exports = { if (match) parts[1] = match[1] else parts[1] = parts[1].toLowerCase() } + if (parts[0] === 'language') { + const languages = require('../internal/dirscan')('../languages') + if (!languages.includes(`${parts[1]}.json`)) { + return global.i18n.send('LANGUAGE_UNAVAILABLE', msg.channel, { + available: languages.map(x => x.match(/(.+).json/)[1]).join(', ') + }) + } + } await engine.modify(msg.channel.guild, parts[0], parts.slice(1).join(' ')) - return global.i18n.send('SETTINGS_MODIFIED', msg.channel, { - setting: parts[0], - value: parts.slice(1).join(' '), - disclaim: ((parts[0] === 'language') ? `\n${global.i18n.raw('LANGUAGE_DISCLAIMER')}` : '') - }) + if (parts[0] === 'language') { + return global.i18n.multiSend([{_key: 'SETTINGS_MODIFIED', opts: {setting: parts[0], value: parts.slice(1).join(' ')}}, {_key: 'LANGUAGE_DISCLAIMER'}], msg.channel) + } else { + return global.i18n.send('SETTINGS_MODIFIED', msg.channel, { + setting: parts[0], + value: parts.slice(1).join(' ') + }) + } } } else { // send current settings diff --git a/src/commands/skip.js b/src/commands/skip.js index 20d32d68c..d187dd254 100644 --- a/src/commands/skip.js +++ b/src/commands/skip.js @@ -1,4 +1,4 @@ -const {guildInfo, hhMMss, skip} = require('../internal/encoder-selector.js') +const {guildInfo, hhMMss, skip, stop} = require('../internal/encoder-selector.js') module.exports = { meta: { help: 'Skip the current track.', @@ -9,6 +9,7 @@ module.exports = { }, fn: async (msg) => { if (global.bot.voiceConnections.get(msg.channel.guild.id)) { + guildInfo[msg.channel.guild.id].endedEarly = true if (guildInfo[msg.channel.guild.id].tracks.length > 1) { const trackRequester = msg.channel.guild.members.find(m => m.id === guildInfo[msg.channel.guild.id].tracks[1].requester) global.i18n.send('SKIP_TRACK', msg.channel, { @@ -19,15 +20,7 @@ module.exports = { }) await skip(msg) } else if (guildInfo[msg.channel.guild.id].tracks.length <= 1) { - if (!process.env.WILDBEAST_VOICE_PERSIST) { - global.bot.leaveVoiceChannel(global.bot.voiceConnections.get(msg.channel.guild.id).channelId) - guildInfo[msg.channel.guild.id] = undefined - global.i18n.send('QUEUE_END', msg.channel) - } else { - guildInfo[msg.channel.guild.id].tracks.shift() - guildInfo[msg.channel.guild.id].skips = [] - global.i18n.send('VOICE_PERSIST', msg.channel) - } + await stop(msg) } } else { global.i18n.send('VOICE_NOT_CONNECTED', msg.channel) diff --git a/src/commands/stroke.js b/src/commands/stroke.js index 1e93b928a..22bfa56ec 100644 --- a/src/commands/stroke.js +++ b/src/commands/stroke.js @@ -3,7 +3,7 @@ const request = require('superagent') module.exports = { meta: { help: 'Stroke someone\'s ego.', - usage: '', + usage: '""', module: 'Fun', level: 0, timeout: 5 diff --git a/src/commands/tag.js b/src/commands/tag.js index 60ec33beb..80db724a1 100644 --- a/src/commands/tag.js +++ b/src/commands/tag.js @@ -57,13 +57,13 @@ module.exports = { if (tag.owner !== msg.author.id && !process.env['WILDBEAST_MASTERS'].split('|').includes(msg.author.id)) { return global.i18n.send('TAG_NOT_OWNED', msg.channel) } - const content = parts.slice(3).join(' ') + const content = parts.slice(2).join(' ') if (!content || content.length < 0) { return global.i18n.send('TAG_TOO_SHORT', msg.channel) } await driver.edit(parts[1], { content: content - }) + }, 'tags') global.i18n.send('TAG_EDITED', msg.channel) } break diff --git a/src/commands/urbandictionary.js b/src/commands/urbandictionary.js index 147a24edc..478a9f63c 100644 --- a/src/commands/urbandictionary.js +++ b/src/commands/urbandictionary.js @@ -11,7 +11,7 @@ module.exports = { }, fn: function (msg, suffix) { if (!suffix) { - msg.channel.createMessage(`<@${msg.author.id}>, Yes, let's just look up absolutely nothing.`) + msg.channel.createMessage(`Please enter a search term.`) } else { request.get('http://api.urbandictionary.com/v0/define') .query({term: suffix}) @@ -38,6 +38,7 @@ module.exports = { msg.channel.createMessage(`<@${msg.author.id}>, ${suffix}: This word is so screwed up, even Urban Dictionary doesn't have it in its database`) } } else { + global.i18n.send('API_ERROR', msg.channel) global.logger.error(`Got an error: ${err}, status code: ${res.status}`) } }) diff --git a/src/commands/yesno.js b/src/commands/yesno.js index dc1b8c129..401734d0b 100644 --- a/src/commands/yesno.js +++ b/src/commands/yesno.js @@ -14,6 +14,7 @@ module.exports = { if (!err && res.status === 200) { msg.channel.createMessage(`<@${msg.author.id}>, ${res.body.image}`) } else { + global.i18n.send('API_ERROR', msg.channel) global.logger.error(`REST call failed: ${err}`) } }) diff --git a/src/commands/yomomma.js b/src/commands/yomomma.js index 8dbd0880d..19b7e71f4 100644 --- a/src/commands/yomomma.js +++ b/src/commands/yomomma.js @@ -14,12 +14,13 @@ module.exports = { try { JSON.parse(res.text) } catch (e) { - msg.channel.createMessage('The API returned an unconventional response.') + global.i18n.send('API_ERROR', msg.channel) return } const joke = JSON.parse(res.text) msg.channel.createMessage(joke.joke) } else { + global.i18n.send('API_ERROR', msg.channel) global.logger.error(`REST call failed: ${err}`) } }) diff --git a/src/drivers/lokijs.js b/src/drivers/lokijs.js new file mode 100644 index 000000000..cee53b61b --- /dev/null +++ b/src/drivers/lokijs.js @@ -0,0 +1,100 @@ +let guilds, system, tags +const Loki = require('lokijs') +const db = new Loki('wildbeast.db', { + autoload: true, + autosave: true, + autoloadCallback: loadCollections, + autosaveInterval: 1000 +}) + +function loadCollections () { + guilds = db.getCollection('guild_data') || db.addCollection('guild_data') + system = db.getCollection('system') || db.addCollection('system') + tags = db.getCollection('tags') || db.addCollection('tags') +} + +module.exports = { + getGuildData: async (guild) => { + return ensureGuild(guild) + }, + getPerms: async (guild) => { + const data = ensureGuild(guild) + return data.perms + }, + getSettings: async (guild) => { + const data = ensureGuild(guild) + return data.settings + }, + getFlags: async (guild) => { + const data = ensureSystem(guild) + return data.flags + }, + getOverrides: async (guild) => { + const data = ensureSystem(guild) + return data.overrides + }, + getArbitrary: async (key, coll) => { + const collection = db.getCollection(coll) + return collection.findOne({ wb_id: key }) + }, + getTag: async (key) => { + return tags.findOne({ wb_id: key }) + }, + create: async (coll, data) => { + if (data._key) { + data.wb_id = data._key + delete data._key + } + const collection = db.getCollection(coll) + return collection.insert(data) + }, + delete: async (coll, key) => { + const collection = db.getCollection(coll) + return collection.remove(collection.findOne({ wb_id: key })) + }, + edit: async (handle, data, coll = 'guild_data') => { + const collection = db.getCollection(coll) + const orig = collection.find({ wb_id: handle })[0] + const newdata = {...orig, ...data} // spread ops, fancy! + return collection.update(newdata) + } +} + +function ensureGuild (guild) { + const data = guilds.find({ + wb_id: guild.id + })[0] + if (data) { + global.logger.trace(data) + return data + } else { + const shim = { + wb_id: guild.id, + perms: { + users: {}, + roles: {} + }, + settings: {} + } + guilds.insert(shim) + return shim + } +} + +function ensureSystem (guild) { + const data = system.find({ + wb_id: guild.id + })[0] + if (data) { + global.logger.trace(data) + return data + } else { + const shim = { + wb_id: guild.id, + flags: [], + overrides: {} + } + system.insert(shim) + return shim + } +} diff --git a/src/encoders/ffmpeg.js b/src/encoders/ffmpeg.js new file mode 100644 index 000000000..d76431c42 --- /dev/null +++ b/src/encoders/ffmpeg.js @@ -0,0 +1,153 @@ +const youtubedl = require('youtube-dl') +const util = require('util') +const guildInfo = {} + +module.exports = { + guildInfo: guildInfo, + init: () => { + global.logger.debug('Using FFmpeg encoder.') + }, + getPlayer: async (channel) => { + if (!channel || !channel.guild) { + throw new Error('Not a guild channel.') + } + + let player = global.bot.voiceConnections.get(channel.guild.id) + if (player) { + return (player) + } + + return global.bot.joinVoiceChannel(channel.id) + }, + resolveTracks: async (search) => { + const getInfo = util.promisify(youtubedl.getInfo) + let info = await getInfo(search.replace('%20', ' '), ['--skip-download', '-f', '[acodec*=opus]/[protocol=https]/bestaudio/best']) + if (!Array.isArray(info)) info = [info] + let tracks = [] + for (let t of info) { + tracks.push({ + track: t.url, + info: { + identifier: t.id, + author: t.uploader, + length: t._duration_raw * 1000, + title: t.title, + uri: t.webpage_url + } + }) + } + return tracks + }, + addTracks: async (msg, tracks) => { + if (guildInfo[msg.channel.guild.id].tracks.length <= 0) { + module.exports.getPlayer(msg.channel).then(p => { + p.play(tracks[0].track, {inlineVolume: true}) + }) + } + for (let track of tracks) { + track.requester = msg.author.id + guildInfo[msg.channel.guild.id].tracks.push(track) + } + }, + skip: async (msg) => { + guildInfo[msg.channel.guild.id].tracks.shift() + guildInfo[msg.channel.guild.id].skips = [] + module.exports.getPlayer(msg.channel).then(p => p.stopPlaying()) + if (guildInfo[msg.channel.guild.id].paused === true) guildInfo[msg.channel.guild.id].paused = false + }, + stop: async (msg) => { + if (!process.env.WILDBEAST_VOICE_PERSIST) { + const channelID = global.bot.voiceConnections.get(msg.channel.guild.id).channelId === undefined ? global.bot.voiceConnections.get(msg.channel.guild.id).channelID : global.bot.voiceConnections.get(msg.channel.guild.id).channelId + global.bot.leaveVoiceChannel(channelID) + guildInfo[msg.channel.guild.id] = undefined + } else { + guildInfo[msg.channel.guild.id].tracks.shift() + guildInfo[msg.channel.guild.id].skips = [] + module.exports.getPlayer(msg.channel).then(p => p.stopPlaying()) + } + }, + pause: async (guild) => { + module.exports.getPlayer(guild).then(p => { + p.pause() + }) + }, + resume: async (guild) => { + module.exports.getPlayer(guild).then(p => { + p.resume() + }) + }, + setVolume: async (guild, volume) => { + module.exports.getPlayer(guild).then(p => { + p.setVolume(volume / 100) + }) + }, + getTimestamp: async (guild) => { + return module.exports.getPlayer(guild).then(p => p.current.playTime) + }, + hhMMss: async (time) => { + if (time || !isNaN(time)) { + let hours = (Math.floor(time / ((60 * 60)) % 24)) + let minutes = (Math.floor(time / (60)) % 60) + let seconds = (Math.floor(time) % 60) + let parsedTime = [] + if (hours >= 1) parsedTime.push(hours) + minutes >= 10 ? parsedTime.push(minutes) : parsedTime.push(`0${minutes}`) + seconds >= 10 ? parsedTime.push(seconds) : parsedTime.push(`0${seconds}`) + return parsedTime.join(':') + } else { + return ('00:00:00') + } + }, + leaveVoiceChannel: async (msg) => { + const channelID = global.bot.voiceConnections.get(msg.channel.guild.id).channelID + guildInfo[msg.channel.guild.id].leave = true + global.i18n.send('VOICE_DISCONNECT', msg.channel, {channel: msg.channel.guild.channels.find(c => c.id === channelID).name}) + global.bot.leaveVoiceChannel(channelID) + guildInfo[msg.channel.guild.id] = undefined + }, + createPlayer: async (msg, tracks) => { + let player = await msg.channel.guild.channels.find(c => c.id === msg.member.voiceState.channelID).join() + guildInfo[msg.channel.guild.id] = { + tracks: [], + volume: 100, + skips: [], + paused: false, + endedEarly: false, + textChan: msg.channel.id + } + if (tracks) { + module.exports.addTracks(msg, tracks) + } + player.on('error', err => global.logger.error(err)) + player.on('disconnect', wat => { + if (wat !== undefined) global.logger.error(`voice disconnect: ${wat}`) + }) + player.on('end', async () => { + if (guildInfo[player.id].leave) return + if (guildInfo[player.id].endedEarly && guildInfo[player.id].tracks.length > 1) { + guildInfo[player.id].endedEarly = false + player.play(guildInfo[player.id].tracks[0].track) + } else if (guildInfo[player.id].tracks.length > 1) { + const trackRequester = global.bot.users.get(guildInfo[player.id].tracks[1].requester) + global.i18n.send('NEXT_TRACK', global.bot.guilds.get(player.id).channels.find(c => c.id === guildInfo[player.id].textChan), { + current: guildInfo[player.id].tracks[0].info.title, + next: guildInfo[player.id].tracks[1].info.title, + duration: await module.exports.hhMMss(guildInfo[player.id].tracks[1].info.length), + user: trackRequester ? trackRequester.username : 'Unknown user' // In case user is not in guild + }) + guildInfo[player.id].tracks.shift() + guildInfo[player.id].skips = [] + player.play(guildInfo[player.id].tracks[0].track, {inlineVolume: true}) + } else { + if (!process.env.WILDBEAST_VOICE_PERSIST) { + global.i18n.send('QUEUE_END', global.bot.guilds.get(player.id).channels.find(c => c.id === guildInfo[player.id].textChan)) + global.bot.leaveVoiceChannel(player.channelID) + guildInfo[player.id] = undefined + } else { + guildInfo[player.id].tracks.shift() + global.i18n.send('VOICE_PERSIST', global.bot.guilds.get(player.id).channels.find(c => c.id === guildInfo[player.id].textChan)) + } + } + }) + } +} diff --git a/src/encoders/lavalink.js b/src/encoders/lavalink.js index bbe2e9e50..a02c68b49 100644 --- a/src/encoders/lavalink.js +++ b/src/encoders/lavalink.js @@ -62,16 +62,16 @@ module.exports = { throw new Error('Unable play that video.') } - return (result.body) + return (result.body.tracks) }, addTracks: async (msg, tracks) => { - if (guildInfo[msg.channel.guild.id].tracks.length <= 0) { + if (guildInfo[msg.channel.guild.id] !== undefined && guildInfo[msg.channel.guild.id].tracks.length <= 0) { let player = await global.bot.voiceConnections.get(msg.channel.guild.id) player.play(tracks[0].track) - } - for (let track of tracks) { - track.requester = msg.author.id - guildInfo[msg.channel.guild.id].tracks.push(track) + for (let track of tracks) { + track.requester = msg.author.id + guildInfo[msg.channel.guild.id].tracks.push(track) + } } }, skip: async (msg) => { @@ -82,6 +82,19 @@ module.exports = { if (player.playing === false) player.setPause(false) if (guildInfo[msg.channel.guild.id].paused === true) guildInfo[msg.channel.guild.id].paused = false }, + stop: async (msg) => { + if (!process.env.WILDBEAST_VOICE_PERSIST) { + const channelID = global.bot.voiceConnections.get(msg.channel.guild.id).channelId === undefined ? global.bot.voiceConnections.get(msg.channel.guild.id).channelID : global.bot.voiceConnections.get(msg.channel.guild.id).channelId + global.bot.leaveVoiceChannel(channelID) + guildInfo[msg.channel.guild.id] = undefined + global.i18n.send('QUEUE_END', msg.channel) + } else { + guildInfo[msg.channel.guild.id].tracks.shift() + guildInfo[msg.channel.guild.id].skips = [] + module.exports.getPlayer(msg.channel).then(p => p.stop()) + global.i18n.send('VOICE_PERSIST', msg.channel) + } + }, pause: async (guild) => { module.exports.getPlayer(guild).then(p => { p.setPause(true) @@ -97,8 +110,11 @@ module.exports = { p.setVolume(volume) }) }, + getTimestamp: async (guild) => { + return module.exports.getPlayer(guild).then(p => p.getTimestamp()) + }, hhMMss: async (time) => { - if (time || isNaN(time)) { + if (time || !isNaN(time)) { let hours = (Math.floor(time / ((60 * 60)) % 24)) let minutes = (Math.floor(time / (60)) % 60) let seconds = (Math.floor(time) % 60) @@ -111,6 +127,13 @@ module.exports = { return ('00:00:00') } }, + leaveVoiceChannel: async (msg) => { + const channelID = global.bot.voiceConnections.get(msg.channel.guild.id).channelId + guildInfo[msg.channel.guild.id].leave = true + global.i18n.send('VOICE_DISCONNECT', msg.channel, {channel: msg.channel.guild.channels.find(c => c.id === channelID).name}) + global.bot.leaveVoiceChannel(channelID) + guildInfo[msg.channel.guild.id] = undefined + }, createPlayer: async (msg, tracks) => { module.exports.getPlayer(msg.channel.guild.channels.find(c => c.id === msg.member.voiceState.channelID)).then(player => { guildInfo[msg.channel.guild.id] = { @@ -129,7 +152,7 @@ module.exports = { if (wat !== undefined) global.logger.error(`lava disconnect: ${wat}`) }) player.on('end', async data => { - if (data.reason && data.reason !== 'REPLACED') { + if (data.reason && !['STOPPED', 'REPLACED'].includes(data.reason)) { if (guildInfo[data.guildId].tracks.length > 1) { const trackRequester = global.bot.users.get(guildInfo[data.guildId].tracks[1].requester) global.i18n.send('NEXT_TRACK', global.bot.guilds.get(data.guildId).channels.find(c => c.id === guildInfo[data.guildId].textChan), { diff --git a/src/internal/command-info-appender.js b/src/internal/command-info-appender.js index 2d1957ccc..9a9c2e4ac 100644 --- a/src/internal/command-info-appender.js +++ b/src/internal/command-info-appender.js @@ -26,6 +26,7 @@ function cleanupJSON (parsedJSON) { let str = JSON.stringify(parsedJSON) str = str.replaceAll('\\"', '\\\\"') // Properly escape double quotes str = str.replaceAll('\\n', '\\\\n') // Properly escape newlines + str = str.replaceAll('`', '\\`') // Properly escape backticks return str } diff --git a/src/internal/database-selector.js b/src/internal/database-selector.js index d9b6344e0..cfa036bf8 100644 --- a/src/internal/database-selector.js +++ b/src/internal/database-selector.js @@ -1,9 +1,9 @@ -const drivers = require('./directory-loader')('../drivers') -const preferred = process.env['WILDBEAST_PREFERRED_DATABASE'] || 'arangodb' -const available = Object.getOwnPropertyNames(drivers) +const drivers = require('./dirscan')('../drivers') +const preferred = `${process.env['WILDBEAST_PREFERRED_DATABASE'] || 'arangodb'}.js` -if (available.indexOf(preferred) === -1) { - global.logger.error(`No database driver available called ${preferred}, available choices: ${available.join(', ')}`, true) +if (drivers.indexOf(preferred) === -1) { + global.logger.error(`No database driver available called ${preferred}, available choices: ${drivers.join(', ')}`, true) } -module.exports = drivers[preferred] +if (global.logger) global.logger.debug(`Using ${preferred} database driver`) +module.exports = require(`../drivers/${preferred}`) diff --git a/src/internal/dirscan.js b/src/internal/dirscan.js new file mode 100644 index 000000000..c3f865a15 --- /dev/null +++ b/src/internal/dirscan.js @@ -0,0 +1,8 @@ +const fs = require('fs') +const path = require('path') + +module.exports = (location) => { + const currentPath = path.dirname(new Error().stack.split('\n')[2].replace(/[^(]+\((.+):\d+:\d+\).*/g, (m, g1) => g1)) + location = path.resolve(currentPath, location) + return fs.readdirSync(location) +} diff --git a/src/internal/encoder-selector.js b/src/internal/encoder-selector.js index ad69ac62d..2193b120e 100644 --- a/src/internal/encoder-selector.js +++ b/src/internal/encoder-selector.js @@ -1,9 +1,9 @@ -const drivers = require('./directory-loader')('../encoders') -const preferred = process.env['WILDBEAST_PREFERRED_ENCODER'] || 'lavalink' -const available = Object.getOwnPropertyNames(drivers) +const drivers = require('./dirscan')('../encoders') +const preferred = `${process.env['WILDBEAST_PREFERRED_ENCODER'] || 'lavalink'}.js` -if (!available.includes(preferred)) { - global.logger.error(`No encoder available called ${preferred}, available choices: ${available.join(', ')}`, true) +if (!drivers.includes(preferred)) { + global.logger.error(`No encoder available called ${preferred}, available choices: ${drivers.join(', ')}`, true) } -module.exports = drivers[preferred] +if (global.logger) global.logger.debug(`Using ${preferred} encoder`) // HACK: docgen +module.exports = require(`../encoders/${preferred}`) diff --git a/src/internal/i18n.js b/src/internal/i18n.js index 70f2d050b..060b99417 100644 --- a/src/internal/i18n.js +++ b/src/internal/i18n.js @@ -1,25 +1,37 @@ -const standard = process.env.WILDBEAST_LANGUAGE || 'en' +const standard = process.env.WILDBEAST_LANGUAGE || 'en-EN' const available = require('./directory-loader')('../languages', {regex: /\.json$/}) const driver = require('./database-selector') -if (!available[standard]) { - if (standard === 'en') { - global.logger.error('The language file is missing, did you forget to run "git submodule update --init --remote"?', true) - } else { - global.logger.error(`Unable to load language file ${standard}. It does not exist.`, true) - } -} +if (!available[standard]) global.logger.error(`Unable to load language file ${standard}. It does not exist.`, true) module.exports = { - raw: (key, opts) => { - return transform(available[standard][key], opts) + raw: (key, opts, lang) => { + if (available[lang]) return transform(available[lang][key], opts) + else return transform(available[standard][key], opts) }, send: async (key, channel, opts) => { let settings if (channel.guild) settings = await driver.getSettings(channel.guild) - if (settings && available[settings.language]) return channel.createMessage(transform(available[settings.language][key], opts)) + if (settings && available[settings.language] && available[settings.language][key]) return channel.createMessage(transform(available[settings.language][key], opts)) else if (!available[standard][key]) return global.logger.error(`Missing i18n key ${key} from standard language file!`) else return channel.createMessage(transform(available[standard][key], opts)) + }, + multiRaw: async (strings, language) => { + if (language && available[language]) { + return strings.map(v => transform(available[language][v._key], v.opts)) + } else { + return strings.map(v => transform(available[standard][v._key], v.opts)) + } + }, + multiSend: async (strings, channel) => { + const requested = strings.map(v => v._key) + for (let v of requested) { + if (!available[standard][v]) return global.logger.error(`Missing i18n key ${v} from standard language file!`) + } + let settings + if (channel.guild) settings = await driver.getSettings(channel.guild) + if (settings && available[settings.language]) channel.createMessage(strings.map(v => transform(available[settings.language][v._key], v.opts)).join('\n')) + else channel.createMessage(strings.map(v => transform(available[standard][v._key], v.opts)).join('\n')) } } diff --git a/src/internal/logger.js b/src/internal/logger.js index 2159b95c7..e21e8a907 100644 --- a/src/internal/logger.js +++ b/src/internal/logger.js @@ -64,7 +64,7 @@ module.exports = { sendToES({ type: 'command', cmd: opts.cmd, - full: opts.cmd + ' ' + opts.opts, + args: opts.opts.split(' '), author: opts.m.author, channel: opts.m.channel, guild: transform(opts.m.channel.guild) diff --git a/src/internal/version-check.js b/src/internal/version-check.js index b31fcdcbc..5e803bc7a 100644 --- a/src/internal/version-check.js +++ b/src/internal/version-check.js @@ -1,4 +1,8 @@ -(async () => { +const { promisify } = require('util') +const exec = promisify(require('child_process').exec) + +exec('git remote update').then(gitVerify).catch(async () => { + global.logger.warn('Git commit checking failed, falling back to version checking') const SA = require('superagent') const local = require('../../package.json').version const stable = await SA.get('https://raw.githubusercontent.com/TheSharks/WildBeast/master/package.json') @@ -7,4 +11,13 @@ if (local !== JSON.parse(stable.text).version && local !== JSON.parse(exp.text).version) { global.logger.warn('Not up-to-date with any remote version, update recommended') } -})() +}) + +async function gitVerify () { + const currentHead = await exec('git rev-parse --abbrev-ref HEAD') + const remoteRef = await exec(`git rev-parse origin/${currentHead.stdout.trim()}`) + const currentRef = await exec('git rev-parse HEAD') + const count = await exec(`git rev-list ${currentRef.stdout.trim()}..${remoteRef.stdout.trim()} --count`) + if (count.stdout.trim() > 0) global.logger.warn(`You're behind ${count.stdout.trim()} commit(s) compared to remote branch ${currentHead.stdout.trim()}, update recommended`) + else global.logger.log('Fully up-to-date, nice!') +} diff --git a/src/languages b/src/languages deleted file mode 160000 index 8f8339306..000000000 --- a/src/languages +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8f83393061047f18b259c398d6f51bfd5dcffa16 diff --git a/src/languages/en-EN.json b/src/languages/en-EN.json new file mode 100644 index 000000000..43ef710a9 --- /dev/null +++ b/src/languages/en-EN.json @@ -0,0 +1,67 @@ +{ + "LANGUAGE_DISCLAIMER": "Please note: translations are provided by the community, we cannot guarantee their correctness, completeness, or quality.", + "NSFW_NOT_ENABLED": "Please mark the channel as NSFW in order to use this command.", + "BOT_OWNER_ONLY": "This command is only for the bot owner.", + "NO_DM": "This command cannot be used in Direct Messages.", + "COOLDOWN": "This command is on cooldown for another {time} seconds.", + "NO_PERMS": "You have no permission to run this command.", + "COMMAND_ERROR": "Something went wrong while running this command, if you see my owner, show them this: ```\n{message}\n```", + "SETTINGS_ALL_INTRO": "These are the current settings for this server:", + "SETTINGS_NOT_WHITELISTED": "You cannot edit that.", + "SETTINGS_SINGLE_NOT_SET": "There's currently nothing set for {setting}.", + "SETTINGS_SINGLE_REPLY": "`{setting}` is currently set to `{value}`.", + "SETTINGS_RESET": "`{setting}` has been reset.", + "SETTINGS_NO_DATA": "No settings have been changed in this server.", + "SETTINGS_MODIFIED": "`{setting}` has been adjusted to `{value}`.", + "SETTINGS_WELCOMING_MALFORMED": "`welcome` can only be a channel or `dm`.", + "TAG_CREATED": "Tag with name {tag} successfully created.", + "TAG_NOT_FOUND": "There's no tag called {tag}.", + "TAG_DELETED": "Tag {tag} successfully deleted.", + "TAG_OWNER": "Tag {tag} belongs to {owner}.", + "TAG_NOT_OWNED": "You can't do that on a tag you don't own.", + "TAG_NAME_CONFLICT": "A tag with that name already exists.", + "TAG_EDITED": "Tag successfully edited.", + "TAG_TOO_SHORT": "Your tag is too short or doesn't have any content.", + "TAG_NAME_BLACKLISTED": "You cannot call your tag that.", + "BOORU_SITE_UNKNOWN": "I don't have a configuration for a site called `{site}`.", + "BOORU_NO_RESULTS": "Nothing found for query `{query}`.", + "BOORU_SUCCESS": "You've searched for `{query}`\n{url}", + "PERMISSIONS_OVERFLOW": "Access levels higher than 10 cannot be set.", + "PERMISSIONS_UPDATED": "Permissions successfully updated.", + "PERMISSIONS_MALFORMED": "Your syntax is incorrect. Check the help for this command for the proper syntax.", + "API_ERROR": "The API failed to respond or returned unusable data, please try again later.", + "WELCOME_MESSAGE": "Please welcome {user_name} to {guild_name}!", + "CMD_DISABLED_CHANNEL": "This command cannot be used in this channel.", + "NO_VOICE_CHANNELS": "Sorry bucko, there's no voice channels for me to join.", + "JOIN_VOICE_CHANNEL": "You must be connected to a voice channel before using this command.", + "NO_VOICE_CONNECT_PERM": "I don't have permission to connect or speak in {channel}.", + "VOICE_NOT_CONNECTED": "I am not connected to any voice channels in this guild.", + "VOICE_DISCONNECT": "Leaving voice channel **{channel}**.", + "VOICE_CONNECTED": "I am already connected to the **{channel}** voice channel.", + "NO_SEARCH_SUFFIX": "{user}, enter what you'd like to search for.", + "SEARCH_NO_TRACKS": "{user}, I could not find any tracks with those keywords.", + "LINK_NO_TRACK": "{user}, I could not add {url} to the queue.", + "YOUTUBE_PLAYLIST_MALFORMED_LINK": "Try that again with either a link to the video or the playlist. \n**Video:** <{video}> \n**Playlist:** ", + "TRACK_ADDED": "The track **{title}** _[{duration}]_ has been added to the queue by request of {user}.", + "TRACKS_ADDED": "Added **{count}** tracks to the queue by request of {user}.", + "NEXT_TRACK": "Track **{current}** has ended, now playing **{next}** _[{duration}]_ by request of {user}.", + "SKIP_TRACK": "Track **{current}** has been skipped, now playing **{next}** _[{duration}]_ by request of {user}.", + "MUSIC_PAUSE": "Music is now paused.", + "MUSIC_PAUSED": "Music is already paused, did you mean `resume`?", + "MUSIC_RESUME": "Music has been resumed.", + "MUSIC_PLAYING": "Music is already playing, did you mean `pause`?", + "VOICE_PERSIST": "Your queue is empty, add more to it using the `request` command.", + "QUEUE_END": "There are no more tracks in the queue to play, I'll leave the voice channel now.", + "QUEUE_EMPTY": "There are no tracks in the queue.", + "QUEUE_LIST": "Currently playing **{current}** _[{duration}]_ by request of {user}\n{list}", + "TRACK_REQUESTED_BY": "requested by {user}", + "MORE_SONGS": "And **{count}** more track(s).", + "QUEUE_SHUFFLED": "The queue has been shuffled.", + "NOW_PLAYING": "The current track is **{current}** _[{duration}]_\n**URL**: <{url}>\n{state}{progress}", + "VOLUME_NO_SUFFIX": "The volume is currently set to **{volume}**, if you'd like to change the volume use this command again with a number from 0 to 100.", + "VOLUME_SUFFIX_MALFORMED": "Only a number from 0 to 100 is allowed for this command.", + "VOLUME_ADJUSTED": "The volume has been adjusted to **{volume}**", + "INVITE_BOT_PRIVATE": "This bot is set to private, please ask `{owner}` if you'd like to invite this bot.", + "INVITE_GENERATED_RESULT": "Please use {invite} to invite this bot.", + "LANGUAGE_UNAVAILABLE": "That language is not available, you can choose from the following languages: `{available}`" +}