From ed5ef9d1507ec38989cadbcc302472275ade8fe0 Mon Sep 17 00:00:00 2001 From: AlexInCube Date: Fri, 23 Aug 2024 13:11:18 +0300 Subject: [PATCH] 3.7.0-dev-6 Refactored pagination. Added pagination to /pl-display. Added playlist name and songs count in /pl-display. Added info about /pl-* commands to wiki. --- package.json | 4 +- pnpm-lock.yaml | 210 +++++++++++------------ src/CommandTypes.ts | 4 +- src/audioplayer/AudioPlayersManager.ts | 35 +--- src/audioplayer/PaginationList.ts | 38 ++++ src/commands/audio/history.command.ts | 59 +------ src/commands/audio/pl-add.command.ts | 4 +- src/commands/audio/pl-create.command.ts | 4 +- src/commands/audio/pl-delete.command.ts | 4 +- src/commands/audio/pl-display.command.ts | 48 ++++-- src/commands/audio/pl-my.command.ts | 4 +- src/commands/audio/pl-play.command.ts | 4 +- src/commands/audio/pl-remove.command.ts | 4 +- src/commands/audio/play.command.ts | 4 +- src/schemas/SchemaPlaylist.ts | 2 +- src/utilities/commandEmptyReply.ts | 4 +- wiki/Commands.md | 44 +++++ 17 files changed, 255 insertions(+), 221 deletions(-) create mode 100644 src/audioplayer/PaginationList.ts diff --git a/package.json b/package.json index 04ea5de..9e46319 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aicbot", - "version": "3.7.0-dev", + "version": "3.7.0", "description": "Discord Bot for playing music", "main": "build/main.js", "scripts": { @@ -61,7 +61,7 @@ "@typescript-eslint/eslint-plugin": "^8.0.1", "@typescript-eslint/parser": "^8.0.1", "@eslint/js": "^9.9.0", - "typescript-eslint": "7.18.0", + "typescript-eslint": "^8.2.0", "eslint": "^9.9.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0188dae..4596eeb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -142,8 +142,8 @@ importers: specifier: ^5.5.4 version: 5.5.4 typescript-eslint: - specifier: 7.18.0 - version: 7.18.0(eslint@9.9.0)(typescript@5.5.4) + specifier: 8.2.0 + version: 8.2.0(eslint@9.9.0)(typescript@5.5.4) packages: @@ -348,19 +348,19 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@typescript-eslint/eslint-plugin@7.18.0': - resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/eslint-plugin@8.0.1': + resolution: {integrity: sha512-5g3Y7GDFsJAnY4Yhvk8sZtFfV6YNF2caLzjrRPUBzewjPCaj0yokePB4LJSobyCzGMzjZZYFbwuzbfDHlimXbQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^7.0.0 - eslint: ^8.56.0 + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 typescript: '*' peerDependenciesMeta: typescript: optional: true - '@typescript-eslint/eslint-plugin@8.0.1': - resolution: {integrity: sha512-5g3Y7GDFsJAnY4Yhvk8sZtFfV6YNF2caLzjrRPUBzewjPCaj0yokePB4LJSobyCzGMzjZZYFbwuzbfDHlimXbQ==} + '@typescript-eslint/eslint-plugin@8.2.0': + resolution: {integrity: sha512-02tJIs655em7fvt9gps/+4k4OsKULYGtLBPJfOsmOq1+3cdClYiF0+d6mHu6qDnTcg88wJBkcPLpQhq7FyDz0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 @@ -370,18 +370,18 @@ packages: typescript: optional: true - '@typescript-eslint/parser@7.18.0': - resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/parser@8.0.1': + resolution: {integrity: sha512-5IgYJ9EO/12pOUwiBKFkpU7rS3IU21mtXzB81TNwq2xEybcmAZrE9qwDtsb5uQd9aVO9o0fdabFyAmKveXyujg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 + eslint: ^8.57.0 || ^9.0.0 typescript: '*' peerDependenciesMeta: typescript: optional: true - '@typescript-eslint/parser@8.0.1': - resolution: {integrity: sha512-5IgYJ9EO/12pOUwiBKFkpU7rS3IU21mtXzB81TNwq2xEybcmAZrE9qwDtsb5uQd9aVO9o0fdabFyAmKveXyujg==} + '@typescript-eslint/parser@8.2.0': + resolution: {integrity: sha512-j3Di+o0lHgPrb7FxL3fdEy6LJ/j2NE8u+AP/5cQ9SKb+JLH6V6UHDqJ+e0hXBkHP1wn1YDFjYCS9LBQsZDlDEg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -390,26 +390,25 @@ packages: typescript: optional: true - '@typescript-eslint/scope-manager@7.18.0': - resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} - engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/scope-manager@8.0.1': resolution: {integrity: sha512-NpixInP5dm7uukMiRyiHjRKkom5RIFA4dfiHvalanD2cF0CLUuQqxfg8PtEUo9yqJI2bBhF+pcSafqnG3UBnRQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@7.18.0': - resolution: {integrity: sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/scope-manager@8.2.0': + resolution: {integrity: sha512-OFn80B38yD6WwpoHU2Tz/fTz7CgFqInllBoC3WP+/jLbTb4gGPTy9HBSTsbDWkMdN55XlVU0mMDYAtgvlUspGw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@8.0.1': + resolution: {integrity: sha512-+/UT25MWvXeDX9YaHv1IS6KI1fiuTto43WprE7pgSMswHbn1Jm9GEM4Txp+X74ifOWV8emu2AWcbLhpJAvD5Ng==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 typescript: '*' peerDependenciesMeta: typescript: optional: true - '@typescript-eslint/type-utils@8.0.1': - resolution: {integrity: sha512-+/UT25MWvXeDX9YaHv1IS6KI1fiuTto43WprE7pgSMswHbn1Jm9GEM4Txp+X74ifOWV8emu2AWcbLhpJAvD5Ng==} + '@typescript-eslint/type-utils@8.2.0': + resolution: {integrity: sha512-g1CfXGFMQdT5S+0PSO0fvGXUaiSkl73U1n9LTK5aRAFnPlJ8dLKkXr4AaLFvPedW8lVDoMgLLE3JN98ZZfsj0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -417,25 +416,25 @@ packages: typescript: optional: true - '@typescript-eslint/types@7.18.0': - resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} - engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/types@8.0.1': resolution: {integrity: sha512-PpqTVT3yCA/bIgJ12czBuE3iBlM3g4inRSC5J0QOdQFAn07TYrYEQBBKgXH1lQpglup+Zy6c1fxuwTk4MTNKIw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@7.18.0': - resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/types@8.2.0': + resolution: {integrity: sha512-6a9QSK396YqmiBKPkJtxsgZZZVjYQ6wQ/TlI0C65z7vInaETuC6HAHD98AGLC8DyIPqHytvNuS8bBVvNLKyqvQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.0.1': + resolution: {integrity: sha512-8V9hriRvZQXPWU3bbiUV4Epo7EvgM6RTs+sUmxp5G//dBGy402S7Fx0W0QkB2fb4obCF8SInoUzvTYtc3bkb5w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true - '@typescript-eslint/typescript-estree@8.0.1': - resolution: {integrity: sha512-8V9hriRvZQXPWU3bbiUV4Epo7EvgM6RTs+sUmxp5G//dBGy402S7Fx0W0QkB2fb4obCF8SInoUzvTYtc3bkb5w==} + '@typescript-eslint/typescript-estree@8.2.0': + resolution: {integrity: sha512-kiG4EDUT4dImplOsbh47B1QnNmXSoUqOjWDvCJw/o8LgfD0yr7k2uy54D5Wm0j4t71Ge1NkynGhpWdS0dEIAUA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -443,26 +442,26 @@ packages: typescript: optional: true - '@typescript-eslint/utils@7.18.0': - resolution: {integrity: sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - '@typescript-eslint/utils@8.0.1': resolution: {integrity: sha512-CBFR0G0sCt0+fzfnKaciu9IBsKvEKYwN9UZ+eeogK1fYHg4Qxk1yf/wLQkLXlq8wbU2dFlgAesxt8Gi76E8RTA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - '@typescript-eslint/visitor-keys@7.18.0': - resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/utils@8.2.0': + resolution: {integrity: sha512-O46eaYKDlV3TvAVDNcoDzd5N550ckSe8G4phko++OCSC1dYIb9LTc3HDGYdWqWIAT5qDUKphO6sd9RrpIJJPfg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 '@typescript-eslint/visitor-keys@8.0.1': resolution: {integrity: sha512-W5E+o0UfUcK5EgchLZsyVWqARmsM7v54/qEq6PY3YI5arkgmCzHiuk0zKSJJbm71V0xdRna4BGomkCTXz2/LkQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.2.0': + resolution: {integrity: sha512-sbgsPMW9yLvS7IhCi8IpuK1oBmtbWUNP+hBdwl/I9nzqVsszGnNGti5r9dUtF5RLivHUFFIdRvLiTsPhzSyJ3Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vladfrangu/async_event_emitter@2.4.5': resolution: {integrity: sha512-J7T3gUr3Wz0l7Ni1f9upgBZ7+J22/Q1B7dl0X6fG+fTsD+H+31DIosMHj4Um1dWQwqbcQ3oQf+YS2foYkDc9cQ==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} @@ -1728,11 +1727,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typescript-eslint@7.18.0: - resolution: {integrity: sha512-PonBkP603E3tt05lDkbOMyaxJjvKqQrXsnow72sVeOFINDE/qNmnnd+f9b4N+U7W6MXnnYyrhtmF2t08QWwUbA==} - engines: {node: ^18.18.0 || >=20.0.0} + typescript-eslint@8.2.0: + resolution: {integrity: sha512-DmnqaPcML0xYwUzgNbM1XaKXpEb7BShYf2P1tkUmmcl8hyeG7Pj08Er7R9bNy6AufabywzJcOybQAtnD/c9DGw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 typescript: '*' peerDependenciesMeta: typescript: @@ -2127,24 +2125,6 @@ snapshots: '@types/node': 22.2.0 optional: true - '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.9.0)(typescript@5.5.4))(eslint@9.9.0)(typescript@5.5.4)': - dependencies: - '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 7.18.0(eslint@9.9.0)(typescript@5.5.4) - '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/type-utils': 7.18.0(eslint@9.9.0)(typescript@5.5.4) - '@typescript-eslint/utils': 7.18.0(eslint@9.9.0)(typescript@5.5.4) - '@typescript-eslint/visitor-keys': 7.18.0 - eslint: 9.9.0 - graphemer: 1.4.0 - ignore: 5.3.1 - natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.5.4) - optionalDependencies: - typescript: 5.5.4 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/eslint-plugin@8.0.1(@typescript-eslint/parser@8.0.1(eslint@9.9.0)(typescript@5.5.4))(eslint@9.9.0)(typescript@5.5.4)': dependencies: '@eslint-community/regexpp': 4.11.0 @@ -2163,14 +2143,19 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.18.0(eslint@9.9.0)(typescript@5.5.4)': + '@typescript-eslint/eslint-plugin@8.2.0(@typescript-eslint/parser@8.2.0(eslint@9.9.0)(typescript@5.5.4))(eslint@9.9.0)(typescript@5.5.4)': dependencies: - '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4) - '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.3.6 + '@eslint-community/regexpp': 4.11.0 + '@typescript-eslint/parser': 8.2.0(eslint@9.9.0)(typescript@5.5.4) + '@typescript-eslint/scope-manager': 8.2.0 + '@typescript-eslint/type-utils': 8.2.0(eslint@9.9.0)(typescript@5.5.4) + '@typescript-eslint/utils': 8.2.0(eslint@9.9.0)(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 8.2.0 eslint: 9.9.0 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + ts-api-utils: 1.3.0(typescript@5.5.4) optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: @@ -2189,32 +2174,45 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@7.18.0': + '@typescript-eslint/parser@8.2.0(eslint@9.9.0)(typescript@5.5.4)': dependencies: - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/visitor-keys': 7.18.0 + '@typescript-eslint/scope-manager': 8.2.0 + '@typescript-eslint/types': 8.2.0 + '@typescript-eslint/typescript-estree': 8.2.0(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 8.2.0 + debug: 4.3.6 + eslint: 9.9.0 + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color '@typescript-eslint/scope-manager@8.0.1': dependencies: '@typescript-eslint/types': 8.0.1 '@typescript-eslint/visitor-keys': 8.0.1 - '@typescript-eslint/type-utils@7.18.0(eslint@9.9.0)(typescript@5.5.4)': + '@typescript-eslint/scope-manager@8.2.0': + dependencies: + '@typescript-eslint/types': 8.2.0 + '@typescript-eslint/visitor-keys': 8.2.0 + + '@typescript-eslint/type-utils@8.0.1(eslint@9.9.0)(typescript@5.5.4)': dependencies: - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4) - '@typescript-eslint/utils': 7.18.0(eslint@9.9.0)(typescript@5.5.4) + '@typescript-eslint/typescript-estree': 8.0.1(typescript@5.5.4) + '@typescript-eslint/utils': 8.0.1(eslint@9.9.0)(typescript@5.5.4) debug: 4.3.6 - eslint: 9.9.0 ts-api-utils: 1.3.0(typescript@5.5.4) optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: + - eslint - supports-color - '@typescript-eslint/type-utils@8.0.1(eslint@9.9.0)(typescript@5.5.4)': + '@typescript-eslint/type-utils@8.2.0(eslint@9.9.0)(typescript@5.5.4)': dependencies: - '@typescript-eslint/typescript-estree': 8.0.1(typescript@5.5.4) - '@typescript-eslint/utils': 8.0.1(eslint@9.9.0)(typescript@5.5.4) + '@typescript-eslint/typescript-estree': 8.2.0(typescript@5.5.4) + '@typescript-eslint/utils': 8.2.0(eslint@9.9.0)(typescript@5.5.4) debug: 4.3.6 ts-api-utils: 1.3.0(typescript@5.5.4) optionalDependencies: @@ -2223,14 +2221,14 @@ snapshots: - eslint - supports-color - '@typescript-eslint/types@7.18.0': {} - '@typescript-eslint/types@8.0.1': {} - '@typescript-eslint/typescript-estree@7.18.0(typescript@5.5.4)': + '@typescript-eslint/types@8.2.0': {} + + '@typescript-eslint/typescript-estree@8.0.1(typescript@5.5.4)': dependencies: - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/visitor-keys': 7.18.0 + '@typescript-eslint/types': 8.0.1 + '@typescript-eslint/visitor-keys': 8.0.1 debug: 4.3.6 globby: 11.1.0 is-glob: 4.0.3 @@ -2242,10 +2240,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.0.1(typescript@5.5.4)': + '@typescript-eslint/typescript-estree@8.2.0(typescript@5.5.4)': dependencies: - '@typescript-eslint/types': 8.0.1 - '@typescript-eslint/visitor-keys': 8.0.1 + '@typescript-eslint/types': 8.2.0 + '@typescript-eslint/visitor-keys': 8.2.0 debug: 4.3.6 globby: 11.1.0 is-glob: 4.0.3 @@ -2257,36 +2255,36 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@7.18.0(eslint@9.9.0)(typescript@5.5.4)': + '@typescript-eslint/utils@8.0.1(eslint@9.9.0)(typescript@5.5.4)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.0) - '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4) + '@typescript-eslint/scope-manager': 8.0.1 + '@typescript-eslint/types': 8.0.1 + '@typescript-eslint/typescript-estree': 8.0.1(typescript@5.5.4) eslint: 9.9.0 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/utils@8.0.1(eslint@9.9.0)(typescript@5.5.4)': + '@typescript-eslint/utils@8.2.0(eslint@9.9.0)(typescript@5.5.4)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@9.9.0) - '@typescript-eslint/scope-manager': 8.0.1 - '@typescript-eslint/types': 8.0.1 - '@typescript-eslint/typescript-estree': 8.0.1(typescript@5.5.4) + '@typescript-eslint/scope-manager': 8.2.0 + '@typescript-eslint/types': 8.2.0 + '@typescript-eslint/typescript-estree': 8.2.0(typescript@5.5.4) eslint: 9.9.0 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/visitor-keys@7.18.0': + '@typescript-eslint/visitor-keys@8.0.1': dependencies: - '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/types': 8.0.1 eslint-visitor-keys: 3.4.3 - '@typescript-eslint/visitor-keys@8.0.1': + '@typescript-eslint/visitor-keys@8.2.0': dependencies: - '@typescript-eslint/types': 8.0.1 + '@typescript-eslint/types': 8.2.0 eslint-visitor-keys: 3.4.3 '@vladfrangu/async_event_emitter@2.4.5': {} @@ -3562,15 +3560,15 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript-eslint@7.18.0(eslint@9.9.0)(typescript@5.5.4): + typescript-eslint@8.2.0(eslint@9.9.0)(typescript@5.5.4): dependencies: - '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.9.0)(typescript@5.5.4))(eslint@9.9.0)(typescript@5.5.4) - '@typescript-eslint/parser': 7.18.0(eslint@9.9.0)(typescript@5.5.4) - '@typescript-eslint/utils': 7.18.0(eslint@9.9.0)(typescript@5.5.4) - eslint: 9.9.0 + '@typescript-eslint/eslint-plugin': 8.2.0(@typescript-eslint/parser@8.2.0(eslint@9.9.0)(typescript@5.5.4))(eslint@9.9.0)(typescript@5.5.4) + '@typescript-eslint/parser': 8.2.0(eslint@9.9.0)(typescript@5.5.4) + '@typescript-eslint/utils': 8.2.0(eslint@9.9.0)(typescript@5.5.4) optionalDependencies: typescript: 5.5.4 transitivePeerDependencies: + - eslint - supports-color typescript@5.5.4: {} diff --git a/src/CommandTypes.ts b/src/CommandTypes.ts index 5905033..309f065 100644 --- a/src/CommandTypes.ts +++ b/src/CommandTypes.ts @@ -1,6 +1,7 @@ import { AutocompleteInteraction, ChatInputCommandInteraction, + CommandInteraction, Message, PermissionResolvable, SlashCommandBuilder, @@ -66,4 +67,5 @@ interface IGuildData { voice_with_bot_only?: boolean; // Property enabled only if voice_required is true } -export type ICommandContext = Message | ChatInputCommandInteraction; +export type InteractionReplyContext = ChatInputCommandInteraction | CommandInteraction; +export type ReplyContext = Message | InteractionReplyContext; diff --git a/src/audioplayer/AudioPlayersManager.ts b/src/audioplayer/AudioPlayersManager.ts index 8fd72d1..b5bb938 100644 --- a/src/audioplayer/AudioPlayersManager.ts +++ b/src/audioplayer/AudioPlayersManager.ts @@ -1,7 +1,5 @@ import { DisTube, Events as DistubeEvents, Playlist, PlayOptions, Queue, RepeatMode, Song } from 'distube'; import { AudioPlayersStore } from './AudioPlayersStore.js'; -import { pagination } from '../utilities/pagination/pagination.js'; -import { ButtonStyles, ButtonTypes } from '../utilities/pagination/paginationTypes.js'; import { clamp } from '../utilities/clamp.js'; import { generateErrorEmbed } from '../utilities/generateErrorEmbed.js'; import i18next from 'i18next'; @@ -14,7 +12,6 @@ import { ButtonInteraction, Client, CommandInteraction, - Embed, EmbedBuilder, Guild, Interaction, @@ -26,6 +23,7 @@ import { generateWarningEmbed } from '../utilities/generateWarningEmbed.js'; import { generateLyricsEmbed } from './Lyrics.js'; import { getGuildOptionLeaveOnEmpty, setGuildOptionLeaveOnEmpty } from '../schemas/SchemaGuild.js'; import { addSongToGuildSongsHistory } from '../schemas/SchemaSongsHistory.js'; +import { PaginationList } from './PaginationList.js'; export const loggerPrefixAudioplayer = `Audioplayer`; @@ -276,36 +274,7 @@ export class AudioPlayersManager { arrayEmbeds.push(buildPage(queue, i, entriesPerPage)); } - await pagination({ - embeds: arrayEmbeds as unknown as Embed[], - author: interaction.user, - interaction: interaction as CommandInteraction, - ephemeral: true, - fastSkip: true, - pageTravel: false, - buttons: [ - { - type: ButtonTypes.first, - emoji: '⬅️', - style: ButtonStyles.Secondary - }, - { - type: ButtonTypes.previous, - emoji: '◀️', - style: ButtonStyles.Secondary - }, - { - type: ButtonTypes.next, - emoji: '▶️', - style: ButtonStyles.Secondary - }, - { - type: ButtonTypes.last, - emoji: '➡️', - style: ButtonStyles.Secondary - } - ] - }); + await PaginationList(interaction as CommandInteraction, arrayEmbeds, interaction.user); } async setLeaveOnEmpty(guild: Guild, mode: boolean) { diff --git a/src/audioplayer/PaginationList.ts b/src/audioplayer/PaginationList.ts new file mode 100644 index 0000000..fb51432 --- /dev/null +++ b/src/audioplayer/PaginationList.ts @@ -0,0 +1,38 @@ +import { pagination } from '../utilities/pagination/pagination.js'; +import { Embed, EmbedBuilder, Message, User } from 'discord.js'; +import { ButtonStyles, ButtonTypes } from '../utilities/pagination/paginationTypes.js'; +import { ReplyContext } from '../CommandTypes.js'; + +export async function PaginationList(ctx: ReplyContext, pages: Array, user: User) { + await pagination({ + embeds: pages as unknown as Array, + author: user, + message: ctx instanceof Message ? ctx : undefined, + interaction: ctx instanceof Message ? undefined : ctx, + ephemeral: true, + fastSkip: true, + pageTravel: false, + buttons: [ + { + type: ButtonTypes.first, + emoji: '⬅️', + style: ButtonStyles.Secondary + }, + { + type: ButtonTypes.previous, + emoji: '◀️', + style: ButtonStyles.Secondary + }, + { + type: ButtonTypes.next, + emoji: '▶️', + style: ButtonStyles.Secondary + }, + { + type: ButtonTypes.last, + emoji: '➡️', + style: ButtonStyles.Secondary + } + ] + }); +} diff --git a/src/commands/audio/history.command.ts b/src/commands/audio/history.command.ts index 02f9977..e1ae96d 100644 --- a/src/commands/audio/history.command.ts +++ b/src/commands/audio/history.command.ts @@ -1,19 +1,10 @@ -import { ICommand } from '../../CommandTypes.js'; +import { ReplyContext, ICommand } from '../../CommandTypes.js'; import i18next from 'i18next'; -import { - CommandInteraction, - Embed, - EmbedBuilder, - Guild, - Message, - PermissionsBitField, - SlashCommandBuilder -} from 'discord.js'; +import { EmbedBuilder, Guild, PermissionsBitField, SlashCommandBuilder, User } from 'discord.js'; import { GroupAudio } from './AudioTypes.js'; import { getOrCreateGuildSongsHistory, ISchemaSongsHistory } from '../../schemas/SchemaSongsHistory.js'; -import { pagination } from '../../utilities/pagination/pagination.js'; -import { ButtonStyles, ButtonTypes } from '../../utilities/pagination/paginationTypes.js'; import { ENV } from '../../EnvironmentVariables.js'; +import { PaginationList } from '../../audioplayer/PaginationList.js'; export default function (): ICommand { return { @@ -22,13 +13,13 @@ export default function (): ICommand { name: 'history', description: i18next.t('commands:history_desc'), execute: async (message) => { - await replyWithSongHistory(message.guild as Guild, undefined, message); + await replyWithSongHistory(message.guild as Guild, message, message.author); } }, slash_data: { slash_builder: new SlashCommandBuilder().setName('history').setDescription(i18next.t('commands:history_desc')), execute: async (interaction) => { - await replyWithSongHistory(interaction.guild as Guild, interaction); + await replyWithSongHistory(interaction.guild as Guild, interaction, interaction.user); } }, guild_data: { @@ -39,16 +30,13 @@ export default function (): ICommand { }; } -async function replyWithSongHistory(guild: Guild, interaction?: CommandInteraction, message?: Message): Promise { +async function replyWithSongHistory(guild: Guild, ctx: ReplyContext, user: User): Promise { const history: ISchemaSongsHistory | null = await getOrCreateGuildSongsHistory(guild.id); if (!history) throw Error(`Can't find guild songs history: ${guild.id}`); if (history.songsHistory.length === 0) { - await interaction?.reply({ - embeds: [new EmbedBuilder().setTitle(i18next.t('commands:history_embed_no_songs'))] - }); - await message?.reply({ + await ctx.reply({ embeds: [new EmbedBuilder().setTitle(i18next.t('commands:history_embed_no_songs'))] }); } @@ -79,36 +67,5 @@ async function replyWithSongHistory(guild: Guild, interaction?: CommandInteracti arrayEmbeds.push(buildPage(history, i, entriesPerPage)); } - await pagination({ - embeds: arrayEmbeds as unknown as Embed[], - // @ts-expect-error I need to provide Interaction or Message for different command systems. - author: interaction?.user ?? message?.author, - message: message, - interaction: interaction, - ephemeral: true, - fastSkip: true, - pageTravel: false, - buttons: [ - { - type: ButtonTypes.first, - emoji: '⬅️', - style: ButtonStyles.Secondary - }, - { - type: ButtonTypes.previous, - emoji: '◀️', - style: ButtonStyles.Secondary - }, - { - type: ButtonTypes.next, - emoji: '▶️', - style: ButtonStyles.Secondary - }, - { - type: ButtonTypes.last, - emoji: '➡️', - style: ButtonStyles.Secondary - } - ] - }); + await PaginationList(ctx, arrayEmbeds, user); } diff --git a/src/commands/audio/pl-add.command.ts b/src/commands/audio/pl-add.command.ts index 43bb57b..beb281c 100644 --- a/src/commands/audio/pl-add.command.ts +++ b/src/commands/audio/pl-add.command.ts @@ -1,4 +1,4 @@ -import { CommandArgument, ICommand, ICommandContext } from '../../CommandTypes.js'; +import { CommandArgument, ICommand, ReplyContext } from '../../CommandTypes.js'; import { GroupAudio } from './AudioTypes.js'; import { Message, PermissionsBitField, SlashCommandBuilder, User } from 'discord.js'; import i18next from 'i18next'; @@ -60,7 +60,7 @@ export default function (): ICommand { }; } -async function plAddAndReply(playlistName: string, url: string, ctx: ICommandContext, user: User) { +async function plAddAndReply(playlistName: string, url: string, ctx: ReplyContext, user: User) { try { if (!isValidURL(url)) { await ctx.reply({ diff --git a/src/commands/audio/pl-create.command.ts b/src/commands/audio/pl-create.command.ts index 317ce1e..fd3fbed 100644 --- a/src/commands/audio/pl-create.command.ts +++ b/src/commands/audio/pl-create.command.ts @@ -1,4 +1,4 @@ -import { CommandArgument, ICommand, ICommandContext } from '../../CommandTypes.js'; +import { CommandArgument, ICommand, ReplyContext } from '../../CommandTypes.js'; import { GroupAudio } from './AudioTypes.js'; import { Message, PermissionsBitField, SlashCommandBuilder } from 'discord.js'; import i18next from 'i18next'; @@ -49,7 +49,7 @@ export default function (): ICommand { }; } -async function plCreateAndReply(playlistName: string, ctx: ICommandContext, userID: string) { +async function plCreateAndReply(playlistName: string, ctx: ReplyContext, userID: string) { try { await UserPlaylistCreate(userID, playlistName); diff --git a/src/commands/audio/pl-delete.command.ts b/src/commands/audio/pl-delete.command.ts index 69f4b2c..6e3c052 100644 --- a/src/commands/audio/pl-delete.command.ts +++ b/src/commands/audio/pl-delete.command.ts @@ -1,4 +1,4 @@ -import { CommandArgument, ICommand, ICommandContext } from '../../CommandTypes.js'; +import { CommandArgument, ICommand, ReplyContext } from '../../CommandTypes.js'; import { GroupAudio } from './AudioTypes.js'; import { Message, PermissionsBitField, SlashCommandBuilder } from 'discord.js'; import i18next from 'i18next'; @@ -47,7 +47,7 @@ export default function (): ICommand { }; } -async function plDeleteAndReply(ctx: ICommandContext, userID: string, playlistName: string) { +async function plDeleteAndReply(ctx: ReplyContext, userID: string, playlistName: string) { try { await UserPlaylistDelete(userID, playlistName); diff --git a/src/commands/audio/pl-display.command.ts b/src/commands/audio/pl-display.command.ts index 3b8c791..94da183 100644 --- a/src/commands/audio/pl-display.command.ts +++ b/src/commands/audio/pl-display.command.ts @@ -1,14 +1,17 @@ -import { CommandArgument, ICommand, ICommandContext } from '../../CommandTypes.js'; +import { CommandArgument, ICommand, ReplyContext } from '../../CommandTypes.js'; import { GroupAudio } from './AudioTypes.js'; import { EmbedBuilder, Message, PermissionsBitField, SlashCommandBuilder, User } from 'discord.js'; import i18next from 'i18next'; import { + ISchemaPlaylist, PlaylistNameMaxLength, PlaylistNameMinLength, UserPlaylistGet, UserPlaylistNamesAutocomplete } from '../../schemas/SchemaPlaylist.js'; import { generateErrorEmbed } from '../../utilities/generateErrorEmbed.js'; +import { PaginationList } from '../../audioplayer/PaginationList.js'; +import { getSongsNoun } from '../../audioplayer/util/getSongsNoun.js'; export default function (): ICommand { return { @@ -47,7 +50,7 @@ export default function (): ICommand { }; } -async function plDisplayAndReply(playlistName: string, ctx: ICommandContext, user: User) { +async function plDisplayAndReply(playlistName: string, ctx: ReplyContext, user: User) { const playlist = await UserPlaylistGet(user.id, playlistName, true); if (!playlist) { @@ -60,19 +63,42 @@ async function plDisplayAndReply(playlistName: string, ctx: ICommandContext, use const playlistEmbed = new EmbedBuilder().setAuthor({ name: user.displayName, iconURL: user.displayAvatarURL() }); - let songs = ``; + function buildPage(playlist: ISchemaPlaylist, pageNumber: number, entriesPerPage: number) { + let songsList = ''; - playlist.songs.forEach((song, index) => { - const songDate = song.createdAt ? `` : ''; + const startingIndex = pageNumber * entriesPerPage; - songs += `${index + 1}. [${song.name}](${song.url}) - ${songDate} \n`; - }); + for (let i = startingIndex; i < Math.min(startingIndex + entriesPerPage, playlist.songs.length); i++) { + const song = playlist.songs[i]; - if (songs === '') { + const songDate = song.createdAt ? `` : ''; + + songsList += `${i + 1}. [${song.name}](${song.url}) - ${songDate} \n`; + } + + return ( + new EmbedBuilder() + .setAuthor({ + name: `${user.displayName} - ${playlist.name} - ${playlist.songs.length} ${getSongsNoun(playlist.songs.length)}`, + iconURL: user.displayAvatarURL() + }) + //.setTitle(`${i18next.t('commands:history_embed_title')} ${guild.name}`) + .setDescription(`${songsList}`.slice(0, 4096)) + ); + } + + if (playlist.songsSize === 0) { playlistEmbed.setDescription(i18next.t('commands:pl-display_embed_no_songs')); + await ctx.reply({ embeds: [playlistEmbed], ephemeral: true }); } else { - playlistEmbed.setDescription(songs); - } + const arrayEmbeds: Array = []; + const entriesPerPage = 20; + const pages = Math.ceil(playlist.songs.length / entriesPerPage); + + for (let i = 0; i < pages; i++) { + arrayEmbeds.push(buildPage(playlist, i, entriesPerPage)); + } - await ctx.reply({ embeds: [playlistEmbed], ephemeral: true }); + await PaginationList(ctx, arrayEmbeds, user); + } } diff --git a/src/commands/audio/pl-my.command.ts b/src/commands/audio/pl-my.command.ts index 25f6353..50af28d 100644 --- a/src/commands/audio/pl-my.command.ts +++ b/src/commands/audio/pl-my.command.ts @@ -1,4 +1,4 @@ -import { ICommand, ICommandContext } from '../../CommandTypes.js'; +import { ICommand, ReplyContext } from '../../CommandTypes.js'; import { GroupAudio } from './AudioTypes.js'; import { EmbedBuilder, Message, PermissionsBitField, SlashCommandBuilder } from 'discord.js'; import i18next from 'i18next'; @@ -25,7 +25,7 @@ export default function (): ICommand { }; } -async function plMyAndReply(ctx: ICommandContext, userID: string) { +async function plMyAndReply(ctx: ReplyContext, userID: string) { const playlists = await UserPlaylistGetPlaylists(userID); if (playlists && playlists.length > 0) { diff --git a/src/commands/audio/pl-play.command.ts b/src/commands/audio/pl-play.command.ts index 7c935ed..deca9f4 100644 --- a/src/commands/audio/pl-play.command.ts +++ b/src/commands/audio/pl-play.command.ts @@ -1,4 +1,4 @@ -import { CommandArgument, ICommand, ICommandContext } from '../../CommandTypes.js'; +import { CommandArgument, ICommand, ReplyContext } from '../../CommandTypes.js'; import { GroupAudio } from './AudioTypes.js'; import { Guild, @@ -64,7 +64,7 @@ export default function (): ICommand { }; } -async function plPlayAndReply(ctx: ICommandContext, playlistName: string, userID: string) { +async function plPlayAndReply(ctx: ReplyContext, playlistName: string, userID: string) { try { if (queueSongsIsFull(ctx.client, ctx.guild as Guild)) { await ctx.reply({ diff --git a/src/commands/audio/pl-remove.command.ts b/src/commands/audio/pl-remove.command.ts index 5f7419e..786e346 100644 --- a/src/commands/audio/pl-remove.command.ts +++ b/src/commands/audio/pl-remove.command.ts @@ -1,4 +1,4 @@ -import { CommandArgument, ICommand, ICommandContext } from '../../CommandTypes.js'; +import { CommandArgument, ICommand, ReplyContext } from '../../CommandTypes.js'; import { GroupAudio } from './AudioTypes.js'; import { Message, PermissionsBitField, SlashCommandBuilder } from 'discord.js'; import i18next from 'i18next'; @@ -58,7 +58,7 @@ export default function (): ICommand { }; } -async function plRemoveAndReply(playlistName: string, songID: number, ctx: ICommandContext, userID: string) { +async function plRemoveAndReply(playlistName: string, songID: number, ctx: ReplyContext, userID: string) { try { const playlistSong = await UserPlaylistRemoveSong(userID, playlistName, Number(songID)); diff --git a/src/commands/audio/play.command.ts b/src/commands/audio/play.command.ts index c0f1aef..8230da7 100644 --- a/src/commands/audio/play.command.ts +++ b/src/commands/audio/play.command.ts @@ -1,4 +1,4 @@ -import { CommandArgument, ICommand, ICommandContext } from '../../CommandTypes.js'; +import { CommandArgument, ICommand, ReplyContext } from '../../CommandTypes.js'; import { ApplicationCommandOptionChoiceData, AutocompleteInteraction, @@ -97,7 +97,7 @@ export async function songSearchAutocomplete(interaction: AutocompleteInteractio await interaction.respond([]); } -async function playAndReply(ctx: ICommandContext, songQuery: string) { +async function playAndReply(ctx: ReplyContext, songQuery: string) { if (queueSongsIsFull(ctx.client, ctx.guild as Guild)) { await ctx.reply({ embeds: [ diff --git a/src/schemas/SchemaPlaylist.ts b/src/schemas/SchemaPlaylist.ts index 9bf3652..14b7a1d 100644 --- a/src/schemas/SchemaPlaylist.ts +++ b/src/schemas/SchemaPlaylist.ts @@ -159,7 +159,7 @@ export async function UserPlaylistGet( const user = await getOrCreateUser(userID); const userWithPlaylists = await user.populate({ path: 'playlists', - select: withSongs ? ['name', 'songs', 'createdAt'] : undefined + select: withSongs ? ['name', 'songs', 'createdAt', 'updatedAt', 'songsSize'] : undefined }); if (!userWithPlaylists.playlists) throw new PlaylistIsNotExists(name); diff --git a/src/utilities/commandEmptyReply.ts b/src/utilities/commandEmptyReply.ts index 4ec8588..979b3d6 100644 --- a/src/utilities/commandEmptyReply.ts +++ b/src/utilities/commandEmptyReply.ts @@ -1,10 +1,10 @@ import { ChatInputCommandInteraction } from 'discord.js'; import i18next from 'i18next'; -import { ICommandContext } from '../CommandTypes.js'; +import { ReplyContext } from '../CommandTypes.js'; // Every chat interaction must be replied, but I don't want to reply, // So the code below is a workaround -export async function commandEmptyReply(ctx: ICommandContext) { +export async function commandEmptyReply(ctx: ReplyContext) { if (ctx instanceof ChatInputCommandInteraction) { await ctx.reply(i18next.t('general:thinking')); await ctx.deleteReply(); diff --git a/wiki/Commands.md b/wiki/Commands.md index 4e0891e..108ac3d 100644 --- a/wiki/Commands.md +++ b/wiki/Commands.md @@ -6,6 +6,10 @@ Bot supports slash and text command systems. Nothing special, start writing / and select command from the list +> [!NOTE] +> Recommend using slash commands because most of these commands have an "invisible/ephemeral" messages, +> so no one can see except you the output of command + ## Text commands If a bot owner does not change the default prefix in .env.production file, the prefix is // @@ -122,6 +126,46 @@ And when all users leave a voice channel with bot Return song history of the current server where the command is executed. +### pl-create + +Example: /pl-create Funny Music + +Create a playlist linked to user with given name + +### pl-play + +Example: /pl-play Funny Music + +Do the same things as a /play command, but add songs from playlist + +### pl-add + +Example: /pl-add Funny Music https://www.youtube.com/watch?v=dQw4w9WgXcQ + +Add a song to playlist + +### pl-display + +Example: /pl-display Funny Music + +Display the list of songs of given playlist + +### pl-remove + +Example: /pl-remove Funny Music 1 + +Remove the song from playlist by ID, to get song ID in playlist, use the /pl-display + +### pl-my + +Display the list of playlists + +### pl-delete + +Example: /pl-delete Funny Music + +Delete the playlist + ### audiodebug Give the current count of spawned audioplayers