mirror of
https://github.com/BluemediaGER/muse.git
synced 2024-11-23 09:15:29 +01:00
Parse duration strings for /fseek and /seek (#565)
This commit is contained in:
parent
03d5cfffd1
commit
6c00727a4a
|
@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Changed
|
### Changed
|
||||||
- Now uses [esmo](https://github.com/antfu/esno) so we don't have to build
|
- Now uses [esmo](https://github.com/antfu/esno) so we don't have to build
|
||||||
|
- `/seek` and `/fseek` can now be given duration strings. For example, `1m` and `2m 15s` work. If the input consists only of numbers, Muse will treat it as the number of seconds to advance (backwards-compatible behavior).
|
||||||
|
|
||||||
## [1.5.0] - 2022-03-12
|
## [1.5.0] - 2022-03-12
|
||||||
### Changed
|
### Changed
|
||||||
|
|
|
@ -11,7 +11,9 @@
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0"
|
"node": ">=16.0.0"
|
||||||
},
|
},
|
||||||
"files": ["src"],
|
"files": [
|
||||||
|
"src"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint \"src/**/*.{ts,tsx}\"",
|
"lint": "eslint \"src/**/*.{ts,tsx}\"",
|
||||||
"lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix",
|
"lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix",
|
||||||
|
@ -31,6 +33,7 @@
|
||||||
"@types/debug": "^4.1.5",
|
"@types/debug": "^4.1.5",
|
||||||
"@types/fluent-ffmpeg": "^2.1.17",
|
"@types/fluent-ffmpeg": "^2.1.17",
|
||||||
"@types/fs-capacitor": "^2.0.0",
|
"@types/fs-capacitor": "^2.0.0",
|
||||||
|
"@types/ms": "0.7.31",
|
||||||
"@types/node": "^17.0.0",
|
"@types/node": "^17.0.0",
|
||||||
"@types/node-emoji": "^1.8.1",
|
"@types/node-emoji": "^1.8.1",
|
||||||
"@types/spotify-web-api-node": "^5.0.2",
|
"@types/spotify-web-api-node": "^5.0.2",
|
||||||
|
@ -100,6 +103,7 @@
|
||||||
"p-event": "^5.0.1",
|
"p-event": "^5.0.1",
|
||||||
"p-limit": "^4.0.0",
|
"p-limit": "^4.0.0",
|
||||||
"p-queue": "^7.2.0",
|
"p-queue": "^7.2.0",
|
||||||
|
"parse-duration": "1.0.2",
|
||||||
"read-pkg": "7.1.0",
|
"read-pkg": "7.1.0",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"spotify-uri": "^2.2.0",
|
"spotify-uri": "^2.2.0",
|
||||||
|
|
|
@ -5,15 +5,16 @@ import {inject, injectable} from 'inversify';
|
||||||
import PlayerManager from '../managers/player.js';
|
import PlayerManager from '../managers/player.js';
|
||||||
import Command from '.';
|
import Command from '.';
|
||||||
import {prettyTime} from '../utils/time.js';
|
import {prettyTime} from '../utils/time.js';
|
||||||
|
import durationStringToSeconds from '../utils/duration-string-to-seconds.js';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export default class implements Command {
|
export default class implements Command {
|
||||||
public readonly slashCommand = new SlashCommandBuilder()
|
public readonly slashCommand = new SlashCommandBuilder()
|
||||||
.setName('fseek')
|
.setName('fseek')
|
||||||
.setDescription('seek forward in the current song')
|
.setDescription('seek forward in the current song')
|
||||||
.addNumberOption(option => option
|
.addStringOption(option => option
|
||||||
.setName('seconds')
|
.setName('time')
|
||||||
.setDescription('the number of seconds to skip forward')
|
.setDescription('an interval expression or number of seconds (1m, 30s, 100)')
|
||||||
.setRequired(true));
|
.setRequired(true));
|
||||||
|
|
||||||
public requiresVC = true;
|
public requiresVC = true;
|
||||||
|
@ -37,12 +38,14 @@ export default class implements Command {
|
||||||
throw new Error('can\'t seek in a livestream');
|
throw new Error('can\'t seek in a livestream');
|
||||||
}
|
}
|
||||||
|
|
||||||
const seekTime = interaction.options.getNumber('seconds');
|
const seekValue = interaction.options.getString('value');
|
||||||
|
|
||||||
if (!seekTime) {
|
if (!seekValue) {
|
||||||
throw new Error('missing number of seconds to seek');
|
throw new Error('missing seek value');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const seekTime = durationStringToSeconds(seekValue);
|
||||||
|
|
||||||
if (seekTime + player.getPosition() > currentSong.length) {
|
if (seekTime + player.getPosition() > currentSong.length) {
|
||||||
throw new Error('can\'t seek past the end of the song');
|
throw new Error('can\'t seek past the end of the song');
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import PlayerManager from '../managers/player.js';
|
||||||
import Command from '.';
|
import Command from '.';
|
||||||
import {parseTime, prettyTime} from '../utils/time.js';
|
import {parseTime, prettyTime} from '../utils/time.js';
|
||||||
import {SlashCommandBuilder} from '@discordjs/builders';
|
import {SlashCommandBuilder} from '@discordjs/builders';
|
||||||
|
import durationStringToSeconds from '../utils/duration-string-to-seconds.js';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export default class implements Command {
|
export default class implements Command {
|
||||||
|
@ -13,7 +14,7 @@ export default class implements Command {
|
||||||
.setDescription('seek to a position from beginning of song')
|
.setDescription('seek to a position from beginning of song')
|
||||||
.addStringOption(option =>
|
.addStringOption(option =>
|
||||||
option.setName('time')
|
option.setName('time')
|
||||||
.setDescription('time to seek')
|
.setDescription('an interval expression or number of seconds (1m, 30s, 100)')
|
||||||
.setRequired(true),
|
.setRequired(true),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -45,7 +46,7 @@ export default class implements Command {
|
||||||
if (time.includes(':')) {
|
if (time.includes(':')) {
|
||||||
seekTime = parseTime(time);
|
seekTime = parseTime(time);
|
||||||
} else {
|
} else {
|
||||||
seekTime = parseInt(time, 10);
|
seekTime = durationStringToSeconds(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seekTime > currentSong.length) {
|
if (seekTime > currentSong.length) {
|
||||||
|
|
|
@ -5,7 +5,17 @@ import ytdl from 'ytdl-core';
|
||||||
import {WriteStream} from 'fs-capacitor';
|
import {WriteStream} from 'fs-capacitor';
|
||||||
import ffmpeg from 'fluent-ffmpeg';
|
import ffmpeg from 'fluent-ffmpeg';
|
||||||
import shuffle from 'array-shuffle';
|
import shuffle from 'array-shuffle';
|
||||||
import {AudioPlayer, AudioPlayerStatus, createAudioPlayer, createAudioResource, joinVoiceChannel, StreamType, VoiceConnection, VoiceConnectionStatus} from '@discordjs/voice';
|
import {
|
||||||
|
AudioPlayer,
|
||||||
|
AudioPlayerState,
|
||||||
|
AudioPlayerStatus,
|
||||||
|
createAudioPlayer,
|
||||||
|
createAudioResource,
|
||||||
|
joinVoiceChannel,
|
||||||
|
StreamType,
|
||||||
|
VoiceConnection,
|
||||||
|
VoiceConnectionStatus,
|
||||||
|
} from '@discordjs/voice';
|
||||||
import FileCacheProvider from './file-cache.js';
|
import FileCacheProvider from './file-cache.js';
|
||||||
import debug from '../utils/debug.js';
|
import debug from '../utils/debug.js';
|
||||||
import {prisma} from '../utils/db.js';
|
import {prisma} from '../utils/db.js';
|
||||||
|
@ -493,7 +503,7 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.audioPlayer.listeners('stateChange').length === 0) {
|
if (this.audioPlayer.listeners('stateChange').length === 0) {
|
||||||
this.audioPlayer.on('stateChange', this.onAudioPlayerStateChange.bind(this));
|
this.audioPlayer.on(AudioPlayerStatus.Idle, this.onAudioPlayerIdle.bind(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -501,7 +511,7 @@ export default class {
|
||||||
this.disconnect();
|
this.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async onAudioPlayerStateChange(_oldState: {status: AudioPlayerStatus}, newState: {status: AudioPlayerStatus}): Promise<void> {
|
private async onAudioPlayerIdle(_oldState: AudioPlayerState, newState: AudioPlayerState): Promise<void> {
|
||||||
// Automatically advance queued song at end
|
// Automatically advance queued song at end
|
||||||
if (newState.status === AudioPlayerStatus.Idle && this.status === STATUS.PLAYING) {
|
if (newState.status === AudioPlayerStatus.Idle && this.status === STATUS.PLAYING) {
|
||||||
await this.forward(1);
|
await this.forward(1);
|
||||||
|
|
21
src/utils/duration-string-to-seconds.ts
Normal file
21
src/utils/duration-string-to-seconds.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import parse from 'parse-duration';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse duration strings to seconds.
|
||||||
|
* @param str any common duration format, like 1m or 1hr 30s. If the input is a number it's assumed to be in seconds.
|
||||||
|
* @returns seconds
|
||||||
|
*/
|
||||||
|
const durationStringToSeconds = (str: string) => {
|
||||||
|
let seconds;
|
||||||
|
const isInputSeconds = Boolean(/\d+$/.exec(str));
|
||||||
|
|
||||||
|
if (isInputSeconds) {
|
||||||
|
seconds = Number.parseInt(str, 10);
|
||||||
|
} else {
|
||||||
|
seconds = parse(str) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
return seconds;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default durationStringToSeconds;
|
|
@ -384,7 +384,7 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/libsodium-wrappers/-/libsodium-wrappers-0.7.9.tgz#89c3ad2156d5143e64bce86cfeb0045a983aeccc"
|
resolved "https://registry.yarnpkg.com/@types/libsodium-wrappers/-/libsodium-wrappers-0.7.9.tgz#89c3ad2156d5143e64bce86cfeb0045a983aeccc"
|
||||||
integrity sha512-LisgKLlYQk19baQwjkBZZXdJL0KbeTpdEnrAfz5hQACbklCY0gVFnsKUyjfNWF1UQsCSjw93Sj5jSbiO8RPfdw==
|
integrity sha512-LisgKLlYQk19baQwjkBZZXdJL0KbeTpdEnrAfz5hQACbklCY0gVFnsKUyjfNWF1UQsCSjw93Sj5jSbiO8RPfdw==
|
||||||
|
|
||||||
"@types/ms@*":
|
"@types/ms@*", "@types/ms@0.7.31":
|
||||||
version "0.7.31"
|
version "0.7.31"
|
||||||
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
|
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
|
||||||
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
|
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
|
||||||
|
@ -2855,6 +2855,11 @@ parent-module@^1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
callsites "^3.0.0"
|
callsites "^3.0.0"
|
||||||
|
|
||||||
|
parse-duration@1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/parse-duration/-/parse-duration-1.0.2.tgz#b9aa7d3a1363cc7e8845bea8fd3baf8a11df5805"
|
||||||
|
integrity sha512-Dg27N6mfok+ow1a2rj/nRjtCfaKrHUZV2SJpEn/s8GaVUSlf4GGRCRP1c13Hj+wfPKVMrFDqLMLITkYKgKxyyg==
|
||||||
|
|
||||||
parse-json@5.2.0, parse-json@^5.0.0, parse-json@^5.2.0:
|
parse-json@5.2.0, parse-json@^5.0.0, parse-json@^5.2.0:
|
||||||
version "5.2.0"
|
version "5.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
|
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
|
||||||
|
|
Loading…
Reference in a new issue