Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { PlayerNodeInitializationResult } from 'discord-player'
import type { CommandExecuteParams } from '../../../../../types/CommandData'
import { ENVIRONMENT_CONFIG } from '@lucky/shared/config'
import { errorLog, debugLog, warnLog } from '@lucky/shared/utils'
import { assertDefined } from '@lucky/shared/utils/guards'
import { createErrorEmbed } from '../../../../../utils/general/embeds'
import { interactionReply } from '../../../../../utils/general/interactionReply'
import { createUserFriendlyError } from '@lucky/shared/utils/general/errorSanitizer'
Expand Down Expand Up @@ -44,7 +45,7 @@ export async function executePlayHandler({
}

const member = interaction.member as GuildMember
const voiceChannel = member.voice.channel!
const voiceChannel = assertDefined(member.voice.channel, 'Voice channel guaranteed by requireVoiceChannel check')

try {
await interaction.deferReply()
Expand Down Expand Up @@ -88,7 +89,7 @@ export async function executePlayHandler({
try {
const deferredMsg = await interaction.fetchReply()
registerNowPlayingMessage(
interaction.guildId!,
assertDefined(interaction.guildId, 'Guild ID guaranteed by requireGuild check'),
deferredMsg.id,
interaction.channelId,
)
Expand Down Expand Up @@ -213,7 +214,7 @@ export async function executePlayHandler({
// single failure never silently skips the others (#1085).
void runPostPlayBackgroundOps({
queue,
guildId: interaction.guildId!,
guildId: assertDefined(interaction.guildId, 'Guild ID guaranteed by requireGuild check'),
track,
hadQueueBeforePlay,
isPlaylist,
Expand Down
15 changes: 15 additions & 0 deletions packages/bot/src/functions/music/commands/stop.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { beforeEach, describe, expect, it, jest } from '@jest/globals'
import stopCommand from './stop'

const requireGuildMock = jest.fn()
const requireQueueMock = jest.fn()
const requireDJRoleMock = jest.fn()
const interactionReplyMock = jest.fn()
Expand All @@ -11,6 +12,7 @@ const deleteSnapshotMock = jest.fn()
const clearSessionMoodCacheMock = jest.fn()

jest.mock('../../../utils/command/commandValidations', () => ({
requireGuild: (...args: unknown[]) => requireGuildMock(...args),
requireQueue: (...args: unknown[]) => requireQueueMock(...args),
requireDJRole: (...args: unknown[]) => requireDJRoleMock(...args),
}))
Expand Down Expand Up @@ -63,6 +65,7 @@ function createQueue(guildId = 'guild-1') {
describe('stop command', () => {
beforeEach(() => {
jest.clearAllMocks()
requireGuildMock.mockResolvedValue(true)
requireQueueMock.mockResolvedValue(true)
requireDJRoleMock.mockResolvedValue(true)
interactionReplyMock.mockResolvedValue(undefined)
Expand Down Expand Up @@ -145,6 +148,18 @@ describe('stop command', () => {
expect(queue.delete).toHaveBeenCalled()
})

it('returns early if guild check fails', async () => {
requireGuildMock.mockResolvedValue(false)

await stopCommand.execute({
interaction: createInteraction('guild-1'),
client: {} as any,
})

expect(requireQueueMock).not.toHaveBeenCalled()
expect(clearSessionMoodCacheMock).not.toHaveBeenCalled()
})

it('returns early if queue check fails', async () => {
resolveGuildQueueMock.mockReturnValue({ queue: null })
requireQueueMock.mockResolvedValue(false)
Expand Down
6 changes: 5 additions & 1 deletion packages/bot/src/functions/music/commands/stop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,28 @@ import { interactionReply } from '../../../utils/general/interactionReply'
import { clearSessionMoodCache } from '../../../utils/music/autoplay/replenisher'
import type { CommandExecuteParams } from '../../../types/CommandData'
import {
requireGuild,
requireQueue,
requireDJRole,
} from '../../../utils/command/commandValidations'
import { resolveGuildQueue } from '../../../utils/music/queueResolver'
import { createSuccessEmbed } from '../../../utils/general/embeds'
import { musicWatchdogService } from '../../../utils/music/watchdog'
import { musicSessionSnapshotService } from '../../../utils/music/sessionSnapshots'
import { assertDefined } from '@lucky/shared/utils/guards'

export default new Command({
data: new SlashCommandBuilder()
.setName('stop')
.setDescription('⏹️ Stop playback and clear the queue.'),
category: 'music',
execute: async ({ client, interaction }: CommandExecuteParams) => {
if (!(await requireGuild(interaction))) return

const { queue } = resolveGuildQueue(client, interaction.guildId ?? '')

if (!(await requireQueue(queue, interaction))) return
if (!(await requireDJRole(interaction, interaction.guildId!))) return
if (!(await requireDJRole(interaction, assertDefined(interaction.guildId, 'Guild ID required after requireGuild check')))) return

if (queue) {
musicWatchdogService.markIntentionalStop(queue.guild.id)
Expand Down
3 changes: 2 additions & 1 deletion packages/bot/src/functions/music/commands/volume.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
requireDJRole
} from "../../../utils/command/commandValidations"
import { resolveGuildQueue } from '../../../utils/music/queueResolver'
import { assertDefined } from '@lucky/shared/utils/guards'

/**
* Validate volume value
Expand Down Expand Up @@ -78,7 +79,7 @@ export default new Command({
category: 'music',
execute: async ({ client, interaction }: CommandExecuteParams) => {
if (!(await requireGuild(interaction))) return
if (!(await requireDJRole(interaction, interaction.guildId!))) return
if (!(await requireDJRole(interaction, assertDefined(interaction.guildId, 'Guild ID required after requireGuild check')))) return

const { queue } = resolveGuildQueue(client, interaction.guildId ?? '')
if (!(await requireQueue(queue, interaction))) return
Expand Down
5 changes: 3 additions & 2 deletions packages/bot/src/functions/music/commands/voteskip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
} from '../../../utils/music/voteSkipStore'
import { guildSettingsService } from '@lucky/shared/services'
import { debugLog } from '@lucky/shared/utils'
import { assertDefined } from '@lucky/shared/utils/guards'
import type { GuildMember } from 'discord.js'

const DEFAULT_THRESHOLD = 50
Expand All @@ -35,7 +36,7 @@ export default new Command({
}: CommandExecuteParams): Promise<void> => {
if (!(await requireGuild(interaction))) return

const guildId = interaction.guildId!
const guildId = assertDefined(interaction.guildId, 'Guild ID required after requireGuild check')
const { queue } = resolveGuildQueue(client, guildId)

if (!(await requireQueue(queue, interaction))) return
Expand Down Expand Up @@ -111,7 +112,7 @@ export default new Command({

if (voteCount >= required) {
clearVotes(guildId)
queue!.node.skip()
assertDefined(queue, 'Queue required after requireQueue check').node.skip()
await interactionReply({
interaction,
content: {
Expand Down
6 changes: 4 additions & 2 deletions packages/bot/src/handlers/message/autoModHandler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Message } from 'discord.js'
import { autoModService, moderationService } from '@lucky/shared/services'
import { errorLog } from '@lucky/shared/utils'
import { assertDefined } from '@lucky/shared/utils/guards'
import type {
MessageContext,
MessageHandler,
Expand Down Expand Up @@ -112,12 +113,13 @@ export const autoModHandler: MessageHandler = {

await message.delete().catch(() => {})

const clientUser = assertDefined(message.client.user, 'Client user guaranteed when bot is ready')
const caseInput = {
guildId,
userId,
username: message.author.tag,
moderatorId: message.client.user!.id,
moderatorName: message.client.user!.tag,
moderatorId: clientUser.id,
moderatorName: clientUser.tag,
reason: `[AutoMod] ${violations[0].reason}`,
channelId: message.channelId,
}
Expand Down
7 changes: 4 additions & 3 deletions packages/bot/src/handlers/player/streamBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { PassThrough } from 'stream'
import type { Readable } from 'stream'
import type { Track } from 'discord-player'
import { errorLog, infoLog, warnLog, debugLog } from '@lucky/shared/utils'
import { assertDefined } from '@lucky/shared/utils/guards'
import {
cleanTitle,
cleanAuthor,
Expand Down Expand Up @@ -68,17 +69,17 @@ export function streamViaYtDlp(url: string): Promise<Readable> {
}, 15_000)

const stderrChunks: Buffer[] = []
proc.stderr!.on('data', (chunk: Buffer) => stderrChunks.push(chunk))
assertDefined(proc.stderr, 'stderr guaranteed by stdio config').on('data', (chunk: Buffer) => stderrChunks.push(chunk))

let settled = false

proc.stdout!.once('data', (firstChunk: Buffer) => {
assertDefined(proc.stdout, 'stdout guaranteed by stdio config').once('data', (firstChunk: Buffer) => {
if (settled) return
settled = true
clearTimeout(timeout)
const through = new PassThrough()
through.write(firstChunk)
proc.stdout!.pipe(through)
assertDefined(proc.stdout, 'stdout guaranteed by stdio config').pipe(through)
resolve(through)
})

Expand Down
13 changes: 7 additions & 6 deletions packages/bot/src/spotify/spotifyApi.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { logAndSwallow } from '@lucky/shared/utils/error'
import { debugLog, warnLog } from '@lucky/shared/utils/general/log'
import { assertDefined } from '@lucky/shared/utils/guards'

export interface SpotifyRecommendationTrack {
id: string
Expand Down Expand Up @@ -218,8 +219,8 @@ export async function getSpotifyRecommendations(
return (data?.tracks ?? [])
.filter((t) => t.id && t.name)
.map((t) => ({
id: t.id!,
name: t.name!,
id: assertDefined(t.id, 'Track ID guaranteed by filter'),
name: assertDefined(t.name, 'Track name guaranteed by filter'),
artists: (t.artists ?? []).map((a) => ({
name: a.name ?? '',
})),
Expand Down Expand Up @@ -591,16 +592,16 @@ export async function getUserTopArtistsAndTracks(
const artists: SpotifyTopArtist[] = (artistsData.items ?? [])
.filter((a) => a.id && a.name)
.map((a) => ({
id: a.id!,
name: a.name!,
id: assertDefined(a.id, 'Artist ID guaranteed by filter'),
name: assertDefined(a.name, 'Artist name guaranteed by filter'),
genres: a.genres ?? [],
}))

const tracks: SpotifyTopTrack[] = (tracksData.items ?? [])
.filter((t) => t.id && t.name)
.map((t) => ({
id: t.id!,
name: t.name!,
id: assertDefined(t.id, 'Track ID guaranteed by filter'),
name: assertDefined(t.name, 'Track name guaranteed by filter'),
artist: t.artists?.[0]?.name ?? 'Unknown',
}))

Expand Down
5 changes: 3 additions & 2 deletions packages/bot/src/utils/music/queueEditOps.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { type Track, type GuildQueue } from 'discord-player'
import { randomInt } from 'node:crypto'
import { debugLog, errorLog } from '@lucky/shared/utils'
import { assertDefined } from '@lucky/shared/utils/guards'
import { replenishQueue } from './autoplay/replenisher'
import { markAsAutoplayTrack } from './autoplay/queueMarkers'

Expand Down Expand Up @@ -174,7 +175,7 @@ export function moveUserTrackToPriority(queue: GuildQueue, track: Track): void {
const tracks = queue.tracks.toArray()
let trackIndex = -1
for (let i = tracks.length - 1; i >= 0; i--) {
const t = tracks[i]!
const t = assertDefined(tracks[i], 'Array index guaranteed by loop bounds')
if (
t === track ||
(Boolean(track.id) && t.id === track.id) ||
Expand Down Expand Up @@ -202,7 +203,7 @@ export function moveUserTrackToPriority(queue: GuildQueue, track: Track): void {
return
}

const queuedTrack = tracks[trackIndex]!
const queuedTrack = assertDefined(tracks[trackIndex], 'Track index guaranteed by prior check')

try {
queue.node.remove(queuedTrack)
Expand Down
Loading