nowplaying: half-assed lastfm support #4

Merge aplicado
amy mesclou 2 commits from lastfm into main 2025-08-01 10:22:30 +00:00
2 arquivos alterados com 72 adições e 25 exclusões
Mostrando apenas as alterações do commit cd0174ef6f - Mostrar todos os commits

nowplaying: half-assed lastfm support

sunnie 2025-07-31 19:34:10 +03:00

Ver arquivo

@ -4,52 +4,94 @@ import {
ApplicationIntegrationType,
ButtonBuilder,
ButtonStyle,
ChatInputCommandInteraction, ContainerBuilder,
ChatInputCommandInteraction,
EmbedBuilder,
MessageFlags,
InteractionContextType, type MessageActionRowComponentBuilder, MessageFlagsBitField,
SlashCommandBuilder, TextDisplayBuilder, SectionBuilder, ThumbnailBuilder, type ButtonInteraction
InteractionContextType, type MessageActionRowComponentBuilder,
SlashCommandBuilder
} from "discord.js";
import { getSongOnPreferredProvider, kyzaify, lobotomizedSongButton, musicCache, nowPlayingView, type Song } from "../music.ts"
import { getSongOnPreferredProvider, lobotomizedSongButton, musicCache, nowPlayingView } from "../music.ts"
import { type Config } from "../config.ts";
import { hash } from "crypto"
async function getNowPlaying(username: string, lastFMApiKey?: string): 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
// it also doesnt provide a streaming platform url, im sorry i have to do this
const page = await (await fetch(track.url)).text()
const [, link] = page.match(/class="header-new-playlink"\s+href="(.+?)"/m) || []
return {
songName: track.name,
artistName: track.artist["#text"],
link
}
}
}
}
export default class PingCommand extends Command {
async run(interaction: ChatInputCommandInteraction, config: Config): Promise<void> {
await interaction.deferReply()
const user = interaction.options.getString("user") ?? config.listenbrainzAccount;
const lobotomized = interaction.options.getBoolean("lobotomized") ?? config.commandDefaults.nowplaying.lobotomized;
const usesonglink = interaction.options.getBoolean("usesonglink") ?? config.commandDefaults.nowplaying.useSonglink
const useitunes = interaction.options.getBoolean("useitunes") ?? config.commandDefaults.nowplaying.useItunes
const meow = await fetch(`https://api.listenbrainz.org/1/user/${user}/playing-now`).then((res) => res.json());
if (!meow) {
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)
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 (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 (meow.payload.count === 0) {
await interaction.followUp(user + " isnt listening to music");
} else {
const track_metadata = meow.payload.listens[0].track_metadata
const paramsObj = { entity: "song", term: track_metadata.artist_name + " " + track_metadata.track_name };
const searchParams = new URLSearchParams(paramsObj);
let link = track_metadata.additional_info.origin_url
if (useitunes) {
const itunesinfo = (await (await fetch(`https://itunes.apple.com/search?${searchParams.toString()}`)).json()).results[0];
link = itunesinfo.trackViewUrl
}
if (!link) useSonglink = false
let preferredApi, songlink, isCached = false
if (musicCache[link]) {
if (link && musicCache[link]) {
preferredApi = musicCache[link].preferredApi
songlink = musicCache[link].songlink
isCached = true
} else {
} 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 (preferredApi && useSonglink) {
if (!isCached) musicCache[link] ??= {
preferredApi,
songlink
@ -72,7 +114,7 @@ export default class PingCommand extends Command {
];
await interaction.followUp({
content: `### ${preferredApi.title.replace(/([#*_~`|])/g, "\\$1")} ${emoji}\n-# by ${preferredApi.artist}`,
components: components,
components,
})
// we dont have infinite emoji slots
await emoji.delete()
@ -86,9 +128,9 @@ export default class PingCommand extends Command {
} else {
const embedfallback = new EmbedBuilder()
.setAuthor({
name: meow.payload.listens[0].track_metadata.artist_name
name: nowPlaying.artistName
})
.setTitle(meow.payload.listens[0].track_metadata.track_name)
.setTitle(nowPlaying.songName)
.setFooter({
text: "song.link proxying was turned off or failed - amy jr",
});
@ -112,11 +154,14 @@ export default class PingCommand extends Command {
.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("listenbrainz username").setRequired(false)
return option.setName("user").setDescription("username").setRequired(false)
})
.setContexts([
InteractionContextType.BotDM,

Ver arquivo

@ -4,6 +4,7 @@ import { PrismaClient } from "./generated/prisma/index.js";
const configT = z.object({
token: z.string(),
listenbrainzAccount: z.string(),
lastFMApiKey: z.string(),
gitapi: z.string(),
sharkeyInstance: z.string(),
radioURL: z.string(),
@ -13,7 +14,8 @@ const configT = z.object({
nowplaying: z.object({
lobotomized: z.boolean(),
useSonglink: z.boolean(),
useItunes: z.boolean()
useItunes: z.boolean(),
useLastFM: z.boolean()
}),
pat: z.object({
speed: z.number(),