Version 1.1.0 - BACKEND - Release STABLE - Ajout de l'historique, de l'ajout de playlist privé, lecture de fichier média

This commit is contained in:
2025-05-30 11:37:55 +02:00
parent e686edb0e6
commit a59d7a66db
23 changed files with 1021 additions and 274 deletions

View File

@@ -1,6 +1,3 @@
# List # List
TODO: Lecture de fichiers depuis le site, nécéssite hébergement.
TODO: Récupération des recommendations, playlists Youtube et Spotify TODO: Récupération des recommendations, playlists Youtube et Spotify
TODO: Acces à un historique personnel (LOCAL ?)
TODO: Faire un systême de parole.

View File

@@ -1,16 +1,16 @@
{ {
"name": "chopin-backend", "name": "chopin-backend",
"version": "1.0.1", "version": "1.1.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "chopin-backend", "name": "chopin-backend",
"version": "1.0.1", "version": "1.1.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@discordjs/voice": "^0.18.0", "@discordjs/voice": "^0.18.0",
"@distube/ytdl-core": "^4.16.9", "@distube/ytdl-core": "^4.16.10",
"@distube/ytsr": "2.0.4", "@distube/ytsr": "2.0.4",
"cors": "^2.8.5", "cors": "^2.8.5",
"discord-player": "^7.1.0", "discord-player": "^7.1.0",
@@ -20,8 +20,10 @@
"ffprobe": "^1.1.2", "ffprobe": "^1.1.2",
"ffprobe-static": "^3.1.0", "ffprobe-static": "^3.1.0",
"fluent-ffmpeg": "^2.1.3", "fluent-ffmpeg": "^2.1.3",
"googleapis": "^149.0.0",
"libsodium-wrappers": "^0.7.15", "libsodium-wrappers": "^0.7.15",
"loguix": "^1.4.2", "loguix": "^1.4.2",
"mime-types": "^3.0.1",
"nodemon": "^3.1.9", "nodemon": "^3.1.9",
"pm2": "^5.4.3", "pm2": "^5.4.3",
"socket.io": "^4.8.1", "socket.io": "^4.8.1",
@@ -267,9 +269,9 @@
} }
}, },
"node_modules/@distube/ytdl-core": { "node_modules/@distube/ytdl-core": {
"version": "4.16.9", "version": "4.16.10",
"resolved": "https://registry.npmjs.org/@distube/ytdl-core/-/ytdl-core-4.16.9.tgz", "resolved": "https://registry.npmjs.org/@distube/ytdl-core/-/ytdl-core-4.16.10.tgz",
"integrity": "sha512-eRYM3lDR1/1ZB+k6jzIdR+8m9VsYEqjz9+DstX1S/aW1f2rlbj22WCdrRbE+sE3DJW8DLJEp69akfjWqQ+nKIw==", "integrity": "sha512-KFKZtNlynOO0PYxelUF5h2bKyAU1d8fe6aZmo+gxWt7H2MQbd0bUeyV4y9iWhI57nukjkSXXQGB625CfhrVdGQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"http-cookie-agent": "^7.0.1", "http-cookie-agent": "^7.0.1",
@@ -1482,6 +1484,27 @@
"node": ">= 0.6" "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": { "node_modules/agent-base": {
"version": "7.1.3", "version": "7.1.3",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
@@ -1623,6 +1646,27 @@
"node": ">= 6" "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": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -1667,6 +1711,15 @@
"node": ">=10.0.0" "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": { "node_modules/binary-extensions": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -1785,6 +1838,12 @@
"ieee754": "^1.1.13" "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": { "node_modules/buffer-from": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -2341,6 +2400,15 @@
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"license": "MIT" "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": { "node_modules/ee-first": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -2693,6 +2761,12 @@
"url": "https://opencollective.com/express" "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": { "node_modules/extrareqp2": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/extrareqp2/-/extrareqp2-1.0.0.tgz", "resolved": "https://registry.npmjs.org/extrareqp2/-/extrareqp2-1.0.0.tgz",
@@ -2972,6 +3046,27 @@
"node": ">= 6" "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": { "node_modules/formdata-polyfill": {
"version": "4.0.10", "version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
@@ -3036,6 +3131,69 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/get-intrinsic": {
"version": "1.2.7", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz",
@@ -3178,6 +3336,75 @@
"url": "https://github.com/sponsors/isaacs" "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": { "node_modules/gopd": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -3190,6 +3417,19 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/has-flag": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@@ -3502,6 +3742,18 @@
"node": ">=0.12.0" "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": { "node_modules/isexe": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -3573,6 +3825,15 @@
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
"license": "MIT" "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": { "node_modules/json-stringify-safe": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
@@ -3605,6 +3866,27 @@
"node": "*" "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": { "node_modules/lazy": {
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/lazy/-/lazy-1.0.11.tgz", "resolved": "https://registry.npmjs.org/lazy/-/lazy-1.0.11.tgz",
@@ -4054,21 +4336,21 @@
} }
}, },
"node_modules/mime-db": { "node_modules/mime-db": {
"version": "1.52.0", "version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/mime-types": { "node_modules/mime-types": {
"version": "2.1.35", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"mime-db": "1.52.0" "mime-db": "^1.54.0"
}, },
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@@ -6130,8 +6412,7 @@
"version": "0.0.3", "version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/tree-kill": { "node_modules/tree-kill": {
"version": "1.2.2", "version": "1.2.2",
@@ -6341,6 +6622,27 @@
"node": ">= 0.6" "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": { "node_modules/typedarray": {
"version": "0.0.6", "version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
@@ -6400,6 +6702,12 @@
"node": ">= 0.8" "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": { "node_modules/util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -6475,8 +6783,7 @@
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause", "license": "BSD-2-Clause"
"peer": true
}, },
"node_modules/webmetrik": { "node_modules/webmetrik": {
"version": "0.1.4", "version": "0.1.4",
@@ -6489,7 +6796,6 @@
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"tr46": "~0.0.3", "tr46": "~0.0.3",
"webidl-conversions": "^3.0.0" "webidl-conversions": "^3.0.0"

View File

@@ -1,6 +1,6 @@
{ {
"name": "chopin-backend", "name": "chopin-backend",
"version": "1.0.2", "version": "1.1.0",
"description": "Discord Bot for music - Fetching everywhere !", "description": "Discord Bot for music - Fetching everywhere !",
"main": "src/main.js", "main": "src/main.js",
"nodemonConfig": { "nodemonConfig": {
@@ -21,7 +21,7 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@discordjs/voice": "^0.18.0", "@discordjs/voice": "^0.18.0",
"@distube/ytdl-core": "^4.16.9", "@distube/ytdl-core": "^4.16.10",
"@distube/ytsr": "2.0.4", "@distube/ytsr": "2.0.4",
"cors": "^2.8.5", "cors": "^2.8.5",
"discord-player": "^7.1.0", "discord-player": "^7.1.0",
@@ -31,8 +31,10 @@
"ffprobe": "^1.1.2", "ffprobe": "^1.1.2",
"ffprobe-static": "^3.1.0", "ffprobe-static": "^3.1.0",
"fluent-ffmpeg": "^2.1.3", "fluent-ffmpeg": "^2.1.3",
"googleapis": "^149.0.0",
"libsodium-wrappers": "^0.7.15", "libsodium-wrappers": "^0.7.15",
"loguix": "^1.4.2", "loguix": "^1.4.2",
"mime-types": "^3.0.1",
"nodemon": "^3.1.9", "nodemon": "^3.1.9",
"pm2": "^5.4.3", "pm2": "^5.4.3",
"socket.io": "^4.8.1", "socket.io": "^4.8.1",

View File

@@ -9,6 +9,7 @@ const { Player } = require("../player/Player")
const {refreshAllUserInformation} = require("../server/auth/User") const {refreshAllUserInformation} = require("../server/auth/User")
const dlog = new LogType("Discord") const dlog = new LogType("Discord")
dlog.log("Initialisation du Bot Discord")
const membersVoices = new Map() const membersVoices = new Map()
const timers = new Map() const timers = new Map()

View File

@@ -2,6 +2,7 @@ const {Command} = require('../Command');
const {Embed, EmbedError} = require('../Embed'); const {Embed, EmbedError} = require('../Embed');
const { Player } = require('../../player/Player'); const { Player } = require('../../player/Player');
const { Song } = require('../../player/Song'); 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) => { 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**') embed.setTitle('**Ajout à liste de lecture**')
} }
history.addToPersonalHistory(interaction.user.id, song)
embed.setDescription('**Titre : **' + song.title) embed.setDescription('**Titre : **' + song.title)
embed.addField('**Durée : **', song.readduration) embed.addField('**Durée : **', song.readduration)

View File

@@ -4,6 +4,7 @@ const { Player } = require("../../player/Player");
const Finder = require("../../player/Finder"); const Finder = require("../../player/Finder");
const { Playlist } = require("../../playlists/Playlist"); const { Playlist } = require("../../playlists/Playlist");
const spotify = require("../../media/SpotifyInformation"); 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) => { 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 { } else {
player.add(song) player.add(song)
} }
history.addToPersonalHistory(interaction.user.id, song)
} }

View File

@@ -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,
}

View File

@@ -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 };

View File

@@ -0,0 +1,4 @@
{
"urlSearch": "http://api.deezer.com/search?limit=5&q=",
"urlGet": "https://api.lyrics.ovh/v1/"
}

View File

@@ -5,7 +5,6 @@
*/ */
const { LogType } = require('loguix');
const { __glob } = require("./utils/GlobalVars") const { __glob } = require("./utils/GlobalVars")
require("loguix").setup(__glob.LOGS, __glob.PACKAGEINFO) require("loguix").setup(__glob.LOGS, __glob.PACKAGEINFO)
const config = require("./utils/Database/Configuration") const config = require("./utils/Database/Configuration")
@@ -20,7 +19,7 @@ setup();
async function setup() { async function setup() {
const DiscordBot = require("./discord/Bot") const DiscordBot = require("./discord/Bot")
DiscordBot.init() await DiscordBot.init()
const Server = require("./server/Server") const Server = require("./server/Server")
Server.init() await Server.init()
} }

View File

@@ -29,4 +29,30 @@ async function getMediaInformation(instance, media, provider) {
} }
} }
module.exports = {getMediaInformation} 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};

View File

@@ -2,7 +2,7 @@ const { LogType } = require('loguix');
const clog = new LogType("YoutubeInformation"); const clog = new LogType("YoutubeInformation");
const { Song } = require('../player/Song'); const { Song } = require('../player/Song');
const { Playlist } = require('../playlists/Playlist'); const { Playlist } = require('../playlists/Playlist');
const { getReadableDuration } = require('../utils/TimeConverter'); const { getReadableDuration, getSecondsDuration } = require('../utils/TimeConverter');
const ytsr = require('@distube/ytsr'); const ytsr = require('@distube/ytsr');
const ytfps = require('ytfps'); 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 };

View File

@@ -13,6 +13,7 @@ class Song {
thumbnail = "https://radomisol.fr/wp-content/uploads/2016/08/cropped-note-radomisol-musique.png" ; thumbnail = "https://radomisol.fr/wp-content/uploads/2016/08/cropped-note-radomisol-musique.png" ;
duration; duration;
readduration; readduration;
form = "SONG";
type; type;
userAddedId; userAddedId;

View File

@@ -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'];

View File

@@ -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
};

View File

@@ -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<Object>}
* @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
};

View File

@@ -11,6 +11,7 @@ class Playlist {
duration = 0; duration = 0;
readduration; readduration;
description; description;
form = "PLAYLIST";
type; type;
constructor(title, url, author, authorId, songs, thumbnail, duration, readduration, description) { constructor(title, url, author, authorId, songs, thumbnail, duration, readduration, description) {
this.title = title; this.title = title;

View File

@@ -6,6 +6,10 @@ const {LogType} = require('loguix');
const clog = new LogType("PlaylistManager"); const clog = new LogType("PlaylistManager");
const Finder = require('../player/Finder'); const Finder = require('../player/Finder');
const spotify = require('../media/SpotifyInformation'); 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, {}); 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}`); 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 = { module.exports = {
getPlaylistsOfUser, getPlaylistsOfUser,
getPlaylistOfUser, getPlaylistOfUser,
@@ -191,5 +273,6 @@ module.exports = {
copyPlaylist, copyPlaylist,
renamePlaylist, renamePlaylist,
addSong, addSong,
removeSong removeSong,
processYoutubeData
} }

View File

@@ -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` sil 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 lauthentification (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 lidentité 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 lutilisateur.
- **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 dattente.
- **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 dune 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"]
```

View File

@@ -15,10 +15,18 @@ const Finder = require("../player/Finder")
const fs = require("fs") const fs = require("fs")
const {__glob} = require("../utils/GlobalVars") const {__glob} = require("../utils/GlobalVars")
const playlists = require("../playlists/PlaylistManager") 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 configuration = require("../utils/Database/Configuration")
const { List } = require('../player/List') const { List } = require('../player/List')
const { restart } = require('../utils/Maintenance') 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 allConnectedUsers = new Array()
const guildConnectedUsers = new Map() const guildConnectedUsers = new Map()
@@ -205,18 +213,21 @@ function init() {
identity: socketUser.identity, identity: socketUser.identity,
guilds: guildPresents, guilds: guildPresents,
labels: socketUser.labels, labels: socketUser.labels,
history: history.getPersonalHistory(socketUser.identity.id),
}) })
wlog.log("Envoi des informations Discord de '" + socketUser.identity.id + "' à '" + socket.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 //CHECKED : 24/04/2025
IORequest("/USER/SIGNOUT", () => { IORequest("/USER/SIGNOUT", () => {
socketUser.removeToken(token) socketUser.removeToken(token)
socket.disconnect() socket.disconnect()
}) })
// CHECKED : 24/04/2025 // CHECKED : 24/04/2025
IORequest("/USERS/LIST", (guildId) => { IORequest("/USERS/LIST", (guildId) => {
if(!checkUserGuild(socketUser, guildId)) return if(!checkUserGuild(socketUser, guildId)) return
@@ -226,6 +237,28 @@ function init() {
// PLAYERS // 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 //CHECKED : 03/05/2025
IORequest("/PLAYER/PREVIOUS/LIST", (guildId) => { IORequest("/PLAYER/PREVIOUS/LIST", (guildId) => {
if(!checkUserGuild(socketUser, guildId)) return if(!checkUserGuild(socketUser, guildId)) return
@@ -316,9 +349,9 @@ function init() {
}); });
// CHECKED : 04/05/2025 // CHECKED : 04/05/2025
IORequest("/QUEUE/PLAY/NOW", (data) => { IORequest("/QUEUE/PLAY", (data) => {
if(!data) return IOAnswer("/QUEUE/PLAY/NOW", false) 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(!index) return IOAnswer("/QUEUE/PLAY/NOW", false)
if(!guildId) return IOAnswer("/QUEUE/PLAY/NOW", false) if(!guildId) return IOAnswer("/QUEUE/PLAY/NOW", false)
if(!listType) 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(!song) return IOAnswer("/QUEUE/PLAY/NOW", false)
if(listType == "next") player.queue.removeNextByIndex(index) 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) IOAnswer("/QUEUE/PLAY/NOW", true)
}) })
@@ -395,6 +433,7 @@ function init() {
} else { } else {
player.add(song) player.add(song)
} }
history.addToPersonalHistory(socketUser.identity.id, song)
IOAnswer("/SEARCH/PLAY", true) IOAnswer("/SEARCH/PLAY", true)
}) })
@@ -412,9 +451,72 @@ function init() {
IOAnswer("/SEARCH/PLAYLIST", true) 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 // PLAYLISTS
// CHECKED : 30/04/2025 // CHECKED : 30/04/2025
IORequest("/PLAYLISTS/CREATE", async (data) => { IORequest("/PLAYLISTS/CREATE", async (data) => {
if(!data) return IOAnswer("/PLAYLISTS/CREATE", false) if(!data) return IOAnswer("/PLAYLISTS/CREATE", false)

View File

@@ -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
};

View File

@@ -14,7 +14,10 @@ const config = new Database("config", __glob.DATA + path.sep + "config.json", {
contact : "" contact : ""
}, },
api: { api: {
youtube: "", youtube: {
clientId : "" ,
clientSecret: ""
},
spotify: { spotify: {
clientId: "", clientId: "",
clientSecret: "" clientSecret: ""
@@ -22,6 +25,10 @@ const config = new Database("config", __glob.DATA + path.sep + "config.json", {
}, },
website: "", website: "",
server_port: 5000, server_port: 5000,
media: {
guildId: "",
channelId: "",
}
}) })
function getToken() { function getToken() {
@@ -36,8 +43,12 @@ function getReportContact() {
return config.data.report.contact return config.data.report.contact
} }
function getYoutubeApiKey() { function getYoutubeApiClientId() {
return config.data.api.youtube return config.data.api.youtube.clientId
}
function getYoutubeApiClientSecret() {
return config.data.api.youtube.clientSecret
} }
function getSpotifyClientId() { function getSpotifyClientId() {
@@ -61,9 +72,31 @@ function getClientSecret() {
return config.data.client_secret return config.data.client_secret
} }
function getMediaGuildId() {
return config.data.media.guildId
}
function getMediaChannelId() {
return config.data.media.channelId
}
if(getToken() == "") { if(getToken() == "") {
clog.error("Impossible de démarrer sans token valide") clog.error("Impossible de démarrer sans token valide")
process.exit(1) process.exit(1)
} }
module.exports = {getToken, getClientSecret, getReportChannel, getReportContact, getYoutubeApiKey, getSpotifyClientId, getSpotifyClientSecret, getWebsiteLink, getPort} module.exports = {
getToken,
getClientSecret,
getReportChannel,
getReportContact,
getYoutubeApiClientId,
getYoutubeApiClientSecret,
getSpotifyClientId,
getSpotifyClientSecret,
getWebsiteLink,
getPort,
getMediaGuildId,
getMediaChannelId
}

View File

@@ -12,6 +12,8 @@ const __glob = {
PREVIOUSFILE: root + path.sep + "data" + path.sep + "previous.json", PREVIOUSFILE: root + path.sep + "data" + path.sep + "previous.json",
USERFILE: root + path.sep + "data" + path.sep + "users.json", USERFILE: root + path.sep + "data" + path.sep + "users.json",
PLAYLISTFILE: root + path.sep + "data" + path.sep + "playlists.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} module.exports = {__glob}