amyjr/src/commands/nowplaying.ts

173 linhas
7,3 KiB
TypeScript

import {
ActionRowBuilder,
ApplicationIntegrationType,
ButtonBuilder,
ButtonStyle,
ChatInputCommandInteraction,
EmbedBuilder,
MessageFlags,
InteractionContextType, type MessageActionRowComponentBuilder,
SlashCommandBuilder
} from "discord.js";
import { getSongOnPreferredProvider, lobotomizedSongButton, musicCache, nowPlayingView } from "../music.ts"
import { type Config } from "../config.ts";
import { hash } from "crypto"
import { declareCommand } from "../command.ts";
import { z } from "zod";
async function getNowPlaying(username: string, lastFMApiKey?: string, lastFMFetchLink?: boolean): Promise<{
songName: string, artistName: string, link: string
} | false | undefined> {
if (!lastFMApiKey) {
const res = await fetch(`https://api.listenbrainz.org/1/user/${username}/playing-now`).then((res) => res.json());
if (!res?.payload) return
else if (res.payload.count === 0) return false
else {
const trackMetadata = res.payload.listens[0].track_metadata
return {
songName: trackMetadata.artist_name,
artistName: trackMetadata.track_name,
link: trackMetadata.additional_info.origin_url
}
}
} else {
const res = await fetch(`https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=${username}&api_key=${lastFMApiKey}&limit=1&format=json`)
.then((res) => res.json());
if (!res?.recenttracks) return
else if (!res.recenttracks?.track?.[0]) return false
else {
const track = res.recenttracks.track[0]
// yes its a string, horror
if (track["@attr"]?.nowplaying !== "true") return false
return {
songName: track.name,
artistName: track.artist["#text"],
link: ""
}
}
}
}
export default declareCommand({
async run(interaction: ChatInputCommandInteraction, config): Promise<void> {
await interaction.deferReply()
const user = interaction.options.getString("user") ?? config.musicAccount!;
const lobotomized = interaction.options.getBoolean("lobotomized") ?? config.commandDefaults.nowplaying.lobotomized;
const useLastFM = interaction.options.getBoolean("uselastfm") ?? config.commandDefaults.nowplaying.useLastFM
let useSonglink = interaction.options.getBoolean("usesonglink") ?? config.commandDefaults.nowplaying.useSonglink
const useiTunes = interaction.options.getBoolean("useitunes") ?? config.commandDefaults.nowplaying.useItunes
const nowPlaying = await getNowPlaying(user, useLastFM ? config.lastFMApiKey : undefined, !useiTunes)
if (typeof nowPlaying === "undefined") {
await interaction.followUp("something shat itself!");
return;
} else if (!nowPlaying) {
await interaction.followUp(user + " isn't listening to music");
return
} else {
const paramsObj = { entity: "song", term: `${nowPlaying.artistName} ${nowPlaying.songName}` };
const searchParams = new URLSearchParams(paramsObj);
let { link } = nowPlaying
if (!link || useiTunes) {
const itunesinfo = (await (await fetch(`https://itunes.apple.com/search?${searchParams.toString()}`)).json()).results[0];
link = itunesinfo?.trackViewUrl
if (!link) {
await interaction.followUp("something shat itself!");
return;
}
}
if (!link) useSonglink = false
let preferredApi, songlink, isCached = false
if (link && musicCache[link]) {
preferredApi = musicCache[link].preferredApi
songlink = musicCache[link].songlink
isCached = true
} else if (useSonglink) {
songlink = await fetch(`https://api.song.link/v1-alpha.1/links?url=${link}`).then(a => a.json())
preferredApi = getSongOnPreferredProvider(songlink, link)
}
if (preferredApi && useSonglink) {
if (!isCached) musicCache[link] ??= {
preferredApi,
songlink
}
if (link.length > 100)
musicCache[link].hash = hash("md5", link)
if (lobotomized) {
const emoji = await interaction.client.application.emojis.create({
attachment: preferredApi.thumbnailUrl,
name: hash("md5", preferredApi.thumbnailUrl),
});
const components = [
new ActionRowBuilder<MessageActionRowComponentBuilder>()
.addComponents(
new ButtonBuilder()
.setStyle(ButtonStyle.Secondary)
.setLabel("expand")
.setCustomId(musicCache[link].hash || link),
),
];
await interaction.followUp({
content: `### ${preferredApi.title.replace(/([#*_~`|])/g, "\\$1")} ${emoji}\n-# by ${preferredApi.artist}`,
components,
})
// we dont have infinite emoji slots
await emoji.delete()
return
}
const components = nowPlayingView(songlink, preferredApi)
await interaction.followUp({
components,
flags: [MessageFlags.IsComponentsV2],
})
} else {
const embedfallback = new EmbedBuilder()
.setAuthor({
name: nowPlaying.artistName
})
.setTitle(nowPlaying.songName)
.setFooter({
text: "song.link proxying was turned off or failed - amy jr",
});
await interaction.followUp({ embeds: [embedfallback] })
}
}
},
button: lobotomizedSongButton,
dependsOn: z.object({
musicAccount: z.string(),
lastFMApiKey: z.string()
}),
slashCommand: new SlashCommandBuilder()
.setName("nowplaying")
.setDescription("balls").setIntegrationTypes([
ApplicationIntegrationType.UserInstall
])
.addBooleanOption(option => {
return option.setName("lobotomized").setDescription("smol").setRequired(false);
})
.addBooleanOption(option => {
return option.setName("usesonglink").setDescription("use songlink or not").setRequired(false)
})
.addBooleanOption(option => {
return option.setName("uselastfm").setDescription("use last.fm or not").setRequired(false)
})
.addBooleanOption(option => {
return option.setName("useitunes").setDescription("use itunes or not").setRequired(false)
})
.addStringOption(option => {
return option.setName("user").setDescription("username").setRequired(false)
})
.setContexts([
InteractionContextType.BotDM,
InteractionContextType.Guild,
InteractionContextType.PrivateChannel
]),
})