diff --git a/package.json b/package.json index 8893fbbe2..4367edb4f 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "moment-duration-format": "^2.3.2", "mongoose": "^6.6.4", "ms": "^2.1.3", + "multisort": "^0.5.3", "node-superfetch": "^0.3.3" }, "engines": { diff --git a/src/config.js b/src/config.js index 05f0bcb70..c360ff6c1 100644 --- a/src/config.js +++ b/src/config.js @@ -12,6 +12,7 @@ module.exports = { logs: process.env.LOGS || "channel_id", // channel id for guild create and delete logs errorLogsChannel: process.env.errLogs || "channel_id", //error logs channel id SearchPlatform: process.env.searchPlatform || "youtube music", // Sets the Search Platform. Possibilities: youtube || youtube music || soundcloud + AggregatedSearchOrder: process.env.searchOrder || "youtube music,youtube,soundcloud", // Sets the order of Slash command's AutoComplete results links: { img: process.env.IMG || 'https://media.discordapp.net/attachments/963097935820750878/983300268131225651/20220606_145403.png', //setup system background image support: process.env.SUPPORT || 'https://discord.gg/ns8CTk9J3e', //support server invite link diff --git a/src/events/Client/interactionCreate.js b/src/events/Client/interactionCreate.js index 0468bed4c..29ee909c1 100644 --- a/src/events/Client/interactionCreate.js +++ b/src/events/Client/interactionCreate.js @@ -6,6 +6,7 @@ const { EmbedBuilder, } = require("discord.js"); const { SearchResult, Track } = require("erela.js"); +const { AggregatedSearchSuggestions } = require("../../utils/SearchAggregator"); const MusicBot = require("../../structures/Client"); const db = require("../../schema/prefix.js"); const db2 = require("../../schema/dj"); @@ -36,44 +37,13 @@ module.exports = { /** * @type {SearchResult} */ - const result = await client.manager.search( + const searchSuggestions = await AggregatedSearchSuggestions( + client, focused.value, interaction.user ); - - if (result.loadType === "TRACK_LOADED" || "SEARCH_RESULT") { - /** - * @type {Track[]} - */ - const sliced = result.tracks.slice(0, 5).sort(); - - if ( - focused.value.match( - /(?:https:\/\/open\.spotify\.com\/|spotify:)(?:.+)?(track|playlist|artist|episode|show|album)[\/:]([A-Za-z0-9]+)/ || - /^(?:https?:\/\/|)?(?:www\.)?deezer\.com\/(?:\w{2}\/)?(track|album|playlist)\/(\d+)/ || - /^((?:https?:)\/\/)?((?:deezer)\.)?((?:page.link))\/([a-zA-Z0-9]+)/ || - /(?:https:\/\/music\.apple\.com\/)(?:\w{2}\/)?(track|album|playlist)/g || - /(http(s|):\/\/music\.apple\.com\/..\/.....\/.*\/([0-9]){1,})\?i=([0-9]){1,}/gim || - /(?:https?:\/\/)?(?:www.|web.|m.)?(facebook|fb).(com|watch)\/(?:video.php\?v=\d+|(\S+)|photo.php\?v=\d+|\?v=\d+)|\S+\/videos\/((\S+)\/(\d+)|(\d+))\/?/g - ) - ) { - await interaction.respond( - sliced.map((track) => ({ - name: track.title, - value: focused.value, - })) - ); - return; - } else { - await interaction.respond( - sliced.map((track) => ({ - name: track.title, - value: track.uri, - })) - ); - } - } else if (result.loadType === "LOAD_FAILED" || "NO_MATCHES") - return; + if(searchSuggestions) await interaction.respond(searchSuggestions); + return; } break; } diff --git a/src/utils/SearchAggregator.js b/src/utils/SearchAggregator.js new file mode 100644 index 000000000..c1dee62d8 --- /dev/null +++ b/src/utils/SearchAggregator.js @@ -0,0 +1,88 @@ +const multisort = require("multisort"); + +const validPlatforms = ["youtube music", "youtube", "soundcloud"]; + +class SearchQuery { + constructor(platform, query) { + this.source = platform, + this.query = query + } +} + +function isUrl(string) { + let url; + try { + url = new URL(string); + } catch (_) { + return false; + } + return url.protocol === "http:" || url.protocol === "https:"; + } + +function loadPlatforms(client) { + const {SearchPlatform, AggregatedSearchOrder} = client.config; + + const searchPlatforms = []; + if(AggregatedSearchOrder) { + let platforms = AggregatedSearchOrder.split(','); + platforms.forEach(platform => { + platform = platform.trim(); + if(validPlatforms.includes(platform) && !searchPlatforms.includes(platform)) { + searchPlatforms.push(platform); + } + }); + } + if(searchPlatforms.length == 0) searchPlatforms.push(SearchPlatform.trim()); + + return searchPlatforms; +} + +module.exports = { + + AggregatedSearchSuggestions: async function(client, query, requester) + { + const sortCriteria = [ + "author", //ASC //DESC = "~name" + "name" + ] + + if(isUrl(query)) { + const result = await client.manager.search(query, requester); + const searchSuggestions = result.tracks.slice(0, 5); + return result.loadType === "TRACK_LOADED" || "SEARCH_RESULT" ? searchSuggestions.map(track => ({ + name: `${track.title} – ${track.author}`.slice(0,100), + value: query + })) : false; + } + + const searchPlatforms = loadPlatforms(client); + const searchTasks = []; + + searchPlatforms.forEach(platform => { + let searchQuery = new SearchQuery(platform, query); + searchTasks.push(client.manager.search(searchQuery, requester)); + }); + + const searchResults = await Promise.all(searchTasks); + let searchSuggestions = []; + + for(i = 0; i < searchPlatforms.length; i++ ) + { + if(!(searchResults[i].loadType === "TRACK_LOADED" || "SEARCH_RESULT")) continue; + + const platformBadge = client.emoji[searchPlatforms[i]]; + let platformSuggestions = searchResults[i].tracks.slice(0, 4); + platformSuggestions = multisort(platformSuggestions, sortCriteria); + + searchSuggestions = searchSuggestions.concat( + platformSuggestions.map(track => ({ + name: `${platformBadge}${track.title} – ${track.author}`.slice(0,100), + value: track.uri + })) + ); + } + // Workaround: SoundCloud and its excessively long URL is breaking AutoComplete + return searchSuggestions.length > 0 ? searchSuggestions.filter(item => item.value.length < 100) : false; + + } +} \ No newline at end of file diff --git a/src/utils/emoji.json b/src/utils/emoji.json index e3318c5cd..aab0d2136 100644 --- a/src/utils/emoji.json +++ b/src/utils/emoji.json @@ -23,5 +23,9 @@ "join": "📥", "leave": "📤", "about": "🔎", - "jump": "⏭️" + "jump": "⏭️", + + "youtube music": "💿YM | ", + "youtube": "🎬YT | ", + "soundcloud": "☁️SC | " } \ No newline at end of file