From 379b4ca02189a4791a0834acb723cfbaf1424120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Sasovsky?= Date: Wed, 31 Jul 2024 19:16:55 +0200 Subject: [PATCH] feat: random radio (#100) --- lib/i18n/strings.g.dart | 108 ++++++++++++++++++++++++++++- lib/i18n/strings.i18n.json | 10 +++ lib/i18n/strings_es.i18n.json | 10 +++ lib/src/commands/radio.dart | 90 ++++++++++++++++++++++++ lib/src/helpers/random_string.dart | 12 ++++ 5 files changed, 228 insertions(+), 2 deletions(-) create mode 100644 lib/src/helpers/random_string.dart diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index e8e2066..1a4a6af 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 2 -/// Strings: 184 (92 per locale) +/// Strings: 196 (98 per locale) /// -/// Built on 2023-10-01 at 22:09 UTC +/// Built on 2023-10-01 at 23:21 UTC // coverage:ignore-file // ignore_for_file: type=lint @@ -427,6 +427,8 @@ class StringsCommandsRadioChildrenEn { StringsCommandsRadioChildrenRecognizeEn._(_root); late final StringsCommandsRadioChildrenUpvoteEn upvote = StringsCommandsRadioChildrenUpvoteEn._(_root); + late final StringsCommandsRadioChildrenPlayRandomEn playRandom = + StringsCommandsRadioChildrenPlayRandomEn._(_root); } // Path: services.music.trackStuck @@ -546,6 +548,24 @@ class StringsCommandsRadioChildrenUpvoteEn { StringsCommandsRadioChildrenUpvoteErrorsEn._(_root); } +// Path: commands.radio.children.playRandom +class StringsCommandsRadioChildrenPlayRandomEn { + StringsCommandsRadioChildrenPlayRandomEn._(this._root); + + final StringsEn _root; // ignore: unused_field + + // Translations + String get command => 'play-random'; + String get description => 'Plays a random radio station'; + String get searching => 'Searching for a random radio station...'; + String get startedPlaying => 'Started playing'; + String startedPlayingDescription( + {required Object radio, required Object mention}) => + 'Radio ${radio} started playing.\n\nRequested by ${mention}'; + late final StringsCommandsRadioChildrenPlayRandomErrorsEn errors = + StringsCommandsRadioChildrenPlayRandomErrorsEn._(_root); +} + // Path: commands.radio.children.recognize.errors class StringsCommandsRadioChildrenRecognizeErrorsEn { StringsCommandsRadioChildrenRecognizeErrorsEn._(this._root); @@ -570,6 +590,17 @@ class StringsCommandsRadioChildrenUpvoteErrorsEn { String get noRadioPlaying => 'Couldn\'t find a radio playing!'; } +// Path: commands.radio.children.playRandom.errors +class StringsCommandsRadioChildrenPlayRandomErrorsEn { + StringsCommandsRadioChildrenPlayRandomErrorsEn._(this._root); + + final StringsEn _root; // ignore: unused_field + + // Translations + String get noResults => + 'Couldn\'t find a random radio station :( Try again later!'; +} + // Path: class StringsEs extends StringsEn { /// You can call this constructor and build your own translation instance of this locale. @@ -1043,6 +1074,9 @@ class StringsCommandsRadioChildrenEs extends StringsCommandsRadioChildrenEn { @override late final StringsCommandsRadioChildrenUpvoteEs upvote = StringsCommandsRadioChildrenUpvoteEs._(_root); + @override + late final StringsCommandsRadioChildrenPlayRandomEs playRandom = + StringsCommandsRadioChildrenPlayRandomEs._(_root); } // Path: services.music.trackStuck @@ -1226,6 +1260,34 @@ class StringsCommandsRadioChildrenUpvoteEs StringsCommandsRadioChildrenUpvoteErrorsEs._(_root); } +// Path: commands.radio.children.playRandom +class StringsCommandsRadioChildrenPlayRandomEs + extends StringsCommandsRadioChildrenPlayRandomEn { + StringsCommandsRadioChildrenPlayRandomEs._(StringsEs root) + : this._root = root, + super._(root); + + @override + final StringsEs _root; // ignore: unused_field + + // Translations + @override + String get command => 'play-random'; + @override + String get description => 'Reproduce una radio aleatoria'; + @override + String get searching => 'Buscando una radio aleatoria...'; + @override + String get startedPlaying => 'Se ha comenzado a reproducir'; + @override + String startedPlayingDescription( + {required Object radio, required Object mention}) => + 'La radio ${radio} ha comenzado a reproducirse.\n\nPedido por ${mention}'; + @override + late final StringsCommandsRadioChildrenPlayRandomErrorsEs errors = + StringsCommandsRadioChildrenPlayRandomErrorsEs._(_root); +} + // Path: commands.radio.children.recognize.errors class StringsCommandsRadioChildrenRecognizeErrorsEs extends StringsCommandsRadioChildrenRecognizeErrorsEn { @@ -1264,6 +1326,22 @@ class StringsCommandsRadioChildrenUpvoteErrorsEs String get noRadioPlaying => 'No se está reproduciendo ninguna radio'; } +// Path: commands.radio.children.playRandom.errors +class StringsCommandsRadioChildrenPlayRandomErrorsEs + extends StringsCommandsRadioChildrenPlayRandomErrorsEn { + StringsCommandsRadioChildrenPlayRandomErrorsEs._(StringsEs root) + : this._root = root, + super._(root); + + @override + final StringsEs _root; // ignore: unused_field + + // Translations + @override + String get noResults => + 'No se ha podido encontrar una radio aleatoria :( Inténtalo de nuevo más tarde!'; +} + /// Flat map(s) containing all translations. /// Only for edge cases! For simple maps, use the map function of this library. @@ -1420,6 +1498,19 @@ extension on StringsEn { 'You have successfully voted for the radio ${radio}! Thank you for your support :D'; case 'commands.radio.children.upvote.errors.noRadioPlaying': return 'Couldn\'t find a radio playing!'; + case 'commands.radio.children.playRandom.command': + return 'play-random'; + case 'commands.radio.children.playRandom.description': + return 'Plays a random radio station'; + case 'commands.radio.children.playRandom.searching': + return 'Searching for a random radio station...'; + case 'commands.radio.children.playRandom.startedPlaying': + return 'Started playing'; + case 'commands.radio.children.playRandom.startedPlayingDescription': + return ({required Object radio, required Object mention}) => + 'Radio ${radio} started playing.\n\nRequested by ${mention}'; + case 'commands.radio.children.playRandom.errors.noResults': + return 'Couldn\'t find a random radio station :( Try again later!'; case 'services.music.trackStuck.title': return 'Track stuck'; case 'services.music.trackStuck.description': @@ -1634,6 +1725,19 @@ extension on StringsEs { 'Has votado positivamente por la radio ${radio}! Gracias por tu apoyo :D'; case 'commands.radio.children.upvote.errors.noRadioPlaying': return 'No se está reproduciendo ninguna radio'; + case 'commands.radio.children.playRandom.command': + return 'play-random'; + case 'commands.radio.children.playRandom.description': + return 'Reproduce una radio aleatoria'; + case 'commands.radio.children.playRandom.searching': + return 'Buscando una radio aleatoria...'; + case 'commands.radio.children.playRandom.startedPlaying': + return 'Se ha comenzado a reproducir'; + case 'commands.radio.children.playRandom.startedPlayingDescription': + return ({required Object radio, required Object mention}) => + 'La radio ${radio} ha comenzado a reproducirse.\n\nPedido por ${mention}'; + case 'commands.radio.children.playRandom.errors.noResults': + return 'No se ha podido encontrar una radio aleatoria :( Inténtalo de nuevo más tarde!'; case 'services.music.trackStuck.title': return 'La canción se ha quedado atascada'; case 'services.music.trackStuck.description': diff --git a/lib/i18n/strings.i18n.json b/lib/i18n/strings.i18n.json index aa8f5ff..41ceaa0 100644 --- a/lib/i18n/strings.i18n.json +++ b/lib/i18n/strings.i18n.json @@ -105,6 +105,16 @@ "errors": { "noRadioPlaying": "Couldn't find a radio playing!" } + }, + "playRandom": { + "command": "play-random", + "description": "Plays a random radio station", + "searching": "Searching for a random radio station...", + "startedPlaying": "Started playing", + "startedPlayingDescription": "Radio $radio started playing.\n\nRequested by $mention", + "errors": { + "noResults": "Couldn't find a random radio station :( Try again later!" + } } } } diff --git a/lib/i18n/strings_es.i18n.json b/lib/i18n/strings_es.i18n.json index 5d7206b..f015a72 100644 --- a/lib/i18n/strings_es.i18n.json +++ b/lib/i18n/strings_es.i18n.json @@ -105,6 +105,16 @@ "errors": { "noRadioPlaying": "No se está reproduciendo ninguna radio" } + }, + "playRandom": { + "command": "play-random", + "description": "Reproduce una radio aleatoria", + "searching": "Buscando una radio aleatoria...", + "startedPlaying": "Se ha comenzado a reproducir", + "startedPlayingDescription": "La radio $radio ha comenzado a reproducirse.\n\nPedido por $mention", + "errors": { + "noResults": "No se ha podido encontrar una radio aleatoria :( Inténtalo de nuevo más tarde!" + } } } } diff --git a/lib/src/commands/radio.dart b/lib/src/commands/radio.dart index cff463e..4079baf 100644 --- a/lib/src/commands/radio.dart +++ b/lib/src/commands/radio.dart @@ -15,6 +15,7 @@ import 'package:nyxx_interactions/nyxx_interactions.dart'; import 'package:radio_browser_api/radio_browser_api.dart'; import 'package:radio_horizon/radio_horizon.dart'; import 'package:radio_horizon/src/checks.dart'; +import 'package:radio_horizon/src/helpers/random_string.dart'; import 'package:radio_horizon/src/models/song_recognition/current_station_info.dart'; import 'package:retry/retry.dart'; import 'package:shazam_client/shazam_client.dart'; @@ -149,6 +150,95 @@ ChatCommand:radio-play: { (translations) => translations.commands.radio.children.play.command, ), ), + ChatCommand( + _enPlayCommand.command, + _enPlayCommand.description, + id('radio-play-random', ( + IChatContext context, + ) async { + context as InteractionChatContext; + final commandTranslations = + getCommandTranslations(context).radio.children.playRandom; + + await context.respond( + MessageBuilder.content( + commandTranslations.searching, + ), + ); + + final radios = await _radioBrowserClient.getStationsByName( + name: getRandomString(1), + parameters: const InputParameters(limit: 10), + ); + + final randomIndex = math.Random().nextInt(radios.items.length); + final radio = radios.items[randomIndex]; + + _logger.info( + ''' +ChatCommand:radio-play-random: { + 'guild': ${context.guild?.id.toString() ?? 'null'}, + 'guild_name': ${context.guild?.name ?? 'null'}, + 'guild_preferred_locale': ${context.guild?.preferredLocale ?? 'null'}, + 'channel': ${context.channel.id}, + 'user': ${context.member?.id.toString() ?? 'null'}, +}''', + ); + + await connectIfNeeded(context, replace: true); + + final node = MusicService.instance.cluster + .getOrCreatePlayerNode(context.guild!.id); + + await _radioBrowserClient.clickStation( + uuid: radio.stationUUID, + ); + + final result = await node.searchTracks(radio.urlResolved ?? radio.url); + if (result.tracks.isEmpty) { + await context.respond( + MessageBuilder.content( + commandTranslations.errors.noResults, + ), + ); + return; + } + + final track = result.tracks.first; + node + ..players[context.guild!.id]!.queue.clear() + ..play( + context.guild!.id, + track, + replace: true, + requester: context.member!.id, + channelId: context.channel.id, + ).startPlaying(); + + await SongRecognitionService.instance.setCurrentRadio( + context.guild!.id, + context.member!.voiceState!.channel!.id, + context.channel.id, + radio, + ); + + final embed = EmbedBuilder() + ..color = getRandomColor() + ..title = commandTranslations.startedPlaying + ..description = commandTranslations.startedPlayingDescription( + radio: radio.name, + mention: context.member?.mention ?? '(Unknown)', + ); + + await context.respond(MessageBuilder.embed(embed)); + }), + localizedDescriptions: localizedValues( + (translations) => translations.commands.radio.children.play.description, + ), + localizedNames: localizedValues( + (translations) => translations.commands.radio.children.play.command, + ), + ), ChatCommand( _enRecognizeCommand.command, _enRecognizeCommand.description, diff --git a/lib/src/helpers/random_string.dart b/lib/src/helpers/random_string.dart new file mode 100644 index 0000000..b4e6d3f --- /dev/null +++ b/lib/src/helpers/random_string.dart @@ -0,0 +1,12 @@ +import 'dart:math'; + +const chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoP' // pragma: allowlist secret + 'pQqRrSsTtUuVvWwXxYyZz1234567890'; // pragma: allowlist secret +final rnd = Random(); + +String getRandomString(int length) => String.fromCharCodes( + Iterable.generate( + length, + (_) => chars.codeUnitAt(rnd.nextInt(chars.length)), + ), + );