diff --git a/TODOS.md b/TODOS.md index be1e503..043c975 100644 --- a/TODOS.md +++ b/TODOS.md @@ -1,6 +1,3 @@ # List -TODO: Lecture de fichiers depuis le site, nécéssite hébergement. TODO: Récupération des recommendations, playlists Youtube et Spotify -TODO: Acces à un historique personnel (LOCAL ?) -TODO: Faire un systême de parole. diff --git a/backend/package-lock.json b/backend/package-lock.json index 4b697c5..b92a817 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,16 +1,16 @@ { "name": "chopin-backend", - "version": "1.0.1", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "chopin-backend", - "version": "1.0.1", + "version": "1.1.0", "license": "ISC", "dependencies": { "@discordjs/voice": "^0.18.0", - "@distube/ytdl-core": "^4.16.9", + "@distube/ytdl-core": "^4.16.10", "@distube/ytsr": "2.0.4", "cors": "^2.8.5", "discord-player": "^7.1.0", @@ -20,8 +20,10 @@ "ffprobe": "^1.1.2", "ffprobe-static": "^3.1.0", "fluent-ffmpeg": "^2.1.3", + "googleapis": "^149.0.0", "libsodium-wrappers": "^0.7.15", "loguix": "^1.4.2", + "mime-types": "^3.0.1", "nodemon": "^3.1.9", "pm2": "^5.4.3", "socket.io": "^4.8.1", @@ -267,9 +269,9 @@ } }, "node_modules/@distube/ytdl-core": { - "version": "4.16.9", - "resolved": "https://registry.npmjs.org/@distube/ytdl-core/-/ytdl-core-4.16.9.tgz", - "integrity": "sha512-eRYM3lDR1/1ZB+k6jzIdR+8m9VsYEqjz9+DstX1S/aW1f2rlbj22WCdrRbE+sE3DJW8DLJEp69akfjWqQ+nKIw==", + "version": "4.16.10", + "resolved": "https://registry.npmjs.org/@distube/ytdl-core/-/ytdl-core-4.16.10.tgz", + "integrity": "sha512-KFKZtNlynOO0PYxelUF5h2bKyAU1d8fe6aZmo+gxWt7H2MQbd0bUeyV4y9iWhI57nukjkSXXQGB625CfhrVdGQ==", "license": "MIT", "dependencies": { "http-cookie-agent": "^7.0.1", @@ -1482,6 +1484,27 @@ "node": ">= 0.6" } }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/agent-base": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", @@ -1623,6 +1646,27 @@ "node": ">= 6" } }, + "node_modules/axios/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/axios/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1667,6 +1711,15 @@ "node": ">=10.0.0" } }, + "node_modules/bignumber.js": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.0.tgz", + "integrity": "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1785,6 +1838,12 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -2341,6 +2400,15 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -2693,6 +2761,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/extrareqp2": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/extrareqp2/-/extrareqp2-1.0.0.tgz", @@ -2972,6 +3046,27 @@ "node": ">= 6" } }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -3036,6 +3131,69 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/gaxios/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/get-intrinsic": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", @@ -3178,6 +3336,75 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis": { + "version": "149.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-149.0.0.tgz", + "integrity": "sha512-LTMc/njwYy7KTeaUHDcQt7KxftHyghdzm2XzbL46PRLd1AXB09utT9Po2ZJn2X0EApz0pE2T5x5A9zM8iue6zw==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^9.0.0", + "googleapis-common": "^7.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.2.0.tgz", + "integrity": "sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^6.0.3", + "google-auth-library": "^9.7.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -3190,6 +3417,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -3502,6 +3742,18 @@ "node": ">=0.12.0" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3573,6 +3825,15 @@ "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", "license": "MIT" }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -3605,6 +3866,27 @@ "node": "*" } }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/lazy": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/lazy/-/lazy-1.0.11.tgz", @@ -4054,21 +4336,21 @@ } }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "mime-db": "^1.54.0" }, "engines": { "node": ">= 0.6" @@ -6130,8 +6412,7 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tree-kill": { "version": "1.2.2", @@ -6341,6 +6622,27 @@ "node": ">= 0.6" } }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -6400,6 +6702,12 @@ "node": ">= 0.8" } }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", + "license": "BSD" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -6475,8 +6783,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/webmetrik": { "version": "0.1.4", @@ -6489,7 +6796,6 @@ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "license": "MIT", - "peer": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" diff --git a/backend/package.json b/backend/package.json index 41d1ebc..3eb208c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "chopin-backend", - "version": "1.0.2", + "version": "1.1.0", "description": "Discord Bot for music - Fetching everywhere !", "main": "src/main.js", "nodemonConfig": { @@ -21,7 +21,7 @@ "license": "ISC", "dependencies": { "@discordjs/voice": "^0.18.0", - "@distube/ytdl-core": "^4.16.9", + "@distube/ytdl-core": "^4.16.10", "@distube/ytsr": "2.0.4", "cors": "^2.8.5", "discord-player": "^7.1.0", @@ -31,8 +31,10 @@ "ffprobe": "^1.1.2", "ffprobe-static": "^3.1.0", "fluent-ffmpeg": "^2.1.3", + "googleapis": "^149.0.0", "libsodium-wrappers": "^0.7.15", "loguix": "^1.4.2", + "mime-types": "^3.0.1", "nodemon": "^3.1.9", "pm2": "^5.4.3", "socket.io": "^4.8.1", diff --git a/backend/src/discord/Bot.js b/backend/src/discord/Bot.js index db7d516..7d0e0f4 100644 --- a/backend/src/discord/Bot.js +++ b/backend/src/discord/Bot.js @@ -9,6 +9,7 @@ const { Player } = require("../player/Player") const {refreshAllUserInformation} = require("../server/auth/User") const dlog = new LogType("Discord") +dlog.log("Initialisation du Bot Discord") const membersVoices = new Map() const timers = new Map() diff --git a/backend/src/discord/Commands/Media.js b/backend/src/discord/Commands/Media.js index 006299c..0d746b8 100644 --- a/backend/src/discord/Commands/Media.js +++ b/backend/src/discord/Commands/Media.js @@ -2,6 +2,7 @@ const {Command} = require('../Command'); const {Embed, EmbedError} = require('../Embed'); const { Player } = require('../../player/Player'); const { Song } = require('../../player/Song'); +const history = require('../../playlists/History'); const command = new Command("media", "Lire un média MP3/WAV dans un salon vocal", async (client, interaction) => { @@ -34,6 +35,8 @@ const command = new Command("media", "Lire un média MP3/WAV dans un salon vocal embed.setTitle('**Ajout à liste de lecture**') } + + history.addToPersonalHistory(interaction.user.id, song) embed.setDescription('**Titre : **' + song.title) embed.addField('**Durée : **', song.readduration) diff --git a/backend/src/discord/Commands/Play.js b/backend/src/discord/Commands/Play.js index 1debb6e..645c457 100644 --- a/backend/src/discord/Commands/Play.js +++ b/backend/src/discord/Commands/Play.js @@ -4,6 +4,7 @@ const { Player } = require("../../player/Player"); const Finder = require("../../player/Finder"); const { Playlist } = require("../../playlists/Playlist"); const spotify = require("../../media/SpotifyInformation"); +const history = require("../../playlists/History"); const command = new Command("play", "Jouer une musique à partir d'un lien dans un salon vocal", async (client, interaction) => { @@ -75,6 +76,8 @@ const command = new Command("play", "Jouer une musique à partir d'un lien dans } else { player.add(song) } + + history.addToPersonalHistory(interaction.user.id, song) } diff --git a/backend/src/discord/MediaBase.js b/backend/src/discord/MediaBase.js new file mode 100644 index 0000000..03ea395 --- /dev/null +++ b/backend/src/discord/MediaBase.js @@ -0,0 +1,104 @@ +const {LogType} = require("loguix") +const config = require("../utils/Database/Configuration") +const wlog = new LogType("MediaBase") +const { Database } = require("../utils/Database/Database") +const {__glob} = require("../utils/GlobalVars") +const { AttachmentBuilder } = require("discord.js") +const discordBot = require("./Bot") + + +var connected = false +var mediaDB = new Database("media", __glob.MEDIA_DB, []) + +wlog.step.init("init_db", "Initialisation de la base de données multimédia") + +if(!config.getMediaGuildId() || !config.getMediaChannelId()) { + wlog.warn("La configuration de la base de données multimédia n'est pas définie, vérifiez le fichier de configuration.") + wlog.step.error("init_db","Impossible d'initialiser la base de données multimédia, vérifiez le fichier de configuration.") +} + + + +var channel = null + +discordBot.getClient().on("ready", () => { + channel = discordBot.getChannel(config.getMediaGuildId(), config.getMediaChannelId()) + if(!channel) { + wlog.warn("Le canal multimédia n'existe pas, vérifiez le fichier de configuration.") + wlog.step.error("init_db","Impossible d'initialiser la base de données multimédia, vérifiez le fichier de configuration.") + } + + + try { + const dateTime = new Date() + const date = dateTime.toLocaleDateString('fr-FR', { timeZone: 'Europe/Paris' }) + const time = dateTime.toLocaleTimeString('fr-FR', { timeZone: 'Europe/Paris' }) + const message = `[LOGS] La base de données multimédia a été initialisée le ${date} à ${time}` + channel.send(message) + wlog.log("La base de données multimédia a été initialisée avec succès.") + wlog.step.end("init_db") + connected = true + } catch (e) { + wlog.error("Impossible d'envoyer un message au canal multimédia, vérifiez le fichier de configuration.") + wlog.step.error("init_db","Impossible d'envoyer un message au canal multimédia, vérifiez le fichier de configuration.") + connected = false + } + +}) + + +// SEND FILE TO DISCORD AND GET THE URL ID + +async function postMedia(file) { + if(!connected) { + wlog.error("La base de données multimédia n'est pas connectée, impossible d'envoyer le fichier.") + return null + } + + try { + const attachment = new AttachmentBuilder(file.file) + attachment.setName(file.name) // Set the name of the file + attachment.setDescription("Fichier envoyé par Subsonics Chopin - Raphix") + const message = await channel.send({ files: [attachment] }) + const url = message.attachments.first().url + wlog.log(`Fichier envoyé avec succès : ${url}`) + // add the file to the database + mediaDB.data.push({ + id: message.id, + url: url, + name: file.name, + size: file.size, + createdAt: new Date().toISOString() + }) + mediaDB.save() + return url + } catch (error) { + wlog.error(`Erreur lors de l'envoi du fichier : ${error.message}`) + return null + } +} + +async function getMedia(id) { + if(!connected) { + wlog.error("La base de données multimédia n'est pas connectée, impossible de récupérer le fichier.") + return null + } + + const media = mediaDB.data.find(m => m.id === id) + if(!media) { + wlog.error(`Aucun média trouvé avec l'ID : ${id}`) + return null + } + + try { + return media.url + } catch (error) { + wlog.error(`Erreur lors de la récupération du média : ${error.message}`) + return null + } +} + +module.exports = { + postMedia, + getMedia, +} \ No newline at end of file diff --git a/backend/src/lyrics/Lyrics.js b/backend/src/lyrics/Lyrics.js new file mode 100644 index 0000000..981ae66 --- /dev/null +++ b/backend/src/lyrics/Lyrics.js @@ -0,0 +1,58 @@ +const { LogType } = require("loguix"); +const plog = new LogType('Lyrics'); +const urls = require('./urls.json'); + +// Make sure Url exists and get lyrics for the first item only +async function getLyrics(name) { + let result = null; + try { + const searchResponse = await fetch(`${urls.urlSearch}${encodeURIComponent(name)}`, { + method: 'GET', + headers: { + 'content-type': 'application/json' + } + }); + const searchData = await searchResponse.json(); + + // Check if data exists and has at least one item + if (searchData && searchData.data && searchData.data.length > 0) { + const firstItem = searchData.data[0]; + const artist = firstItem.artist && firstItem.artist.name ? firstItem.artist.name : null; + const title = firstItem.title || null; + + if (artist && title) { + try { + const lyricsResponse = await fetch(`${urls.urlGet}${encodeURIComponent(artist)}/${encodeURIComponent(title)}`, { + method: 'GET', + headers: { + 'content-type': 'application/json' + } + }); + const lyricsData = await lyricsResponse.json(); + console.log(lyricsData); + if (lyricsData && lyricsData && lyricsData.lyrics) { + result = lyricsData.lyrics; + } else { + plog.error('Invalid response structure:', lyricsData); + return null; + } + } catch (error) { + plog.error('Error fetching lyrics data:', error); + return null; + } + } else { + plog.error('Artist or title missing in search result'); + return null; + } + } else { + plog.error('No search results found'); + return null; + } + } catch (error) { + plog.error('Error fetching search data:', error); + return null; + } + return result; +} + +module.exports = { getLyrics }; \ No newline at end of file diff --git a/backend/src/lyrics/urls.json b/backend/src/lyrics/urls.json new file mode 100644 index 0000000..5a2701b --- /dev/null +++ b/backend/src/lyrics/urls.json @@ -0,0 +1,4 @@ +{ + "urlSearch": "http://api.deezer.com/search?limit=5&q=", + "urlGet": "https://api.lyrics.ovh/v1/" +} \ No newline at end of file diff --git a/backend/src/main.js b/backend/src/main.js index 0c531f6..1949008 100644 --- a/backend/src/main.js +++ b/backend/src/main.js @@ -5,7 +5,6 @@ */ -const { LogType } = require('loguix'); const { __glob } = require("./utils/GlobalVars") require("loguix").setup(__glob.LOGS, __glob.PACKAGEINFO) const config = require("./utils/Database/Configuration") @@ -20,7 +19,7 @@ setup(); async function setup() { const DiscordBot = require("./discord/Bot") - DiscordBot.init() + await DiscordBot.init() const Server = require("./server/Server") - Server.init() + await Server.init() } \ No newline at end of file diff --git a/backend/src/media/MediaInformation.js b/backend/src/media/MediaInformation.js index fe0f03b..8d90e35 100644 --- a/backend/src/media/MediaInformation.js +++ b/backend/src/media/MediaInformation.js @@ -29,4 +29,30 @@ async function getMediaInformation(instance, media, provider) { } } -module.exports = {getMediaInformation} \ No newline at end of file +async function getMediaInformationFromUrl(instance, url) { + try { + const info = await ffprobe(url, { path: ffprobeStatic.path }); + if (info.streams?.[0]?.duration_ts) { + instance.duration = info.streams[0].duration; + instance.readduration = getReadableDuration(instance.duration); + } + + // Vérification pour éviter une erreur si `streams[0]` ou `tags` n'existe pas + instance.thumbnail = info.streams?.[0]?.tags?.thumbnail ?? + "https://radomisol.fr/wp-content/uploads/2016/08/cropped-note-radomisol-musique.png"; + + // Obtenir le titre (sinon utiliser le nom du fichier) + instance.title = info.streams?.[0]?.tags?.title ?? "Titre inconnu"; + + // Obtenir l'auteur (s'il existe) + instance.author = info.streams?.[0]?.tags?.artist ?? "Auteur inconnu"; + + } catch (err) { + clog.error("Impossible de récupérer les informations de la musique depuis l'URL : " + url); + console.log(err) + clog.error(err); + return null; + } +} + +module.exports = {getMediaInformation, getMediaInformationFromUrl}; \ No newline at end of file diff --git a/backend/src/media/YoutubeInformation.js b/backend/src/media/YoutubeInformation.js index 1451f7e..fe0f81f 100644 --- a/backend/src/media/YoutubeInformation.js +++ b/backend/src/media/YoutubeInformation.js @@ -2,7 +2,7 @@ const { LogType } = require('loguix'); const clog = new LogType("YoutubeInformation"); const { Song } = require('../player/Song'); const { Playlist } = require('../playlists/Playlist'); -const { getReadableDuration } = require('../utils/TimeConverter'); +const { getReadableDuration, getSecondsDuration } = require('../utils/TimeConverter'); const ytsr = require('@distube/ytsr'); const ytfps = require('ytfps'); @@ -110,4 +110,26 @@ async function getPlaylist(url) { } } -module.exports = { getQuery, getVideo, getPlaylist }; +async function getSecondsFromUrl(url) { + const videoId = url.match(/(?:youtu\.be\/|youtube\.com\/|music\.youtube\.com\/)(?:watch\?v=)?([a-zA-Z0-9_-]{11})/); + if (videoId === null) { + clog.error("Impossible de récupérer l'identifiant de la vidéo YouTube à partir de l'URL"); + return null; + } + try { + const searchResults = await ytsr(videoId[1], { limit: 1 }); + const video = searchResults.items.find(item => item.type === 'video'); + console.log(video); + if (video) { + return getSecondsDuration(video.duration); // Convert seconds to milliseconds + } else { + clog.error("Impossible de récupérer la vidéo YouTube à partir de l'identifiant"); + return null; + } + } catch (error) { + clog.error('Erreur lors de la recherche de la vidéo YouTube:' + error); + return null; + } +} + +module.exports = { getQuery, getVideo, getPlaylist, getSecondsFromUrl }; diff --git a/backend/src/player/Song.js b/backend/src/player/Song.js index 0f8f2d1..318487d 100644 --- a/backend/src/player/Song.js +++ b/backend/src/player/Song.js @@ -13,6 +13,7 @@ class Song { thumbnail = "https://radomisol.fr/wp-content/uploads/2016/08/cropped-note-radomisol-musique.png" ; duration; readduration; + form = "SONG"; type; userAddedId; diff --git a/backend/src/playlists/Google/OAuth2.js b/backend/src/playlists/Google/OAuth2.js new file mode 100644 index 0000000..72c21ce --- /dev/null +++ b/backend/src/playlists/Google/OAuth2.js @@ -0,0 +1,87 @@ +const { LogType } = require('loguix'); +const alog = new LogType("GoogleOAuth2"); +const { google } = require('googleapis'); +const config = require("../../utils/Database/Configuration"); +const Users = require('../../server/auth/User'); + +const clientId = config.getYoutubeApiClientId(); +const clientSecret = config.getYoutubeApiClientSecret(); +const redirectUri = config.getWebsiteLink() + "/oauth2callback"; + + +const oAuth2Map = new Map(); + +function createAuthUrl(userId) { + if(!checkCredientials()) return null; + var oAuth2Client; + const user = Users.getUserById(userId); + if (!user) { + alog.error(`User with ID ${userId} not found.`); + return null; + } + if (!clientId || !clientSecret) { + alog.error("YouTube API client ID or secret is not set in the configuration."); + } else { + oAuth2Client = new google.auth.OAuth2( + clientId, + clientSecret, + redirectUri + ); + + alog.log("Google OAuth2 client initialized successfully."); + } + + if (!oAuth2Client) { + alog.error("OAuth2 client is not initialized. Please check your configuration."); + return null; + } + oAuth2Map.set(userId, oAuth2Client); + alog.log(`OAuth2 client created for user ${userId}.`); + return oAuth2Client.generateAuthUrl({ + access_type: 'offline', + scope: SCOPES, + }); +} + +async function getAuthorization(userId, code) { + if(!checkCredientials()) return null; + try { + const user = Users.getUserById(userId); + if (!user) { + alog.error(`User with ID ${userId} not found.`); + return null; + } + oAuth2Client = oAuth2Map.get(userId); + if (!oAuth2Client) { + alog.error(`OAuth2 client for user ${userId} not found. Please create an OAuth2 client first.`); + return null; + } + const { tokens } = await oAuth2Client.getToken(code); + oAuth2Client.setCredentials(tokens); + alog.log(`OAuth2 client credentials set for user ${userId}.`); + return oAuth2Client; + } catch (error) { + alog.error(`Error during OAuth2 authorization for user ${userId}:`, error); + return null; + } + + +} + +function checkCredientials() { + if (!clientId || !clientSecret) { + alog.error("YouTube API client ID or secret is not set in the configuration."); + return false; + } + return true; +} + +module.exports = { + createAuthUrl, + getAuthorization, + getOAuth2Client: (userId) => oAuth2Map.get(userId), + oAuth2Map +}; + +const SCOPES = ['https://www.googleapis.com/auth/youtube.readonly']; + diff --git a/backend/src/playlists/Google/YoutubeList.js b/backend/src/playlists/Google/YoutubeList.js new file mode 100644 index 0000000..10759ae --- /dev/null +++ b/backend/src/playlists/Google/YoutubeList.js @@ -0,0 +1,62 @@ +const { google } = require('googleapis'); +const { LogType } = require('loguix'); +const alog = new LogType("YoutubeAPI"); + +const OAuth2 = require('./OAuth2'); +const Users = require('../../server/auth/User'); + +async function getYoutubePlaylists(userId) { + const user = Users.getUserById(userId); + if (!user) { + alog.error(`User with ID ${userId} not found.`); + return null; + } + + const oAuth2Client = OAuth2.getOAuth2Client(userId); + if (!oAuth2Client) { + alog.error(`OAuth2 client for user ${userId} not found. Please authenticate first.`); + return null; + } + + const youtube = google.youtube({ version: 'v3', auth: oAuth2Client }); + + try { + const response = await youtube.playlists.list({ + part: 'snippet,contentDetails', + mine: true, + maxResults: 50 + }); + alog.log(`Retrieved playlists for user ${userId}.`); + return response.data.items; + } catch (error) { + alog.error(`Error retrieving playlists for user ${userId}:`, error); + return null; + } +} + +function getYoutubePlaylistSongs(playlistId, userId) { + const user = Users.getUserById(userId); + if (!user) { + alog.error(`User with ID ${userId} not found.`); + return null; + } + + const oAuth2Client = OAuth2.getOAuth2Client(userId); + if (!oAuth2Client) { + alog.error(`OAuth2 client for user ${userId} not found. Please authenticate first.`); + return null; + } + + const youtube = google.youtube({ version: 'v3', auth: oAuth2Client }); + + return youtube.playlistItems.list({ + part: 'snippet', + playlistId: playlistId, + maxResults: 50 + }); +} + +module.exports = { + getYoutubePlaylists, + getYoutubePlaylistSongs +}; \ No newline at end of file diff --git a/backend/src/playlists/History.js b/backend/src/playlists/History.js new file mode 100644 index 0000000..d003be4 --- /dev/null +++ b/backend/src/playlists/History.js @@ -0,0 +1,54 @@ +const {LogType} = require("loguix") +const hlog = new LogType("PersonalHistory") +const {__glob} = require("../utils/GlobalVars") +const { Database } = require("../utils/Database/Database") +const historyDb = new Database("history", __glob.HISTORY_DB, {}) +historyDb.load() + +/** + * @param {string} userId + * @returns {Array} + * @description Renvoie l'historique personnel de l'utilisateur + */ +function getPersonalHistory(userId) { + if (historyDb.data[userId]) { + return historyDb.data[userId]; + } else { + hlog.log(`Création d'une clé pour l'utilisateur : ${userId}`); + historyDb.data[userId] = []; + historyDb.save(); + return historyDb.data[userId]; + } +} + +/** + * @param {string} userId + * @param {Object} entry + * @description Ajoute une entrée à l'historique personnel de l'utilisateur + */ +function addToPersonalHistory(userId, entry) { + hlog.log(`Ajout d'une entrée à l'historique personnel de l'utilisateur : ${userId}`); + const history = getPersonalHistory(userId); + // Limit to 25 entries + if (history.length >= 25) { + history.shift(); + } + history.push(entry) + historyDb.save(); +} + +/** + * @param {string} userId + * @description Vide l'historique personnel de l'utilisateur + */ +function clearPersonalHistory(userId) { + hlog.log(`Vidage de l'historique personnel de l'utilisateur : ${userId}`); + historyDb.data[userId] = []; + historyDb.save(); +} + +module.exports = { + getPersonalHistory, + addToPersonalHistory, + clearPersonalHistory +}; diff --git a/backend/src/playlists/Playlist.js b/backend/src/playlists/Playlist.js index 2531d6e..f510d90 100644 --- a/backend/src/playlists/Playlist.js +++ b/backend/src/playlists/Playlist.js @@ -11,6 +11,7 @@ class Playlist { duration = 0; readduration; description; + form = "PLAYLIST"; type; constructor(title, url, author, authorId, songs, thumbnail, duration, readduration, description) { this.title = title; diff --git a/backend/src/playlists/PlaylistManager.js b/backend/src/playlists/PlaylistManager.js index bdb597d..4899858 100644 --- a/backend/src/playlists/PlaylistManager.js +++ b/backend/src/playlists/PlaylistManager.js @@ -6,6 +6,10 @@ const {LogType} = require('loguix'); const clog = new LogType("PlaylistManager"); const Finder = require('../player/Finder'); const spotify = require('../media/SpotifyInformation'); +const { getYoutubePlaylistSongs } = require('./Google/YoutubeList'); +const { auth } = require('googleapis/build/src/apis/abusiveexperiencereport'); +const { getReadableDuration } = require('../utils/TimeConverter'); +const { getSecondsFromUrl } = require('../media/YoutubeInformation'); const playlistDB = new Database("Playlists", __glob.PLAYLISTFILE, {}); @@ -182,6 +186,84 @@ function removeSong(id, playlistName, songId) { clog.log(`Suppression de la chanson ${songId} de la playlist ${playlistName} pour l'utilisateur ${id}`); } +async function processYoutubeData(userId, data) { + if (!data || data.length === 0) { + clog.warn(`Aucune donnée YouTube trouvée pour l'utilisateur ${userId}`); + return []; + } + + const playlists = []; + for (const item of data) { + if (item.snippet && item.contentDetails) { + const playlist = new Playlist(); + playlist.id = item.id; + playlist.title = item.snippet.title; + playlist.url = `https://www.youtube.com/playlist?list=${item.id}`; + playlist.description = item.snippet.description || "Aucune description disponible"; + playlist.author = item.snippet.channelTitle; + playlist.thumbnail = item.snippet.thumbnails.default.url; + playlist.authorId = `https://www.youtube.com/channel/${item.snippet.channelId}`; + playlist.songs = []; // You can fetch songs later if needed + await getYoutubePlaylistSongs(item.id, userId).then(songsData => { + if (songsData && songsData.data && songsData.data.items) { + playlist.songs = songsData.data.items.map(song => ({ + id: song.snippet.resourceId.videoId, + title: song.snippet.title, + author: song.snippet.videoOwnerChannelTitle, + authorId: `https://www.youtube.com/channel/${song.snippet.videoOwnerChannelId}`, + url: `https://www.youtube.com/watch?v=${song.snippet.resourceId.videoId}`, + thumbnail: song.snippet?.thumbnails?.default?.url || "https://radomisol.fr/wp-content/uploads/2016/08/cropped-note-radomisol-musique.png", + })); + // Add readduration for every items in songs + + } else { + clog.warn(`Aucune chanson trouvée pour la playlist ${item.id}`); + } + }).catch(err => { + clog.error(`Erreur lors de la récupération des chansons pour la playlist ${item.id}:`, err); + }); + for (const song of playlist.songs) { + // If authorId is not defined, delete the song + if (song.authorId == "https://www.youtube.com/channel/undefined") { + clog.warn(`L'auteur de la chanson ${song.title} (${song.id}) n'est pas défini. Suppression de la chanson.`); + playlist.songs.splice(playlist.songs.indexOf(song), 1); + continue; // Skip this song + } + song.duration = await getSecondsFromUrl(song.url); + if (song.duration === null) { + clog.warn(`Impossible de récupérer la durée de la chanson ${song.title} (${song.id})`); + song.duration = 0; // Set to 0 if duration cannot be fetched + } else { + song.readduration = getReadableDuration(song.duration); + playlist.duration += song.duration; // Initialize duration if not set + } + + } + + playlist.readduration = getReadableDuration(playlist.duration); + playlist.type = "youtube"; + playlists.push(playlist); + } else { + clog.warn(`Données YouTube manquantes pour l'élément ${item.id}`); + } + }; + + clog.log(`Traitement des données YouTube pour l'utilisateur ${userId} terminé. Nombre de playlists trouvées : ${playlists.length}`); + // Save the playlists to the user's playlist collection + const userPlaylists = getPlaylistsOfUser(userId); + // Remove existing playlists with the same IDs to avoid duplicates + for (const playlist of playlists) { + const existingIndex = userPlaylists.findIndex(p => p.id === playlist.id); + if (existingIndex !== -1) { + userPlaylists.splice(existingIndex, 1); // Remove existing playlist with the same ID + } + } + userPlaylists.push(...playlists); + playlistDB.save(); + clog.log(`Playlists ajoutées pour l'utilisateur ${userId}. Nombre total de playlists : ${userPlaylists.length}`); + return playlists; +} + module.exports = { getPlaylistsOfUser, getPlaylistOfUser, @@ -191,5 +273,6 @@ module.exports = { copyPlaylist, renamePlaylist, addSong, - removeSong + removeSong, + processYoutubeData } \ No newline at end of file diff --git a/backend/src/server/Documentation.md b/backend/src/server/Documentation.md deleted file mode 100644 index ab828e9..0000000 --- a/backend/src/server/Documentation.md +++ /dev/null @@ -1,234 +0,0 @@ -# Documentation des Requêtes `socket.io` - -Les requêtes sont du point de vue du serveur. - -Le Client doit être initialisé comme ceci : - -```js -const socket = io("subsonics.raphix.fr:5000", { - auth: { - token: "TOKEN_HERE", - auth_code: "AUTH_FROM_DISCORD_HERE", - session: "SESSION_ID_HERE" - } -}); -``` - -REDIRECT_CALLBACK = `/callback` - ---- - -## Requêtes Envoyées - -### Événement : `NEW_SESSION` - -- **Description** : `/login` et `/` : Envoie un jeton de session utile pour la traçabilité de la connexion par Discord. Dès réception, le client le stocke dans les cookies sous le nom `session`. Si l'utilisateur n'est pas sur `/login`, il doit y être redirigé. Supprime également le cookie `token` s’il existe. -- **Données envoyées** : -```json -"SESSION_ID" -``` - -### Événement : `NEW_TOKEN` - -- **Description** : `/callback` : Lors de la redirection depuis Discord, ce jeton est généré après vérification du code d'autorisation. Il est envoyé au client qui est ensuite redirigé vers `/`. -- **Données envoyées** : -```json -"TOKEN_ID" -``` - -### Événement : `BANNED` - -- **Description** : `/callback` et `/` : Si reçu, le client est redirigé vers la page de connexion avec l'erreur "BANNI". - -### Événement : `AUTH_ERROR` - -- **Description** : `/callback` et `/` : Erreur lors de l’authentification (ex. code Discord invalide ou accès refusé). - ---- - -## Requêtes Reçues - -> Toutes les requêtes commencent par l’événement `socket.on("EVENT_NAME", callback)` côté client, et sont traitées côté serveur par `IORequest("EVENT_NAME", callback)`. - -### Utilisateur - -#### `/USER/INFO` - -- **Description** : Renvoie l’identité Discord, les guildes et les labels de l'utilisateur connecté. -- **Données envoyées** : -```json -{} -``` -- **Réponse** : -```json -{ - "identity": { ... }, - "guilds": [ ... ], - "labels": [ "admin", ... ] -} -``` - -#### `/USERS/LIST` - -- **Description** : Renvoie la liste des utilisateurs connectés à une guilde. -- **Données envoyées** : -```json -"GUILD_ID" -``` -- **Réponse** : -```json -[ { "id": "...", "username": "...", ... }, ... ] -``` - -### Player (Musique) - -#### `/PLAYER/STATE` - -- **Description** : Récupère l'état actuel du player pour une guilde. -- **Données envoyées** : -```json -"GUILD_ID" -``` - -#### `/PLAYER/JOIN` / `/PLAYER/LEAVE` - -- **Description** : Rejoint ou quitte l’écoute du player pour une guilde. -- **Données envoyées** : -```json -"GUILD_ID" -``` - -#### `/PLAYER/PAUSE`, `/PLAYER/BACKWARD`, `/PLAYER/FORWARD`, `/PLAYER/LOOP`, `/PLAYER/SHUFFLE`, `/PLAYER/DISCONNECT` - -- **Description** : Contrôle du player (pause, chanson précédente/suivante, boucle, aléatoire, déconnexion). -- **Données envoyées** : -```json -"GUILD_ID" -``` - -#### `/PLAYER/CHANNEL/CHANGE` - -- **Description** : Change le salon vocal du player vers celui de l’utilisateur. -- **Données envoyées** : -```json -"GUILD_ID" -``` - -#### `/PLAYER/SEEK` - -- **Description** : Change la position de la lecture. -- **Données envoyées** : -```json -["GUILD_ID", TEMPS_EN_SECONDES] -``` - -### Queue - -#### `/QUEUE/PLAY/NOW` - -- **Description** : Joue une chanson de la queue immédiatement. -- **Données envoyées** : -```json -["GUILD_ID", "previous"|"next", INDEX] -``` - -#### `/QUEUE/NEXT/DELETE`, `/QUEUE/NEXT/DELETEALL`, `/QUEUE/NEXT/MOVE` - -- **Description** : Supprime ou déplace une chanson dans la file d’attente. -- **Données envoyées** : -```json -["GUILD_ID", INDEX (ou NEW_INDEX)] -``` - -### Recherche - -#### `/SEARCH` - -- **Description** : Effectue une recherche de musique. -- **Données envoyées** : -```json -"QUERY" -``` - -#### `/SEARCH/PLAY` - -- **Description** : Joue un morceau directement ou l'ajoute à la queue. -- **Données envoyées** : -```json -["GUILD_ID", SONG, now (bool)] -``` - -### Playlists - -#### `/PLAYLISTS/CREATE`, `/DELETE`, `/RENAME`, `/ADD_SONG`, `/REMOVE_SONG`, `/SEND`, `/PLAY` - -- **Description** : Gère les playlists (création, suppression, renommage, ajout, lecture, envoi à un autre utilisateur). -- **Données envoyées** : Variable selon l'action, ex : -```json -["PLAYLIST_NAME", SONG] -``` - -#### `/PLAYLISTS/LIST` - -- **Description** : Renvoie la liste des playlists de l'utilisateur. -- **Données envoyées** : -```json -{} -``` - -### Admin - -> Nécessite le label `"admin"` dans `socketUser.labels`. - -#### `/ADMIN/LOGS` - -- **Description** : Renvoie les logs du serveur. - -#### `/ADMIN/MAINTENANCE/RESTART` - -- **Description** : Redémarre le serveur avec une raison. -- **Données envoyées** : -```json -"RAISON" -``` - -#### `/ADMIN/USERS/SWITCH_ADMIN`, `/FULL_BAN`, `/DELETE` - -- **Description** : Gère les utilisateurs (promotion admin, ban complet, suppression). -- **Données envoyées** : -```json -"USER_ID" -``` - -#### `/ADMIN/PLAYER/GETALLSTATE` - -- **Description** : Renvoie l’état de tous les players. - -### Owner / Modérateur - -#### `/OWNER/USERS/SWITCH_MOD` - -- **Description** : Nomme ou enlève un modérateur. -- **Données envoyées** : -```json -["USER_ID", "GUILD_ID"] -``` - -#### `/MOD/USERS/BAN` - -- **Description** : Bannit un utilisateur d’une guilde. -- **Données envoyées** : -```json -["USER_ID", "GUILD_ID"] -``` - -### Utilitaires - -#### `/REPORT` - -- **Description** : Envoie un rapport avec un niveau et une description. -- **Données envoyées** : -```json -["LEVEL", "DESCRIPTION"] -``` - diff --git a/backend/src/server/Server.js b/backend/src/server/Server.js index a792739..34c2152 100644 --- a/backend/src/server/Server.js +++ b/backend/src/server/Server.js @@ -15,10 +15,18 @@ const Finder = require("../player/Finder") const fs = require("fs") const {__glob} = require("../utils/GlobalVars") const playlists = require("../playlists/PlaylistManager") +const history = require("../playlists/History") +const lyrics = require("../lyrics/Lyrics") +const mediaBase = require("../discord/MediaBase") +const googleApis = require("../playlists/Google/OAuth2") +const youtubeApi = require("../playlists/Google/YoutubeList") const configuration = require("../utils/Database/Configuration") const { List } = require('../player/List') const { restart } = require('../utils/Maintenance') +const { isAudioFile } = require('../utils/AudioBufferCheck') +const { Song } = require('../player/Song') +const { getMediaInformationFromUrl } = require('../media/MediaInformation') const allConnectedUsers = new Array() const guildConnectedUsers = new Map() @@ -205,18 +213,21 @@ function init() { identity: socketUser.identity, guilds: guildPresents, labels: socketUser.labels, - + history: history.getPersonalHistory(socketUser.identity.id), }) wlog.log("Envoi des informations Discord de '" + socketUser.identity.id + "' à '" + socket.id + "'" ) }) + IORequest("/USER/HISTORY", () => { + IOAnswer("/USER/HISTORY", history.getPersonalHistory(socketUser.identity.id)) + }) + //CHECKED : 24/04/2025 IORequest("/USER/SIGNOUT", () => { socketUser.removeToken(token) socket.disconnect() }) - // CHECKED : 24/04/2025 IORequest("/USERS/LIST", (guildId) => { if(!checkUserGuild(socketUser, guildId)) return @@ -226,6 +237,28 @@ function init() { // PLAYERS + IORequest("/PLAYER/LYRICS", async (guildId) => { + if(!checkUserGuild(socketUser, guildId)) return + const player = await verifyPlayerAction(guildId) + if(!player) return IOAnswer("/PLAYER/LYRICS", false) + if(!player.queue?.current) { + wlog.warn("Le player de la guilde : " + guildId + " n'a pas de musique en cours") + IOAnswer("/PLAYER/LYRICS", false) + return + } + const song = player.queue.current + const lyricsData = await lyrics.getLyrics(song.title + " " + song.author) + console.log(lyricsData) + if(!lyricsData) { + wlog.warn("Aucune lyrics trouvée pour la musique : " + song.title + " de l'artiste : " + song.author) + IOAnswer("/PLAYER/LYRICS", false) + return + } + IOAnswer("/PLAYER/LYRICS", lyricsData) + }) + + + //CHECKED : 03/05/2025 IORequest("/PLAYER/PREVIOUS/LIST", (guildId) => { if(!checkUserGuild(socketUser, guildId)) return @@ -316,9 +349,9 @@ function init() { }); // CHECKED : 04/05/2025 - IORequest("/QUEUE/PLAY/NOW", (data) => { + IORequest("/QUEUE/PLAY", (data) => { if(!data) return IOAnswer("/QUEUE/PLAY/NOW", false) - const {guildId, index, listType} = data + const {guildId, index, listType, now} = data if(!index) return IOAnswer("/QUEUE/PLAY/NOW", false) if(!guildId) return IOAnswer("/QUEUE/PLAY/NOW", false) if(!listType) return IOAnswer("/QUEUE/PLAY/NOW", false) @@ -335,7 +368,12 @@ function init() { } if(!song) return IOAnswer("/QUEUE/PLAY/NOW", false) if(listType == "next") player.queue.removeNextByIndex(index) - player.play(song) + if(now) { + player.play(song) + } else { + player.add(song) + } + history.addToPersonalHistory(socketUser.identity.id, song) IOAnswer("/QUEUE/PLAY/NOW", true) }) @@ -395,6 +433,7 @@ function init() { } else { player.add(song) } + history.addToPersonalHistory(socketUser.identity.id, song) IOAnswer("/SEARCH/PLAY", true) }) @@ -412,9 +451,72 @@ function init() { IOAnswer("/SEARCH/PLAYLIST", true) }) - + IORequest("/SEARCH/LYRICS", async (name) => { + if(!name) return IOAnswer("/SEARCH/LYRICS", false) + const lyricsData = await lyrics.getLyrics(name) + if(!lyricsData) return IOAnswer("/SEARCH/LYRICS", false) + IOAnswer("/SEARCH/LYRICS", lyricsData) + }) + + // UPLOAD + + // CHECKED : 29/05/2025 + + IORequest("/UPLOAD/FILE", async (data) => { + if(!data) return IOAnswer("/UPLOAD/FILE", false) + if(!data.name) return IOAnswer("/UPLOAD/FILE", false) + const file = data.file + // Check wav or mp3 + if(isAudioFile(file) == false) { + wlog.warn("Le fichier envoyé n'est pas un fichier audio valide (MP3/WAV)") + return IOAnswer("/UPLOAD/FILE", false) + } + const url = await mediaBase.postMedia(data) + if(!url) return IOAnswer("/UPLOAD/FILE", false) + IOAnswer("/UPLOAD/FILE", {"url": url, "name": data.name}) + }) + + // CHECKED : 29/05/2025 + IORequest("/UPLOAD/FILE/GET_SONG", async (data) => { + if(!data) return IOAnswer("/UPLOAD/FILE/GET_SONG", false) + const {name, url} = data + if(!url) return IOAnswer("/UPLOAD/FILE/GET_SONG", false) + if(!name) return IOAnswer("/UPLOAD/FILE/GET_SONG", false) + const song = new Song() + if(!song) return IOAnswer("/UPLOAD/FILE/GET_SONG", false) + await getMediaInformationFromUrl(song, url) + song.type = "attachment" + song.author = socketUser.identity.username + song.authorId = socketUser.identity.id + song.title = name + song.url = url + IOAnswer("/UPLOAD/FILE/GET_SONG", song) + }) + + // GOOGLE API + + IORequest("/GOOGLE/AUTH", () => { + IOAnswer("/GOOGLE/AUTH", googleApis.createAuthUrl(socketUser.identity.id)) + }) + + IORequest("/GOOGLE/YOUTUBE/ADD_PLAYLIST", async (code) => { + if(!code) { + IOAnswer("/GOOGLE/YOUTUBE/ADD_PLAYLIST", false) + } + const token = await googleApis.getAuthorization(socketUser.identity.id, code) + if(!token) { + IOAnswer("/GOOGLE/YOUTUBE/ADD_PLAYLIST", false) + return + } + playlists.processYoutubeData(socketUser.identity.id, await youtubeApi.getYoutubePlaylists(socketUser.identity.id)) + IOAnswer("/GOOGLE/YOUTUBE/ADD_PLAYLIST", true) + }) + + // PLAYLISTS + + // CHECKED : 30/04/2025 IORequest("/PLAYLISTS/CREATE", async (data) => { if(!data) return IOAnswer("/PLAYLISTS/CREATE", false) diff --git a/backend/src/utils/AudioBufferCheck.js b/backend/src/utils/AudioBufferCheck.js new file mode 100644 index 0000000..cf4a593 --- /dev/null +++ b/backend/src/utils/AudioBufferCheck.js @@ -0,0 +1,31 @@ +function isAudioFile(buffer) { + // Ensure the buffer is long enough to contain the magic number + if (!Buffer.isBuffer(buffer)) { + throw new TypeError('Expected a Buffer'); + } + if (buffer.length < 2) { + return false; + } + + // Convert the first few bytes to a hex string + const signature = buffer.subarray(0, 4).toString('hex').toUpperCase(); + + // Audio file signatures + const audioSignatures = [ + '494433', // ID3 tag for MP3 + 'FFFB', // Another possible MP3 signature + 'FFF3', // Another possible MP3 signature + 'FFF2', // Another possible MP3 signature + '52494646', // RIFF header for WAV + '4F676753', // OGG container + '66747970', // MP4 container + // Add more audio file signatures as needed + ]; + + // Check if the signature matches any known audio file type + return audioSignatures.some(magicNumber => signature.startsWith(magicNumber)); +} + +module.exports = { + isAudioFile +}; \ No newline at end of file diff --git a/backend/src/utils/Database/Configuration.js b/backend/src/utils/Database/Configuration.js index 581b631..9933a8e 100644 --- a/backend/src/utils/Database/Configuration.js +++ b/backend/src/utils/Database/Configuration.js @@ -14,7 +14,10 @@ const config = new Database("config", __glob.DATA + path.sep + "config.json", { contact : "" }, api: { - youtube: "", + youtube: { + clientId : "" , + clientSecret: "" + }, spotify: { clientId: "", clientSecret: "" @@ -22,6 +25,10 @@ const config = new Database("config", __glob.DATA + path.sep + "config.json", { }, website: "", server_port: 5000, + media: { + guildId: "", + channelId: "", + } }) function getToken() { @@ -36,8 +43,12 @@ function getReportContact() { return config.data.report.contact } -function getYoutubeApiKey() { - return config.data.api.youtube +function getYoutubeApiClientId() { + return config.data.api.youtube.clientId +} + +function getYoutubeApiClientSecret() { + return config.data.api.youtube.clientSecret } function getSpotifyClientId() { @@ -61,9 +72,31 @@ function getClientSecret() { return config.data.client_secret } +function getMediaGuildId() { + return config.data.media.guildId +} + +function getMediaChannelId() { + return config.data.media.channelId +} + if(getToken() == "") { clog.error("Impossible de démarrer sans token valide") process.exit(1) } -module.exports = {getToken, getClientSecret, getReportChannel, getReportContact, getYoutubeApiKey, getSpotifyClientId, getSpotifyClientSecret, getWebsiteLink, getPort} \ No newline at end of file +module.exports = { + getToken, + getClientSecret, + getReportChannel, + getReportContact, + getYoutubeApiClientId, + getYoutubeApiClientSecret, + getSpotifyClientId, + getSpotifyClientSecret, + getWebsiteLink, + getPort, + getMediaGuildId, + getMediaChannelId + +} \ No newline at end of file diff --git a/backend/src/utils/GlobalVars.js b/backend/src/utils/GlobalVars.js index 9fa55de..4a4b5f1 100644 --- a/backend/src/utils/GlobalVars.js +++ b/backend/src/utils/GlobalVars.js @@ -12,6 +12,8 @@ const __glob = { PREVIOUSFILE: root + path.sep + "data" + path.sep + "previous.json", USERFILE: root + path.sep + "data" + path.sep + "users.json", PLAYLISTFILE: root + path.sep + "data" + path.sep + "playlists.json", + HISTORY_DB: root + path.sep + "data" + path.sep + "history.json", + MEDIA_DB: root + path.sep + "data" + path.sep + "media.json", } module.exports = {__glob} \ No newline at end of file