diff --git a/.env_example b/.env_example index 8ff64e7..51fcae4 100644 --- a/.env_example +++ b/.env_example @@ -1,2 +1,5 @@ DISCORD_TOKEN= RADIOX_STATIONSLISTURL=https://gitea.cwinfo.org/cwchristerw/radio/raw/branch/master/playlist.json +DEV_MODE=false +DEBUG_MODE=false +STREAMER_MODE=manual diff --git a/package-lock.json b/package-lock.json index b3d384d..d49e66c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "eximiabots-radiox", - "version": "0.3.8", + "version": "0.3.9", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "eximiabots-radiox", - "version": "0.3.8", + "version": "0.3.9", "license": "MIT", "dependencies": { "@discordjs/builders": "^0.6.0", @@ -21,22 +21,22 @@ "path": "^0.12.7" }, "devDependencies": { - "@types/node": "^16.9.0", + "@types/node": "^16.9.1", "@types/ws": "^7.4.7", - "@typescript-eslint/eslint-plugin": "^4.31.0", - "@typescript-eslint/parser": "^4.31.0", + "@typescript-eslint/eslint-plugin": "^4.31.1", + "@typescript-eslint/parser": "^4.31.1", "eslint": "^7.32.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "nodemon": "^2.0.12", - "prettier": "^2.3.2", + "prettier": "^2.4.1", "rimraf": "^3.0.2", "ts-node": "^10.2.1", "tsc-watch": "^4.5.0", "typescript": "^4.4.2" }, "engines": { - "node": ">=16.8.0", + "node": ">=16.9.1", "npm": ">=7.0.0" } }, @@ -447,9 +447,9 @@ } }, "node_modules/@sindresorhus/is": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz", - "integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz", + "integrity": "sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw==", "engines": { "node": ">=10" }, @@ -500,9 +500,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "16.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", - "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==" + "version": "16.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.2.tgz", + "integrity": "sha512-ZHty/hKoOLZvSz6BtP1g7tc7nUeJhoCf3flLjh8ZEv1vFKBWHXcnMbJMyN/pftSljNyy0kNW/UqI3DccnBnZ8w==" }, "node_modules/@types/ws": { "version": "7.4.7", @@ -513,13 +513,13 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.31.0.tgz", - "integrity": "sha512-iPKZTZNavAlOhfF4gymiSuUkgLne/nh5Oz2/mdiUmuZVD42m9PapnCnzjxuDsnpnbH3wT5s2D8bw6S39TC6GNw==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.31.1.tgz", + "integrity": "sha512-UDqhWmd5i0TvPLmbK5xY3UZB0zEGseF+DHPghZ37Sb83Qd3p8ujhvAtkU4OF46Ka5Pm5kWvFIx0cCTBFKo0alA==", "dev": true, "dependencies": { - "@typescript-eslint/experimental-utils": "4.31.0", - "@typescript-eslint/scope-manager": "4.31.0", + "@typescript-eslint/experimental-utils": "4.31.1", + "@typescript-eslint/scope-manager": "4.31.1", "debug": "^4.3.1", "functional-red-black-tree": "^1.0.1", "regexpp": "^3.1.0", @@ -544,15 +544,15 @@ } }, "node_modules/@typescript-eslint/experimental-utils": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.0.tgz", - "integrity": "sha512-Hld+EQiKLMppgKKkdUsLeVIeEOrwKc2G983NmznY/r5/ZtZCDvIOXnXtwqJIgYz/ymsy7n7RGvMyrzf1WaSQrw==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.1.tgz", + "integrity": "sha512-NtoPsqmcSsWty0mcL5nTZXMf7Ei0Xr2MT8jWjXMVgRK0/1qeQ2jZzLFUh4QtyJ4+/lPUyMw5cSfeeME+Zrtp9Q==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.31.0", - "@typescript-eslint/types": "4.31.0", - "@typescript-eslint/typescript-estree": "4.31.0", + "@typescript-eslint/scope-manager": "4.31.1", + "@typescript-eslint/types": "4.31.1", + "@typescript-eslint/typescript-estree": "4.31.1", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0" }, @@ -568,14 +568,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.31.0.tgz", - "integrity": "sha512-oWbzvPh5amMuTmKaf1wp0ySxPt2ZXHnFQBN2Szu1O//7LmOvgaKTCIDNLK2NvzpmVd5A2M/1j/rujBqO37hj3w==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.31.1.tgz", + "integrity": "sha512-dnVZDB6FhpIby6yVbHkwTKkn2ypjVIfAR9nh+kYsA/ZL0JlTsd22BiDjouotisY3Irmd3OW1qlk9EI5R8GrvRQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "4.31.0", - "@typescript-eslint/types": "4.31.0", - "@typescript-eslint/typescript-estree": "4.31.0", + "@typescript-eslint/scope-manager": "4.31.1", + "@typescript-eslint/types": "4.31.1", + "@typescript-eslint/typescript-estree": "4.31.1", "debug": "^4.3.1" }, "engines": { @@ -595,13 +595,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.31.0.tgz", - "integrity": "sha512-LJ+xtl34W76JMRLjbaQorhR0hfRAlp3Lscdiz9NeI/8i+q0hdBZ7BsiYieLoYWqy+AnRigaD3hUwPFugSzdocg==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.31.1.tgz", + "integrity": "sha512-N1Uhn6SqNtU2XpFSkD4oA+F0PfKdWHyr4bTX0xTj8NRx1314gBDRL1LUuZd5+L3oP+wo6hCbZpaa1in6SwMcVQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "4.31.0", - "@typescript-eslint/visitor-keys": "4.31.0" + "@typescript-eslint/types": "4.31.1", + "@typescript-eslint/visitor-keys": "4.31.1" }, "engines": { "node": "^8.10.0 || ^10.13.0 || >=11.10.1" @@ -612,9 +612,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.0.tgz", - "integrity": "sha512-9XR5q9mk7DCXgXLS7REIVs+BaAswfdHhx91XqlJklmqWpTALGjygWVIb/UnLh4NWhfwhR5wNe1yTyCInxVhLqQ==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.1.tgz", + "integrity": "sha512-kixltt51ZJGKENNW88IY5MYqTBA8FR0Md8QdGbJD2pKZ+D5IvxjTYDNtJPDxFBiXmka2aJsITdB1BtO1fsgmsQ==", "dev": true, "engines": { "node": "^8.10.0 || ^10.13.0 || >=11.10.1" @@ -625,13 +625,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.0.tgz", - "integrity": "sha512-QHl2014t3ptg+xpmOSSPn5hm4mY8D4s97ftzyk9BZ8RxYQ3j73XcwuijnJ9cMa6DO4aLXeo8XS3z1omT9LA/Eg==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.1.tgz", + "integrity": "sha512-EGHkbsUvjFrvRnusk6yFGqrqMBTue5E5ROnS5puj3laGQPasVUgwhrxfcgkdHNFECHAewpvELE1Gjv0XO3mdWg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "4.31.0", - "@typescript-eslint/visitor-keys": "4.31.0", + "@typescript-eslint/types": "4.31.1", + "@typescript-eslint/visitor-keys": "4.31.1", "debug": "^4.3.1", "globby": "^11.0.3", "is-glob": "^4.0.1", @@ -652,12 +652,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.0.tgz", - "integrity": "sha512-HUcRp2a9I+P21+O21yu3ezv3GEPGjyGiXoEUQwZXjR8UxRApGeLyWH4ZIIUSalE28aG4YsV6GjtaAVB3QKOu0w==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.1.tgz", + "integrity": "sha512-PCncP8hEqKw6SOJY+3St4LVtoZpPPn+Zlpm7KW5xnviMhdqcsBty4Lsg4J/VECpJjw1CkROaZhH4B8M1OfnXTQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "4.31.0", + "@typescript-eslint/types": "4.31.1", "eslint-visitor-keys": "^2.0.0" }, "engines": { @@ -795,9 +795,9 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "engines": { "node": ">=8" @@ -1799,9 +1799,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.12.0.tgz", - "integrity": "sha512-VNX0QkHK3RsXVKr9KrlUv/FoTa0NdbYoHHl7uXHv2rzyHSlxjdNAKug2twd9luJxpcyNeAgf5iPPMutJO67Dfg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -2564,9 +2564,9 @@ "dev": true }, "node_modules/minipass": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", - "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz", + "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==", "dependencies": { "yallist": "^4.0.0" }, @@ -2929,9 +2929,9 @@ } }, "node_modules/prettier": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.0.tgz", - "integrity": "sha512-DsEPLY1dE5HF3BxCRBmD4uYZ+5DCbvatnolqTqcxEgKVZnL2kUfyu7b8pPQ5+hTBkdhU9SLUmK0/pHb07RE4WQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz", + "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==", "dev": true, "bin": { "prettier": "bin-prettier.js" @@ -3281,9 +3281,9 @@ } }, "node_modules/signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.4.tgz", + "integrity": "sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==" }, "node_modules/slash": { "version": "3.0.0", @@ -3449,9 +3449,9 @@ } }, "node_modules/table/node_modules/ajv": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", - "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", + "version": "8.6.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", + "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", @@ -3735,9 +3735,9 @@ } }, "node_modules/typescript": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz", - "integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz", + "integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -4325,9 +4325,9 @@ "integrity": "sha512-gPSOQKmEWeuif2ftmW7gyToCnOoqpDqTSNrxX7HsoMwGNLtrT9p5DHcQolkBg5CEK3ooDFy5DNEFKUshQU7vyQ==" }, "@sindresorhus/is": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz", - "integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz", + "integrity": "sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw==" }, "@szmarczak/http-timer": { "version": "1.1.2", @@ -4369,9 +4369,9 @@ "dev": true }, "@types/node": { - "version": "16.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", - "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==" + "version": "16.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.2.tgz", + "integrity": "sha512-ZHty/hKoOLZvSz6BtP1g7tc7nUeJhoCf3flLjh8ZEv1vFKBWHXcnMbJMyN/pftSljNyy0kNW/UqI3DccnBnZ8w==" }, "@types/ws": { "version": "7.4.7", @@ -4382,13 +4382,13 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.31.0.tgz", - "integrity": "sha512-iPKZTZNavAlOhfF4gymiSuUkgLne/nh5Oz2/mdiUmuZVD42m9PapnCnzjxuDsnpnbH3wT5s2D8bw6S39TC6GNw==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.31.1.tgz", + "integrity": "sha512-UDqhWmd5i0TvPLmbK5xY3UZB0zEGseF+DHPghZ37Sb83Qd3p8ujhvAtkU4OF46Ka5Pm5kWvFIx0cCTBFKo0alA==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "4.31.0", - "@typescript-eslint/scope-manager": "4.31.0", + "@typescript-eslint/experimental-utils": "4.31.1", + "@typescript-eslint/scope-manager": "4.31.1", "debug": "^4.3.1", "functional-red-black-tree": "^1.0.1", "regexpp": "^3.1.0", @@ -4397,55 +4397,55 @@ } }, "@typescript-eslint/experimental-utils": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.0.tgz", - "integrity": "sha512-Hld+EQiKLMppgKKkdUsLeVIeEOrwKc2G983NmznY/r5/ZtZCDvIOXnXtwqJIgYz/ymsy7n7RGvMyrzf1WaSQrw==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.1.tgz", + "integrity": "sha512-NtoPsqmcSsWty0mcL5nTZXMf7Ei0Xr2MT8jWjXMVgRK0/1qeQ2jZzLFUh4QtyJ4+/lPUyMw5cSfeeME+Zrtp9Q==", "dev": true, "requires": { "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.31.0", - "@typescript-eslint/types": "4.31.0", - "@typescript-eslint/typescript-estree": "4.31.0", + "@typescript-eslint/scope-manager": "4.31.1", + "@typescript-eslint/types": "4.31.1", + "@typescript-eslint/typescript-estree": "4.31.1", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0" } }, "@typescript-eslint/parser": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.31.0.tgz", - "integrity": "sha512-oWbzvPh5amMuTmKaf1wp0ySxPt2ZXHnFQBN2Szu1O//7LmOvgaKTCIDNLK2NvzpmVd5A2M/1j/rujBqO37hj3w==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.31.1.tgz", + "integrity": "sha512-dnVZDB6FhpIby6yVbHkwTKkn2ypjVIfAR9nh+kYsA/ZL0JlTsd22BiDjouotisY3Irmd3OW1qlk9EI5R8GrvRQ==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "4.31.0", - "@typescript-eslint/types": "4.31.0", - "@typescript-eslint/typescript-estree": "4.31.0", + "@typescript-eslint/scope-manager": "4.31.1", + "@typescript-eslint/types": "4.31.1", + "@typescript-eslint/typescript-estree": "4.31.1", "debug": "^4.3.1" } }, "@typescript-eslint/scope-manager": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.31.0.tgz", - "integrity": "sha512-LJ+xtl34W76JMRLjbaQorhR0hfRAlp3Lscdiz9NeI/8i+q0hdBZ7BsiYieLoYWqy+AnRigaD3hUwPFugSzdocg==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.31.1.tgz", + "integrity": "sha512-N1Uhn6SqNtU2XpFSkD4oA+F0PfKdWHyr4bTX0xTj8NRx1314gBDRL1LUuZd5+L3oP+wo6hCbZpaa1in6SwMcVQ==", "dev": true, "requires": { - "@typescript-eslint/types": "4.31.0", - "@typescript-eslint/visitor-keys": "4.31.0" + "@typescript-eslint/types": "4.31.1", + "@typescript-eslint/visitor-keys": "4.31.1" } }, "@typescript-eslint/types": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.0.tgz", - "integrity": "sha512-9XR5q9mk7DCXgXLS7REIVs+BaAswfdHhx91XqlJklmqWpTALGjygWVIb/UnLh4NWhfwhR5wNe1yTyCInxVhLqQ==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.1.tgz", + "integrity": "sha512-kixltt51ZJGKENNW88IY5MYqTBA8FR0Md8QdGbJD2pKZ+D5IvxjTYDNtJPDxFBiXmka2aJsITdB1BtO1fsgmsQ==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.0.tgz", - "integrity": "sha512-QHl2014t3ptg+xpmOSSPn5hm4mY8D4s97ftzyk9BZ8RxYQ3j73XcwuijnJ9cMa6DO4aLXeo8XS3z1omT9LA/Eg==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.1.tgz", + "integrity": "sha512-EGHkbsUvjFrvRnusk6yFGqrqMBTue5E5ROnS5puj3laGQPasVUgwhrxfcgkdHNFECHAewpvELE1Gjv0XO3mdWg==", "dev": true, "requires": { - "@typescript-eslint/types": "4.31.0", - "@typescript-eslint/visitor-keys": "4.31.0", + "@typescript-eslint/types": "4.31.1", + "@typescript-eslint/visitor-keys": "4.31.1", "debug": "^4.3.1", "globby": "^11.0.3", "is-glob": "^4.0.1", @@ -4454,12 +4454,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.0.tgz", - "integrity": "sha512-HUcRp2a9I+P21+O21yu3ezv3GEPGjyGiXoEUQwZXjR8UxRApGeLyWH4ZIIUSalE28aG4YsV6GjtaAVB3QKOu0w==", + "version": "4.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.1.tgz", + "integrity": "sha512-PCncP8hEqKw6SOJY+3St4LVtoZpPPn+Zlpm7KW5xnviMhdqcsBty4Lsg4J/VECpJjw1CkROaZhH4B8M1OfnXTQ==", "dev": true, "requires": { - "@typescript-eslint/types": "4.31.0", + "@typescript-eslint/types": "4.31.1", "eslint-visitor-keys": "^2.0.0" } }, @@ -4559,9 +4559,9 @@ "dev": true }, "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "ansi-styles": { @@ -5317,9 +5317,9 @@ "dev": true }, "fastq": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.12.0.tgz", - "integrity": "sha512-VNX0QkHK3RsXVKr9KrlUv/FoTa0NdbYoHHl7uXHv2rzyHSlxjdNAKug2twd9luJxpcyNeAgf5iPPMutJO67Dfg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -5908,9 +5908,9 @@ "dev": true }, "minipass": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", - "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz", + "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==", "requires": { "yallist": "^4.0.0" } @@ -6177,9 +6177,9 @@ "dev": true }, "prettier": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.0.tgz", - "integrity": "sha512-DsEPLY1dE5HF3BxCRBmD4uYZ+5DCbvatnolqTqcxEgKVZnL2kUfyu7b8pPQ5+hTBkdhU9SLUmK0/pHb07RE4WQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz", + "integrity": "sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==", "dev": true }, "prettier-linter-helpers": { @@ -6406,9 +6406,9 @@ "dev": true }, "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.4.tgz", + "integrity": "sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==" }, "slash": { "version": "3.0.0", @@ -6536,9 +6536,9 @@ }, "dependencies": { "ajv": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", - "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", + "version": "8.6.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", + "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -6742,9 +6742,9 @@ } }, "typescript": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz", - "integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz", + "integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==", "dev": true }, "undefsafe": { diff --git a/package.json b/package.json index d394755..e88cfe4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eximiabots-radiox", - "version": "0.3.8", + "version": "0.3.9", "description": "Internet Radio to your Discord guild", "main": "index.js", "scripts": { @@ -30,22 +30,22 @@ "path": "^0.12.7" }, "devDependencies": { - "@types/node": "^16.9.0", + "@types/node": "^16.9.1", "@types/ws": "^7.4.7", - "@typescript-eslint/eslint-plugin": "^4.31.0", - "@typescript-eslint/parser": "^4.31.0", + "@typescript-eslint/eslint-plugin": "^4.31.1", + "@typescript-eslint/parser": "^4.31.1", "eslint": "^7.32.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "nodemon": "^2.0.12", - "prettier": "^2.3.2", + "prettier": "^2.4.1", "rimraf": "^3.0.2", "ts-node": "^10.2.1", "tsc-watch": "^4.5.0", "typescript": "^4.4.2" }, "engines": { - "node": ">=16.8.0", + "node": ">=16.9.1", "npm": ">=7.0.0" } } diff --git a/src/Client.ts b/src/Client.ts index 70e0095..b15df2e 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -1,6 +1,10 @@ import Discord, { Client, Collection } from "discord.js"; +import Datastore from "./client/classes/Datastore.js"; +import Radio from "./client/classes/Radio.js"; +import Stations from "./client/classes/Stations.js"; +import Streamer from "./client/classes/Streamer.js"; +import Statistics from "./client/classes/Statistics.js"; import fs from "fs"; -import Datastore from "./client/datastore.js"; import { command, radio } from "./client/utils/typings.js"; import config from "./config.js"; import messages from "./client/messages.js"; @@ -17,33 +21,34 @@ GatewayIntents.add( class RadioClient extends Client { readonly commands: Collection; - readonly radio: Map; public funcs: any; readonly config = config; readonly messages = messages; public datastore: Datastore | null; + public stations: Stations | null; + public streamer: Streamer | null; + public statistics: Statistics | null; + public radio: Radio | null; constructor() { super({ intents: GatewayIntents }); this.commands = new Collection(); - this.radio = new Map(); this.datastore = null; + this.stations = null; + this.streamer = null; + this.statistics = null; + this.radio = null; this.funcs = {}; this.funcs.check = require("./client/funcs/check.js"); - this.funcs.checkFetchStatus = require("./client/funcs/checkFetchStatus.js"); this.funcs.isDev = require("./client/funcs/isDev.js"); this.funcs.logger = require("./client/funcs/logger.js"); this.funcs.msToTime = require("./client/funcs/msToTime.js"); - this.funcs.statisticsUpdate = require("./client/funcs/statisticsUpdate.js"); this.funcs.saveState = require("./client/funcs/saveState.js"); this.funcs.loadState = require("./client/funcs/loadState.js"); - this.funcs.searchStation = require("./client/funcs/searchStation.js"); this.funcs.play = require("./client/funcs/play.js"); this.funcs.listStations = require("./client/funcs/listStations.js"); - this.funcs.restoreRadios = require("./client/funcs/restoreRadios.js"); - this.funcs.saveRadios = require("./client/funcs/saveRadios.js"); console.log('RadioX ' + this.config.version); console.log('Internet Radio to your Discord guild'); @@ -53,13 +58,7 @@ class RadioClient extends Client { this.funcs.logger("Bot", "Starting"); this.funcs.logger("Maintenance Mode", "Enabled"); - this.config.maintenance = true; - - const commandFiles = fs.readdirSync(path.join("./src/client/commands")).filter(f => f.endsWith(".js")); - for (const file of commandFiles) { - const command = require(`./client/commands/${file}`); - this.commands.set(command.name, command); - } + this.config.maintenanceMode = true; this.on("ready", () => { require(`${events}ready`).execute(this); diff --git a/src/client/datastore.js b/src/client/classes/Datastore.js similarity index 50% rename from src/client/datastore.js rename to src/client/classes/Datastore.js index 4db0eee..d219b27 100644 --- a/src/client/datastore.js +++ b/src/client/classes/Datastore.js @@ -8,15 +8,15 @@ module.exports = class { } loadData() { - const dir = path.join(path.dirname(__dirname), '../datastore'); + const dir = path.join(path.dirname(__dirname), '../../datastore'); if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } //console.log(""); - const dataFiles = fs.readdirSync(path.join(path.dirname(__dirname), '../datastore')).filter(f => f.endsWith('.json')); + const dataFiles = fs.readdirSync(path.join(path.dirname(__dirname), '../../datastore')).filter(f => f.endsWith('.json')); for (const file of dataFiles) { try { - const json = require(`../../datastore/${file}`); + const json = require(`../../../datastore/${file}`); this.map.set(json.guild.id, json); //console.log('[LOADED] ' + file + " (" + json.guild.id + ")"); //console.log(JSON.stringify(json, null, 4)); @@ -27,46 +27,8 @@ module.exports = class { //console.log(""); } - calculateGlobal(client){ - let guilds = this.map.keys(); - let stations = client.stations; - var statistics = {}; - - if(!client.stations) return; - - let calculation = guilds.next(); - - while (!calculation.done) { - let currentGuild = this.getEntry(calculation.value); - if(calculation.value != 'global'){ - if(stations){ - Object.keys(stations).forEach(function(station) { - if(currentGuild.statistics[stations[station].name] && currentGuild.statistics[stations[station].name].time && parseInt(currentGuild.statistics[stations[station].name].time) != 0 && currentGuild.statistics[stations[station].name].used && parseInt(currentGuild.statistics[stations[station].name].used) != 0){ - if(!statistics[stations[station].name]){ - statistics[stations[station].name] = {}; - statistics[stations[station].name].time = 0; - statistics[stations[station].name].used = 0; - } - - statistics[stations[station].name].time = parseInt(statistics[stations[station].name].time)+parseInt(currentGuild.statistics[stations[station].name].time); - statistics[stations[station].name].used = parseInt(statistics[stations[station].name].used)+parseInt(currentGuild.statistics[stations[station].name].used); - } - }); - } - } - calculation = guilds.next(); - } - - let newData = {}; - newData.guild = {}; - newData.guild.id = "global"; - newData.guild.name = "global"; - newData.statistics = statistics; - this.updateEntry(newData.guild, newData); - } - - checkEntry(id){ + this.loadEntry(id); if(!this.map.has(id)){ this.createEntry(id); //this.showEntry(this.getEntry(id)); @@ -85,6 +47,11 @@ module.exports = class { this.saveEntry(id, newData); } + loadEntry(id){ + const json = require(`../../../datastore/` + id + '.json'); + this.map.set(id, json); + } + getEntry(id){ return this.map.get(id); } @@ -127,7 +94,7 @@ module.exports = class { saveEntry(file, data) { data = JSON.stringify(data, null, 4); - fs.writeFile(path.join(path.dirname(__dirname), '../datastore') + "/" + file + ".json", data, 'utf8', function(err) { + fs.writeFile(path.join(path.dirname(__dirname), '../../datastore') + "/" + file + ".json", data, 'utf8', function(err) { if (err) { //console.log(err); } diff --git a/src/client/classes/Radio.js b/src/client/classes/Radio.js new file mode 100644 index 0000000..ca0af5b --- /dev/null +++ b/src/client/classes/Radio.js @@ -0,0 +1,86 @@ +const { + getVoiceConnection, + joinVoiceChannel +} = require("@discordjs/voice"); + +module.exports = class Radio extends Map { + constructor() { + super(); + } + + save(client) { + let currentRadios = this.keys(); + let radio = currentRadios.next(); + + while (!radio.done) { + let currentRadio = this.get(radio.value); + + if (currentRadio) { + currentRadio.guild = client.datastore.getEntry(radio.value).guild; + + client.statistics.update(client, currentRadio.guild, currentRadio); + client.funcs.saveState(client, currentRadio.guild, currentRadio); + currentRadio.connection?.destroy(); + currentRadio.message?.delete(); + this.delete(radio.value); + } + + radio = currentRadios.next(); + } + } + + restore(client, guilds) { + if(!client.stations) return; + + guilds.forEach(async guild => { + let state = client.funcs.loadState(client, guild); + if(!state) return; + if(!state.station || !state.channels.voice || !state.channels.text) return; + let voiceChannel = client.channels.cache.get(state.channels.voice); + if(!voiceChannel) return; + if(voiceChannel.members.filter(member => !member.user.bot).size === 0) return; + + + const sstation = await client.stations.search(state.station.name); + let station = sstation; + + const construct = { + textChannel: client.channels.cache.get(state.channels.text), + voiceChannel: client.channels.cache.get(state.channels.voice), + connection: null, + message: null, + station: station + }; + this.set(guild.id, construct); + + try { + const connection = + getVoiceConnection(guild.id) ?? + joinVoiceChannel({ + channelId: voiceChannel.id, + guildId: voiceChannel.guild.id, + adapterCreator: voiceChannel.guild.voiceAdapterCreator + }); + + construct.connection = connection; + let date = new Date(); + construct.startTime = date.getTime(); + + client.funcs.play(client, null, guild, station); + + client.datastore.checkEntry(guild.id); + construct.datastore = client.datastore.getEntry(guild.id); + + if (!construct.datastore.statistics[construct.station.name]) { + construct.datastore.statistics[construct.station.name] = {}; + construct.datastore.statistics[construct.station.name].time = 0; + construct.datastore.statistics[construct.station.name].used = 0; + client.datastore.updateEntry(guild, construct.datastore); + } + } catch (error) { + console.log(error); + } + }); + } + +}; diff --git a/src/client/classes/Stations.js b/src/client/classes/Stations.js new file mode 100644 index 0000000..76493f5 --- /dev/null +++ b/src/client/classes/Stations.js @@ -0,0 +1,109 @@ +const _importDynamic = new Function('modulePath', 'return import(modulePath)'); +const fetch = (...args) => _importDynamic('node-fetch').then(({default: fetch}) => fetch(...args)); + +module.exports = class Stations extends Array { + constructor() { + super(); + this.logger = require("../funcs/logger.js"); + } + + async fetch(options){ + try { + this.logger('Stations', 'Started fetching list – ' + options.url); + let list = await fetch(options.url) + .then(this.checkFetchStatus) + .then(response => response.json()); + + if(list){ + this.logger('Stations'); + this.forEach(oldStation => { + if(list.findIndex(station => station.name == oldStation.name)) return; + delete this[this.findIndex(station => station.name == oldStation.name)]; + }); + list.forEach(station => { + console.log("- " + station.name); + this.push(station); + }); + console.log("\n"); + } + + this.logger('Stations', 'Successfully fetched list'); + } catch (error) { + this.logger('Stations', 'Fetching list failed'); + console.error(error + "\n"); + + this.fetch(options); + } + } + + checkFetchStatus(response) { + if (response.ok) { // res.status >= 200 && res.status < 300 + return response; + } else { + throw new Error(response.status + " " + response.statusText); + } + } + + search(key) { + if (this === null) return false; + let foundStations = []; + if (!key) return false; + if (key == "radio") return false; + + this + .filter( + x => x.name.toUpperCase().includes(key.toUpperCase()) || x === key + ) + .forEach(x => + foundStations.push({ station: x, name: x.name, probability: 100 }) + ); + + if (key.startsWith("radio ")) key = key.slice(6); + const probabilityIncrement = 100 / key.split(" ").length / 2; + for (let i = 0; i < key.split(" ").length; i++) { + this + .filter( + x => x.name.toUpperCase().includes(key.split(" ")[i].toUpperCase()) || x === key + ) + .forEach(x => + foundStations.push({ station: x, name: x.name, probability: probabilityIncrement }) + ); + } + if (foundStations.length === 0) return false; + for (let i = 0; i < foundStations.length; i++) { + for (let j = 0; j < foundStations.length; j++) { + if (foundStations[i] === foundStations[j] && i !== j) foundStations.splice(i, 1); + } + } + for (let i = 0; i < foundStations.length; i++) { + if (foundStations[i].name.length > key.length) { + foundStations[i].probability -= + (foundStations[i].name.split(" ").length - key.split(" ").length) * + (probabilityIncrement * 0.5); + } else if (foundStations[i].name.length === key.length) { + foundStations[i].probability += probabilityIncrement * 0.9; + } + + for (let j = 0; j < key.split(" ").length; j++) { + if (!foundStations[i].name.toUpperCase().includes(key.toUpperCase().split(" ")[j])) { + foundStations[i].probability -= probabilityIncrement * 0.5; + } + } + } + let highestProbabilityStation; + for (let i = 0; i < foundStations.length; i++) { + if ( + !highestProbabilityStation || + highestProbabilityStation.probability < foundStations[i].probability + ) + highestProbabilityStation = foundStations[i]; + if ( + highestProbabilityStation && + highestProbabilityStation.probability === foundStations[i].probability + ) { + highestProbabilityStation = foundStations[i].station; + } + } + return highestProbabilityStation; + } +}; diff --git a/src/client/classes/Statistics.js b/src/client/classes/Statistics.js new file mode 100644 index 0000000..78bfac5 --- /dev/null +++ b/src/client/classes/Statistics.js @@ -0,0 +1,70 @@ +module.exports = class { + constructor() { + this.map = new Map(); + } + + update(client, guild, radio) { + + client.datastore.checkEntry(guild.id); + + radio.datastore = client.datastore.getEntry(guild.id); + + if(!radio.datastore.statistics[radio.station.name]){ + radio.datastore.statistics[radio.station.name] = {}; + radio.datastore.statistics[radio.station.name].time = 0; + radio.datastore.statistics[radio.station.name].used = 0; + client.datastore.updateEntry(guild, radio.datastore); + } + + let date = new Date(); + radio.currentTime = date.getTime(); + radio.playTime = parseInt(radio.currentTime)-parseInt(radio.startTime); + radio.datastore.statistics[radio.station.name].time = parseInt(radio.datastore.statistics[radio.station.name].time)+parseInt(radio.playTime); + + radio.datastore.statistics[radio.station.name].used = parseInt(radio.datastore.statistics[radio.station.name].used)+1; + client.datastore.updateEntry(guild, radio.datastore); + this.calculateGlobal(client); + } + + calculateGlobal(client){ + if(!client.stations) return; + if(!client.datastore.map) return; + + let guilds = client.datastore.map.keys(); + let stations = client.stations; + let statistics = {}; + + if(!client.stations) return; + + let calculation = guilds.next(); + + while (!calculation.done) { + let currentGuild = client.datastore.getEntry(calculation.value); + if(calculation.value != 'global'){ + if(stations){ + Object.keys(stations).forEach(function(station) { + if(currentGuild.statistics[stations[station].name] && currentGuild.statistics[stations[station].name].time && parseInt(currentGuild.statistics[stations[station].name].time) != 0 && currentGuild.statistics[stations[station].name].used && parseInt(currentGuild.statistics[stations[station].name].used) != 0){ + if(!statistics[stations[station].name]){ + statistics[stations[station].name] = {}; + statistics[stations[station].name].time = 0; + statistics[stations[station].name].used = 0; + } + + statistics[stations[station].name].time = parseInt(statistics[stations[station].name].time)+parseInt(currentGuild.statistics[stations[station].name].time); + statistics[stations[station].name].used = parseInt(statistics[stations[station].name].used)+parseInt(currentGuild.statistics[stations[station].name].used); + } + }); + } + } + calculation = guilds.next(); + } + + let newData = {}; + newData.guild = {}; + newData.guild.id = "global"; + newData.guild.name = "global"; + newData.statistics = statistics; + client.datastore.updateEntry(newData.guild, newData); + } + +}; diff --git a/src/client/classes/Streamer.js b/src/client/classes/Streamer.js new file mode 100644 index 0000000..5aacf9a --- /dev/null +++ b/src/client/classes/Streamer.js @@ -0,0 +1,128 @@ +const { + createAudioPlayer, + createAudioResource, + AudioPlayerStatus, + NoSubscriberBehavior +} = require("@discordjs/voice"); + +module.exports = class { + constructor() { + this.map = new Map(); + this.mode = null; + this.logger = require("../funcs/logger.js"); + } + + init(client){ + if(!client.config.streamerMode) return; + + switch(client.config.streamerMode){ + case "manual": + this.mode = "manual"; + break; + case "auto": + this.mode = "auto"; + break; + default: + this.mode = "manual"; + } + + if(this.mode == "auto"){ + if(!client.stations) return; + + client.stations.forEach(station => { + this.play(station); + }); + } + } + + refresh(client){ + this.init(client); + + let streamers = this.map.keys(); + streamers.forEach(streamer => { + if(client.stations.findIndex(station => station.name == streamer)) return; + this.stop(streamer); + }); + } + + play(station) { + let audioPlayer = this.map.get(station.name); + if(!audioPlayer) { + audioPlayer = createAudioPlayer({ + behaviors: { + noSubscriber: NoSubscriberBehavior.Play, + maxMissedFrames: Math.round(5000 / 20), + }, + }); + this.map.set(station.name, audioPlayer); + } + const url = station.stream[station.stream.default]; + const resource = createAudioResource(url); + audioPlayer.play(resource); + resource.playStream + .on("readable", () => { + this.logger('Streamer', station.name + " / " + "Readable"); + this.map.set(station.name, audioPlayer); + }) + .on("finish", () => { + this.logger('Streamer', station.name + " / " + "Finished"); + this.play(station); + }) + .on("error", error => { + this.logger('Streamer', station.name + " / " + "Error"); + this.play(station); + }); + + + + audioPlayer + .on('playing', () => { + this.logger('Streamer', station.name + " / " + "Playing"); + }) + .on('idle', () => { + this.logger('Streamer', station.name + " / " + "Idle"); + this.play(station); + }) + .on('paused', () => { + this.logger('Streamer', station.name + " / " + "Paused"); + this.play(station); + }) + .on('buffering', () => { + this.logger('Streamer', station.name + " / " + "Buffering"); + }) + .on('autopaused', () => { + this.logger('Streamer', station.name + " / " + "AutoPaused"); + this.play(station); + }) + .on('error', error => { + this.logger('Streamer', station.name + " / " + "Error" + "\n" + error); + this.play(station); + }); + return audioPlayer; + } + + stop(station){ + let audioPlayer = this.map.get(station.name); + if(audioPlayer){ + this.logger('Streamer', station.name + " / " + "Stop"); + audioPlayer.stop(); + } + this.map.delete(station.name); + } + + listen(station) { + let audioPlayer = this.map.get(station.name); + if(!audioPlayer){ + audioPlayer = this.play(station); + } + return audioPlayer; + } + + leave(client) { + if(!client.stations) return; + + client.stations.forEach(station => { + this.stop(station); + }); + } +}; diff --git a/src/client/commands.js b/src/client/commands.js index 9641933..9d4325b 100644 --- a/src/client/commands.js +++ b/src/client/commands.js @@ -1,17 +1,19 @@ const { SlashCommandBuilder } = require('@discordjs/builders'); const { REST } = require('@discordjs/rest'); const { Routes } = require('discord-api-types/v9'); -const { token, version } = require('../config.js'); const fs = require('fs'); const path = require ('path'); module.exports = { async execute(client) { + const commands = []; const commandFiles = fs.readdirSync(path.join("./src/client/commands")).filter(f => f.endsWith(".js")); for (const file of commandFiles) { const command = require(`./commands/${file}`); + client.commands.set(command.name, command); + command.data = new SlashCommandBuilder() .setName(command.name) .setDescription(command.description); @@ -28,13 +30,13 @@ module.exports = { commands.push(command.data); } - const rest = new REST({ version: '9' }).setToken(token); + const rest = new REST({ version: '9' }).setToken(client.config.token); (async () => { try { client.funcs.logger('Slash Commands', 'Started refreshing application (/) commands.'); - if(version.includes("-dev")){ + if(client.config.devMode){ await rest.put( Routes.applicationCommands(client.user.id), { body: [] }, diff --git a/src/client/commands/list.js b/src/client/commands/list.js index 59772f0..70ba290 100644 --- a/src/client/commands/list.js +++ b/src/client/commands/list.js @@ -17,7 +17,7 @@ module.exports = { const radio = client.radio.get(interaction.guild.id); - if(radio && !client.config.maintenance){ + if(radio && !client.config.maintenanceMode){ client.funcs.listStations(client, interaction); } else { let stations = `${client.stations.map(s => `**#** ${s.name}`).join('\n')}` diff --git a/src/client/commands/maintenance.js b/src/client/commands/maintenance.js index 8f7745e..d9d3547 100644 --- a/src/client/commands/maintenance.js +++ b/src/client/commands/maintenance.js @@ -1,4 +1,5 @@ import Discord from "discord.js"; +import Streamer from "../classes/Streamer.js"; const _importDynamic = new Function('modulePath', 'return import(modulePath)'); const fetch = (...args) => _importDynamic('node-fetch').then(({default: fetch}) => fetch(...args)); @@ -6,9 +7,6 @@ module.exports = { name: 'maintenance', description: 'Bot Maintenance', category: 'info', - options: [ - { type: "NUMBER", name: "action", description: "Select action", required: false} - ], async execute(interaction, client) { let message = {}; @@ -59,6 +57,18 @@ module.exports = { label: "Disable Maintenance Mode", description: "", value: "9" + }, + { + emoji: "💤", + label: "Streamer Mode – Manual", + description: "", + value: "10" + }, + { + emoji: "📡", + label: "Streamer Mode – Auto", + description: "", + value: "11" } ); @@ -91,47 +101,102 @@ module.exports = { ephemeral: true }); + let guilds = await client.guilds.fetch(); + switch(action){ case "0": + client.config.maintenanceMode = true; process.emit('SIGINT'); break; case "4": + client.config.maintenanceMode = true; client.user.setStatus('idle'); - client.funcs.saveRadios(client); + client.radio.save(client); client.user.setStatus('online'); + client.config.maintenanceMode = false; break; case "5": + client.config.maintenanceMode = true; client.user.setStatus('idle'); - let guilds = await client.guilds.fetch(); - client.funcs.restoreRadios(client, guilds); + client.radio.restore(client, guilds); client.user.setStatus('online'); + client.config.maintenanceMode = false; break; case "6": + client.config.maintenanceMode = true; client.user.setStatus('idle'); require(`../commands.js`).execute(client); client.user.setStatus('online'); + client.config.maintenanceMode = false; break; case "7": try { - client.funcs.logger('Stations', 'Started fetching list – ' + client.config.stationslistUrl); - client.stations = await fetch(client.config.stationslistUrl) - .then(client.funcs.checkFetchStatus) - .then(response => response.json()); + client.stations.fetch({ + url: client.config.stationslistUrl + }); + client.streamer.refresh(client); - client.funcs.logger('Stations', 'Successfully fetched list'); } catch (error) { - client.funcs.logger('Stations', 'Fetching list failed'); + } break; case "8": client.user.setStatus('dnd'); client.funcs.logger("Maintenance Mode", "Enabled"); - client.config.maintenance = false; + client.config.maintenanceMode = true; break; case "9": client.user.setStatus('online'); client.funcs.logger("Maintenance Mode", "Disabled"); - client.config.maintenance = false; + client.config.maintenanceMode = false; + break; + case "10": + client.config.streamerMode = "manual"; + client.config.maintenanceMode = true; + + client.user.setStatus('idle'); + client.funcs.saveRadios(client); + + setInterval(() => { + if(client.radio.size == 0 && client.config.streamerMode == "manual" && client.config.maintenanceMode){ + client.streamer.leave(client); + client.streamer = new Streamer(); + client.streamer.init(client); + + client.radio.restore(client, guilds); + client.user.setStatus('online'); + client.config.maintenanceMode = false; + } + + if(!client.config.maintenanceMode){ + clearInterval(); + } + }, 500); + + break; + case "11": + client.config.streamerMode = "auto"; + client.config.maintenanceMode = true; + + client.user.setStatus('idle'); + client.funcs.save(client); + + setInterval(() => { + if(client.radio.size == 0 && client.config.streamerMode == "auto" && client.config.maintenanceMode){ + client.streamer.leave(client); + client.streamer = new Streamer(); + client.streamer.init(client); + + client.radio.restore(client, guilds); + client.user.setStatus('online'); + client.config.maintenanceMode = false; + } + + if(!client.config.maintenanceMode){ + clearInterval(); + } + }, 500); + break; default: diff --git a/src/client/commands/next.js b/src/client/commands/next.js index ab3a0f0..0712d74 100644 --- a/src/client/commands/next.js +++ b/src/client/commands/next.js @@ -16,10 +16,7 @@ module.exports = { ephemeral: true }); - let url = station.stream[station.stream.default]; - - client.funcs.statisticsUpdate(client, interaction.guild, radio); - radio.audioPlayer.stop(); + client.statistics.update(client, interaction.guild, radio); let date = new Date(); radio.station = station; @@ -27,13 +24,12 @@ module.exports = { radio.startTime = date.getTime(); if(interaction.isCommand()) { - client.funcs.play(interaction, interaction.guild, client, url); + client.funcs.play(client, interaction, interaction.guild, station); } if(interaction.isButton()) { interaction.deferUpdate(); - client.funcs.play(null, interaction.guild, client, url); + client.funcs.play(client, null, interaction.guild, station); } - } } } diff --git a/src/client/commands/nowplaying.js b/src/client/commands/nowplaying.js index b863dc5..46cda34 100644 --- a/src/client/commands/nowplaying.js +++ b/src/client/commands/nowplaying.js @@ -15,7 +15,7 @@ module.exports = { const completed = (radio.playTime); message.nowplayingDescription = client.messages.nowplayingDescription.replace("%radio.station.name%", radio.station.name); - message.nowplayingDescription = message.nowplayingDescription.replace("%radio.station.owner%", radio.station.owner); + message.nowplayingDescription = message.nowplayingDescription.replace("%radio.station.owner%" + "\n", radio.station.name != radio.station.owner ? radio.station.owner + "\n" : ""); message.nowplayingDescription = message.nowplayingDescription.replace("%client.funcs.msToTime(completed)%", client.funcs.msToTime(completed)); const embed = new Discord.MessageEmbed() diff --git a/src/client/commands/play.js b/src/client/commands/play.js index cdbd6b4..47cd34f 100644 --- a/src/client/commands/play.js +++ b/src/client/commands/play.js @@ -1,5 +1,4 @@ const { - createAudioPlayer, getVoiceConnection, joinVoiceChannel } = require("@discordjs/voice"); @@ -15,12 +14,11 @@ module.exports = { async execute(interaction, client) { let message = {}; - if(client.config.maintenance){ - interaction.reply({ + if(client.config.maintenanceMode){ + return interaction.reply({ content: client.messageEmojis["error"] + client.messages.maintenance, ephemeral: true }); - return false; } if(!client.stations) { @@ -79,7 +77,6 @@ module.exports = { ephemeral: true }); } else { - url = client.stations[number].stream[client.stations[number].stream.default]; station = client.stations[number]; } } else { @@ -87,24 +84,22 @@ module.exports = { content: client.messageEmojis["error"] + client.messages.tooShortSearch, ephemeral: true }); - const sstation = await client.funcs.searchStation(query, client); + const sstation = await client.stations.search(query, client); if (!sstation) return interaction.reply({ content: client.messageEmojis["error"] + client.messages.noSearchResults, ephemeral: true }); - url = sstation.stream[sstation.stream.default]; station = sstation; } if (radio) { - client.funcs.statisticsUpdate(client, interaction.guild, radio); - radio.audioPlayer.stop(); + client.statistics.update(client, interaction.guild, radio); let date = new Date(); radio.station = station; radio.textChannel = interaction.channel; radio.startTime = date.getTime(); - client.funcs.play(interaction, interaction.guild, client, url); + client.funcs.play(client, interaction, interaction.guild, station); return; } @@ -114,7 +109,6 @@ module.exports = { voiceChannel: voiceChannel, connection: null, message: null, - audioPlayer: createAudioPlayer(), station: station }; client.radio.set(interaction.guild.id, construct); @@ -130,17 +124,8 @@ module.exports = { construct.connection = connection; let date = new Date(); construct.startTime = date.getTime(); - client.funcs.play(interaction, interaction.guild, client, url); - client.datastore.checkEntry(interaction.guild.id); - construct.currentGuild = client.datastore.getEntry(interaction.guild.id); - - if (!construct.currentGuild.statistics[construct.station.name]) { - construct.currentGuild.statistics[construct.station.name] = {}; - construct.currentGuild.statistics[construct.station.name].time = 0; - construct.currentGuild.statistics[construct.station.name].used = 0; - client.datastore.updateEntry(interaction.guild, construct.currentGuild); - } + client.funcs.play(client, interaction, interaction.guild, station); } catch (error) { console.log(error); client.radio.delete(interaction.guild.id); diff --git a/src/client/commands/prev.js b/src/client/commands/prev.js index 78ac583..75a79d0 100644 --- a/src/client/commands/prev.js +++ b/src/client/commands/prev.js @@ -16,10 +16,7 @@ module.exports = { ephemeral: true }); - let url = station.stream[station.stream.default]; - - client.funcs.statisticsUpdate(client, interaction.guild, radio); - radio.audioPlayer.stop(); + client.statistics.update(client, interaction.guild, radio); let date = new Date(); radio.station = station; @@ -27,11 +24,11 @@ module.exports = { radio.startTime = date.getTime(); if(interaction.isCommand()) { - client.funcs.play(interaction, interaction.guild, client, url); + client.funcs.play(client, interaction, interaction.guild, station); } if(interaction.isButton()) { interaction.deferUpdate(); - client.funcs.play(null, interaction.guild, client, url); + client.funcs.play(client, null, interaction.guild, station); } } diff --git a/src/client/commands/status.js b/src/client/commands/status.js index 66eb727..3f9ae9d 100644 --- a/src/client/commands/status.js +++ b/src/client/commands/status.js @@ -14,11 +14,11 @@ module.exports = { .setTitle(message.statusTitle) .setThumbnail("https://cdn.discordapp.com/emojis/" + client.messageEmojis["logo"].replace(/[^0-9]+/g, '')) .setColor(client.config.embedColor) - .addField(client.messages.statusField1, Date.now() - interaction.createdTimestamp + "ms", true) - .addField(client.messages.statusField2, client.ws.ping + "ms", true) - .addField(client.messages.statusField3, uptime, true) - .addField(client.messages.statusField4, client.config.version, true) - .addField(client.messages.statusField5, client.config.hostedBy, true) + .addField(client.messages.statusField1, uptime, false) + .addField(client.messages.statusField2, client.config.version, false) + .addField(client.messages.statusField3, Date.now() - interaction.createdTimestamp + "ms", false) + .addField(client.messages.statusField4, client.ws.ping + "ms", false) + .addField(client.messages.statusField5, client.config.hostedBy, false) .setImage('https://waren.io/berriabot-temp-sa7a36a9xm6837br/images/empty-3.png') .setFooter(client.messages.footerText, "https://cdn.discordapp.com/emojis/" + client.messageEmojis["eximiabots"].replace(/[^0-9]+/g, '')); diff --git a/src/client/commands/stop.js b/src/client/commands/stop.js index dd6174f..55d0212 100644 --- a/src/client/commands/stop.js +++ b/src/client/commands/stop.js @@ -7,10 +7,9 @@ module.exports = { async execute(interaction, client, command) { if (client.funcs.check(client, interaction, command)) { const radio = client.radio.get(interaction.guild.id); - client.funcs.statisticsUpdate(client, interaction.guild, radio); + client.statistics.update(client, interaction.guild, radio); radio.connection?.destroy(); - radio.audioPlayer?.stop(); - client.funcs.logger('Radio', 'Stream stopped' + " / " + interaction.guild.id); + client.funcs.logger('Radio', interaction.guild.id + " / " + 'Stop'); const embed = new Discord.MessageEmbed() .setTitle(client.user.username) @@ -23,7 +22,11 @@ module.exports = { if(!radio.message){ radio.message = radio.textChannel.send({ embeds: [embed], components: [] }); } else { - radio.message.edit({ embeds: [embed], components: [] }); + if(radio.textChannel.id == radio.message.channel.id){ + radio.message.edit({ embeds: [embed], components: [] }); + } else { + radio.message?.delete(); + } } setTimeout(async function() { diff --git a/src/client/events/SIGINT.js b/src/client/events/SIGINT.js index 6445029..b7f11f1 100644 --- a/src/client/events/SIGINT.js +++ b/src/client/events/SIGINT.js @@ -3,7 +3,8 @@ module.exports = { execute(client) { client.user.setStatus('dnd'); - client.funcs.saveRadios(client); + client.streamer.leave(client); + client.radio.save(client); setInterval(() => { if(client.radio.size == 0){ diff --git a/src/client/events/interactionCreate.js b/src/client/events/interactionCreate.js index 4b37466..c546259 100644 --- a/src/client/events/interactionCreate.js +++ b/src/client/events/interactionCreate.js @@ -6,7 +6,7 @@ module.exports = { if (!permissions.has('VIEW_CHANNEL')) return; if (!permissions.has('EMBED_LINKS')) return interaction.reply({ - content: client.messages.noPermsEmbed, + content: client.messageEmojis["error"] + client.messages.noPermsEmbed, ephemeral: true }); @@ -19,7 +19,7 @@ module.exports = { command.execute(interaction, client); } catch (error) { interaction.reply({ - content: client.messages.runningCommandFailed, + content: client.messageEmojis["error"] + client.messages.runningCommandFailed, ephemeral: true }); console.error(error); @@ -33,7 +33,7 @@ module.exports = { command.execute(interaction, client, command); } catch (error) { interaction.reply({ - content: client.messages.runningCommandFailed, + content: client.messageEmojis["error"] + client.messages.runningCommandFailed, ephemeral: true }); console.error(error); diff --git a/src/client/events/ready.js b/src/client/events/ready.js index ccc6a66..8ffcd6e 100644 --- a/src/client/events/ready.js +++ b/src/client/events/ready.js @@ -1,6 +1,8 @@ -import Datastore from "../datastore.js"; -const _importDynamic = new Function('modulePath', 'return import(modulePath)'); -const fetch = (...args) => _importDynamic('node-fetch').then(({default: fetch}) => fetch(...args)); +import Datastore from "../classes/Datastore.js"; +import Radio from "../classes/Radio.js"; +import Stations from "../classes/Stations.js"; +import Streamer from "../classes/Streamer.js"; +import Statistics from "../classes/Statistics.js"; module.exports = { name: 'ready', @@ -37,38 +39,21 @@ module.exports = { console.log("\n"); /*STATIONS*/ - try { - client.funcs.logger('Stations', 'Started fetching list – ' + client.config.stationslistUrl); - client.stations = await fetch(client.config.stationslistUrl) - .then(client.funcs.checkFetchStatus) - .then(response => response.json()); + client.stations = new Stations(); - client.funcs.logger('Stations'); - client.stations.forEach(station => { - console.log("- " + station.name); - }); - console.log("\n"); - - client.funcs.logger('Stations', 'Successfully fetched list'); - } catch (error) { - client.funcs.logger('Stations', 'Fetching list failed'); - console.error(error + "\n"); - } + await client.stations.fetch({ + url: client.config.stationslistUrl + }); setInterval(async () => { - try { - client.funcs.logger('Stations', 'Started fetching list – ' + client.config.stationslistUrl); - client.stations = await fetch(client.config.stationslistUrl) - .then(client.funcs.checkFetchStatus) - .then(response => response.json()); - - client.funcs.logger('Stations', 'Successfully fetched list'); - } catch (error) { - client.funcs.logger('Stations', 'Fetching list failed'); - //console.error(error); - } + await client.stations.fetch({ + url: client.config.stationslistUrl + }); }, 3600000); + client.streamer = new Streamer(); + client.streamer.init(client); + if(!client.stations) { client.user.setStatus('dnd'); } @@ -86,7 +71,8 @@ module.exports = { client.funcs.logger('Guilds', 'Successfully fetched list'); /*STATISTICS*/ - client.datastore.calculateGlobal(client); + client.statistics = new Statistics(); + client.statistics.calculateGlobal(client); /*EMOJIS*/ require(`../emojis.js`).execute(client); @@ -94,15 +80,20 @@ module.exports = { /*COMMANDS*/ require(`../commands.js`).execute(client); + /*RADIO*/ + client.radio = new Radio(); + setTimeout(function () { /*RESTORE RADIOS*/ - client.funcs.restoreRadios(client, guilds); + client.radio.restore(client, guilds); }, 5000); setTimeout(function () { - /*MAINTENANCE MODE*/ - client.funcs.logger("Maintenance Mode", "Disabled"); - client.config.maintenance = false; + if(client.stations) { + /*MAINTENANCE MODE*/ + client.funcs.logger("Maintenance Mode", "Disabled"); + client.config.maintenanceMode = false; + } }, 10000); } diff --git a/src/client/events/voiceStateUpdate.js b/src/client/events/voiceStateUpdate.js index e6da785..8ae732e 100644 --- a/src/client/events/voiceStateUpdate.js +++ b/src/client/events/voiceStateUpdate.js @@ -14,11 +14,10 @@ module.exports = { if (newState.member.id === client.user.id && oldState.member.id === client.user.id) { if (newState.channel === null) { - client.funcs.statisticsUpdate(client, newState.guild, radio); + client.statistics.update(client, newState.guild, radio); radio.connection?.destroy(); - radio.audioPlayer?.stop(); radio.message?.delete(); - client.funcs.logger('Radio', 'Stream stopped' + " / " + newState.guild.id); + client.funcs.logger('Radio', newState.guild.id + " / " + 'Stop'); return client.radio.delete(newState.guild.id); } @@ -37,11 +36,10 @@ module.exports = { 1000 ); } catch (error) { - client.funcs.statisticsUpdate(client, newState.guild, radio); + client.statistics.update(client, newState.guild, radio); radio.connection?.destroy(); - radio.audioPlayer?.stop(); radio.message?.delete(); - client.funcs.logger('Radio', 'Stream stopped' + " / " + newState.guild.id); + client.funcs.logger('Radio', newState.guild.id + " / " + 'Stop'); client.radio.delete(oldState.guild.id); } return; @@ -53,18 +51,17 @@ module.exports = { //radio.connection = await newState.channel.join(); } } - if ((oldState.channel.members.size === 1 && oldState.channel === radio.voiceChannel) || change) { + if ((oldState.channel.members.filter(member => !member.user.bot).size === 0 && oldState.channel === radio.voiceChannel) || change) { setTimeout(() => { if (!radio || !radio.connection || !radio.connection === null) return; - if (radio.voiceChannel.members.size === 1) { - client.funcs.statisticsUpdate(client, newState.guild, radio); + if (radio.voiceChannel.members.filter(member => !member.user.bot).size === 0) { + client.statistics.update(client, newState.guild, radio); radio.connection?.destroy(); - radio.audioPlayer?.stop(); radio.message?.delete(); - client.funcs.logger('Radio', 'Stream stopped' + " / " + newState.guild.id); + client.funcs.logger('Radio', newState.guild.id + " / " + 'Stop'); client.radio.delete(newState.guild.id); } - }, 60000); + }, 5000); } }, }; diff --git a/src/client/funcs/check.js b/src/client/funcs/check.js index e164cbb..4a5a216 100644 --- a/src/client/funcs/check.js +++ b/src/client/funcs/check.js @@ -1,8 +1,8 @@ -module.exports = function (client, interaction, command) { +module.exports = function check(client, interaction, command) { let message = {}; const radio = client.radio.get(interaction.guild.id); const permissions = interaction.channel.permissionsFor(interaction.user); - if(client.config.maintenance){ + if(client.config.maintenanceMode){ interaction.reply({ content: client.messageEmojis["error"] + client.messages.maintenance, ephemeral: true diff --git a/src/client/funcs/checkFetchStatus.js b/src/client/funcs/checkFetchStatus.js deleted file mode 100644 index 4774d77..0000000 --- a/src/client/funcs/checkFetchStatus.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = function (response) { - if (response.ok) { // res.status >= 200 && res.status < 300 - return response; - } else { - throw new Error(response.status + " " + response.statusText); - } -} diff --git a/src/client/funcs/isDev.js b/src/client/funcs/isDev.js index 3992c1e..8be3ac9 100644 --- a/src/client/funcs/isDev.js +++ b/src/client/funcs/isDev.js @@ -1,4 +1,4 @@ -module.exports = function (devList, authorID){ +module.exports = function isDev(devList, authorID){ let response = false; Object.keys(devList).forEach(function(oneDev) { let devID = devList[oneDev]; diff --git a/src/client/funcs/listStations.js b/src/client/funcs/listStations.js index d0cb036..2dd73db 100644 --- a/src/client/funcs/listStations.js +++ b/src/client/funcs/listStations.js @@ -1,6 +1,6 @@ import Discord from "discord.js"; -module.exports = function (client, interaction){ +module.exports = function listStations(client, interaction){ let stations = new Array(); let options = new Array(); diff --git a/src/client/funcs/logger.js b/src/client/funcs/logger.js index 610fdc5..ec39d1a 100644 --- a/src/client/funcs/logger.js +++ b/src/client/funcs/logger.js @@ -1,4 +1,4 @@ -module.exports = function (area, text){ +module.exports = function logger(area, text){ let date = new Date(); console.log('[' + area + '] – ' + date.toISOString()); if(text) console.log(text + '\n'); diff --git a/src/client/funcs/play.js b/src/client/funcs/play.js index 464e242..d369c1a 100644 --- a/src/client/funcs/play.js +++ b/src/client/funcs/play.js @@ -1,42 +1,15 @@ import Discord from "discord.js"; -const { - createAudioResource -} = require("@discordjs/voice"); -module.exports = async function play(interaction, guild, client, url) { +module.exports = async function play(client, interaction, guild, station) { let message = {}; const radio = client.radio.get(guild.id); - const resource = createAudioResource(url); - radio.connection.subscribe(radio.audioPlayer); - radio.audioPlayer.play(resource); - resource.playStream - .on("readable", () => { - client.funcs.logger('Radio', 'Stream started' + " / " + guild.id + " / " + radio.station.name); - }) - .on("finish", () => { - client.funcs.logger('Radio', 'Stream finished' + " / " + guild.id); - client.funcs.statisticsUpdate(client, guild, radio); - radio.connection?.destroy(); - radio.audioPlayer?.stop(); - client.radio.delete(guild.id); - return; - }) - .on("error", error => { - client.funcs.logger('Radio', 'Stream errored'); - console.error(error); - radio.connection?.destroy(); - radio.audioPlayer?.stop(); - client.radio.delete(guild.id); - return interaction.reply({ - content: client.messages.errorPlaying, - ephemeral: true - }); - }); + const audioPlayer = client.streamer.listen(station); + radio.connection.subscribe(audioPlayer); + client.funcs.logger('Radio', guild.id + " / " + "Play" + " / " + radio.station.name); message.nowplayingDescription = client.messages.nowplayingDescription.replace("%radio.station.name%", radio.station.name); - message.nowplayingDescription = message.nowplayingDescription.replace("%radio.station.owner%", radio.station.owner); + message.nowplayingDescription = message.nowplayingDescription.replace("%radio.station.owner%", radio.station.name != radio.station.owner ? radio.station.owner + "\n" : ""); message.nowplayingDescription = message.nowplayingDescription.replace("%client.funcs.msToTime(completed)%", ""); - message.nowplayingDescription = message.nowplayingDescription.replace("Owner: ", ""); message.nowplayingDescription = message.nowplayingDescription.replace("**", ""); message.nowplayingDescription = message.nowplayingDescription.replace("**", ""); @@ -83,7 +56,12 @@ module.exports = async function play(interaction, guild, client, url) { if(!radio.message){ radio.message = await radio.textChannel.send({ embeds: [embed], components: [buttons] }); } else { - radio.message.edit({ embeds: [embed], components: [buttons] }); + if(radio.textChannel.id == radio.message.channel.id){ + radio.message.edit({ embeds: [embed], components: [buttons] }); + } else { + radio.message?.delete(); + radio.message = await radio.textChannel.send({ embeds: [embed], components: [buttons] }); + } } message.play = client.messages.play.replace("%radio.station.name%", radio.station.name); diff --git a/src/client/funcs/restoreRadios.js b/src/client/funcs/restoreRadios.js deleted file mode 100644 index 10aff24..0000000 --- a/src/client/funcs/restoreRadios.js +++ /dev/null @@ -1,62 +0,0 @@ -import Discord from "discord.js"; -const { - createAudioPlayer, - getVoiceConnection, - joinVoiceChannel -} = require("@discordjs/voice"); - -module.exports = async function restoreRadios(client, guilds) { - if(!client.stations) return; - - guilds.forEach(async guild => { - let state = client.funcs.loadState(client, guild); - if(!state) return; - if(!state.station || !state.channels.voice || !state.channels.text) return; - let voiceChannel = client.channels.cache.get(state.channels.voice); - if(!voiceChannel) return; - if(voiceChannel.members.size === 0) return; - - - const sstation = await client.funcs.searchStation(state.station.name, client); - let url = sstation.stream[sstation.stream.default]; - let station = sstation; - - const construct = { - textChannel: client.channels.cache.get(state.channels.text), - voiceChannel: client.channels.cache.get(state.channels.voice), - connection: null, - message: null, - audioPlayer: createAudioPlayer(), - station: station - }; - client.radio.set(guild.id, construct); - - try { - const connection = - getVoiceConnection(guild.id) ?? - joinVoiceChannel({ - channelId: voiceChannel.id, - guildId: voiceChannel.guild.id, - adapterCreator: voiceChannel.guild.voiceAdapterCreator - }); - - construct.connection = connection; - let date = new Date(); - construct.startTime = date.getTime(); - - client.funcs.play(null, guild, client, url, Discord); - - client.datastore.checkEntry(guild.id); - construct.datastore = client.datastore.getEntry(guild.id); - - if (!construct.datastore.statistics[construct.station.name]) { - construct.datastore.statistics[construct.station.name] = {}; - construct.datastore.statistics[construct.station.name].time = 0; - construct.datastore.statistics[construct.station.name].used = 0; - client.datastore.updateEntry(guild, construct.datastore); - } - } catch (error) { - console.log(error); - } - }); -} diff --git a/src/client/funcs/saveRadios.js b/src/client/funcs/saveRadios.js deleted file mode 100644 index 9e7b187..0000000 --- a/src/client/funcs/saveRadios.js +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = function saveRadios(client) { - let currentRadios = client.radio.keys(); - let radio = currentRadios.next(); - - while (!radio.done) { - let currentRadio = client.radio.get(radio.value); - currentRadio.guild = client.datastore.getEntry(radio.value).guild; - - if (currentRadio) { - client.funcs.statisticsUpdate(client, currentRadio.guild, currentRadio); - client.funcs.saveState(client, currentRadio.guild, currentRadio); - currentRadio.connection?.destroy(); - currentRadio.audioPlayer?.stop(); - currentRadio.message?.delete(); - client.radio.delete(radio.value); - } - - radio = currentRadios.next(); - } -} diff --git a/src/client/funcs/searchStation.js b/src/client/funcs/searchStation.js deleted file mode 100644 index 9206722..0000000 --- a/src/client/funcs/searchStation.js +++ /dev/null @@ -1,62 +0,0 @@ -module.exports = function searchStation(key, client) { - if (client.stations === null) return false; - let foundStations = []; - if (!key) return false; - if (key == "radio") return false; - - client.stations - .filter( - x => x.name.toUpperCase().includes(key.toUpperCase()) || x === key - ) - .forEach(x => - foundStations.push({ station: x, name: x.name, probability: 100 }) - ); - - if (key.startsWith("radio ")) key = key.slice(6); - const probabilityIncrement = 100 / key.split(" ").length / 2; - for (let i = 0; i < key.split(" ").length; i++) { - client.stations - .filter( - x => x.name.toUpperCase().includes(key.split(" ")[i].toUpperCase()) || x === key - ) - .forEach(x => - foundStations.push({ station: x, name: x.name, probability: probabilityIncrement }) - ); - } - if (foundStations.length === 0) return false; - for (let i = 0; i < foundStations.length; i++) { - for (let j = 0; j < foundStations.length; j++) { - if (foundStations[i] === foundStations[j] && i !== j) foundStations.splice(i, 1); - } - } - for (let i = 0; i < foundStations.length; i++) { - if (foundStations[i].name.length > key.length) { - foundStations[i].probability -= - (foundStations[i].name.split(" ").length - key.split(" ").length) * - (probabilityIncrement * 0.5); - } else if (foundStations[i].name.length === key.length) { - foundStations[i].probability += probabilityIncrement * 0.9; - } - - for (let j = 0; j < key.split(" ").length; j++) { - if (!foundStations[i].name.toUpperCase().includes(key.toUpperCase().split(" ")[j])) { - foundStations[i].probability -= probabilityIncrement * 0.5; - } - } - } - let highestProbabilityStation; - for (let i = 0; i < foundStations.length; i++) { - if ( - !highestProbabilityStation || - highestProbabilityStation.probability < foundStations[i].probability - ) - highestProbabilityStation = foundStations[i]; - if ( - highestProbabilityStation && - highestProbabilityStation.probability === foundStations[i].probability - ) { - highestProbabilityStation = foundStations[i].station; - } - } - return highestProbabilityStation; -} diff --git a/src/client/funcs/statisticsUpdate.js b/src/client/funcs/statisticsUpdate.js deleted file mode 100644 index fcd3ad1..0000000 --- a/src/client/funcs/statisticsUpdate.js +++ /dev/null @@ -1,22 +0,0 @@ -module.exports = function statisticsUpdate(client, guild, radio) { - - client.datastore.checkEntry(guild.id); - - radio.datastore = client.datastore.getEntry(guild.id); - - if(!radio.datastore.statistics[radio.station.name]){ - radio.datastore.statistics[radio.station.name] = {}; - radio.datastore.statistics[radio.station.name].time = 0; - radio.datastore.statistics[radio.station.name].used = 0; - client.datastore.updateEntry(guild, radio.datastore); - } - - let date = new Date(); - radio.currentTime = date.getTime(); - radio.playTime = parseInt(radio.currentTime)-parseInt(radio.startTime); - radio.datastore.statistics[radio.station.name].time = parseInt(radio.datastore.statistics[radio.station.name].time)+parseInt(radio.playTime); - - radio.datastore.statistics[radio.station.name].used = parseInt(radio.datastore.statistics[radio.station.name].used)+1; - client.datastore.updateEntry(guild, radio.datastore); - client.datastore.calculateGlobal(client); -} diff --git a/src/client/messages.js b/src/client/messages.js index f893ff8..b50f160 100644 --- a/src/client/messages.js +++ b/src/client/messages.js @@ -11,7 +11,7 @@ module.exports = { inviteTitle: "Invite %client.user.username% to your Discord server!", listTitle: "Radio Stations", nowplayingTitle: "Now Playing", - nowplayingDescription: "**%radio.station.name%**" + "\n" + "Owner: %radio.station.owner%" + "\n" + "%client.funcs.msToTime(completed)%", + nowplayingDescription: "**%radio.station.name%**" + "\n" + "%radio.station.owner%" + "\n" + "%client.funcs.msToTime(completed)%", noVoiceChannel: "You need to be in a voice channel to play radio!", noQuery: "You need to use a number or search for a supported station!", noPermsConnect: "I cannot connect to your voice channel.", @@ -29,11 +29,11 @@ module.exports = { sendedMaintenanceMessage: "This bot is going to be under maintenance!", footerText: "EximiaBots by Warén Group", statusTitle: "%client.user.username% Status", - statusField1: "Bot Latency", - statusField2: "API Latency", - statusField3: "Uptime", - statusField4: "Version", - statusField5: "Hosted by", + statusField1: ":clock1: Bot Uptime", + statusField2: ":floppy_disk: Bot Version", + statusField3: ":heartbeat: WebSocket Ping", + statusField4: ":hourglass: Latency", + statusField5: ":globe_with_meridians: Hosted by", errorStationURL: "Station can't be URL", messageCommandsDeprecatedTitle: "%client.user.username%", messageCommandsDeprecatedDescription: "We recommend you to reauthorize our bot by clicking the invite link down below, because Discord is planning to remove message content from verified bots [Read More](https://support-dev.discord.com/hc/en-us/articles/4404772028055)" + "\n\n" + "**Invite Bot**" + "\n" + "https://wgi.fi/radiox_invite" + "\n\n" + "This bot now supports slash commands, you should start using them instead. Type / into the message box and select the bot you wish to use. Remember to be careful as there are a few bugs here and there on Discord." + "\n\n" + "We will remove this deprecation message in March of 2022 when RadioX 1.0.0 is released.", diff --git a/src/config.js b/src/config.js index b161603..e15f034 100644 --- a/src/config.js +++ b/src/config.js @@ -20,7 +20,9 @@ module.exports = { hostedBy: "[Warén Group](https://waren.io)", //Settings - version: process.env.RADIOX_VERSION || process.env.npm_package_version, + version: process.env.DEV_MODE ? process.env.npm_package_version + "-dev" : process.env.npm_package_version, debug: process.env.DEBUG_MODE || false, - maintenance: false + devMode: process.env.DEV_MODE || false, + maintenanceMode: false, + streamerMode: process.env.STREAMER_MODE || "manual" }