mirror of
https://github.com/JonnyBro/JaBa.git
synced 2024-11-22 05:04:58 +05:00
temp fix for music (only youtube works!)
This commit is contained in:
parent
a3e906a8f8
commit
54cfde2a05
4 changed files with 87 additions and 43 deletions
|
@ -1,7 +1,9 @@
|
||||||
const { Client, Collection, SlashCommandBuilder, ContextMenuCommandBuilder, EmbedBuilder, PermissionsBitField, ChannelType } = require("discord.js"),
|
const { Client, Collection, SlashCommandBuilder, ContextMenuCommandBuilder, EmbedBuilder, PermissionsBitField, ChannelType } = require("discord.js"),
|
||||||
{ GiveawaysManager } = require("discord-giveaways"),
|
{ GiveawaysManager } = require("discord-giveaways"),
|
||||||
{ REST } = require("@discordjs/rest"),
|
{ REST } = require("@discordjs/rest"),
|
||||||
{ Player } = require("discord-player"),
|
{ Player: DiscordPlayer } = require("discord-player"),
|
||||||
|
{ SpotifyExtractor } = require("@discord-player/extractor"),
|
||||||
|
{ YoutubeiExtractor, createYoutubeiStream } = require("discord-player-youtubei"),
|
||||||
{ Routes } = require("discord-api-types/v10");
|
{ Routes } = require("discord-api-types/v10");
|
||||||
|
|
||||||
const BaseEvent = require("./BaseEvent.js"),
|
const BaseEvent = require("./BaseEvent.js"),
|
||||||
|
@ -31,8 +33,17 @@ class JaBaClient extends Client {
|
||||||
this.databaseCache.guilds = new Collection();
|
this.databaseCache.guilds = new Collection();
|
||||||
this.databaseCache.members = new Collection();
|
this.databaseCache.members = new Collection();
|
||||||
this.databaseCache.usersReminds = new Collection();
|
this.databaseCache.usersReminds = new Collection();
|
||||||
|
}
|
||||||
|
|
||||||
this.player = new Player(this, {
|
/**
|
||||||
|
* Initializes the client by logging in with the provided token and connecting to the MongoDB database.
|
||||||
|
*
|
||||||
|
* This method is called during the client's startup process to set up the necessary connections and resources.
|
||||||
|
*
|
||||||
|
* @returns {Promise<void>} A Promise that resolves when the client is fully initialized.
|
||||||
|
*/
|
||||||
|
async init() {
|
||||||
|
this.player = new DiscordPlayer(this, {
|
||||||
ytdlOptions: {
|
ytdlOptions: {
|
||||||
requestOptions: {
|
requestOptions: {
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -41,13 +52,25 @@ class JaBaClient extends Client {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.player.extractors.loadDefault(null, {
|
|
||||||
SpotifyExtractor: {
|
await this.player.extractors.register(YoutubeiExtractor, {
|
||||||
clientId: this.config.spotify.clientId,
|
// authentication: {
|
||||||
clientSecret: this.config.spotify.clientSecret,
|
// access_token: process.env.YT_ACCESS_TOKEN || "",
|
||||||
},
|
// refresh_token: process.env.YT_REFRESH_TOKEN || "",
|
||||||
|
// scope: "https://www.googleapis.com/auth/youtube https://www.googleapis.com/auth/youtube-paid-content",
|
||||||
|
// token_type: "Bearer",
|
||||||
|
// expiry_date: "2024-07-10T11:37:01.093Z",
|
||||||
|
// },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await this.player.extractors.register(SpotifyExtractor, {
|
||||||
|
createStream: createYoutubeiStream,
|
||||||
|
clientId: this.config.spotify.clientId,
|
||||||
|
clientSecret: this.config.spotify.clientSecret,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.player.extractors.loadDefault(ext => !["YouTubeExtractor", "SpotifyExtractor"].includes(ext));
|
||||||
|
|
||||||
this.player.events.on("playerStart", async (queue, track) => {
|
this.player.events.on("playerStart", async (queue, track) => {
|
||||||
const m = (
|
const m = (
|
||||||
await queue.metadata.channel.send({
|
await queue.metadata.channel.send({
|
||||||
|
@ -88,17 +111,6 @@ class JaBaClient extends Client {
|
||||||
reaction: "🎉",
|
reaction: "🎉",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the client by logging in with the provided token and connecting to the MongoDB database.
|
|
||||||
*
|
|
||||||
* This method is called during the client's startup process to set up the necessary connections and resources.
|
|
||||||
*
|
|
||||||
* @returns {Promise<void>} A Promise that resolves when the client is fully initialized.
|
|
||||||
*/
|
|
||||||
async init() {
|
|
||||||
this.login(this.config.token);
|
|
||||||
|
|
||||||
mongoose
|
mongoose
|
||||||
.connect(this.config.mongoDB)
|
.connect(this.config.mongoDB)
|
||||||
|
@ -109,8 +121,7 @@ class JaBaClient extends Client {
|
||||||
this.logger.error(`Unable to connect to the Mongodb database.\nError: ${err}`);
|
this.logger.error(`Unable to connect to the Mongodb database.\nError: ${err}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// const autoUpdateDocs = require("../helpers/autoUpdateDocs");
|
this.login(this.config.token);
|
||||||
// autoUpdateDocs.update(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -307,7 +318,7 @@ class JaBaClient extends Client {
|
||||||
* Returns a User data from the database.
|
* Returns a User data from the database.
|
||||||
* @param {string} userID - The ID of the user to find or create.
|
* @param {string} userID - The ID of the user to find or create.
|
||||||
* @returns {Promise<import("./User")>} The user data object, either retrieved from the database or newly created.
|
* @returns {Promise<import("./User")>} The user data object, either retrieved from the database or newly created.
|
||||||
*/
|
*/
|
||||||
async getUserData(userID) {
|
async getUserData(userID) {
|
||||||
let userData = await this.usersData.findOne({ id: userID });
|
let userData = await this.usersData.findOne({ id: userID });
|
||||||
|
|
||||||
|
|
|
@ -49,14 +49,11 @@ class Play extends BaseCommand {
|
||||||
if (!perms.has(PermissionsBitField.Flags.Connect) || !perms.has(PermissionsBitField.Flags.Speak)) return interaction.error("music/play:VOICE_CHANNEL_CONNECT", null, { edit: true });
|
if (!perms.has(PermissionsBitField.Flags.Connect) || !perms.has(PermissionsBitField.Flags.Speak)) return interaction.error("music/play:VOICE_CHANNEL_CONNECT", null, { edit: true });
|
||||||
|
|
||||||
const searchResult = await client.player.search(query, {
|
const searchResult = await client.player.search(query, {
|
||||||
requestedBy: interaction.user,
|
requestedBy: interaction.member,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!searchResult.hasTracks()) {
|
if (!searchResult.hasTracks()) return interaction.error("music/play:NO_RESULT", { query }, { edit: true, ephemeral: true });
|
||||||
console.log(searchResult);
|
else {
|
||||||
|
|
||||||
return interaction.error("music/play:NO_RESULT", { query }, { edit: true });
|
|
||||||
} else {
|
|
||||||
await client.player.play(voice, searchResult, {
|
await client.player.play(voice, searchResult, {
|
||||||
nodeOptions: {
|
nodeOptions: {
|
||||||
metadata: interaction,
|
metadata: interaction,
|
||||||
|
@ -85,10 +82,11 @@ class Play extends BaseCommand {
|
||||||
*/
|
*/
|
||||||
async autocompleteRun(client, interaction) {
|
async autocompleteRun(client, interaction) {
|
||||||
const query = interaction.options.getString("query");
|
const query = interaction.options.getString("query");
|
||||||
if (query === "" || query === null) return;
|
if (query === "" || query === null) return interaction.respond([ { name: "No Query Provided", value: "" } ]);
|
||||||
|
if (query.startsWith("http")) return interaction.respond([ { name: "Current Link", value: query } ]);
|
||||||
|
|
||||||
const youtubeResults = await client.player.search(query, { searchEngine: QueryType.YOUTUBE });
|
const youtubeResults = await client.player.search(query, { searchEngine: QueryType.YOUTUBE });
|
||||||
const spotifyResults = await client.player.search(query, { searchEngine: QueryType.SPOTIFY_SEARCH });
|
// const spotifyResults = await client.player.search(query, { searchEngine: QueryType.SPOTIFY_SEARCH });
|
||||||
const tracks = [];
|
const tracks = [];
|
||||||
|
|
||||||
youtubeResults.tracks
|
youtubeResults.tracks
|
||||||
|
@ -99,13 +97,13 @@ class Play extends BaseCommand {
|
||||||
}))
|
}))
|
||||||
.forEach(t => tracks.push({ name: t.name, value: t.value }));
|
.forEach(t => tracks.push({ name: t.name, value: t.value }));
|
||||||
|
|
||||||
spotifyResults.tracks
|
// spotifyResults.tracks
|
||||||
.slice(0, 5)
|
// .slice(0, 5)
|
||||||
.map(t => ({
|
// .map(t => ({
|
||||||
name: `Spotify: ${`${t.title} - ${t.author} (${t.duration})`.length > 75 ? `${`${t.title} - ${t.author}`.substring(0, 75)}... (${t.duration})` : `${t.title} - ${t.author} (${t.duration})`}`,
|
// name: `Spotify: ${`${t.title} - ${t.author} (${t.duration})`.length > 75 ? `${`${t.title} - ${t.author}`.substring(0, 75)}... (${t.duration})` : `${t.title} - ${t.author} (${t.duration})`}`,
|
||||||
value: t.url,
|
// value: t.url,
|
||||||
}))
|
// }))
|
||||||
.forEach(t => tracks.push({ name: t.name, value: t.value }));
|
// .forEach(t => tracks.push({ name: t.name, value: t.value }));
|
||||||
|
|
||||||
return interaction.respond(tracks);
|
return interaction.respond(tracks);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,10 @@
|
||||||
"@napi-rs/canvas": "^0.1.53",
|
"@napi-rs/canvas": "^0.1.53",
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
"cron": "^2.4.4",
|
"cron": "^2.4.4",
|
||||||
"discord-api-types": "^0.37.90",
|
"discord-api-types": "^0.37.92",
|
||||||
"discord-giveaways": "^6.0.1",
|
"discord-giveaways": "^6.0.1",
|
||||||
"discord-player": "^6.6.10",
|
"discord-player": "^6.6.10",
|
||||||
|
"discord-player-youtubei": "^1.1.8",
|
||||||
"discord.js": "^14.15.3",
|
"discord.js": "^14.15.3",
|
||||||
"gamedig": "^4.1.0",
|
"gamedig": "^4.1.0",
|
||||||
"i18next": "^21.10.0",
|
"i18next": "^21.10.0",
|
||||||
|
|
|
@ -30,14 +30,17 @@ importers:
|
||||||
specifier: ^2.4.4
|
specifier: ^2.4.4
|
||||||
version: 2.4.4
|
version: 2.4.4
|
||||||
discord-api-types:
|
discord-api-types:
|
||||||
specifier: ^0.37.90
|
specifier: ^0.37.92
|
||||||
version: 0.37.90
|
version: 0.37.92
|
||||||
discord-giveaways:
|
discord-giveaways:
|
||||||
specifier: ^6.0.1
|
specifier: ^6.0.1
|
||||||
version: 6.0.1(discord.js@14.15.3)
|
version: 6.0.1(discord.js@14.15.3)
|
||||||
discord-player:
|
discord-player:
|
||||||
specifier: ^6.6.10
|
specifier: ^6.6.10
|
||||||
version: 6.6.10(@discord-player/extractor@4.4.7)(@discordjs/opus@0.9.0)
|
version: 6.6.10(@discord-player/extractor@4.4.7)(@discordjs/opus@0.9.0)
|
||||||
|
discord-player-youtubei:
|
||||||
|
specifier: ^1.1.8
|
||||||
|
version: 1.1.8
|
||||||
discord.js:
|
discord.js:
|
||||||
specifier: ^14.15.3
|
specifier: ^14.15.3
|
||||||
version: 14.15.3
|
version: 14.15.3
|
||||||
|
@ -462,8 +465,8 @@ packages:
|
||||||
discord-api-types@0.37.83:
|
discord-api-types@0.37.83:
|
||||||
resolution: {integrity: sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==}
|
resolution: {integrity: sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==}
|
||||||
|
|
||||||
discord-api-types@0.37.90:
|
discord-api-types@0.37.92:
|
||||||
resolution: {integrity: sha512-lpOJSGrqHuXoM4FV/2HtjoaJpCClGFHpRNIdZEW8zPINlsCHNSfIwA2EQ8dxeE6k1QhhTuM9ZlOGVYXoU7FLgA==}
|
resolution: {integrity: sha512-7xnedbQRLRef/O+4jKPyIFwl6YqoyihOG3OSneiRmVJMBk30ph2YuZGcHjeX1Kk/a3yQWeyCKe4RZJB3iECcxg==}
|
||||||
|
|
||||||
discord-giveaways@6.0.1:
|
discord-giveaways@6.0.1:
|
||||||
resolution: {integrity: sha512-hs6Vtb62VdlV7NfB93Phaxc8FW0cH4N1Nkb0bXVZ2npgn73yGOFVmdp8bBP5gsC2ady9OAXXI8Gb66t1IddkFw==}
|
resolution: {integrity: sha512-hs6Vtb62VdlV7NfB93Phaxc8FW0cH4N1Nkb0bXVZ2npgn73yGOFVmdp8bBP5gsC2ady9OAXXI8Gb66t1IddkFw==}
|
||||||
|
@ -471,6 +474,10 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
discord.js: '>=14.0.0'
|
discord.js: '>=14.0.0'
|
||||||
|
|
||||||
|
discord-player-youtubei@1.1.8:
|
||||||
|
resolution: {integrity: sha512-gyIa8dTf1RnLhoo3uo79Z2tMYP/l0iTQfwVvsnbUPUV1iSZMdPcSaBIPUNdPu4s0C+OPUnqNo/Ceo7yDEIAptQ==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
discord-player@6.6.10:
|
discord-player@6.6.10:
|
||||||
resolution: {integrity: sha512-AOZnJYXOoe2hF2OENwKUIaKJ2H5U8VfxcoMKDXE++9Rnbzd7qD8MQr9Am01T1UiiPf2BWjyHin97EUm1nrTJKA==}
|
resolution: {integrity: sha512-AOZnJYXOoe2hF2OENwKUIaKJ2H5U8VfxcoMKDXE++9Rnbzd7qD8MQr9Am01T1UiiPf2BWjyHin97EUm1nrTJKA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -733,6 +740,9 @@ packages:
|
||||||
isomorphic-unfetch@4.0.2:
|
isomorphic-unfetch@4.0.2:
|
||||||
resolution: {integrity: sha512-1Yd+CF/7al18/N2BDbsLBcp6RO3tucSW+jcLq24dqdX5MNbCNTw1z4BsGsp4zNmjr/Izm2cs/cEqZPp4kvWSCA==}
|
resolution: {integrity: sha512-1Yd+CF/7al18/N2BDbsLBcp6RO3tucSW+jcLq24dqdX5MNbCNTw1z4BsGsp4zNmjr/Izm2cs/cEqZPp4kvWSCA==}
|
||||||
|
|
||||||
|
jintr@2.0.0:
|
||||||
|
resolution: {integrity: sha512-RiVlevxttZ4eHEYB2dXKXDXluzHfRuw0DJQGsYuKCc5IvZj5/GbOakeqVX+Bar/G9kTty9xDJREcxukurkmYLA==}
|
||||||
|
|
||||||
js-yaml@4.1.0:
|
js-yaml@4.1.0:
|
||||||
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
@ -1204,6 +1214,10 @@ packages:
|
||||||
resolution: {integrity: sha512-VviMt2tlMg1BvQ0FKXxrz1eJuyrcISrL2sPfBf7ZskX/FCEc/7LeThQaoygsMJpNqrATWQIsRVx+1Dpe4jaYuQ==}
|
resolution: {integrity: sha512-VviMt2tlMg1BvQ0FKXxrz1eJuyrcISrL2sPfBf7ZskX/FCEc/7LeThQaoygsMJpNqrATWQIsRVx+1Dpe4jaYuQ==}
|
||||||
engines: {node: '>=18.17'}
|
engines: {node: '>=18.17'}
|
||||||
|
|
||||||
|
undici@6.19.2:
|
||||||
|
resolution: {integrity: sha512-JfjKqIauur3Q6biAtHJ564e3bWa8VvT+7cSiOJHFbX4Erv6CLGDpg8z+Fmg/1OI/47RA+GI2QZaF48SSaLvyBA==}
|
||||||
|
engines: {node: '>=18.17'}
|
||||||
|
|
||||||
unfetch@5.0.0:
|
unfetch@5.0.0:
|
||||||
resolution: {integrity: sha512-3xM2c89siXg0nHvlmYsQ2zkLASvVMBisZm5lF3gFDqfF2xonNStDJyMpvaOBe0a1Edxmqrf2E0HBdmy9QyZaeg==}
|
resolution: {integrity: sha512-3xM2c89siXg0nHvlmYsQ2zkLASvVMBisZm5lF3gFDqfF2xonNStDJyMpvaOBe0a1Edxmqrf2E0HBdmy9QyZaeg==}
|
||||||
|
|
||||||
|
@ -1287,6 +1301,9 @@ packages:
|
||||||
youtube-sr@4.3.11:
|
youtube-sr@4.3.11:
|
||||||
resolution: {integrity: sha512-3oHiS2x7PpMiDRW7Cq8nz1bkAIBOJHoOwkPl/oncM/+A9/3xxMDgMLGW2dsBEP1DHFyRXYTVABgfbdwHF8sXXQ==}
|
resolution: {integrity: sha512-3oHiS2x7PpMiDRW7Cq8nz1bkAIBOJHoOwkPl/oncM/+A9/3xxMDgMLGW2dsBEP1DHFyRXYTVABgfbdwHF8sXXQ==}
|
||||||
|
|
||||||
|
youtubei.js@10.1.0:
|
||||||
|
resolution: {integrity: sha512-MokZMAnpWH11VYvWuW6qjPiiPmgRl5rfDgPQOpif9qXcVHoVw1hi8ePuRSD0AZSZ+uvWGe8rvas2dzp+Jv5JKQ==}
|
||||||
|
|
||||||
ytdl-core@4.11.5:
|
ytdl-core@4.11.5:
|
||||||
resolution: {integrity: sha512-27LwsW4n4nyNviRCO1hmr8Wr5J1wLLMawHCQvH8Fk0hiRqrxuIu028WzbJetiYH28K8XDbeinYW4/wcHQD1EXA==}
|
resolution: {integrity: sha512-27LwsW4n4nyNviRCO1hmr8Wr5J1wLLMawHCQvH8Fk0hiRqrxuIu028WzbJetiYH28K8XDbeinYW4/wcHQD1EXA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
@ -1698,7 +1715,7 @@ snapshots:
|
||||||
|
|
||||||
discord-api-types@0.37.83: {}
|
discord-api-types@0.37.83: {}
|
||||||
|
|
||||||
discord-api-types@0.37.90: {}
|
discord-api-types@0.37.92: {}
|
||||||
|
|
||||||
discord-giveaways@6.0.1(discord.js@14.15.3):
|
discord-giveaways@6.0.1(discord.js@14.15.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -1706,6 +1723,11 @@ snapshots:
|
||||||
discord.js: 14.15.3
|
discord.js: 14.15.3
|
||||||
serialize-javascript: 6.0.1
|
serialize-javascript: 6.0.1
|
||||||
|
|
||||||
|
discord-player-youtubei@1.1.8:
|
||||||
|
dependencies:
|
||||||
|
undici: 6.19.2
|
||||||
|
youtubei.js: 10.1.0
|
||||||
|
|
||||||
discord-player@6.6.10(@discord-player/extractor@4.4.7)(@discordjs/opus@0.9.0):
|
discord-player@6.6.10(@discord-player/extractor@4.4.7)(@discordjs/opus@0.9.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@discord-player/equalizer': 0.2.3
|
'@discord-player/equalizer': 0.2.3
|
||||||
|
@ -1729,7 +1751,7 @@ snapshots:
|
||||||
'@discord-player/ffmpeg': 0.1.0
|
'@discord-player/ffmpeg': 0.1.0
|
||||||
'@discord-player/opus': 0.1.2
|
'@discord-player/opus': 0.1.2
|
||||||
'@types/ws': 8.5.10
|
'@types/ws': 8.5.10
|
||||||
discord-api-types: 0.37.90
|
discord-api-types: 0.37.92
|
||||||
prism-media: 1.3.5(@discordjs/opus@0.9.0)
|
prism-media: 1.3.5(@discordjs/opus@0.9.0)
|
||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
ws: 8.14.2
|
ws: 8.14.2
|
||||||
|
@ -2063,6 +2085,10 @@ snapshots:
|
||||||
node-fetch: 3.3.2
|
node-fetch: 3.3.2
|
||||||
unfetch: 5.0.0
|
unfetch: 5.0.0
|
||||||
|
|
||||||
|
jintr@2.0.0:
|
||||||
|
dependencies:
|
||||||
|
acorn: 8.11.2
|
||||||
|
|
||||||
js-yaml@4.1.0:
|
js-yaml@4.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
argparse: 2.0.1
|
argparse: 2.0.1
|
||||||
|
@ -2489,6 +2515,8 @@ snapshots:
|
||||||
|
|
||||||
undici@6.15.0: {}
|
undici@6.15.0: {}
|
||||||
|
|
||||||
|
undici@6.19.2: {}
|
||||||
|
|
||||||
unfetch@5.0.0: {}
|
unfetch@5.0.0: {}
|
||||||
|
|
||||||
uri-js@4.4.1:
|
uri-js@4.4.1:
|
||||||
|
@ -2542,6 +2570,12 @@ snapshots:
|
||||||
|
|
||||||
youtube-sr@4.3.11: {}
|
youtube-sr@4.3.11: {}
|
||||||
|
|
||||||
|
youtubei.js@10.1.0:
|
||||||
|
dependencies:
|
||||||
|
jintr: 2.0.0
|
||||||
|
tslib: 2.6.2
|
||||||
|
undici: 5.27.2
|
||||||
|
|
||||||
ytdl-core@4.11.5:
|
ytdl-core@4.11.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
m3u8stream: 0.8.6
|
m3u8stream: 0.8.6
|
||||||
|
|
Loading…
Reference in a new issue