From 8bfa78434c9cc8fdfb882983d3f0ebd50c1da49d Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Wed, 14 Nov 2018 14:25:13 +0100 Subject: [PATCH 001/172] Make ATC use ObjectId --- FredBoat/build.gradle | 1 + .../main/java/fredboat/audio/player/GuildPlayer.kt | 11 ++++++----- .../java/fredboat/audio/queue/AudioTrackContext.kt | 5 +++-- .../main/java/fredboat/audio/queue/ITrackProvider.kt | 6 ++++-- .../java/fredboat/audio/queue/SimpleTrackProvider.kt | 5 +++-- .../fredboat/command/music/control/SkipCommand.kt | 3 ++- 6 files changed, 19 insertions(+), 12 deletions(-) diff --git a/FredBoat/build.gradle b/FredBoat/build.gradle index 38f8153b5..584d5c2f6 100644 --- a/FredBoat/build.gradle +++ b/FredBoat/build.gradle @@ -49,6 +49,7 @@ dependencies { } compile group: 'org.springframework.boot', name: 'spring-boot-starter-webflux', version: springBootVersion + compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-mongodb-reactive', version: springBootVersion compile group: 'it.unimi.dsi', name: 'fastutil', version: fastUtilVersion compile group: 'com.google.code.gson', name: 'gson', version: gsonVersion compile group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlinVersion diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index 20add6e0d..b12c239bb 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -48,6 +48,7 @@ import fredboat.util.ratelimit.Ratelimiter import fredboat.util.rest.YoutubeAPI import org.apache.commons.lang3.tuple.ImmutablePair import org.apache.commons.lang3.tuple.Pair +import org.bson.types.ObjectId import org.slf4j.LoggerFactory import java.util.* import java.util.function.Consumer @@ -296,7 +297,7 @@ class GuildPlayer( } /** Similar to [getTracksInRange], but only gets the trackIds */ - fun getTrackIdsInRange(start: Int, end: Int): List = getTracksInRange(start, end).stream() + fun getTrackIdsInRange(start: Int, end: Int): List = getTracksInRange(start, end).stream() .map { it.trackId } .toList() @@ -320,7 +321,7 @@ class GuildPlayer( } //Success, fail message - private suspend fun canMemberSkipTracks(member: Member, trackIds: Collection): Pair { + private suspend fun canMemberSkipTracks(member: Member, trackIds: Collection): Pair { if (PermsUtil.checkPerms(PermissionLevel.DJ, member)) { return ImmutablePair(true, null) } else { @@ -346,7 +347,7 @@ class GuildPlayer( } } - suspend fun skipTracksForMemberPerms(context: CommandContext, trackIds: Collection, successMessage: String) { + suspend fun skipTracksForMemberPerms(context: CommandContext, trackIds: Collection, successMessage: String) { val pair = canMemberSkipTracks(context.member, trackIds) if (pair.left) { @@ -357,10 +358,10 @@ class GuildPlayer( } } - fun skipTracks(trackIds: Collection) { + fun skipTracks(trackIds: Collection) { var skipCurrentTrack = false - val toRemove = ArrayList() + val toRemove = ArrayList() val playing = if (player.playingTrack != null) context else null for (trackId in trackIds) { if (playing != null && trackId == playing.trackId) { diff --git a/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt b/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt index dd4095fe8..7130e5ddb 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt @@ -31,12 +31,13 @@ import fredboat.feature.I18n import fredboat.main.Launcher import fredboat.sentinel.Member import fredboat.sentinel.TextChannel +import org.bson.types.ObjectId import java.util.concurrent.ThreadLocalRandom open class AudioTrackContext(val track: AudioTrack, val member: Member) : Comparable { val added: Long = System.currentTimeMillis() var rand: Int = 0 - val trackId: Long //used to identify this track even when the track gets cloned and the rand reranded + val trackId: ObjectId //used to identify this track even when the track gets cloned and the rand reranded val userId: Long get() = member.id @@ -62,7 +63,7 @@ open class AudioTrackContext(val track: AudioTrack, val member: Member) : Compar init { this.rand = ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE) - this.trackId = ThreadLocalRandom.current().nextLong(java.lang.Long.MAX_VALUE) + this.trackId = ObjectId() }//It's ok to set a non-existing channelId, since inside the AudioTrackContext, the channel needs to be looked up // every time. See the getTextChannel() below for doing that. diff --git a/FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt b/FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt index 30810e90a..b42d671af 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt @@ -25,6 +25,8 @@ package fredboat.audio.queue +import org.bson.types.ObjectId + interface ITrackProvider { /** @@ -98,7 +100,7 @@ interface ITrackProvider { /** * @param trackIds tracks to be removed from the queue */ - fun removeAllById(trackIds: Collection) + fun removeAllById(trackIds: Collection) /** * @param index the index of the requested track in playing order @@ -126,6 +128,6 @@ interface ITrackProvider { /** * @return false if any of the provided tracks was added by user that is not the provided userId */ - fun isUserTrackOwner(userId: Long, trackIds: Collection): Boolean + fun isUserTrackOwner(userId: Long, trackIds: Collection): Boolean } diff --git a/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt b/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt index ce6778df0..dd7a025b8 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt @@ -26,6 +26,7 @@ package fredboat.audio.queue import fredboat.definitions.RepeatMode +import org.bson.types.ObjectId import java.util.* import java.util.concurrent.ConcurrentLinkedQueue @@ -128,7 +129,7 @@ class SimpleTrackProvider : AbstractTrackProvider() { } } - override fun removeAllById(trackIds: Collection) { + override fun removeAllById(trackIds: Collection) { queue.removeIf { audioTrackContext -> trackIds.contains(audioTrackContext.trackId) } shouldUpdateShuffledQueue = true } @@ -223,7 +224,7 @@ class SimpleTrackProvider : AbstractTrackProvider() { } } - override fun isUserTrackOwner(userId: Long, trackIds: Collection): Boolean { + override fun isUserTrackOwner(userId: Long, trackIds: Collection): Boolean { for (atc in asListOrdered) { if (trackIds.contains(atc.trackId) && atc.userId != userId) { return false diff --git a/FredBoat/src/main/java/fredboat/command/music/control/SkipCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/SkipCommand.kt index 5b13bc4e2..12a5fa334 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/SkipCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/SkipCommand.kt @@ -40,6 +40,7 @@ import fredboat.sentinel.Member import fredboat.util.TextUtils import fredboat.util.extension.escapeAndDefuse import org.apache.commons.lang3.StringUtils +import org.bson.types.ObjectId import java.util.* import java.util.regex.Pattern @@ -171,7 +172,7 @@ class SkipCommand(name: String, vararg aliases: String) : Command(name, *aliases } val listAtc = player.getTracksInRange(0, player.trackCount) - val userAtcIds = ArrayList() + val userAtcIds = ArrayList() val affectedUsers = ArrayList() members.forEach { From 29a0f4c9b19c39b426998fb34767c5404efac75f Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Wed, 14 Nov 2018 14:38:39 +0100 Subject: [PATCH 002/172] Add saving of player state --- .../src/main/java/fredboat/db/mongo/player.kt | 28 +++++++++++++++++++ .../java/fredboat/db/mongo/repositories.kt | 27 ++++++++++++++++++ build.gradle | 2 +- 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 FredBoat/src/main/java/fredboat/db/mongo/player.kt create mode 100644 FredBoat/src/main/java/fredboat/db/mongo/repositories.kt diff --git a/FredBoat/src/main/java/fredboat/db/mongo/player.kt b/FredBoat/src/main/java/fredboat/db/mongo/player.kt new file mode 100644 index 000000000..167ae046c --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/mongo/player.kt @@ -0,0 +1,28 @@ +package fredboat.db.mongo + +import org.bson.types.ObjectId +import org.springframework.data.annotation.Id +import org.springframework.data.mongodb.core.mapping.Document + +@Document +class MongoPlayer( + @Id + val gid: Long, + val paused: Boolean, + val shuffled: Boolean, + /** [fredboat.definitions.RepeatMode] ordinal */ + val repeat: Byte, + /** 0-1 */ + val volume: Float, + /** Time of the playing track at the time of saving in milliseconds */ + val position: Long?, + /** Name of Lavalink node. Used for resuming */ + val node: String?, + val queue: List +) + +class MongoTrack( + val id: ObjectId, + val blob: ByteArray, + val requester: Long +) \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt new file mode 100644 index 000000000..b707aa8e1 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt @@ -0,0 +1,27 @@ +package fredboat.db.mongo + +import fredboat.audio.player.GuildPlayer +import lavalink.client.LavalinkUtil +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import reactor.core.publisher.Mono + +interface PlayerRepository : ReactiveCrudRepository { + + fun save(player: GuildPlayer): Mono { + val mplayer = player.run { MongoPlayer( + guildId, + isPaused, + isShuffle, + repeatMode.ordinal.toByte(), + volume, + playingTrack?.track?.position, + this.player.link?.getNode(false)?.name, + remainingTracks.map { + MongoTrack(it.trackId, LavalinkUtil.toBinary(it.track), it.member.id) + } + )} + return save(mplayer) + } + +} + diff --git a/build.gradle b/build.gradle index 622bb8d3f..db3ee058c 100644 --- a/build.gradle +++ b/build.gradle @@ -64,7 +64,7 @@ subprojects { //audio deps lavaplayerVersion = '1.3.9' - lavalinkVersion = 'cab6c587' + lavalinkVersion = '0fc6ceea' //utility deps jsonOrgVersion = '20180130' From cd05493bd7484db584fc2bb55a268292c051b60e Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Wed, 14 Nov 2018 22:21:19 +0100 Subject: [PATCH 003/172] Add half-done handling of loading players --- .../fredboat/audio/player/PlayerRegistry.kt | 73 +++++++++++++++++-- .../audio/queue/SplitAudioTrackContext.kt | 2 +- .../src/main/java/fredboat/db/mongo/player.kt | 12 ++- .../java/fredboat/db/mongo/repositories.kt | 11 ++- build.gradle | 2 +- 5 files changed, 90 insertions(+), 10 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index 8f0434db7..2e5471eeb 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -27,24 +27,38 @@ package fredboat.audio.player import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager import fredboat.audio.lavalink.SentinelLavalink +import fredboat.audio.queue.AudioTrackContext +import fredboat.audio.queue.SplitAudioTrackContext +import fredboat.config.property.AppConfig import fredboat.db.api.GuildConfigService +import fredboat.db.mongo.PlayerRepository +import fredboat.definitions.RepeatMode import fredboat.sentinel.Guild import fredboat.util.ratelimit.Ratelimiter import fredboat.util.rest.YoutubeAPI +import lavalink.client.LavalinkUtil import lavalink.client.io.Link.State import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Qualifier import org.springframework.stereotype.Component +import reactor.core.publisher.Mono +import java.io.IOException import java.util.concurrent.ConcurrentHashMap import java.util.function.BiConsumer import kotlin.streams.toList @Component -class PlayerRegistry(private val musicTextChannelProvider: MusicTextChannelProvider, - private val guildConfigService: GuildConfigService, private val lavalink: SentinelLavalink, - @param:Qualifier("loadAudioPlayerManager") val audioPlayerManager: AudioPlayerManager, - private val ratelimiter: Ratelimiter, private val youtubeAPI: YoutubeAPI) { +class PlayerRegistry( + private val musicTextChannelProvider: MusicTextChannelProvider, + private val guildConfigService: GuildConfigService, + private val sentinelLavalink: SentinelLavalink, + @param:Qualifier("loadAudioPlayerManager") val audioPlayerManager: AudioPlayerManager, + private val ratelimiter: Ratelimiter, + private val youtubeAPI: YoutubeAPI, + private val playerRepo: PlayerRepository, + private val appConfig: AppConfig +) { companion object { const val DEFAULT_VOLUME = 1f @@ -70,7 +84,7 @@ class PlayerRegistry(private val musicTextChannelProvider: MusicTextChannelProvi fun getOrCreate(guild: Guild): GuildPlayer { return registry.computeIfAbsent( guild.id) { - val p = GuildPlayer(lavalink, guild, musicTextChannelProvider, audioPlayerManager, guildConfigService, + val p = GuildPlayer(sentinelLavalink, guild, musicTextChannelProvider, audioPlayerManager, guildConfigService, ratelimiter, youtubeAPI) p.volume = DEFAULT_VOLUME p @@ -117,4 +131,53 @@ class PlayerRegistry(private val musicTextChannelProvider: MusicTextChannelProvi .count() } } + + // TODO: Debounce + fun createPlayer(guild: Guild): Mono { + val player = GuildPlayer( + sentinelLavalink, + guild, + musicTextChannelProvider, + audioPlayerManager, + guildConfigService, + ratelimiter, + youtubeAPI + ) + + return playerRepo.findById(guild.id) + .map { + player.setPause(it.paused) + player.isShuffle = it.shuffled + player.repeatMode = RepeatMode.values()[it.repeat.toInt()] + + if (appConfig.distribution.volumeSupported()) { + player.volume = it.volume + } + + val queue = it.queue.mapNotNull { track -> + try { + val at = LavalinkUtil.toAudioTrack(track.blob) + val member = guild.getMember(track.requester) ?: guild.selfMember + if (track.startTime != null && track.endTime != null) { + SplitAudioTrackContext(at, member, track.startTime, track.endTime, track.title) + } else { + AudioTrackContext(at, member) + } + } catch (e: IOException) { + log.error("Exception loading track", e) + null + } + } + + // Optionally set current track position + if (it.position != null && queue.isNotEmpty()) { + queue[0].track.position = it.position + } + + player.loadAll(queue) + + player + }.defaultIfEmpty(player) + } + } diff --git a/FredBoat/src/main/java/fredboat/audio/queue/SplitAudioTrackContext.kt b/FredBoat/src/main/java/fredboat/audio/queue/SplitAudioTrackContext.kt index 870ffcdba..cf1f0ec23 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/SplitAudioTrackContext.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/SplitAudioTrackContext.kt @@ -34,7 +34,7 @@ class SplitAudioTrackContext( at: AudioTrack, member: Member, override val startPosition: Long, - private val endPosition: Long, + val endPosition: Long, override val effectiveTitle: String ) : AudioTrackContext(at, member) { diff --git a/FredBoat/src/main/java/fredboat/db/mongo/player.kt b/FredBoat/src/main/java/fredboat/db/mongo/player.kt index 167ae046c..75b229b4b 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/player.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/player.kt @@ -2,12 +2,17 @@ package fredboat.db.mongo import org.bson.types.ObjectId import org.springframework.data.annotation.Id +import org.springframework.data.mongodb.core.index.Indexed import org.springframework.data.mongodb.core.mapping.Document @Document class MongoPlayer( @Id val gid: Long, + /** If the player was playing at the time of saving, + * it may automatically be reloaded when we start the bot */ + @Indexed + val playing: Boolean, val paused: Boolean, val shuffled: Boolean, /** [fredboat.definitions.RepeatMode] ordinal */ @@ -24,5 +29,10 @@ class MongoPlayer( class MongoTrack( val id: ObjectId, val blob: ByteArray, - val requester: Long + val requester: Long, + /** Used by split tracks */ + val startTime: Long?, + /** Used by split tracks */ + val endTime: Long?, + val title: String ) \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt index b707aa8e1..6d7c99fe4 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt @@ -1,25 +1,32 @@ package fredboat.db.mongo import fredboat.audio.player.GuildPlayer +import fredboat.audio.queue.SplitAudioTrackContext import lavalink.client.LavalinkUtil import org.springframework.data.repository.reactive.ReactiveCrudRepository import reactor.core.publisher.Mono -interface PlayerRepository : ReactiveCrudRepository { +interface PlayerRepository : ReactiveCrudRepository { fun save(player: GuildPlayer): Mono { val mplayer = player.run { MongoPlayer( guildId, isPaused, isShuffle, + isPlaying, repeatMode.ordinal.toByte(), volume, playingTrack?.track?.position, this.player.link?.getNode(false)?.name, remainingTracks.map { - MongoTrack(it.trackId, LavalinkUtil.toBinary(it.track), it.member.id) + if (it is SplitAudioTrackContext) { + MongoTrack(it.trackId, LavalinkUtil.toBinary(it.track), it.member.id, it.startPosition, it.endPosition, it.effectiveTitle) + } else { + MongoTrack(it.trackId, LavalinkUtil.toBinary(it.track), it.member.id, null, null, it.effectiveTitle) + } } )} + return save(mplayer) } diff --git a/build.gradle b/build.gradle index db3ee058c..bfaa59466 100644 --- a/build.gradle +++ b/build.gradle @@ -64,7 +64,7 @@ subprojects { //audio deps lavaplayerVersion = '1.3.9' - lavalinkVersion = '0fc6ceea' + lavalinkVersion = '0bb07223' //utility deps jsonOrgVersion = '20180130' From 10dbc1e96658f866e9c9edd664fe2eafefccbcf9 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Thu, 15 Nov 2018 13:34:57 +0100 Subject: [PATCH 004/172] Apply a mono cache pattern to player loading --- .../java/fredboat/audio/player/PlayerRegistry.kt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index 2e5471eeb..42d99f250 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -66,8 +66,8 @@ class PlayerRegistry( } private val registry = ConcurrentHashMap() - private val iteratorLock = Any() //iterators, which are also used by stream(), need to be synced, despite it being a concurrent map + private val monoCache = ConcurrentHashMap>() /** * @return a copied list of the the playing players of the registry. This may be an expensive operation depending on @@ -132,8 +132,12 @@ class PlayerRegistry( } } - // TODO: Debounce - fun createPlayer(guild: Guild): Mono { + /** + * @return a [Mono] with a fully loaded [GuildPlayer], po + * + */ + @Suppress("RedundantLambdaArrow") + private fun createPlayer(guild: Guild): Mono = monoCache.computeIfAbsent(guild.id) { _ -> val player = GuildPlayer( sentinelLavalink, guild, @@ -144,7 +148,7 @@ class PlayerRegistry( youtubeAPI ) - return playerRepo.findById(guild.id) + playerRepo.findById(guild.id) .map { player.setPause(it.paused) player.isShuffle = it.shuffled @@ -178,6 +182,8 @@ class PlayerRegistry( player }.defaultIfEmpty(player) + }.doFinally { + monoCache.remove(guild.id) } } From f805b532145c1df84bde0f801727647984893f48 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Thu, 15 Nov 2018 14:30:47 +0100 Subject: [PATCH 005/172] Make GuildPlayers load from mongo data --- .../fredboat/audio/player/PlayerRegistry.kt | 18 +-- .../fredboat/command/admin/EvalCommand.kt | 2 +- .../fredboat/command/info/DebugCommand.kt | 3 +- .../command/music/control/JoinCommand.kt | 2 +- .../command/music/control/PauseCommand.java | 71 ------------ .../command/music/control/PauseCommand.kt | 57 ++++++++++ .../command/music/control/PlayCommand.kt | 4 +- .../command/music/control/PlaySplitCommand.kt | 2 +- .../command/music/control/RepeatCommand.java | 104 ------------------ .../command/music/control/RepeatCommand.kt | 78 +++++++++++++ .../command/music/control/SelectCommand.kt | 4 +- .../command/music/control/ShuffleCommand.java | 69 ------------ .../command/music/control/ShuffleCommand.kt | 55 +++++++++ .../command/music/control/VolumeCommand.java | 90 --------------- .../command/music/control/VolumeCommand.kt | 78 +++++++++++++ .../fredboat/event/MusicPersistenceHandler.kt | 2 +- .../fredboat/event/ShardLifecycleHandler.kt | 7 +- 17 files changed, 290 insertions(+), 356 deletions(-) delete mode 100644 FredBoat/src/main/java/fredboat/command/music/control/PauseCommand.java create mode 100644 FredBoat/src/main/java/fredboat/command/music/control/PauseCommand.kt delete mode 100644 FredBoat/src/main/java/fredboat/command/music/control/RepeatCommand.java create mode 100644 FredBoat/src/main/java/fredboat/command/music/control/RepeatCommand.kt delete mode 100644 FredBoat/src/main/java/fredboat/command/music/control/ShuffleCommand.java create mode 100644 FredBoat/src/main/java/fredboat/command/music/control/ShuffleCommand.kt delete mode 100644 FredBoat/src/main/java/fredboat/command/music/control/VolumeCommand.java create mode 100644 FredBoat/src/main/java/fredboat/command/music/control/VolumeCommand.kt diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index 42d99f250..d7b5e0531 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -36,6 +36,7 @@ import fredboat.definitions.RepeatMode import fredboat.sentinel.Guild import fredboat.util.ratelimit.Ratelimiter import fredboat.util.rest.YoutubeAPI +import kotlinx.coroutines.experimental.reactive.awaitFirst import lavalink.client.LavalinkUtil import lavalink.client.io.Link.State import org.slf4j.Logger @@ -61,7 +62,6 @@ class PlayerRegistry( ) { companion object { - const val DEFAULT_VOLUME = 1f private val log: Logger = LoggerFactory.getLogger(PlayerRegistry::class.java) } @@ -81,16 +81,16 @@ class PlayerRegistry( .toList() } - fun getOrCreate(guild: Guild): GuildPlayer { - return registry.computeIfAbsent( - guild.id) { - val p = GuildPlayer(sentinelLavalink, guild, musicTextChannelProvider, audioPlayerManager, guildConfigService, - ratelimiter, youtubeAPI) - p.volume = DEFAULT_VOLUME - p - } + fun getOrCreate(guild: Guild): Mono { + val player = registry[guild.id] ?: return createPlayer(guild) + return Mono.just(player) } + /** + * Get or create a guild in a suspending fashion + */ + suspend fun awaitPlayer(guild: Guild) = getOrCreate(guild).awaitFirst() + fun getExisting(guild: Guild): GuildPlayer? { return getExisting(guild.id) } diff --git a/FredBoat/src/main/java/fredboat/command/admin/EvalCommand.kt b/FredBoat/src/main/java/fredboat/command/admin/EvalCommand.kt index 779708b8d..038aa1313 100644 --- a/FredBoat/src/main/java/fredboat/command/admin/EvalCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/admin/EvalCommand.kt @@ -96,7 +96,7 @@ class EvalCommand( engine.put("sentinel", guild.sentinel) engine.put("channel", textChannel) engine.put("tc", textChannel) - val player = Launcher.botController.playerRegistry.getOrCreate(guild) + val player = Launcher.botController.playerRegistry.awaitPlayer(guild) engine.put("player", player) engine.put("players", Launcher.botController.playerRegistry) engine.put("link", (player.player as LavalinkPlayer?)?.link) diff --git a/FredBoat/src/main/java/fredboat/command/info/DebugCommand.kt b/FredBoat/src/main/java/fredboat/command/info/DebugCommand.kt index 0fda6cbcb..353a69ee7 100644 --- a/FredBoat/src/main/java/fredboat/command/info/DebugCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/info/DebugCommand.kt @@ -41,7 +41,6 @@ import fredboat.perms.PermsUtil import fredboat.sentinel.Guild import fredboat.sentinel.getGuild import fredboat.util.extension.asCodeBlock -import lavalink.client.player.LavalinkPlayer class DebugCommand(name: String, vararg aliases: String) : Command(name, *aliases), IInfoCommand, ICommandRestricted { @@ -64,7 +63,7 @@ class DebugCommand(name: String, vararg aliases: String) : Command(name, *aliase } if (guild != null) { - context.reply(getDebugEmbed(Launcher.botController.playerRegistry.getOrCreate(guild))) + context.reply(getDebugEmbed(Launcher.botController.playerRegistry.awaitPlayer(guild))) } else { context.replyWithName(String.format("There is no guild with id `%s`.", context.args[0])) } diff --git a/FredBoat/src/main/java/fredboat/command/music/control/JoinCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/JoinCommand.kt index 79cfff908..ea396450c 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/JoinCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/JoinCommand.kt @@ -39,7 +39,7 @@ class JoinCommand(name: String, vararg aliases: String) : Command(name, *aliases get() = PermissionLevel.USER override suspend fun invoke(context: CommandContext) { - val player = Launcher.botController.playerRegistry.getOrCreate(context.guild) + val player = Launcher.botController.playerRegistry.awaitPlayer(context.guild) val vc = context.member.voiceChannel try { diff --git a/FredBoat/src/main/java/fredboat/command/music/control/PauseCommand.java b/FredBoat/src/main/java/fredboat/command/music/control/PauseCommand.java deleted file mode 100644 index d9a5a4572..000000000 --- a/FredBoat/src/main/java/fredboat/command/music/control/PauseCommand.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package fredboat.command.music.control; - -import fredboat.audio.player.GuildPlayer; -import fredboat.commandmeta.abs.CommandContext; -import fredboat.commandmeta.abs.ICommandRestricted; -import fredboat.commandmeta.abs.IMusicCommand; -import fredboat.commandmeta.abs.JCommand; -import fredboat.definitions.PermissionLevel; -import fredboat.messaging.internal.Context; -import fredboat.util.TextUtils; - -import javax.annotation.Nonnull; - -import static fredboat.main.LauncherKt.getBotController; - -public class PauseCommand extends JCommand implements IMusicCommand, ICommandRestricted { - - public PauseCommand(String name, String... aliases) { - super(name, aliases); - } - - @Override - public void onInvoke(@Nonnull CommandContext context) { - GuildPlayer player = getBotController().getPlayerRegistry().getOrCreate(context.getGuild()); - if (player.isQueueEmpty()) { - context.reply(context.i18n("playQueueEmpty")); - } else if (player.isPaused()) { - context.reply(context.i18n("pauseAlreadyPaused")); - } else { - player.pause(); - context.reply(context.i18nFormat("pauseSuccess", TextUtils.escapeMarkdown(context.getPrefix()))); - } - } - - @Nonnull - @Override - public String help(@Nonnull Context context) { - return "{0}{1}\n#" + context.i18n("helpPauseCommand"); - } - - @Nonnull - @Override - public PermissionLevel getMinimumPerms() { - return PermissionLevel.DJ; - } -} diff --git a/FredBoat/src/main/java/fredboat/command/music/control/PauseCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/PauseCommand.kt new file mode 100644 index 000000000..66afcf051 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/command/music/control/PauseCommand.kt @@ -0,0 +1,57 @@ +/* + * MIT License + * + * Copyright (c) 2017 Frederik Ar. Mikkelsen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package fredboat.command.music.control + +import fredboat.commandmeta.abs.Command +import fredboat.commandmeta.abs.CommandContext +import fredboat.commandmeta.abs.ICommandRestricted +import fredboat.commandmeta.abs.IMusicCommand +import fredboat.definitions.PermissionLevel +import fredboat.main.getBotController +import fredboat.messaging.internal.Context +import fredboat.util.TextUtils + +class PauseCommand(name: String, vararg aliases: String) : Command(name, *aliases), IMusicCommand, ICommandRestricted { + + override val minimumPerms: PermissionLevel + get() = PermissionLevel.DJ + + override suspend fun invoke(context: CommandContext) { + val player = getBotController().playerRegistry.awaitPlayer(context.guild) + when { + player.isQueueEmpty -> context.reply(context.i18n("playQueueEmpty")) + player.isPaused -> context.reply(context.i18n("pauseAlreadyPaused")) + else -> { + player.pause() + context.reply(context.i18nFormat("pauseSuccess", TextUtils.escapeMarkdown(context.prefix))) + } + } + } + + override fun help(context: Context): String { + return "{0}{1}\n#" + context.i18n("helpPauseCommand") + } +} diff --git a/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt index 83bed65ae..f0630d1a3 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt @@ -62,7 +62,7 @@ class PlayCommand(private val playerLimiter: PlayerLimiter, private val trackSea if (!playerLimiter.checkLimitResponsive(context, Launcher.botController.playerRegistry)) return if (!context.msg.attachments.isEmpty()) { - val player = Launcher.botController.playerRegistry.getOrCreate(context.guild) + val player = Launcher.botController.playerRegistry.awaitPlayer(context.guild) for (atc in context.msg.attachments) { player.queue(atc, context) @@ -94,7 +94,7 @@ class PlayCommand(private val playerLimiter: PlayerLimiter, private val trackSea url = url.replaceFirst(FILE_PREFIX.toRegex(), "") //LocalAudioSourceManager does not manage this itself } - val player = Launcher.botController.playerRegistry.getOrCreate(context.guild) + val player = Launcher.botController.playerRegistry.awaitPlayer(context.guild) player.queue(url, context) player.setPause(false) diff --git a/FredBoat/src/main/java/fredboat/command/music/control/PlaySplitCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/PlaySplitCommand.kt index d05debb6b..efed26fa9 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/PlaySplitCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/PlaySplitCommand.kt @@ -55,7 +55,7 @@ class PlaySplitCommand(private val playerLimiter: PlayerLimiter, name: String, v val ic = IdentifierContext(context.args[0], context.textChannel, context.member) ic.isSplit = true - val player = playerRegistry.getOrCreate(context.guild) + val player = playerRegistry.awaitPlayer(context.guild) player.queue(ic) player.setPause(false) diff --git a/FredBoat/src/main/java/fredboat/command/music/control/RepeatCommand.java b/FredBoat/src/main/java/fredboat/command/music/control/RepeatCommand.java deleted file mode 100644 index cd224840b..000000000 --- a/FredBoat/src/main/java/fredboat/command/music/control/RepeatCommand.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package fredboat.command.music.control; - -import fredboat.command.info.HelpCommand; -import fredboat.commandmeta.abs.CommandContext; -import fredboat.commandmeta.abs.ICommandRestricted; -import fredboat.commandmeta.abs.IMusicCommand; -import fredboat.commandmeta.abs.JCommand; -import fredboat.definitions.PermissionLevel; -import fredboat.definitions.RepeatMode; -import fredboat.messaging.internal.Context; - -import javax.annotation.Nonnull; - -import static fredboat.main.LauncherKt.getBotController; - -public class RepeatCommand extends JCommand implements IMusicCommand, ICommandRestricted { - - public RepeatCommand(String name, String... aliases) { - super(name, aliases); - } - - @Override - public void onInvoke(@Nonnull CommandContext context) { - if (!context.hasArguments()) { - HelpCommand.sendFormattedCommandHelp(context); - return; - } - - RepeatMode desiredRepeatMode; - String userInput = context.getArgs()[0]; - switch (userInput) { - case "off": - case "out": - desiredRepeatMode = RepeatMode.OFF; - break; - case "single": - case "one": - case "track": - desiredRepeatMode = RepeatMode.SINGLE; - break; - case "all": - case "list": - case "queue": - desiredRepeatMode = RepeatMode.ALL; - break; - case "help": - default: - HelpCommand.sendFormattedCommandHelp(context); - return; - } - - getBotController().getPlayerRegistry().getOrCreate(context.getGuild()).setRepeatMode(desiredRepeatMode); - - switch (desiredRepeatMode) { - case OFF: - context.reply(context.i18n("repeatOff")); - break; - case SINGLE: - context.reply(context.i18n("repeatOnSingle")); - break; - case ALL: - context.reply(context.i18n("repeatOnAll")); - break; - } - } - - @Nonnull - @Override - public String help(@Nonnull Context context) { - String usage = "{0}{1} single|all|off\n#"; - return usage + context.i18n("helpRepeatCommand"); - } - - @Nonnull - @Override - public PermissionLevel getMinimumPerms() { - return PermissionLevel.DJ; - } -} diff --git a/FredBoat/src/main/java/fredboat/command/music/control/RepeatCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/RepeatCommand.kt new file mode 100644 index 000000000..48c27eb23 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/command/music/control/RepeatCommand.kt @@ -0,0 +1,78 @@ +/* + * MIT License + * + * Copyright (c) 2017 Frederik Ar. Mikkelsen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package fredboat.command.music.control + +import fredboat.command.info.HelpCommand +import fredboat.commandmeta.abs.Command +import fredboat.commandmeta.abs.CommandContext +import fredboat.commandmeta.abs.ICommandRestricted +import fredboat.commandmeta.abs.IMusicCommand +import fredboat.definitions.PermissionLevel +import fredboat.definitions.RepeatMode +import fredboat.main.getBotController +import fredboat.messaging.internal.Context + +class RepeatCommand(name: String, vararg aliases: String) : Command(name, *aliases), IMusicCommand, ICommandRestricted { + + override val minimumPerms: PermissionLevel + get() = PermissionLevel.DJ + + override suspend fun invoke(context: CommandContext) { + if (!context.hasArguments()) { + HelpCommand.sendFormattedCommandHelp(context) + return + } + + val desiredRepeatMode: RepeatMode + val userInput = context.args[0] + desiredRepeatMode = when (userInput) { + "off", "out" -> RepeatMode.OFF + "single", "one", "track" -> RepeatMode.SINGLE + "all", "list", "queue" -> RepeatMode.ALL + "help" -> { + HelpCommand.sendFormattedCommandHelp(context) + return + } + else -> { + HelpCommand.sendFormattedCommandHelp(context) + return + } + } + + getBotController().playerRegistry.awaitPlayer(context.guild).repeatMode = desiredRepeatMode + + when (desiredRepeatMode) { + RepeatMode.OFF -> context.reply(context.i18n("repeatOff")) + RepeatMode.SINGLE -> context.reply(context.i18n("repeatOnSingle")) + RepeatMode.ALL -> context.reply(context.i18n("repeatOnAll")) + } + } + + override fun help(context: Context): String { + val usage = "{0}{1} single|all|off\n#" + return usage + context.i18n("helpRepeatCommand") + } +} diff --git a/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.kt index 78152aa57..3d49b5927 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.kt @@ -55,7 +55,7 @@ class SelectCommand(private val videoSelectionCache: VideoSelectionCache, name: } companion object { - internal fun select(context: CommandContext, videoSelectionCache: VideoSelectionCache) { + internal suspend fun select(context: CommandContext, videoSelectionCache: VideoSelectionCache) { val invoker = context.member val selection = videoSelectionCache[invoker] if (selection == null) { @@ -103,7 +103,7 @@ class SelectCommand(private val videoSelectionCache: VideoSelectionCache, name: } val selectedTracks = arrayOfNulls(validChoices.size) val outputMsgBuilder = StringBuilder() - val player = Launcher.botController.playerRegistry.getOrCreate(context.guild) + val player = Launcher.botController.playerRegistry.awaitPlayer(context.guild) for (i in validChoices.indices) { selectedTracks[i] = selection.choices[validChoices[i] - 1] diff --git a/FredBoat/src/main/java/fredboat/command/music/control/ShuffleCommand.java b/FredBoat/src/main/java/fredboat/command/music/control/ShuffleCommand.java deleted file mode 100644 index bd39bb0b3..000000000 --- a/FredBoat/src/main/java/fredboat/command/music/control/ShuffleCommand.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package fredboat.command.music.control; - -import fredboat.audio.player.GuildPlayer; -import fredboat.commandmeta.abs.CommandContext; -import fredboat.commandmeta.abs.ICommandRestricted; -import fredboat.commandmeta.abs.IMusicCommand; -import fredboat.commandmeta.abs.JCommand; -import fredboat.definitions.PermissionLevel; -import fredboat.messaging.internal.Context; - -import javax.annotation.Nonnull; - -import static fredboat.main.LauncherKt.getBotController; - -public class ShuffleCommand extends JCommand implements IMusicCommand, ICommandRestricted { - - public ShuffleCommand(String name, String... aliases) { - super(name, aliases); - } - - @Override - public void onInvoke(@Nonnull CommandContext context) { - GuildPlayer player = getBotController().getPlayerRegistry().getOrCreate(context.getGuild()); - player.setShuffle(!player.isShuffle()); - - if (player.isShuffle()) { - context.reply(context.i18n("shuffleOn")); - } else { - context.reply(context.i18n("shuffleOff")); - } - } - - @Nonnull - @Override - public String help(@Nonnull Context context) { - return "{0}{1}\n#" + context.i18n("helpShuffleCommand"); - } - - @Nonnull - @Override - public PermissionLevel getMinimumPerms() { - return PermissionLevel.DJ; - } -} diff --git a/FredBoat/src/main/java/fredboat/command/music/control/ShuffleCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/ShuffleCommand.kt new file mode 100644 index 000000000..ea3213909 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/command/music/control/ShuffleCommand.kt @@ -0,0 +1,55 @@ +/* + * MIT License + * + * Copyright (c) 2017 Frederik Ar. Mikkelsen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package fredboat.command.music.control + +import fredboat.commandmeta.abs.Command +import fredboat.commandmeta.abs.CommandContext +import fredboat.commandmeta.abs.ICommandRestricted +import fredboat.commandmeta.abs.IMusicCommand +import fredboat.definitions.PermissionLevel +import fredboat.main.getBotController +import fredboat.messaging.internal.Context + +class ShuffleCommand(name: String, vararg aliases: String) : Command(name, *aliases), IMusicCommand, ICommandRestricted { + + override val minimumPerms: PermissionLevel + get() = PermissionLevel.DJ + + override suspend fun invoke(context: CommandContext) { + val player = getBotController().playerRegistry.awaitPlayer(context.guild) + player.isShuffle = !player.isShuffle + + if (player.isShuffle) { + context.reply(context.i18n("shuffleOn")) + } else { + context.reply(context.i18n("shuffleOff")) + } + } + + override fun help(context: Context): String { + return "{0}{1}\n#" + context.i18n("helpShuffleCommand") + } +} diff --git a/FredBoat/src/main/java/fredboat/command/music/control/VolumeCommand.java b/FredBoat/src/main/java/fredboat/command/music/control/VolumeCommand.java deleted file mode 100644 index d16e25347..000000000 --- a/FredBoat/src/main/java/fredboat/command/music/control/VolumeCommand.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package fredboat.command.music.control; - -import fredboat.audio.player.GuildPlayer; -import fredboat.audio.player.PlayerRegistry; -import fredboat.commandmeta.MessagingException; -import fredboat.commandmeta.abs.CommandContext; -import fredboat.commandmeta.abs.ICommandRestricted; -import fredboat.commandmeta.abs.IMusicCommand; -import fredboat.commandmeta.abs.JCommand; -import fredboat.definitions.PermissionLevel; -import fredboat.messaging.internal.Context; -import fredboat.shared.constant.BotConstants; - -import javax.annotation.Nonnull; -import java.time.Duration; - -import static fredboat.main.LauncherKt.getBotController; - -public class VolumeCommand extends JCommand implements IMusicCommand, ICommandRestricted { - - public VolumeCommand(String name, String... aliases) { - super(name, aliases); - } - - @Override - public void onInvoke(@Nonnull CommandContext context) { - - if (getBotController().getAppConfig().getDistribution().volumeSupported()) { - - GuildPlayer player = getBotController().getPlayerRegistry().getOrCreate(context.getGuild()); - try { - float volume = Float.parseFloat(context.getArgs()[0]) / 100; - volume = Math.max(0, Math.min(1.5f, volume)); - - context.reply(context.i18nFormat("volumeSuccess", - Math.floor(player.getVolume() * 100), Math.floor(volume * 100))); - - player.setVolume(volume); - } catch (NumberFormatException | ArrayIndexOutOfBoundsException ex) { - throw new MessagingException(context.i18nFormat("volumeSyntax", - 100 * PlayerRegistry.DEFAULT_VOLUME, Math.floor(player.getVolume() * 100))); - } - } else { - String out = context.i18n("volumeApology") + "\n<" + BotConstants.DOCS_DONATE_URL + ">"; - context.replyImageMono("https://fred.moe/1vD.png", out) - .subscribe( - (msg) -> context.getTextChannel().deleteMessage(msg.getMessageId()) - .delaySubscription(Duration.ofMinutes(2)) - .subscribe() - ); - } - } - - @Nonnull - @Override - public String help(@Nonnull Context context) { - return "{0}{1} <0-150>\n#" + context.i18n("helpVolumeCommand"); - } - - @Nonnull - @Override - public PermissionLevel getMinimumPerms() { - return PermissionLevel.DJ; - } -} diff --git a/FredBoat/src/main/java/fredboat/command/music/control/VolumeCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/VolumeCommand.kt new file mode 100644 index 000000000..8ae46db7c --- /dev/null +++ b/FredBoat/src/main/java/fredboat/command/music/control/VolumeCommand.kt @@ -0,0 +1,78 @@ +/* + * MIT License + * + * Copyright (c) 2017 Frederik Ar. Mikkelsen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package fredboat.command.music.control + +import fredboat.commandmeta.MessagingException +import fredboat.commandmeta.abs.Command +import fredboat.commandmeta.abs.CommandContext +import fredboat.commandmeta.abs.ICommandRestricted +import fredboat.commandmeta.abs.IMusicCommand +import fredboat.definitions.PermissionLevel +import fredboat.main.getBotController +import fredboat.messaging.internal.Context +import fredboat.shared.constant.BotConstants +import java.time.Duration + +class VolumeCommand(name: String, vararg aliases: String) : Command(name, *aliases), IMusicCommand, ICommandRestricted { + + override val minimumPerms: PermissionLevel + get() = PermissionLevel.DJ + + override suspend fun invoke(context: CommandContext) { + + if (getBotController().appConfig.distribution.volumeSupported()) { + + val player = getBotController().playerRegistry.awaitPlayer(context.guild) + try { + var volume = java.lang.Float.parseFloat(context.args[0]) / 100 + volume = Math.max(0f, Math.min(1.5f, volume)) + + context.reply(context.i18nFormat("volumeSuccess", + Math.floor((player.volume * 100).toDouble()), Math.floor((volume * 100).toDouble()))) + + player.volume = volume + } catch (ex: NumberFormatException) { + throw MessagingException(context.i18nFormat("volumeSyntax", + 100, Math.floor((player.volume * 100).toDouble()))) + } catch (ex: ArrayIndexOutOfBoundsException) { + throw MessagingException(context.i18nFormat("volumeSyntax", 100, Math.floor((player.volume * 100).toDouble()))) + } + + } else { + val out = context.i18n("volumeApology") + "\n<" + BotConstants.DOCS_DONATE_URL + ">" + context.replyImageMono("https://fred.moe/1vD.png", out) + .subscribe { (messageId) -> + context.textChannel.deleteMessage(messageId) + .delaySubscription(Duration.ofMinutes(2)) + .subscribe() + } + } + } + + override fun help(context: Context): String { + return "{0}{1} <0-150>\n#" + context.i18n("helpVolumeCommand") + } +} diff --git a/FredBoat/src/main/java/fredboat/event/MusicPersistenceHandler.kt b/FredBoat/src/main/java/fredboat/event/MusicPersistenceHandler.kt index 3143c0722..bf520e40f 100644 --- a/FredBoat/src/main/java/fredboat/event/MusicPersistenceHandler.kt +++ b/FredBoat/src/main/java/fredboat/event/MusicPersistenceHandler.kt @@ -233,7 +233,7 @@ class MusicPersistenceHandler(private val playerRegistry: PlayerRegistry, privat val repeatMode = data.getEnum(RepeatMode::class.java, "repeatMode") val shuffle = data.getBoolean("shuffle") - val player = playerRegistry.getOrCreate(guild) + val player = playerRegistry.awaitPlayer(guild) if (tc != null) { musicTextChannelProvider.setMusicChannel(tc) diff --git a/FredBoat/src/main/java/fredboat/event/ShardLifecycleHandler.kt b/FredBoat/src/main/java/fredboat/event/ShardLifecycleHandler.kt index 9a60ed175..88f849fb5 100644 --- a/FredBoat/src/main/java/fredboat/event/ShardLifecycleHandler.kt +++ b/FredBoat/src/main/java/fredboat/event/ShardLifecycleHandler.kt @@ -121,9 +121,10 @@ class ShardLifecycleHandler( toRejoin.forEach { ref -> val channel = ref.guild.getVoiceChannel(ref.channelId) ?: return@forEach - val player = playerRegistry.getOrCreate(channel.guild) - channel.connect() - player.play() + playerRegistry.getOrCreate(channel.guild).subscribe { player -> + channel.connect() + player.play() + } } } From 70741a6bec599623b949fe237b6ca1e34c8f92b3 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Thu, 15 Nov 2018 15:44:22 +0100 Subject: [PATCH 006/172] Fix tests --- .../java/fredboat/command/music/SeekingTests.kt | 2 +- .../command/music/control/PlayCommandTest.kt | 17 ++++++++++------- .../java/fredboat/testutil/util/playerUtils.kt | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/FredBoat/src/test/java/fredboat/command/music/SeekingTests.kt b/FredBoat/src/test/java/fredboat/command/music/SeekingTests.kt index 4728b52b7..48f3176a4 100644 --- a/FredBoat/src/test/java/fredboat/command/music/SeekingTests.kt +++ b/FredBoat/src/test/java/fredboat/command/music/SeekingTests.kt @@ -31,7 +31,7 @@ class SeekingTests(private val players: PlayerRegistry) : IntegrationTest(), Ret fun beforeEach() { joinChannel() players.destroyPlayer(cachedGuild) - player = players.getOrCreate(cachedGuild) + player = players.getOrCreate(cachedGuild).block()!! cachedGuild.queue(PlayCommandTest.url) delayUntil { player.playingTrack != null } assertNotNull(player.playingTrack) diff --git a/FredBoat/src/test/java/fredboat/command/music/control/PlayCommandTest.kt b/FredBoat/src/test/java/fredboat/command/music/control/PlayCommandTest.kt index b8a2d161e..ffbe7fb21 100644 --- a/FredBoat/src/test/java/fredboat/command/music/control/PlayCommandTest.kt +++ b/FredBoat/src/test/java/fredboat/command/music/control/PlayCommandTest.kt @@ -72,18 +72,21 @@ internal class PlayCommandTest( assertReply { it.contains("Best of Demetori") && it.contains("will now play") } assertNotNull(players.getExisting(guild)) - delayUntil { players.getOrCreate(guild).playingTrack != null } - assertNotNull(players.getOrCreate(guild).playingTrack) - assertEquals(url, players.getOrCreate(guild).playingTrack?.track?.info?.uri) + delayUntil { players.getOrCreate(guild).block()!!.playingTrack != null } + val track = players.getOrCreate(guild).block()?.playingTrack + assertNotNull(track) + assertEquals(url, track?.track?.info?.uri) } testCommand(";;play $url2") { assertRequest { it.channel == Raws.musicChannel.id } assertReply { it.contains("BEGIERDE DES ZAUBERER") && it.contains("has been added to the queue") } - delayUntil { players.getOrCreate(guild).remainingTracks.size == 2 } - assertNotNull(players.getOrCreate(guild).remainingTracks[1]) - assertEquals(url2, players.getOrCreate(guild).remainingTracks[1].track.info?.uri) + delayUntil { players.getOrCreate(guild).block()!!.remainingTracks.size == 2 } + + val track = players.getOrCreate(guild).block()?.remainingTracks?.get(1) + assertNotNull(track) + assertEquals(url, track?.track?.info?.uri) } } @@ -97,7 +100,7 @@ internal class PlayCommandTest( // We should unpause testCommand(";;play") { delayUntil { players.getExisting(guild)?.isPlaying == true } - assertFalse("Assert unpaused", players.getOrCreate(guild).isPaused) + assertFalse("Assert unpaused", players.awaitPlayer(guild).isPaused) assertReply("The player will now play.") } } diff --git a/FredBoat/src/test/java/fredboat/testutil/util/playerUtils.kt b/FredBoat/src/test/java/fredboat/testutil/util/playerUtils.kt index 76ade27fe..a9a3dc3b2 100644 --- a/FredBoat/src/test/java/fredboat/testutil/util/playerUtils.kt +++ b/FredBoat/src/test/java/fredboat/testutil/util/playerUtils.kt @@ -22,7 +22,7 @@ fun Guild.queue( identifier: String, member: Member = getMember(Raws.owner.id)!! ): GuildPlayer { - val player = Launcher.botController.playerRegistry.getOrCreate(this) + val player = Launcher.botController.playerRegistry.getOrCreate(this).block()!! val track = loadedTracks.getOrPut(identifier) { val loader = TestAudioLoader() Launcher.botController.playerRegistry.audioPlayerManager.loadItem(identifier, loader) From 3b2ff872464ec8724a37edbfc46924d1ba87415b Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 16 Nov 2018 16:29:28 +0100 Subject: [PATCH 007/172] Add mongo service to testing --- FredBoat/src/test/resources/docker-compose.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/FredBoat/src/test/resources/docker-compose.yaml b/FredBoat/src/test/resources/docker-compose.yaml index 5e927f4b8..e00ca521c 100644 --- a/FredBoat/src/test/resources/docker-compose.yaml +++ b/FredBoat/src/test/resources/docker-compose.yaml @@ -5,6 +5,11 @@ services: ports: - 127.0.0.1:5432:5432 + mongo: + image: mongo:latest + ports: + - 127.0.0.1:27017:27017 + quarterdeck: image: fredboat/quarterdeck:dev-v1 depends_on: From 272881b0831e73347eaa1af7d237433ff97cfe1d Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 23 Nov 2018 16:49:54 +0100 Subject: [PATCH 008/172] Add resuming --- .../java/fredboat/audio/lavalink/SentinelLavalink.kt | 9 ++++++--- build.gradle | 6 +++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt index 2e2e95ef0..da324db0d 100644 --- a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt +++ b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt @@ -11,6 +11,7 @@ import lavalink.client.io.metrics.LavalinkCollector import org.json.JSONObject import org.springframework.stereotype.Service +@Suppress("ImplicitThis", "LeakingThis") @Service class SentinelLavalink( val sentinel: Sentinel, @@ -23,13 +24,15 @@ class SentinelLavalink( companion object { lateinit var INSTANCE: SentinelLavalink + private const val DEFAULT_RESUME_TIMEOUT = 300 // 5 mins } init { - @Suppress("LeakingThis") INSTANCE = this - lavalinkConfig.nodes.forEach { addNode(it.name, it.uri, it.password) } - @Suppress("LeakingThis") + lavalinkConfig.nodes.forEach { + addNode(it.name, it.uri, it.password) + .setResuming("fredboat-${sentinel.selfUser.idString}", DEFAULT_RESUME_TIMEOUT) + } LavalinkCollector(this).register() } diff --git a/build.gradle b/build.gradle index bfaa59466..a98c8775c 100644 --- a/build.gradle +++ b/build.gradle @@ -59,12 +59,12 @@ subprojects { //@formatter:off sentinelVersion = 'd8680e86' - springBootVersion = "${springBootVersion}" - kotlinVersion = "${kotlinVersion}" + springBootVersion = springBootVersion + kotlinVersion = kotlinVersion //audio deps lavaplayerVersion = '1.3.9' - lavalinkVersion = '0bb07223' + lavalinkVersion = 'df512a59' //utility deps jsonOrgVersion = '20180130' From eb19a6d84184c6095c1581019f8a6bcfb524b933 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Mon, 26 Nov 2018 17:15:03 +0100 Subject: [PATCH 009/172] Actually set player in registry --- .../src/main/java/fredboat/audio/player/PlayerRegistry.kt | 4 +++- build.gradle | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index d7b5e0531..99d472f06 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -89,7 +89,7 @@ class PlayerRegistry( /** * Get or create a guild in a suspending fashion */ - suspend fun awaitPlayer(guild: Guild) = getOrCreate(guild).awaitFirst() + suspend fun awaitPlayer(guild: Guild): GuildPlayer = getOrCreate(guild).awaitFirst() fun getExisting(guild: Guild): GuildPlayer? { return getExisting(guild.id) @@ -182,6 +182,8 @@ class PlayerRegistry( player }.defaultIfEmpty(player) + }.doOnSuccess { + registry[it.guildId] = it }.doFinally { monoCache.remove(guild.id) } diff --git a/build.gradle b/build.gradle index a98c8775c..9db78951a 100644 --- a/build.gradle +++ b/build.gradle @@ -64,7 +64,7 @@ subprojects { //audio deps lavaplayerVersion = '1.3.9' - lavalinkVersion = 'df512a59' + lavalinkVersion = '54c43f24' //utility deps jsonOrgVersion = '20180130' From 71a23daa5c14702c8626bedd337fd17c0bf69446 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Mon, 26 Nov 2018 17:16:05 +0100 Subject: [PATCH 010/172] Start player creation upon link creation --- .../audio/lavalink/SentinelLavalink.kt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt index da324db0d..87f09fa5f 100644 --- a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt +++ b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt @@ -1,14 +1,18 @@ package fredboat.audio.lavalink import com.fredboat.sentinel.entities.VoiceServerUpdate +import fredboat.audio.player.PlayerRegistry import fredboat.config.idString import fredboat.config.property.AppConfig import fredboat.config.property.LavalinkConfig import fredboat.sentinel.Guild import fredboat.sentinel.Sentinel +import fredboat.sentinel.getGuildMono import lavalink.client.io.Lavalink import lavalink.client.io.metrics.LavalinkCollector import org.json.JSONObject +import org.slf4j.Logger +import org.slf4j.LoggerFactory import org.springframework.stereotype.Service @Suppress("ImplicitThis", "LeakingThis") @@ -16,6 +20,7 @@ import org.springframework.stereotype.Service class SentinelLavalink( val sentinel: Sentinel, val appConfig: AppConfig, + private val playerRegistry: PlayerRegistry, lavalinkConfig: LavalinkConfig ) : Lavalink( sentinel.selfUser.idString, @@ -25,6 +30,7 @@ class SentinelLavalink( companion object { lateinit var INSTANCE: SentinelLavalink private const val DEFAULT_RESUME_TIMEOUT = 300 // 5 mins + private val log: Logger = LoggerFactory.getLogger(SentinelLavalink::class.java) } init { @@ -36,7 +42,17 @@ class SentinelLavalink( LavalinkCollector(this).register() } - override fun buildNewLink(guildId: String) = SentinelLink(this, guildId) + override fun buildNewLink(guildId: String): SentinelLink { + getGuildMono(guildId.toLong()).flatMap { guild -> + if (guild == null) { + log.warn("Built link for non-existing guild. This should not happen.") + return@flatMap null + } + playerRegistry.getOrCreate(guild) + }.subscribe() + + return SentinelLink(this, guildId) + } fun getLink(guild: Guild) = getLink(guild.id.toString()) fun getExistingLink(guild: Guild) = getExistingLink(guild.idString) From 6c0f7f1c79cf0dcf3b8ae9bb3a6ce4f04c31232b Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Tue, 27 Nov 2018 17:50:30 +0100 Subject: [PATCH 011/172] Make LLC provide Sentinel-cached VSU --- .../audio/lavalink/SentinelLavalink.kt | 15 ++++++++--- .../src/main/java/fredboat/db/mongo/player.kt | 4 +-- .../java/fredboat/db/mongo/repositories.kt | 2 +- .../main/java/fredboat/sentinel/guildCache.kt | 25 ------------------- .../java/fredboat/sentinel/wrapperEntities.kt | 21 ++++++++++------ 5 files changed, 28 insertions(+), 39 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt index 87f09fa5f..8efb746a2 100644 --- a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt +++ b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt @@ -6,6 +6,7 @@ import fredboat.config.idString import fredboat.config.property.AppConfig import fredboat.config.property.LavalinkConfig import fredboat.sentinel.Guild +import fredboat.sentinel.InternalGuild import fredboat.sentinel.Sentinel import fredboat.sentinel.getGuildMono import lavalink.client.io.Lavalink @@ -43,13 +44,19 @@ class SentinelLavalink( } override fun buildNewLink(guildId: String): SentinelLink { - getGuildMono(guildId.toLong()).flatMap { guild -> + getGuildMono(guildId.toLong()).subscribe { guild -> if (guild == null) { log.warn("Built link for non-existing guild. This should not happen.") - return@flatMap null + return@subscribe } - playerRegistry.getOrCreate(guild) - }.subscribe() + playerRegistry.getOrCreate(guild).subscribe { player -> + // If FredBoat just restarted, we will want to reconnect the link + @Suppress("LABEL_NAME_CLASH") + val vsu = (guild as InternalGuild).cachedVsu ?: return@subscribe + guild.cachedVsu = null + player.player.link.onVoiceServerUpdate(JSONObject(vsu), vsu.sessionId) + } + } return SentinelLink(this, guildId) } diff --git a/FredBoat/src/main/java/fredboat/db/mongo/player.kt b/FredBoat/src/main/java/fredboat/db/mongo/player.kt index 75b229b4b..a1f8468a7 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/player.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/player.kt @@ -21,8 +21,8 @@ class MongoPlayer( val volume: Float, /** Time of the playing track at the time of saving in milliseconds */ val position: Long?, - /** Name of Lavalink node. Used for resuming */ - val node: String?, + /** Voice channel we were playing in if interrupted */ + val voiceChannel: Long?, val queue: List ) diff --git a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt index 6d7c99fe4..dfddd84fe 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt @@ -17,7 +17,7 @@ interface PlayerRepository : ReactiveCrudRepository { repeatMode.ordinal.toByte(), volume, playingTrack?.track?.position, - this.player.link?.getNode(false)?.name, + currentVoiceChannel?.id, remainingTracks.map { if (it is SplitAudioTrackContext) { MongoTrack(it.trackId, LavalinkUtil.toBinary(it.track), it.member.id, it.startPosition, it.endPosition, it.effectiveTitle) diff --git a/FredBoat/src/main/java/fredboat/sentinel/guildCache.kt b/FredBoat/src/main/java/fredboat/sentinel/guildCache.kt index 343ebbfb6..555a023c7 100644 --- a/FredBoat/src/main/java/fredboat/sentinel/guildCache.kt +++ b/FredBoat/src/main/java/fredboat/sentinel/guildCache.kt @@ -5,7 +5,6 @@ import com.fredboat.sentinel.entities.GuildSubscribeRequest import com.google.common.cache.CacheBuilder import fredboat.audio.lavalink.SentinelLavalink import fredboat.config.property.AppConfig -import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.experimental.reactive.awaitFirstOrNull import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -96,30 +95,6 @@ class GuildCache(private val sentinel: Sentinel, g.roles.size ) - // Asynchronously handle existing VSU from an older FredBoat session, if it exists - it.voiceServerUpdate?.let { vsu -> - launch { - val channelId = g.selfMember.voiceChannel?.idString - - val link = lavalink.getLink(g) - if (channelId == null) { - log.warn("Received voice server update during guild subscribe, but we are not in a channel." + - "This should not happen. Disconnecting...") - link.queueAudioDisconnect() - return@launch - } - - link.setChannel(channelId) - rabbitConsumer.receive(vsu) - /* - // This code is an excellent way to test expired voice server updates - val json = JSONObject(vsu.raw) - json.put("token", "asd") - rabbitConsumer.receive(VoiceServerUpdate(vsu.sessionId, json.toString())) - */ - } - } - return g } diff --git a/FredBoat/src/main/java/fredboat/sentinel/wrapperEntities.kt b/FredBoat/src/main/java/fredboat/sentinel/wrapperEntities.kt index 00bc4956a..6d9ea77aa 100644 --- a/FredBoat/src/main/java/fredboat/sentinel/wrapperEntities.kt +++ b/FredBoat/src/main/java/fredboat/sentinel/wrapperEntities.kt @@ -110,20 +110,26 @@ abstract class Guild(raw: RawGuild) : SentinelEntity { /** Has public members we want to hide */ class InternalGuild(raw: RawGuild) : Guild(raw) { + /** Last time we really needed this [Guild]. + * If this value becomes too old, the [Guild] may be invalidated. + * Refreshed on command invocation + */ + var lastUsed: Long = System.currentTimeMillis() + + /** + * Sentinel caches voice server updates, so that we may restart without renewing it. + * This property may store the voice server update that Sentinel has at the time of subscribing/updating. + */ + var cachedVsu: VoiceServerUpdate? = null + init { update(raw) + cachedVsu = raw.voiceServerUpdate // Must only be set on subscribing, unless setting to null // Any old GuildPlayer needs to be aware of the new guild object val player: GuildPlayer? = getBotController().playerRegistry.getExisting(this) if (player != null) player.guild = this } - - /** Last time we really needed this [Guild]. - * If this value becomes too old, the [Guild] may be invalidated. - * Refreshed on command invocation - */ - var lastUsed: Long = System.currentTimeMillis() - fun update(raw: RawGuild) { if (id != raw.id) throw AmqpRejectAndDontRequeueException("Attempt to update $id with the data of ${raw.id}") @@ -137,6 +143,7 @@ class InternalGuild(raw: RawGuild) : Guild(raw) { val rawOwner = raw.owner _owner = if (rawOwner != null) members[rawOwner] else null + } fun handleMemberAdd(member: RawMember) { From a2c60a97a2491ad4600b015cafce9e2b6d1d148b Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Tue, 27 Nov 2018 18:37:30 +0100 Subject: [PATCH 012/172] Save player state as a shutdown hook --- .../fredboat/audio/player/AbstractPlayer.kt | 1 + .../fredboat/audio/player/PlayerRegistry.kt | 16 +++++- .../java/fredboat/db/mongo/repositories.kt | 55 +++++++++++++------ 3 files changed, 53 insertions(+), 19 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt index 195fc1513..7b34e4c89 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt @@ -119,6 +119,7 @@ abstract class AbstractPlayer internal constructor( get() = player.trackPosition init { + @Suppress("LeakingThis") player.addListener(this) } diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index 99d472f06..9576e2ad6 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -45,8 +45,10 @@ import org.springframework.beans.factory.annotation.Qualifier import org.springframework.stereotype.Component import reactor.core.publisher.Mono import java.io.IOException +import java.time.Duration import java.util.concurrent.ConcurrentHashMap import java.util.function.BiConsumer +import kotlin.concurrent.thread import kotlin.streams.toList @Component @@ -81,6 +83,10 @@ class PlayerRegistry( .toList() } + init { + Runtime.getRuntime().addShutdownHook(thread(start = false) { beforeShutdown() }) + } + fun getOrCreate(guild: Guild): Mono { val player = registry[guild.id] ?: return createPlayer(guild) return Mono.just(player) @@ -134,7 +140,7 @@ class PlayerRegistry( /** * @return a [Mono] with a fully loaded [GuildPlayer], po - * + * */ @Suppress("RedundantLambdaArrow") private fun createPlayer(guild: Guild): Mono = monoCache.computeIfAbsent(guild.id) { _ -> @@ -188,4 +194,12 @@ class PlayerRegistry( monoCache.remove(guild.id) } + private fun beforeShutdown() { + log.info("Running shutdown hook to save player state") + val count = playerRepo.saveAll(registry.values.toList()) + .count() + .block(Duration.ofMinutes(2)) + log.info("Saved $count player states") + } + } diff --git a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt index dfddd84fe..6724a47e5 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt @@ -3,32 +3,51 @@ package fredboat.db.mongo import fredboat.audio.player.GuildPlayer import fredboat.audio.queue.SplitAudioTrackContext import lavalink.client.LavalinkUtil +import org.slf4j.Logger +import org.slf4j.LoggerFactory import org.springframework.data.repository.reactive.ReactiveCrudRepository +import reactor.core.publisher.Flux import reactor.core.publisher.Mono interface PlayerRepository : ReactiveCrudRepository { + companion object { + private val log: Logger = LoggerFactory.getLogger(PlayerRepository::class.java) + } + fun save(player: GuildPlayer): Mono { - val mplayer = player.run { MongoPlayer( - guildId, - isPaused, - isShuffle, - isPlaying, - repeatMode.ordinal.toByte(), - volume, - playingTrack?.track?.position, - currentVoiceChannel?.id, - remainingTracks.map { - if (it is SplitAudioTrackContext) { - MongoTrack(it.trackId, LavalinkUtil.toBinary(it.track), it.member.id, it.startPosition, it.endPosition, it.effectiveTitle) - } else { - MongoTrack(it.trackId, LavalinkUtil.toBinary(it.track), it.member.id, null, null, it.effectiveTitle) - } - } - )} + return save(player.toMongo()) + } - return save(mplayer) + fun saveAll(players: List): Flux { + val entities = players.mapNotNull { + try { + return@mapNotNull it.toMongo() + } catch (e: Exception) { + log.error("Problem saving player state", e) + return@mapNotNull null + } + } + return saveAll(entities) } + private fun GuildPlayer.toMongo() = MongoPlayer( + guildId, + isPaused, + isShuffle, + isPlaying, + repeatMode.ordinal.toByte(), + volume, + playingTrack?.track?.position, + currentVoiceChannel?.id, + remainingTracks.map { + if (it is SplitAudioTrackContext) { + MongoTrack(it.trackId, LavalinkUtil.toBinary(it.track), it.member.id, it.startPosition, it.endPosition, it.effectiveTitle) + } else { + MongoTrack(it.trackId, LavalinkUtil.toBinary(it.track), it.member.id, null, null, it.effectiveTitle) + } + } + ) + } From 48792fc936fc845f1f4314ffc31f53d8cdc2bb91 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Thu, 29 Nov 2018 13:10:03 +0100 Subject: [PATCH 013/172] Give registry shutdown hook a name --- .../src/main/java/fredboat/audio/player/PlayerRegistry.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index 9576e2ad6..e9b8f07a7 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -84,7 +84,9 @@ class PlayerRegistry( } init { - Runtime.getRuntime().addShutdownHook(thread(start = false) { beforeShutdown() }) + Runtime.getRuntime().addShutdownHook( + thread(start = false, name = "player-registry-shutdown") { beforeShutdown() } + ) } fun getOrCreate(guild: Guild): Mono { From 35ad3a4531aa6b72c1493e108e2c6aedcb5f0a21 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 30 Nov 2018 16:38:57 +0100 Subject: [PATCH 014/172] Fix mongodb not loading --- .../src/main/java/fredboat/main/Launcher.kt | 18 ++++++++++-------- .../src/test/resources/docker-compose.yaml | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/main/Launcher.kt b/FredBoat/src/main/java/fredboat/main/Launcher.kt index a6a29e747..e59f1ccfb 100644 --- a/FredBoat/src/main/java/fredboat/main/Launcher.kt +++ b/FredBoat/src/main/java/fredboat/main/Launcher.kt @@ -23,8 +23,8 @@ import org.springframework.context.ApplicationContext import org.springframework.context.ApplicationContextAware import org.springframework.context.ApplicationListener import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.ComponentScan import org.springframework.context.support.AbstractApplicationContext +import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories import java.io.IOException import java.util.concurrent.ExecutorService import java.util.function.Supplier @@ -32,12 +32,14 @@ import java.util.function.Supplier /** * The class responsible for launching FredBoat */ -@SpringBootApplication(exclude = [ // Excluded because we manage these already - DataSourceAutoConfiguration::class, - DataSourceTransactionManagerAutoConfiguration::class, - HibernateJpaAutoConfiguration::class, - FlywayAutoConfiguration::class]) -@ComponentScan(basePackages = ["fredboat"]) +@SpringBootApplication(scanBasePackages = ["fredboat"], + exclude = [ // Excluded because we manage these already + DataSourceAutoConfiguration::class, + DataSourceTransactionManagerAutoConfiguration::class, + HibernateJpaAutoConfiguration::class, + FlywayAutoConfiguration::class] +) +@EnableReactiveMongoRepositories("fredboat.db.mongo") class Launcher( botController: BotController, private val configProvider: ConfigPropertiesProvider, @@ -67,7 +69,7 @@ class Launcher( } //Check imgur creds - executor.submit{ this.hasValidImgurCredentials() } + executor.submit { this.hasValidImgurCredentials() } FredBoatAgent.start(statsAgent) FredBoatAgent.start(invalidationAgent) diff --git a/FredBoat/src/test/resources/docker-compose.yaml b/FredBoat/src/test/resources/docker-compose.yaml index e00ca521c..169b851b0 100644 --- a/FredBoat/src/test/resources/docker-compose.yaml +++ b/FredBoat/src/test/resources/docker-compose.yaml @@ -8,7 +8,7 @@ services: mongo: image: mongo:latest ports: - - 127.0.0.1:27017:27017 + - 127.0.0.1:27017:27017 quarterdeck: image: fredboat/quarterdeck:dev-v1 From bd11f158a7a6b837d796e8368ccdd22289bfd121 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 30 Nov 2018 16:39:12 +0100 Subject: [PATCH 015/172] Fix recursion --- .../audio/lavalink/SentinelLavalink.kt | 3 +- .../fredboat/audio/player/PlayerRegistry.kt | 97 +++++++++++-------- 2 files changed, 60 insertions(+), 40 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt index 8efb746a2..17059f9e6 100644 --- a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt +++ b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt @@ -21,13 +21,14 @@ import org.springframework.stereotype.Service class SentinelLavalink( val sentinel: Sentinel, val appConfig: AppConfig, - private val playerRegistry: PlayerRegistry, lavalinkConfig: LavalinkConfig ) : Lavalink( sentinel.selfUser.idString, appConfig.shardCount ) { + lateinit var playerRegistry: PlayerRegistry + companion object { lateinit var INSTANCE: SentinelLavalink private const val DEFAULT_RESUME_TIMEOUT = 300 // 5 mins diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index e9b8f07a7..3a76ec638 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -31,6 +31,7 @@ import fredboat.audio.queue.AudioTrackContext import fredboat.audio.queue.SplitAudioTrackContext import fredboat.config.property.AppConfig import fredboat.db.api.GuildConfigService +import fredboat.db.mongo.MongoPlayer import fredboat.db.mongo.PlayerRepository import fredboat.definitions.RepeatMode import fredboat.sentinel.Guild @@ -44,8 +45,10 @@ import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Qualifier import org.springframework.stereotype.Component import reactor.core.publisher.Mono +import reactor.core.publisher.toMono import java.io.IOException import java.time.Duration +import java.util.* import java.util.concurrent.ConcurrentHashMap import java.util.function.BiConsumer import kotlin.concurrent.thread @@ -87,6 +90,8 @@ class PlayerRegistry( Runtime.getRuntime().addShutdownHook( thread(start = false, name = "player-registry-shutdown") { beforeShutdown() } ) + @Suppress("LeakingThis") + sentinelLavalink.playerRegistry = this } fun getOrCreate(guild: Guild): Mono { @@ -145,51 +150,65 @@ class PlayerRegistry( * */ @Suppress("RedundantLambdaArrow") - private fun createPlayer(guild: Guild): Mono = monoCache.computeIfAbsent(guild.id) { _ -> - val player = GuildPlayer( - sentinelLavalink, - guild, - musicTextChannelProvider, - audioPlayerManager, - guildConfigService, - ratelimiter, - youtubeAPI - ) - - playerRepo.findById(guild.id) + private fun createPlayer(guild: Guild): Mono = monoCache.computeIfAbsent(guild.id) { _ -> + // GuildPlayer's contructor will indirectly call #createPlayer(). + // We can defer the construction to a different thread, to prevent an IllegalStateException, which + // would be caused by accessing monoCache recursively + Mono.defer { + GuildPlayer( + sentinelLavalink, + guild, + musicTextChannelProvider, + audioPlayerManager, + guildConfigService, + ratelimiter, + youtubeAPI + ).toMono() + }.zipWith(playerRepo.findById(guild.id) .map { - player.setPause(it.paused) - player.isShuffle = it.shuffled - player.repeatMode = RepeatMode.values()[it.repeat.toInt()] + // player repo may complete as empty. + // The zip operator will cancel the other mono, if we return empty-handed. + // We can deal with this by using Optional + Optional.of(it) + } + .switchIfEmpty(Optional.empty().toMono()) + ).map { pair -> + val player = pair.t1 + if (pair.t2.isEmpty) return@map player + val mongo = pair.t2.get() + + player.setPause(mongo.paused) + player.isShuffle = mongo.shuffled + player.repeatMode = RepeatMode.values()[mongo.repeat.toInt()] + + if (appConfig.distribution.volumeSupported()) { + player.volume = mongo.volume + } - if (appConfig.distribution.volumeSupported()) { - player.volume = it.volume + val queue = mongo.queue.mapNotNull { track -> + try { + val at = LavalinkUtil.toAudioTrack(track.blob) + val member = guild.getMember(track.requester) ?: guild.selfMember + if (track.startTime != null && track.endTime != null) { + SplitAudioTrackContext(at, member, track.startTime, track.endTime, track.title) + } else { + AudioTrackContext(at, member) } + } catch (e: IOException) { + log.error("Exception loading track", e) + null + } + } - val queue = it.queue.mapNotNull { track -> - try { - val at = LavalinkUtil.toAudioTrack(track.blob) - val member = guild.getMember(track.requester) ?: guild.selfMember - if (track.startTime != null && track.endTime != null) { - SplitAudioTrackContext(at, member, track.startTime, track.endTime, track.title) - } else { - AudioTrackContext(at, member) - } - } catch (e: IOException) { - log.error("Exception loading track", e) - null - } - } - - // Optionally set current track position - if (it.position != null && queue.isNotEmpty()) { - queue[0].track.position = it.position - } + // Optionally set current track position + if (mongo.position != null && queue.isNotEmpty()) { + queue[0].track.position = mongo.position + } - player.loadAll(queue) + player.loadAll(queue) - player - }.defaultIfEmpty(player) + player + } }.doOnSuccess { registry[it.guildId] = it }.doFinally { From 9573631b2b76466a18fef8dd535014727b7e45f5 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 30 Nov 2018 16:54:59 +0100 Subject: [PATCH 016/172] Add reactor-test library --- FredBoat/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/FredBoat/build.gradle b/FredBoat/build.gradle index 584d5c2f6..e7a776960 100644 --- a/FredBoat/build.gradle +++ b/FredBoat/build.gradle @@ -73,6 +73,7 @@ dependencies { testCompile group: 'com.palantir.docker.compose', name: 'docker-compose-rule-junit4', version: dockerComposeRuleVersion testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: springBootVersion testCompile group: 'org.postgresql', name: 'postgresql', version: postgresJdbcVersion + testCompile 'io.projectreactor:reactor-test' optional group: 'org.springframework.boot', name: 'spring-boot-configuration-processor', version: springBootVersion } From 38b0e8c475f484b1dde2973e8b0c7bdc2ff33a97 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 30 Nov 2018 17:39:11 +0100 Subject: [PATCH 017/172] Save player state on unsub --- .../agent/GuildCacheInvalidationAgent.kt | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/agent/GuildCacheInvalidationAgent.kt b/FredBoat/src/main/java/fredboat/agent/GuildCacheInvalidationAgent.kt index 264dace76..243558d27 100644 --- a/FredBoat/src/main/java/fredboat/agent/GuildCacheInvalidationAgent.kt +++ b/FredBoat/src/main/java/fredboat/agent/GuildCacheInvalidationAgent.kt @@ -2,18 +2,22 @@ package fredboat.agent import com.fredboat.sentinel.entities.GuildUnsubscribeRequest import fredboat.audio.player.PlayerRegistry +import fredboat.db.mongo.PlayerRepository import fredboat.sentinel.GuildCache import fredboat.sentinel.InternalGuild import lavalink.client.io.Link import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.stereotype.Controller +import reactor.core.publisher.Mono +import java.time.Duration import java.util.concurrent.TimeUnit @Controller class GuildCacheInvalidationAgent( val guildCache: GuildCache, - private val playerRegistry: PlayerRegistry + private val playerRegistry: PlayerRegistry, + private val playerRepository: PlayerRepository ) : FredBoatAgent("cache-invalidator", 5, TimeUnit.MINUTES) { companion object { @@ -57,13 +61,14 @@ class GuildCacheInvalidationAgent( } fun invalidateGuild(guild: InternalGuild) { - beforeInvalidation(guild) - guildCache.cache.remove(guild.id) - } - - private fun beforeInvalidation(guild: InternalGuild) { - playerRegistry.destroyPlayer(guild) - guild.sentinel.sendAndForget(guild.routingKey, GuildUnsubscribeRequest(guild.id)) + val mono = playerRegistry.getExisting(guild)?.let { + playerRepository.save(it).timeout(Duration.ofSeconds(60)) + } ?: Mono.empty() + mono.subscribe { + playerRegistry.destroyPlayer(guild) + guild.sentinel.sendAndForget(guild.routingKey, GuildUnsubscribeRequest(guild.id)) + guildCache.cache.remove(guild.id) + } } } \ No newline at end of file From 852077dc8bfcf5a70486a40c4fd2b01bc01a8f88 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Thu, 6 Dec 2018 13:59:07 +0100 Subject: [PATCH 018/172] Fix reactor-test version --- FredBoat/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FredBoat/build.gradle b/FredBoat/build.gradle index e7a776960..d67f8baf2 100644 --- a/FredBoat/build.gradle +++ b/FredBoat/build.gradle @@ -73,7 +73,7 @@ dependencies { testCompile group: 'com.palantir.docker.compose', name: 'docker-compose-rule-junit4', version: dockerComposeRuleVersion testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: springBootVersion testCompile group: 'org.postgresql', name: 'postgresql', version: postgresJdbcVersion - testCompile 'io.projectreactor:reactor-test' + testCompile 'io.projectreactor:reactor-test:3.1.6.RELEASE' optional group: 'org.springframework.boot', name: 'spring-boot-configuration-processor', version: springBootVersion } From 8212d4f3293394a6861fa22c0e6a87b9d10e1c94 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Thu, 6 Dec 2018 14:40:34 +0100 Subject: [PATCH 019/172] Add testLazyMono() test --- .../fredboat/audio/player/PlayerRegistry.kt | 2 +- .../java/fredboat/audio/PlayerRegistryTest.kt | 39 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 FredBoat/src/test/java/fredboat/audio/PlayerRegistryTest.kt diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index 3a76ec638..e49c95f66 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -151,7 +151,7 @@ class PlayerRegistry( */ @Suppress("RedundantLambdaArrow") private fun createPlayer(guild: Guild): Mono = monoCache.computeIfAbsent(guild.id) { _ -> - // GuildPlayer's contructor will indirectly call #createPlayer(). + // GuildPlayer's constructor will indirectly call #createPlayer(). // We can defer the construction to a different thread, to prevent an IllegalStateException, which // would be caused by accessing monoCache recursively Mono.defer { diff --git a/FredBoat/src/test/java/fredboat/audio/PlayerRegistryTest.kt b/FredBoat/src/test/java/fredboat/audio/PlayerRegistryTest.kt new file mode 100644 index 000000000..8dc0b6230 --- /dev/null +++ b/FredBoat/src/test/java/fredboat/audio/PlayerRegistryTest.kt @@ -0,0 +1,39 @@ +package fredboat.audio + +import fredboat.audio.player.GuildPlayer +import fredboat.audio.player.PlayerRegistry +import fredboat.sentinel.getGuildMono +import fredboat.testutil.IntegrationTest +import fredboat.testutil.sentinel.Raws +import kotlinx.coroutines.experimental.reactive.awaitFirst +import kotlinx.coroutines.experimental.runBlocking +import org.junit.Test +import org.mockito.Mockito +import reactor.test.StepVerifier +import java.util.concurrent.ConcurrentHashMap + +class PlayerRegistryTest : IntegrationTest() { + + private val PlayerRegistry.backingRegistry: ConcurrentHashMap + get() { + val field = javaClass.getDeclaredField("registry") + field.isAccessible = true + @Suppress("UNCHECKED_CAST") + return field.get(this) as ConcurrentHashMap + } + + @Test + @Suppress("BlockingMethodInNonBlockingContext") + fun testLazyMono(playerRegistry: PlayerRegistry) { + val guild = runBlocking { + getGuildMono(Raws.guild.id).awaitFirst()!! + } + val mock = Mockito.mock(GuildPlayer::class.java) + playerRegistry.backingRegistry[Raws.guild.id] = mock + StepVerifier.create(playerRegistry.getOrCreate(guild)) + .expectNext(mock) + .expectComplete() + .verify() + } + +} \ No newline at end of file From eabf11e5236f93f44d4908c792c3d8454905143a Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Thu, 6 Dec 2018 16:09:32 +0100 Subject: [PATCH 020/172] Fix Sentinel VSU not getting provided --- .../audio/lavalink/SentinelLavalink.kt | 9 +-------- .../java/fredboat/audio/player/GuildPlayer.kt | 18 +++++++++++------- .../java/fredboat/sentinel/wrapperEntities.kt | 1 + 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt index 17059f9e6..4623ace6f 100644 --- a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt +++ b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt @@ -6,7 +6,6 @@ import fredboat.config.idString import fredboat.config.property.AppConfig import fredboat.config.property.LavalinkConfig import fredboat.sentinel.Guild -import fredboat.sentinel.InternalGuild import fredboat.sentinel.Sentinel import fredboat.sentinel.getGuildMono import lavalink.client.io.Lavalink @@ -50,13 +49,7 @@ class SentinelLavalink( log.warn("Built link for non-existing guild. This should not happen.") return@subscribe } - playerRegistry.getOrCreate(guild).subscribe { player -> - // If FredBoat just restarted, we will want to reconnect the link - @Suppress("LABEL_NAME_CLASH") - val vsu = (guild as InternalGuild).cachedVsu ?: return@subscribe - guild.cachedVsu = null - player.player.link.onVoiceServerUpdate(JSONObject(vsu), vsu.sessionId) - } + playerRegistry.getOrCreate(guild).subscribe() } return SentinelLink(this, guildId) diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index b12c239bb..8c7fd5358 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -39,16 +39,14 @@ import fredboat.definitions.RepeatMode import fredboat.feature.I18n import fredboat.perms.Permission import fredboat.perms.PermsUtil -import fredboat.sentinel.Guild -import fredboat.sentinel.Member -import fredboat.sentinel.TextChannel -import fredboat.sentinel.VoiceChannel +import fredboat.sentinel.* import fredboat.util.extension.escapeAndDefuse import fredboat.util.ratelimit.Ratelimiter import fredboat.util.rest.YoutubeAPI import org.apache.commons.lang3.tuple.ImmutablePair import org.apache.commons.lang3.tuple.Pair import org.bson.types.ObjectId +import org.json.JSONObject import org.slf4j.LoggerFactory import java.util.* import java.util.function.Consumer @@ -56,8 +54,6 @@ import kotlin.streams.toList class GuildPlayer( val lavalink: SentinelLavalink, - // TODO: GuildPlayers should be discarded when expiring the cached guild - // and new ones should be able to re-initiate the same settings var guild: Guild, private val musicTextChannelProvider: MusicTextChannelProvider, audioPlayerManager: AudioPlayerManager, @@ -71,7 +67,6 @@ class GuildPlayer( companion object { private val log = LoggerFactory.getLogger(GuildPlayer::class.java) - } val trackCount: Int @@ -162,6 +157,15 @@ class GuildPlayer( audioLoader = AudioLoader(ratelimiter, audioTrackProvider, audioPlayerManager, this, youtubeAPI) + + // If FredBoat just restarted, we will want to reconnect the link + val iGuild = guild as InternalGuild + val vsu = iGuild.cachedVsu + iGuild.cachedVsu = null + if (vsu != null) { + player.link.onVoiceServerUpdate(JSONObject(vsu.raw), vsu.sessionId) + log.info("Using cached VOICE_SERVER_UPDATE for $guild") + } } private fun announceTrack(atc: AudioTrackContext) { diff --git a/FredBoat/src/main/java/fredboat/sentinel/wrapperEntities.kt b/FredBoat/src/main/java/fredboat/sentinel/wrapperEntities.kt index 6d9ea77aa..7696ed6f7 100644 --- a/FredBoat/src/main/java/fredboat/sentinel/wrapperEntities.kt +++ b/FredBoat/src/main/java/fredboat/sentinel/wrapperEntities.kt @@ -128,6 +128,7 @@ class InternalGuild(raw: RawGuild) : Guild(raw) { // Any old GuildPlayer needs to be aware of the new guild object val player: GuildPlayer? = getBotController().playerRegistry.getExisting(this) if (player != null) player.guild = this + if (raw.voiceServerUpdate != null) log.info("Received cached VSU for $this") } fun update(raw: RawGuild) { From b5b8366179cf5d38d58ca0b780518820a2d73ec3 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Thu, 6 Dec 2018 16:23:05 +0100 Subject: [PATCH 021/172] Fix link channel being null after cached VSU --- .../main/java/fredboat/agent/VoiceChannelCleanupAgent.kt | 2 +- FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/FredBoat/src/main/java/fredboat/agent/VoiceChannelCleanupAgent.kt b/FredBoat/src/main/java/fredboat/agent/VoiceChannelCleanupAgent.kt index 2a3f12862..080e09a3e 100644 --- a/FredBoat/src/main/java/fredboat/agent/VoiceChannelCleanupAgent.kt +++ b/FredBoat/src/main/java/fredboat/agent/VoiceChannelCleanupAgent.kt @@ -84,7 +84,7 @@ class VoiceChannelCleanupAgent( } else if (vc != null && isBeingUsed(vc)) { vcLastUsed[vcId] = System.currentTimeMillis() } else { - // Not being used! But there are users in te VC. Check if we've been here for a while. + // Not being used! But there are users in the VC. Check if we've been here for a while. vcId?.let { if (!vcLastUsed.containsKey(vcId)) { diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index 8c7fd5358..9ebdeda58 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -165,6 +165,12 @@ class GuildPlayer( if (vsu != null) { player.link.onVoiceServerUpdate(JSONObject(vsu.raw), vsu.sessionId) log.info("Using cached VOICE_SERVER_UPDATE for $guild") + + val vc = guild.selfMember.voiceChannel + if (vc == null) + log.warn("Using cached VOICE_SERVER_UPDATE, but it doesn't appear like we are in a voice channel!") + else + player.link.setChannel(vc.idString) } } From 95b71da4c6c631fc7ac54c2eda6ab32556b526e8 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 7 Dec 2018 13:11:01 +0100 Subject: [PATCH 022/172] Fix player state saving --- .../agent/GuildCacheInvalidationAgent.kt | 3 +- .../fredboat/audio/player/PlayerRegistry.kt | 5 +- .../java/fredboat/db/mongo/repositories.kt | 65 +++++++++---------- .../fredboat/util/ratelimit/Blacklist.java | 2 +- .../fredboat/util/ratelimit/Ratelimit.java | 2 +- 5 files changed, 37 insertions(+), 40 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/agent/GuildCacheInvalidationAgent.kt b/FredBoat/src/main/java/fredboat/agent/GuildCacheInvalidationAgent.kt index 243558d27..1d7c5d10f 100644 --- a/FredBoat/src/main/java/fredboat/agent/GuildCacheInvalidationAgent.kt +++ b/FredBoat/src/main/java/fredboat/agent/GuildCacheInvalidationAgent.kt @@ -3,6 +3,7 @@ package fredboat.agent import com.fredboat.sentinel.entities.GuildUnsubscribeRequest import fredboat.audio.player.PlayerRegistry import fredboat.db.mongo.PlayerRepository +import fredboat.db.mongo.convertAndSave import fredboat.sentinel.GuildCache import fredboat.sentinel.InternalGuild import lavalink.client.io.Link @@ -62,7 +63,7 @@ class GuildCacheInvalidationAgent( fun invalidateGuild(guild: InternalGuild) { val mono = playerRegistry.getExisting(guild)?.let { - playerRepository.save(it).timeout(Duration.ofSeconds(60)) + playerRepository.convertAndSave(it).timeout(Duration.ofSeconds(60)) } ?: Mono.empty() mono.subscribe { playerRegistry.destroyPlayer(guild) diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index e49c95f66..37210c734 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -33,6 +33,7 @@ import fredboat.config.property.AppConfig import fredboat.db.api.GuildConfigService import fredboat.db.mongo.MongoPlayer import fredboat.db.mongo.PlayerRepository +import fredboat.db.mongo.convertAndSaveAll import fredboat.definitions.RepeatMode import fredboat.sentinel.Guild import fredboat.util.ratelimit.Ratelimiter @@ -216,8 +217,8 @@ class PlayerRegistry( } private fun beforeShutdown() { - log.info("Running shutdown hook to save player state") - val count = playerRepo.saveAll(registry.values.toList()) + log.info("Running shutdown hook to convertAndSave player state") + val count = playerRepo.convertAndSaveAll(registry.values.toList()) .count() .block(Duration.ofMinutes(2)) log.info("Saved $count player states") diff --git a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt index 6724a47e5..081e67613 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt @@ -9,45 +9,40 @@ import org.springframework.data.repository.reactive.ReactiveCrudRepository import reactor.core.publisher.Flux import reactor.core.publisher.Mono -interface PlayerRepository : ReactiveCrudRepository { +interface PlayerRepository : ReactiveCrudRepository - companion object { - private val log: Logger = LoggerFactory.getLogger(PlayerRepository::class.java) - } +private val log: Logger = LoggerFactory.getLogger(PlayerRepository::class.java) - fun save(player: GuildPlayer): Mono { - return save(player.toMongo()) - } +fun PlayerRepository.convertAndSave(player: GuildPlayer): Mono { + return save(player.toMongo()) +} - fun saveAll(players: List): Flux { - val entities = players.mapNotNull { - try { - return@mapNotNull it.toMongo() - } catch (e: Exception) { - log.error("Problem saving player state", e) - return@mapNotNull null - } +fun PlayerRepository.convertAndSaveAll(players: List): Flux { + val entities = players.mapNotNull { + try { + return@mapNotNull it.toMongo() + } catch (e: Exception) { + log.error("Problem saving player state", e) + return@mapNotNull null } - return saveAll(entities) } - - private fun GuildPlayer.toMongo() = MongoPlayer( - guildId, - isPaused, - isShuffle, - isPlaying, - repeatMode.ordinal.toByte(), - volume, - playingTrack?.track?.position, - currentVoiceChannel?.id, - remainingTracks.map { - if (it is SplitAudioTrackContext) { - MongoTrack(it.trackId, LavalinkUtil.toBinary(it.track), it.member.id, it.startPosition, it.endPosition, it.effectiveTitle) - } else { - MongoTrack(it.trackId, LavalinkUtil.toBinary(it.track), it.member.id, null, null, it.effectiveTitle) - } - } - ) - + return saveAll(entities) } +private fun GuildPlayer.toMongo() = MongoPlayer( + guildId, + isPaused, + isShuffle, + isPlaying, + repeatMode.ordinal.toByte(), + volume, + playingTrack?.track?.position, + currentVoiceChannel?.id, + remainingTracks.map { + if (it is SplitAudioTrackContext) { + MongoTrack(it.trackId, LavalinkUtil.toBinary(it.track), it.member.id, it.startPosition, it.endPosition, it.effectiveTitle) + } else { + MongoTrack(it.trackId, LavalinkUtil.toBinary(it.track), it.member.id, null, null, it.effectiveTitle) + } + } +) diff --git a/FredBoat/src/main/java/fredboat/util/ratelimit/Blacklist.java b/FredBoat/src/main/java/fredboat/util/ratelimit/Blacklist.java index f74a5a864..6829fa22a 100644 --- a/FredBoat/src/main/java/fredboat/util/ratelimit/Blacklist.java +++ b/FredBoat/src/main/java/fredboat/util/ratelimit/Blacklist.java @@ -104,7 +104,7 @@ public long hitRateLimit(long id) { long blacklistingLength = 0; BlacklistEntry blEntry = blacklistService.fetchBlacklistEntry(id); - //synchronize on the individual blacklist entries since we are about to change and save them + //synchronize on the individual blacklist entries since we are about to change and convertAndSave them // we can use these to synchronize because they are backed by a cache, subsequent calls to fetch them // will return the same object //noinspection SynchronizationOnLocalVariableOrMethodParameter diff --git a/FredBoat/src/main/java/fredboat/util/ratelimit/Ratelimit.java b/FredBoat/src/main/java/fredboat/util/ratelimit/Ratelimit.java index a266869ce..38617408d 100644 --- a/FredBoat/src/main/java/fredboat/util/ratelimit/Ratelimit.java +++ b/FredBoat/src/main/java/fredboat/util/ratelimit/Ratelimit.java @@ -140,7 +140,7 @@ public boolean isAllowed(Context context, int weight, @Nullable Blacklist blackl return true; //not expected to happen, let it slip in a user friendly way } - //synchronize on the individual rate objects since we are about to change and save them + //synchronize on the individual rate objects since we are about to change and convertAndSave them // we can use these to synchronize because they are backed by a cache, subsequent calls to fetch them // will return the same object //noinspection SynchronizationOnLocalVariableOrMethodParameter From bb2778698401804c578f7f91bb62c36e8de28f28 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 7 Dec 2018 15:06:40 +0100 Subject: [PATCH 023/172] Clean up mongo player load code --- .../fredboat/audio/player/PlayerRegistry.kt | 75 ++++++++++--------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index 37210c734..7ac4b1db7 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -147,8 +147,7 @@ class PlayerRegistry( } /** - * @return a [Mono] with a fully loaded [GuildPlayer], po - * + * @return a [Mono] with a fully loaded [GuildPlayer] */ @Suppress("RedundantLambdaArrow") private fun createPlayer(guild: Guild): Mono = monoCache.computeIfAbsent(guild.id) { _ -> @@ -174,46 +173,50 @@ class PlayerRegistry( } .switchIfEmpty(Optional.empty().toMono()) ).map { pair -> - val player = pair.t1 - if (pair.t2.isEmpty) return@map player - val mongo = pair.t2.get() - - player.setPause(mongo.paused) - player.isShuffle = mongo.shuffled - player.repeatMode = RepeatMode.values()[mongo.repeat.toInt()] + if (pair.t2.isEmpty) return@map pair.t1 + loadMongoData(pair.t1, pair.t2.get()) + pair.t1 + } + }.doOnSuccess { + registry[it.guildId] = it + }.doFinally { + monoCache.remove(guild.id) + } - if (appConfig.distribution.volumeSupported()) { - player.volume = mongo.volume - } + /** + * Load mongo data for a newly constructed [GuildPlayer] + */ + private fun loadMongoData(player: GuildPlayer, mongo: MongoPlayer) { + val guild = player.guild + player.setPause(mongo.paused) + player.isShuffle = mongo.shuffled + player.repeatMode = RepeatMode.values()[mongo.repeat.toInt()] + + if (appConfig.distribution.volumeSupported()) { + player.volume = mongo.volume + } - val queue = mongo.queue.mapNotNull { track -> - try { - val at = LavalinkUtil.toAudioTrack(track.blob) - val member = guild.getMember(track.requester) ?: guild.selfMember - if (track.startTime != null && track.endTime != null) { - SplitAudioTrackContext(at, member, track.startTime, track.endTime, track.title) - } else { - AudioTrackContext(at, member) - } - } catch (e: IOException) { - log.error("Exception loading track", e) - null + val queue = mongo.queue.mapNotNull { track -> + try { + val at = LavalinkUtil.toAudioTrack(track.blob) + val member = guild.getMember(track.requester) ?: guild.selfMember + if (track.startTime != null && track.endTime != null) { + SplitAudioTrackContext(at, member, track.startTime, track.endTime, track.title) + } else { + AudioTrackContext(at, member) } + } catch (e: IOException) { + log.error("Exception loading track", e) + null } + } - // Optionally set current track position - if (mongo.position != null && queue.isNotEmpty()) { - queue[0].track.position = mongo.position - } - - player.loadAll(queue) - - player + // Optionally set current track position + if (mongo.position != null && queue.isNotEmpty()) { + queue[0].track.position = mongo.position } - }.doOnSuccess { - registry[it.guildId] = it - }.doFinally { - monoCache.remove(guild.id) + + player.loadAll(queue) } private fun beforeShutdown() { From c0072ec365ed043b01c34dfaa3a5e211c8679d57 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 7 Dec 2018 15:20:54 +0100 Subject: [PATCH 024/172] =?UTF-8?q?Maybe=20resuming=20would=20work=20if=20?= =?UTF-8?q?we=20told=20the=20client=20to=20do=20so=20=F0=9F=A4=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/fredboat/audio/lavalink/SentinelLavalink.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt index 4623ace6f..bf6aa88e1 100644 --- a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt +++ b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt @@ -27,18 +27,20 @@ class SentinelLavalink( ) { lateinit var playerRegistry: PlayerRegistry + private val resumeKey = "fredboat-${sentinel.selfUser.idString}" companion object { lateinit var INSTANCE: SentinelLavalink private const val DEFAULT_RESUME_TIMEOUT = 300 // 5 mins private val log: Logger = LoggerFactory.getLogger(SentinelLavalink::class.java) + } init { INSTANCE = this lavalinkConfig.nodes.forEach { - addNode(it.name, it.uri, it.password) - .setResuming("fredboat-${sentinel.selfUser.idString}", DEFAULT_RESUME_TIMEOUT) + addNode(it.name, it.uri, it.password, resumeKey) + .setResuming(resumeKey, DEFAULT_RESUME_TIMEOUT) } LavalinkCollector(this).register() } From a577c8d0195d02cac66958a73e684d8177c64d69 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 7 Dec 2018 16:53:11 +0100 Subject: [PATCH 025/172] Fix parameter mismatch --- FredBoat/src/main/java/fredboat/db/mongo/repositories.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt index 081e67613..9d678fc42 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt @@ -31,9 +31,9 @@ fun PlayerRepository.convertAndSaveAll(players: List): Flux Date: Fri, 7 Dec 2018 16:53:29 +0100 Subject: [PATCH 026/172] Fix exceptions when resuming --- .../audio/lavalink/SentinelLavalink.kt | 19 ++++- .../fredboat/audio/player/PlayerRegistry.kt | 2 +- .../java/fredboat/sentinel/SentinelTracker.kt | 33 ++++++-- .../main/java/fredboat/sentinel/guildCache.kt | 76 ++++++++++++------- 4 files changed, 92 insertions(+), 38 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt index bf6aa88e1..0b5990311 100644 --- a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt +++ b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt @@ -6,19 +6,23 @@ import fredboat.config.idString import fredboat.config.property.AppConfig import fredboat.config.property.LavalinkConfig import fredboat.sentinel.Guild +import fredboat.sentinel.GuildCache import fredboat.sentinel.Sentinel -import fredboat.sentinel.getGuildMono +import fredboat.util.DiscordUtil import lavalink.client.io.Lavalink import lavalink.client.io.metrics.LavalinkCollector import org.json.JSONObject import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.stereotype.Service +import java.time.Duration +import java.util.concurrent.TimeoutException @Suppress("ImplicitThis", "LeakingThis") @Service class SentinelLavalink( val sentinel: Sentinel, + val guildCache: GuildCache, val appConfig: AppConfig, lavalinkConfig: LavalinkConfig ) : Lavalink( @@ -33,7 +37,6 @@ class SentinelLavalink( lateinit var INSTANCE: SentinelLavalink private const val DEFAULT_RESUME_TIMEOUT = 300 // 5 mins private val log: Logger = LoggerFactory.getLogger(SentinelLavalink::class.java) - } init { @@ -46,14 +49,22 @@ class SentinelLavalink( } override fun buildNewLink(guildId: String): SentinelLink { - getGuildMono(guildId.toLong()).subscribe { guild -> + val shardId = DiscordUtil.getShardId(guildId.toLong(), appConfig) + val shardMono = sentinel.tracker.awaitHello(shardId) + val guildMono = guildCache.getGuildMono(guildId.toLong()).doOnSuccess { guild -> if (guild == null) { log.warn("Built link for non-existing guild. This should not happen.") - return@subscribe + return@doOnSuccess } playerRegistry.getOrCreate(guild).subscribe() } + shardMono.timeout(Duration.ofSeconds(120)) + .onErrorMap(TimeoutException::class.java) { + RuntimeException("Attempted to resume Lavalink, but Sentinel hello timed out") + }.then(guildMono) + .subscribe() + return SentinelLink(this, guildId) } diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index 7ac4b1db7..7df6df85d 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -38,7 +38,7 @@ import fredboat.definitions.RepeatMode import fredboat.sentinel.Guild import fredboat.util.ratelimit.Ratelimiter import fredboat.util.rest.YoutubeAPI -import kotlinx.coroutines.experimental.reactive.awaitFirst +import kotlinx.coroutines.reactive.awaitFirst import lavalink.client.LavalinkUtil import lavalink.client.io.Link.State import org.slf4j.Logger diff --git a/FredBoat/src/main/java/fredboat/sentinel/SentinelTracker.kt b/FredBoat/src/main/java/fredboat/sentinel/SentinelTracker.kt index 3ac1e1a77..18e99e79f 100644 --- a/FredBoat/src/main/java/fredboat/sentinel/SentinelTracker.kt +++ b/FredBoat/src/main/java/fredboat/sentinel/SentinelTracker.kt @@ -14,10 +14,13 @@ import org.slf4j.LoggerFactory import org.springframework.amqp.AmqpConnectException import org.springframework.amqp.rabbit.core.RabbitTemplate import org.springframework.stereotype.Service +import reactor.core.publisher.Mono +import reactor.core.publisher.MonoSink import java.text.SimpleDateFormat import java.time.Instant import java.util.* import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentLinkedQueue /** Class that tracks Sentinels and their routing keys */ @Service @@ -30,6 +33,12 @@ class SentinelTracker( private val log: Logger = LoggerFactory.getLogger(SentinelTracker::class.java) } + /** Shard id mapped to [SentinelHello] */ + private val map: ConcurrentHashMap = ConcurrentHashMap() + val sentinels: Set + get() = map.values.toSet() + private val awaitingMonos = ConcurrentLinkedQueue>>() + init { val time = SimpleDateFormat("dd-MM-yyyy-HH:mm:ss").format(Date.from(Instant.now())) val id = "FredBoat@$time" @@ -62,11 +71,6 @@ class SentinelTracker( } } - /** Shard id mapped to [SentinelHello] */ - private val map: ConcurrentHashMap = ConcurrentHashMap() - val sentinels: Set - get() = map.values.toSet() - fun onHello(hello: SentinelHello) = hello.run { log.info("Received hello from $key with shards [$shardStart;$shardEnd] \uD83D\uDC4B") @@ -75,11 +79,19 @@ class SentinelTracker( "but we are configured for ${appConfig.shardCount}!") } - (shardStart..shardEnd).forEach { + asRange.forEach { map[it] = hello } + + awaitingMonos.removeIf { pair -> + if (!asRange.contains(pair.first)) return@removeIf false + pair.second.success() + true + } + Unit } + val SentinelHello.asRange get() = shardStart..shardEnd fun getHello(shardId: Int) = map[shardId] fun getKey(shardId: Int): String { val hello = getHello(shardId) @@ -87,4 +99,13 @@ class SentinelTracker( " but we haven't received hello from it.") return hello.key } + + fun awaitHello(shardId: Int): Mono { + if (map.containsKey(shardId)) return Mono.empty() + return Mono.create { sink -> + // Check again at subscription time just to be sure + if (map.containsKey(shardId)) sink.success() + else awaitingMonos.add(shardId to sink) + } + } } \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/sentinel/guildCache.kt b/FredBoat/src/main/java/fredboat/sentinel/guildCache.kt index 2cf73eca1..fd74b2ba5 100644 --- a/FredBoat/src/main/java/fredboat/sentinel/guildCache.kt +++ b/FredBoat/src/main/java/fredboat/sentinel/guildCache.kt @@ -3,14 +3,10 @@ package fredboat.sentinel import com.fredboat.sentinel.SentinelExchanges import com.fredboat.sentinel.entities.GuildSubscribeRequest import com.google.common.cache.CacheBuilder -import fredboat.audio.lavalink.SentinelLavalink import fredboat.config.property.AppConfig -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch import kotlinx.coroutines.reactive.awaitFirstOrNull import org.slf4j.Logger import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service import reactor.core.publisher.Mono import java.time.Duration @@ -20,8 +16,7 @@ import java.util.concurrent.TimeoutException @Service class GuildCache(private val sentinel: Sentinel, - private val appConfig: AppConfig, - private val lavalink: SentinelLavalink) { + private val appConfig: AppConfig) { init { @Suppress("LeakingThis") @@ -33,9 +28,6 @@ class GuildCache(private val sentinel: Sentinel, private val log: Logger = LoggerFactory.getLogger(GuildCache::class.java) } - @Autowired - /* Cyclic dependency */ - lateinit var rabbitConsumer: RabbitConsumer val cache = ConcurrentHashMap() /** Non-finished requests. Acts as a debounce */ @@ -63,18 +55,22 @@ class GuildCache(private val sentinel: Sentinel, val startTime = System.currentTimeMillis() - val mono = sentinel.genericMonoSendAndReceive( - SentinelExchanges.REQUESTS, - sentinel.tracker.getKey(calculateShardId(id)), - GuildSubscribeRequest(id, channelInvoked = textChannelInvoked), - mayBeEmpty = true, - transform = { - transform(startTime, it) - }) - .timeout(Duration.ofSeconds(30), Mono.error(TimeoutException("Timed out while subscribing to $id"))) - .doFinally { - requestCache.invalidate(id) - } + // Defer the mono, because we may not yet have received SentinelHello + // for the guild's Sentinel at construction time + val mono = Mono.defer { + sentinel.genericMonoSendAndReceive( + SentinelExchanges.REQUESTS, + sentinel.tracker.getKey(calculateShardId(id)), + GuildSubscribeRequest(id, channelInvoked = textChannelInvoked), + mayBeEmpty = true, + transform = { + transform(startTime, it) + }) + .timeout(Duration.ofSeconds(30), Mono.error(TimeoutException("Timed out while subscribing to $id"))) + .doFinally { + requestCache.invalidate(id) + } + } requestCache.put(id, mono) return mono } @@ -104,28 +100,54 @@ class GuildCache(private val sentinel: Sentinel, private fun calculateShardId(guildId: Long): Int = ((guildId shr 22) % appConfig.shardCount.toLong()).toInt() + /** + * @param id the ID of the guild + * @param textChannelInvoked optionally the ID of the text channel used, + * in case we need to warn the user of long loading times + */ + suspend fun getGuild(id: Long, textChannelInvoked: Long? = null) = get(id, textChannelInvoked) + .awaitFirstOrNull() + + /** + * @param id the ID of the guild + * @param textChannelInvoked optionally the ID of the text channel used, + * in case we need to warn the user of long loading times + */ + fun getGuildMono(id: Long, textChannelInvoked: Long? = null) = get(id, textChannelInvoked) + + /** + * @param id the ID of the guild + * @param textChannelInvoked optionally the ID of the text channel used, + * in case we need to warn the user of long loading times + */ + fun getGuild(id: Long, textChannelInvoked: Long? = null, callback: (Guild) -> Unit) { + get(id, textChannelInvoked).subscribe { callback(it!!) } + } + } +/* + The below extension functions are usually safe to use, but the direct ones are preferred. + */ + /** * @param id the ID of the guild * @param textChannelInvoked optionally the ID of the text channel used, * in case we need to warn the user of long loading times */ -suspend fun getGuild(id: Long, textChannelInvoked: Long? = null) = GuildCache.INSTANCE.get(id, textChannelInvoked) - .awaitFirstOrNull() +suspend fun getGuild(id: Long, textChannelInvoked: Long? = null) = GuildCache.INSTANCE.getGuild(id, textChannelInvoked) /** * @param id the ID of the guild * @param textChannelInvoked optionally the ID of the text channel used, * in case we need to warn the user of long loading times */ -fun getGuildMono(id: Long, textChannelInvoked: Long? = null) = GuildCache.INSTANCE.get(id, textChannelInvoked) +fun getGuildMono(id: Long, textChannelInvoked: Long? = null) = GuildCache.INSTANCE.getGuildMono(id, textChannelInvoked) /** * @param id the ID of the guild * @param textChannelInvoked optionally the ID of the text channel used, * in case we need to warn the user of long loading times */ -fun getGuild(id: Long, textChannelInvoked: Long? = null, callback: (Guild) -> Unit) { - GuildCache.INSTANCE.get(id, textChannelInvoked).subscribe { callback(it!!) } -} +fun getGuild(id: Long, textChannelInvoked: Long? = null, callback: (Guild) -> Unit) + = GuildCache.INSTANCE.getGuild(id, textChannelInvoked, callback) From 8f0bd5d87ebd6596b89ba87f04a6d49457ed1313 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 7 Dec 2018 17:08:37 +0100 Subject: [PATCH 027/172] Fall back to default ;;np embed if no YT key found --- .../java/fredboat/command/music/info/NowplayingCommand.kt | 4 +++- config/templates/fredboat.yml | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/command/music/info/NowplayingCommand.kt b/FredBoat/src/main/java/fredboat/command/music/info/NowplayingCommand.kt index ec5103b03..7260e6e21 100644 --- a/FredBoat/src/main/java/fredboat/command/music/info/NowplayingCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/info/NowplayingCommand.kt @@ -49,6 +49,8 @@ import java.awt.Color class NowplayingCommand(private val youtubeAPI: YoutubeAPI, name: String, vararg aliases: String) : Command(name, *aliases), IMusicCommand { + private val hasYtKeys get() = Launcher.botController.credentials.googleKeys.isNotEmpty() + override suspend fun invoke(context: CommandContext) { val player = Launcher.botController.playerRegistry.getExisting(context.guild) @@ -61,7 +63,7 @@ class NowplayingCommand(private val youtubeAPI: YoutubeAPI, name: String, vararg val at = atc!!.track val embed = when { - at is YoutubeAudioTrack -> getYoutubeEmbed(atc, player, at) + at is YoutubeAudioTrack && hasYtKeys -> getYoutubeEmbed(atc, player, at) at is SoundCloudAudioTrack -> getSoundcloudEmbed(atc, player, at) at is BandcampAudioTrack -> getBandcampResponse(atc, player, at) at is TwitchStreamAudioTrack -> getTwitchEmbed(atc, at) diff --git a/config/templates/fredboat.yml b/config/templates/fredboat.yml index c774806c6..404066938 100644 --- a/config/templates/fredboat.yml +++ b/config/templates/fredboat.yml @@ -101,9 +101,9 @@ credentials: # Used by the ;;split and ;;np commands. Must be hooked up to the Youtube Data API. # You can add additional keys in case you are running a big bot # How to get the key: https://developers.google.com/youtube/registering_an_application - # Add your google API key between the quotation marks - googleApiKeys: - - "PutYourGoogleAPIKeyHere" + # Add your google API key between the quotation marks and uncomment the two lines below + #googleApiKeys: + # - "PutYourGoogleAPIKeyHere" # The preferred way of setting the token is described in ./common.sh #discordBotToken: "" From e16be42344c17915621aab7f6adda5d57d29c4d7 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 7 Dec 2018 17:29:37 +0100 Subject: [PATCH 028/172] Use PlayerEventListenerAdapter instead of LP listener --- .../src/main/java/fredboat/audio/player/AbstractPlayer.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt index 7b34e4c89..40f8d23ed 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt @@ -39,8 +39,10 @@ import fredboat.audio.queue.TrackEndMarkerHandler import fredboat.commandmeta.MessagingException import fredboat.sentinel.Guild import fredboat.util.TextUtils +import lavalink.client.player.IPlayer import lavalink.client.player.LavalinkPlayer import lavalink.client.player.event.AudioEventAdapterWrapped +import lavalink.client.player.event.PlayerEventListenerAdapter import org.slf4j.LoggerFactory import java.util.* import java.util.concurrent.ConcurrentLinkedQueue @@ -50,7 +52,7 @@ abstract class AbstractPlayer internal constructor( lavalink: SentinelLavalink, internal val audioTrackProvider: ITrackProvider, guild: Guild -) : AudioEventAdapterWrapped() { +) : PlayerEventListenerAdapter() { val player: LavalinkPlayer = lavalink.getLink(guild.id.toString()).player protected var context: AudioTrackContext? = null @@ -197,7 +199,7 @@ abstract class AbstractPlayer internal constructor( } } - override fun onTrackEnd(player: AudioPlayer?, track: AudioTrack?, endReason: AudioTrackEndReason?) { + override fun onTrackEnd(player: IPlayer?, track: AudioTrack?, endReason: AudioTrackEndReason?) { log.debug("onTrackEnd({} {} {}) called", track!!.info.title, endReason!!.name, endReason.mayStartNext) if (endReason == AudioTrackEndReason.FINISHED || endReason == AudioTrackEndReason.STOPPED) { From e542681c422df77f2e5f36d72973a2ce23fe295b Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 7 Dec 2018 17:36:30 +0100 Subject: [PATCH 029/172] Fix bad method signatures --- .../src/main/java/fredboat/audio/player/AbstractPlayer.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt index 40f8d23ed..f324df66b 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt @@ -26,8 +26,6 @@ package fredboat.audio.player import com.google.common.collect.Lists -import com.sedmelluq.discord.lavaplayer.player.AudioPlayer -import com.sedmelluq.discord.lavaplayer.tools.FriendlyException import com.sedmelluq.discord.lavaplayer.track.AudioTrack import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason import com.sedmelluq.discord.lavaplayer.track.TrackMarker @@ -41,7 +39,6 @@ import fredboat.sentinel.Guild import fredboat.util.TextUtils import lavalink.client.player.IPlayer import lavalink.client.player.LavalinkPlayer -import lavalink.client.player.event.AudioEventAdapterWrapped import lavalink.client.player.event.PlayerEventListenerAdapter import org.slf4j.LoggerFactory import java.util.* @@ -264,12 +261,12 @@ abstract class AbstractPlayer internal constructor( player.link.destroy() } - override fun onTrackException(player: AudioPlayer?, track: AudioTrack, exception: FriendlyException?) { + override fun onTrackException(player: IPlayer?, track: AudioTrack, exception: Exception?) { log.error("Lavaplayer encountered an exception while playing {}", track.identifier, exception) } - override fun onTrackStuck(player: AudioPlayer?, track: AudioTrack, thresholdMs: Long) { + override fun onTrackStuck(player: IPlayer?, track: AudioTrack, thresholdMs: Long) { log.error("Lavaplayer got stuck while playing {}", track.identifier) } From fd41a13e5cf38f0b2c1980e39fd81da1a183965f Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Tue, 11 Dec 2018 17:20:26 +0100 Subject: [PATCH 030/172] Properly populate LLC state on resume --- .../main/java/fredboat/audio/lavalink/SentinelLavalink.kt | 1 + .../src/main/java/fredboat/audio/player/PlayerRegistry.kt | 8 +++++--- build.gradle | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt index 0b5990311..fb9d5509c 100644 --- a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt +++ b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt @@ -41,6 +41,7 @@ class SentinelLavalink( init { INSTANCE = this + setHoldEvents(true) lavalinkConfig.nodes.forEach { addNode(it.name, it.uri, it.password, resumeKey) .setResuming(resumeKey, DEFAULT_RESUME_TIMEOUT) diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index 7df6df85d..3c544dc0c 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -179,6 +179,7 @@ class PlayerRegistry( } }.doOnSuccess { registry[it.guildId] = it + it.player.link.releaseHeldEvents() }.doFinally { monoCache.remove(guild.id) } @@ -211,9 +212,10 @@ class PlayerRegistry( } } - // Optionally set current track position - if (mongo.position != null && queue.isNotEmpty()) { - queue[0].track.position = mongo.position + if (queue.isNotEmpty()) { + player.player.explicitlySetTrack(queue[0].track) + // Optionally set current track position + if (mongo.position != null) queue[0].track.position = mongo.position } player.loadAll(queue) diff --git a/build.gradle b/build.gradle index c8a0aab41..63d11ee6c 100644 --- a/build.gradle +++ b/build.gradle @@ -64,7 +64,7 @@ subprojects { //audio deps lavaplayerVersion = '1.3.9' - lavalinkVersion = '54c43f24' + lavalinkVersion = 'e9dc6511' //utility deps jsonOrgVersion = '20180130' From 09d77531a1f9a29b6c716380904438cd4e088d74 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Wed, 12 Dec 2018 11:29:11 +0100 Subject: [PATCH 031/172] Add mongodb to docker-compose --- config/templates/docker-compose.yml | 24 ++++++--- config/templates/selfhosting.yml | 81 ++++++++++++++++------------- 2 files changed, 62 insertions(+), 43 deletions(-) diff --git a/config/templates/docker-compose.yml b/config/templates/docker-compose.yml index c1d8d8eba..2cec0ce68 100644 --- a/config/templates/docker-compose.yml +++ b/config/templates/docker-compose.yml @@ -25,7 +25,7 @@ version: '3' # This is not the FredBoat version, do not touch this line. services: ################################################################################ - ## Database + ## Database ################################################################################ db: image: fredboat/postgres:latest @@ -45,7 +45,7 @@ services: #- postgres-data-volume:/var/lib/postgresql/data ################################################################################ - ## RabbitMQ + ## RabbitMQ ################################################################################ rabbitmq: image: rabbitmq:3-management @@ -57,7 +57,7 @@ services: - "com.centurylinklabs.watchtower.enable=true" ################################################################################ - ## Lavalink + ## Lavalink ################################################################################ lavalink: image: fredboat/lavalink:master @@ -73,7 +73,7 @@ services: entrypoint: java -Xmx128m -jar Lavalink.jar ################################################################################ - ## Sentinel + ## Sentinel ################################################################################ sentinel: image: fredboat/sentinel:dev @@ -91,7 +91,7 @@ services: #entrypoint: java -Xmx512m -jar sentinel.jar ################################################################################ - ## Quarterdeck (Database Backend) + ## Quarterdeck (Database Backend) ################################################################################ quarterdeck: # versions (example: v2) receive continous non-breaking (best effort) updates via watchtower. switching between versions @@ -122,7 +122,17 @@ services: --spring.application.json={"security": {"admins": [{"name": "docker", "pass": "docker"}]}} ################################################################################ - ## Automatic updates + ## MongoDB + ################################################################################ + mongo: + image: mongo:latest + ports: + - 127.0.0.1:27017:27017 + volumes: + - ./mongo:/data/db" + + ################################################################################ + ## Automatic updates ################################################################################ # if you want automatic updates, uncomment the watchtower lines below # watchtower is not yet officially available for arm64v8. See https://github.com/v2tec/watchtower/pull/178 for progress @@ -139,7 +149,7 @@ services: ################################################################################ - ## Windows stuff + ## Windows stuff ################################################################################ # WINDOWS ONLY: If you are running on Windows and want to be able to backup the postgres data volume, run diff --git a/config/templates/selfhosting.yml b/config/templates/selfhosting.yml index debd1c6dd..69e406718 100644 --- a/config/templates/selfhosting.yml +++ b/config/templates/selfhosting.yml @@ -25,7 +25,7 @@ version: '3' # This is not the FredBoat version, do not touch this line. services: ################################################################################ - ## Database + ## Database ################################################################################ db: image: fredboat/postgres:latest @@ -33,11 +33,11 @@ services: #build: ./FredBoat/docker/database/ #useful alternative for developers restart: always labels: - - "com.centurylinklabs.watchtower.enable=true" + - "com.centurylinklabs.watchtower.enable=true" # WINDOWS ONLY: if you are running under windows you need to comment out the following two lines: volumes: - - ./postgres-data:/var/lib/postgresql/data + - ./postgres-data:/var/lib/postgresql/data # WINDOWS ONLY: if you are running under windows and want to take manual backups of the database # via a docker volume, uncomment the following two lines and read the snippet at the bottom of this file @@ -45,53 +45,53 @@ services: #- postgres-data-volume:/var/lib/postgresql/data ################################################################################ - ## RabbitMQ + ## RabbitMQ ################################################################################ rabbitmq: image: rabbitmq:3-management restart: always ports: - - 127.0.0.1:5672:5672 # The broker - - 127.0.0.1:15672:15672 # Web UI + - 127.0.0.1:5672:5672 # The broker + - 127.0.0.1:15672:15672 # Web UI labels: - - "com.centurylinklabs.watchtower.enable=true" + - "com.centurylinklabs.watchtower.enable=true" ################################################################################ - ## Lavalink + ## Lavalink ################################################################################ lavalink: image: fredboat/lavalink:master restart: always labels: - - "com.centurylinklabs.watchtower.enable=true" + - "com.centurylinklabs.watchtower.enable=true" ports: - - 127.0.01:2333:2333 + - 127.0.01:2333:2333 volumes: - - ./lavalink.yml:/opt/Lavalink/application.yaml - - ./lavalink_logs:/opt/Lavalink/logs + - ./lavalink.yml:/opt/Lavalink/application.yaml + - ./lavalink_logs:/opt/Lavalink/logs # Need a bigger memory size or any other custom JVM args? Edit the line below accordingly entrypoint: java -Xmx128m -jar Lavalink.jar ################################################################################ - ## Sentinel + ## Sentinel ################################################################################ sentinel: image: fredboat/sentinel:dev restart: on-failure:3 depends_on: - - rabbitmq + - rabbitmq labels: - - "com.centurylinklabs.watchtower.enable=true" + - "com.centurylinklabs.watchtower.enable=true" volumes: - - ./sentinel_logs:/opt/sentinel/logs - - ./common.yml:/opt/sentinel/common.yml + - ./sentinel_logs:/opt/sentinel/logs + - ./common.yml:/opt/sentinel/common.yml environment: - - "SPRING_RABBITMQ_ADDRESSES=amqp://guest:guest@rabbitmq" + - "SPRING_RABBITMQ_ADDRESSES=amqp://guest:guest@rabbitmq" # Need a bigger memory size or any other custom JVM args? Uncomment and edit the line below accordingly #entrypoint: java -Xmx512m -jar sentinel.jar ################################################################################ - ## Quarterdeck (Database Backend) + ## Quarterdeck (Database Backend) ################################################################################ quarterdeck: # versions (example: v2) receive continous non-breaking (best effort) updates via watchtower. switching between versions @@ -99,31 +99,31 @@ services: # FredBoat selfhosting website and/or the selfhosters channel (see top of this file for how to get to these places) # Once you use the dev branch, you may not be able to go back to stable without deleting your database. # To run on (some) arm based machines, prepend the tag with arm64v8-, for example: fredboat/quarterdeck:arm64v8-dev-v1 - # NOTE: Don't use the dev-v1 build unless otherwise noted as it has breaking changes with the other builds. - #image: fredboat/quarterdeck:dev-v1 - image: fredboat/quarterdeck:stable-v1 + #image: fredboat/quarterdeck:stable-v1 + image: fredboat/quarterdeck:dev-v1 restart: always labels: - - "com.centurylinklabs.watchtower.enable=true" + - "com.centurylinklabs.watchtower.enable=true" depends_on: - - db + - db ports: - - 127.0.01:4269:4269 + - 127.0.01:4269:4269 volumes: - - ./quarterdeck.yml:/opt/Quarterdeck/quarterdeck.yaml - - ./quarterdeck_logs:/opt/Quarterdeck/logs + - ./quarterdeck.yml:/opt/Quarterdeck/quarterdeck.yaml + - ./quarterdeck_logs:/opt/Quarterdeck/logs # Need a bigger memory size or any other custom JVM args? Edit the list below accordingly entrypoint: - - java - - -Xmx128m - - -jar - - Quarterdeck.jar - - > - --spring.application.json={"security": {"admins": [{"name": "docker", "pass": "docker"}]}} + - java + - -Xmx128m + - -jar + - Quarterdeck.jar + - > + --spring.application.json={"security": {"admins": [{"name": "docker", "pass": "docker"}]}} + ################################################################################ - ## FredBoat + ## FredBoat ################################################################################ bot: # for choosing between stable or dev, read the paragraph above in the Quarterdeck section @@ -158,9 +158,18 @@ services: # Need a bigger memory size or any other custom JVM args? uncomment and edit the line below accordingly #entrypoint: java -Xmx256m -jar FredBoat.jar + ################################################################################ + ## MongoDB + ################################################################################ + mongo: + image: mongo:latest + ports: + - 127.0.0.1:27017:27017 + volumes: + - ./mongo:/data/db" ################################################################################ - ## Automatic updates + ## Automatic updates ################################################################################ # if you want automatic updates, uncomment the watchtower lines below # watchtower is not yet officially available for arm64v8. See https://github.com/v2tec/watchtower/pull/178 for progress @@ -177,7 +186,7 @@ services: ################################################################################ - ## Windows stuff + ## Windows stuff ################################################################################ # WINDOWS ONLY: If you are running on Windows and want to be able to backup the postgres data volume, run From d7e250c44dd873b4e21f494568d6574c693cbd3f Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 14 Dec 2018 20:19:57 +0100 Subject: [PATCH 032/172] Fix not setting player context on resume --- .../java/fredboat/audio/player/AbstractPlayer.kt | 14 +++++++------- .../main/java/fredboat/audio/player/GuildPlayer.kt | 12 ++++++------ .../java/fredboat/audio/player/PlayerRegistry.kt | 6 ++++-- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt index f324df66b..1c8f2e836 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt @@ -52,7 +52,7 @@ abstract class AbstractPlayer internal constructor( ) : PlayerEventListenerAdapter() { val player: LavalinkPlayer = lavalink.getLink(guild.id.toString()).player - protected var context: AudioTrackContext? = null + var internalContext: AudioTrackContext? = null internal var onPlayHook: Consumer? = null internal var onErrorHook: Consumer? = null @@ -82,9 +82,9 @@ abstract class AbstractPlayer internal constructor( get() { log.trace("getPlayingTrack()") - return if (player.playingTrack == null && context == null) { + return if (player.playingTrack == null && internalContext == null) { audioTrackProvider.peek() - } else context + } else internalContext } //the unshuffled playlist @@ -180,7 +180,7 @@ abstract class AbstractPlayer internal constructor( fun stopTrack() { log.trace("stopTrack()") - context = null + internalContext = null player.stopTrack() } @@ -239,7 +239,7 @@ abstract class AbstractPlayer internal constructor( private fun playTrack(trackContext: AudioTrackContext, silent: Boolean = false) { log.trace("playTrack({})", trackContext.effectiveTitle) - context = trackContext + internalContext = trackContext player.playTrack(trackContext.track) trackContext.track.position = trackContext.startPosition @@ -272,10 +272,10 @@ abstract class AbstractPlayer internal constructor( } fun seekTo(position: Long) { - if (context!!.track.isSeekable) { + if (internalContext!!.track.isSeekable) { player.seekTo(position) } else { - throw MessagingException(context!!.i18n("seekDeniedLiveTrack")) + throw MessagingException(internalContext!!.i18n("seekDeniedLiveTrack")) } } } diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index daabeb4ba..8d5acb74e 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -25,7 +25,6 @@ package fredboat.audio.player -import com.sedmelluq.discord.lavaplayer.player.AudioPlayer import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager import com.sedmelluq.discord.lavaplayer.track.AudioTrack import fredboat.audio.lavalink.SentinelLavalink @@ -43,6 +42,7 @@ import fredboat.sentinel.* import fredboat.util.extension.escapeAndDefuse import fredboat.util.ratelimit.Ratelimiter import fredboat.util.rest.YoutubeAPI +import lavalink.client.player.IPlayer import org.apache.commons.lang3.tuple.ImmutablePair import org.apache.commons.lang3.tuple.Pair import org.bson.types.ObjectId @@ -81,7 +81,7 @@ class GuildPlayer( get() { var millis = audioTrackProvider.durationMillis - val currentTrack = if (player.playingTrack != null) context else null + val currentTrack = if (player.playingTrack != null) internalContext else null if (currentTrack != null && !currentTrack.track.info.isStream) { millis += Math.max(0, currentTrack.effectiveDuration - position) } @@ -92,7 +92,7 @@ class GuildPlayer( val streamsCount: Long get() { var streams = audioTrackProvider.streamsCount().toLong() - val atc = if (player.playingTrack != null) context else null + val atc = if (player.playingTrack != null) internalContext else null if (atc != null && atc.track.info.isStream) streams++ return streams } @@ -291,7 +291,7 @@ class GuildPlayer( if (player.playingTrack != null) { if (start_ <= 0) { - result.add(context!!) + result.add(internalContext!!) end_--//shorten the requested range by 1, but still start at 0, since that's the way the trackprovider counts its tracks } else { //dont add the currently playing track, drop the args by one since the "first" track is currently playing @@ -372,7 +372,7 @@ class GuildPlayer( var skipCurrentTrack = false val toRemove = ArrayList() - val playing = if (player.playingTrack != null) context else null + val playing = if (player.playingTrack != null) internalContext else null for (trackId in trackIds) { if (playing != null && trackId == playing.trackId) { //Should be skipped last, in respect to PlayerEventListener @@ -387,7 +387,7 @@ class GuildPlayer( if (skipCurrentTrack) skip() } - override fun onTrackStart(player: AudioPlayer?, track: AudioTrack?) { + override fun onTrackStart(player: IPlayer?, track: AudioTrack?) { voteSkipCleanup() super.onTrackStart(player, track) } diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index 3c544dc0c..b92781ec3 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -213,9 +213,11 @@ class PlayerRegistry( } if (queue.isNotEmpty()) { - player.player.explicitlySetTrack(queue[0].track) + val atc = queue[0] + player.player.explicitlySetTrack(atc.track) + player.internalContext = atc // Optionally set current track position - if (mongo.position != null) queue[0].track.position = mongo.position + if (mongo.position != null) atc.track.position = mongo.position } player.loadAll(queue) From bf00ed15fc466ecfb28c2f7b09c035f407fb9ca5 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 14 Dec 2018 20:20:51 +0100 Subject: [PATCH 033/172] Fix history NPE --- .../src/main/java/fredboat/audio/player/AbstractPlayer.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt index 1c8f2e836..76b5b1dbc 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt @@ -224,6 +224,10 @@ abstract class AbstractPlayer internal constructor( } private fun updateHistoryQueue() { + if (lastLoadedTrack == null) { + log.warn("No lastLoadedTrack in $this after track end") + return + } if (historyQueue.size == MAX_HISTORY_SIZE) { historyQueue.poll() } From 3b25f9000547fcd339b49fc02a4d9b36b1c757bb Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 14 Dec 2018 20:24:19 +0100 Subject: [PATCH 034/172] Stop duplication of playing track on resume --- .../src/main/java/fredboat/audio/player/PlayerRegistry.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index b92781ec3..a6e7c3e8d 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -197,7 +197,7 @@ class PlayerRegistry( player.volume = mongo.volume } - val queue = mongo.queue.mapNotNull { track -> + var queue = mongo.queue.mapNotNull { track -> try { val at = LavalinkUtil.toAudioTrack(track.blob) val member = guild.getMember(track.requester) ?: guild.selfMember @@ -213,7 +213,8 @@ class PlayerRegistry( } if (queue.isNotEmpty()) { - val atc = queue[0] + queue = queue.toMutableList() + val atc = queue.removeAt(0) player.player.explicitlySetTrack(atc.track) player.internalContext = atc // Optionally set current track position From aedde18de9f66d13c47443d69cfa456ba739614e Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Mon, 17 Dec 2018 18:45:08 +0100 Subject: [PATCH 035/172] Fix resuming after Sentinel is restarted --- .../fredboat/audio/lavalink/SentinelLink.kt | 18 +++++-- .../java/fredboat/audio/player/GuildPlayer.kt | 49 ++++++++++++------- .../java/fredboat/sentinel/wrapperEntities.kt | 14 ++++-- 3 files changed, 56 insertions(+), 25 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt index 73bb36812..02f4e8f9d 100644 --- a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt +++ b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt @@ -4,6 +4,7 @@ import com.fredboat.sentinel.entities.AudioQueueRequest import com.fredboat.sentinel.entities.AudioQueueRequestEnum.* import fredboat.perms.InsufficientPermissionException import fredboat.perms.Permission.* +import fredboat.sentinel.Guild import fredboat.sentinel.VoiceChannel import lavalink.client.io.Link import org.slf4j.Logger @@ -33,7 +34,8 @@ class SentinelLink(val lavalink: SentinelLavalink, guildId: String) : Link(laval public override fun queueAudioDisconnect() = lavalink.sentinel.sendAndForget(routingKey, AudioQueueRequest(QUEUE_DISCONNECT, guildId.toLong())) - fun connect(channel: VoiceChannel) { + + fun connect(channel: VoiceChannel, skipIfSameChannel: Boolean = true) { if (channel.guild.id != guild) throw IllegalArgumentException("The provided VoiceChannel is not a part of the Guild that this AudioManager " + "handles. Please provide a VoiceChannel from the proper Guild") @@ -44,8 +46,8 @@ class SentinelLink(val lavalink: SentinelLavalink, guildId: String) : Link(laval throw InsufficientPermissionException(VOICE_CONNECT, "We do not have permission to join $channel") perms.assertHas(VOICE_SPEAK, "We do not have permission to speak in $channel") - // Do nothing if we are already connected - if (super.getChannel() == channel.id.toString()) return + // Do nothing if we are already connected to that channel + if (skipIfSameChannel && super.getChannel() == channel.id.toString()) return if (channel.userLimit > 1 // Is there a user limit? && channel.userLimit <= channel.members.size // Is that limit reached? @@ -58,6 +60,16 @@ class SentinelLink(val lavalink: SentinelLavalink, guildId: String) : Link(laval queueAudioConnect(channel.id) } + fun getChannel(guild: Guild): VoiceChannel? { + val id = channel ?: return null + val vc = guild.getVoiceChannel(id.toLong()) + + if (vc != null) return vc + + log.warn("Lavalink appears to be connected to vc $id, which doesn't seem to exist in $guild") + return null + } + override fun onVoiceWebSocketClosed(code: Int, reason: String, byRemote: Boolean) { val by = if (byRemote) "Discord" else "LLS" log.info("{}: Lavalink voice WS closed by {}, code {}: {}", guild, by, code, reason) diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index 8d5acb74e..54923c2d7 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -28,6 +28,7 @@ package fredboat.audio.player import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager import com.sedmelluq.discord.lavaplayer.track.AudioTrack import fredboat.audio.lavalink.SentinelLavalink +import fredboat.audio.lavalink.SentinelLink import fredboat.audio.queue.* import fredboat.command.music.control.VoteSkipCommand import fredboat.commandmeta.MessagingException @@ -155,30 +156,17 @@ class GuildPlayer( onPlayHook = Consumer { this.announceTrack(it) } onErrorHook = Consumer { this.handleError(it) } - audioLoader = AudioLoader(ratelimiter, audioTrackProvider, audioPlayerManager, - this, youtubeAPI) - - // If FredBoat just restarted, we will want to reconnect the link - val iGuild = guild as InternalGuild - val vsu = iGuild.cachedVsu - iGuild.cachedVsu = null - if (vsu != null) { - player.link.onVoiceServerUpdate(JSONObject(vsu.raw), vsu.sessionId) - log.info("Using cached VOICE_SERVER_UPDATE for $guild") - - val vc = guild.selfMember.voiceChannel - if (vc == null) - log.warn("Using cached VOICE_SERVER_UPDATE, but it doesn't appear like we are in a voice channel!") - else - player.link.setChannel(vc.idString) - } + audioLoader = AudioLoader(ratelimiter, audioTrackProvider, audioPlayerManager, this, youtubeAPI) } private fun announceTrack(atc: AudioTrackContext) { if (repeatMode != RepeatMode.SINGLE && isTrackAnnounceEnabled && !isPaused) { val activeTextChannel = activeTextChannel - activeTextChannel?.send(atc.i18nFormat("trackAnnounce", atc.effectiveTitle.escapeAndDefuse(), atc.member.effectiveName.escapeAndDefuse())) - ?.subscribe() + activeTextChannel?.send(atc.i18nFormat( + "trackAnnounce", + atc.effectiveTitle.escapeAndDefuse(), + atc.member.effectiveName.escapeAndDefuse() + ))?.subscribe() } } @@ -401,4 +389,27 @@ class GuildPlayer( private fun voteSkipCleanup() { VoteSkipCommand.guildSkipVotes.remove(guildId) } + + /** + * Invoked when subscribing to this player's guild, with an already existing guild + */ + fun linkPostProcess() { + val iGuild = guild as InternalGuild + val vsu = iGuild.cachedVsu + val slink = player.link as SentinelLink + if (vsu != null) { + iGuild.cachedVsu = null + slink.onVoiceServerUpdate(JSONObject(vsu.raw), vsu.sessionId) + log.info("Using cached VOICE_SERVER_UPDATE for $guild") + + val vc = guild.selfMember.voiceChannel + if (vc == null) + log.warn("Using cached VOICE_SERVER_UPDATE, but it doesn't appear like we are in a voice channel!") + else + slink.setChannel(vc.idString) + } else { + val vc = slink.getChannel(guild) ?: return + slink.connect(vc, skipIfSameChannel = false) + } + } } diff --git a/FredBoat/src/main/java/fredboat/sentinel/wrapperEntities.kt b/FredBoat/src/main/java/fredboat/sentinel/wrapperEntities.kt index 7696ed6f7..8cfea8af8 100644 --- a/FredBoat/src/main/java/fredboat/sentinel/wrapperEntities.kt +++ b/FredBoat/src/main/java/fredboat/sentinel/wrapperEntities.kt @@ -125,9 +125,17 @@ class InternalGuild(raw: RawGuild) : Guild(raw) { init { update(raw) cachedVsu = raw.voiceServerUpdate // Must only be set on subscribing, unless setting to null - // Any old GuildPlayer needs to be aware of the new guild object - val player: GuildPlayer? = getBotController().playerRegistry.getExisting(this) - if (player != null) player.guild = this + try { + // Any old GuildPlayer needs to be aware of the new guild object + val player: GuildPlayer? = getBotController().playerRegistry.getExisting(this) + if (player != null) { + player.guild = this + player.linkPostProcess() + } + } catch (e: Exception) { + log.error("Failed to update GuildPlayer after subscribe", e) + } + if (raw.voiceServerUpdate != null) log.info("Received cached VSU for $this") } From 175948b99dc244feda2225ca1038dcb50deeb994 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Thu, 27 Dec 2018 17:35:34 +0100 Subject: [PATCH 036/172] Instrument DAU, WAU, and MAU --- .../audio/lavalink/SentinelLavalink.kt | 2 + .../fredboat/audio/player/AbstractPlayer.kt | 3 + .../java/fredboat/audio/player/GuildPlayer.kt | 4 + .../db/mongo/ActivityMetricsController.kt | 91 +++++++++++++++++++ .../java/fredboat/db/mongo/repositories.kt | 12 +++ .../java/fredboat/event/AudioEventHandler.kt | 9 +- .../metrics/collectors/FredBoatCollector.kt | 14 ++- 7 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 FredBoat/src/main/java/fredboat/db/mongo/ActivityMetricsController.kt diff --git a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt index fb9d5509c..d29bf7204 100644 --- a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt +++ b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt @@ -5,6 +5,7 @@ import fredboat.audio.player.PlayerRegistry import fredboat.config.idString import fredboat.config.property.AppConfig import fredboat.config.property.LavalinkConfig +import fredboat.db.mongo.ActivityMetricsController import fredboat.sentinel.Guild import fredboat.sentinel.GuildCache import fredboat.sentinel.Sentinel @@ -24,6 +25,7 @@ class SentinelLavalink( val sentinel: Sentinel, val guildCache: GuildCache, val appConfig: AppConfig, + val activityMetrics: ActivityMetricsController, lavalinkConfig: LavalinkConfig ) : Lavalink( sentinel.selfUser.idString, diff --git a/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt index 76b5b1dbc..7d9a1e8f8 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt @@ -129,6 +129,7 @@ abstract class AbstractPlayer internal constructor( player.isPaused = false } if (player.playingTrack == null) { + logListeners() loadAndPlay() } @@ -282,4 +283,6 @@ abstract class AbstractPlayer internal constructor( throw MessagingException(internalContext!!.i18n("seekDeniedLiveTrack")) } } + + abstract fun logListeners() } diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index 54923c2d7..d385617dc 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -412,4 +412,8 @@ class GuildPlayer( slink.connect(vc, skipIfSameChannel = false) } } + + override fun logListeners() { + humanUsersInCurrentVC.forEach { lavalink.activityMetrics.logListener(it) } + } } diff --git a/FredBoat/src/main/java/fredboat/db/mongo/ActivityMetricsController.kt b/FredBoat/src/main/java/fredboat/db/mongo/ActivityMetricsController.kt new file mode 100644 index 000000000..531b26815 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/mongo/ActivityMetricsController.kt @@ -0,0 +1,91 @@ +package fredboat.db.mongo + +import fredboat.sentinel.Member +import io.netty.util.internal.ConcurrentSet +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Service +import reactor.core.publisher.Mono +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit + +@Service +class ActivityMetricsController(val repo: ActivityRepository) { + + companion object { + private val log: Logger = LoggerFactory.getLogger(ActivityMetricsController::class.java) + } + + private val executor = Executors.newSingleThreadScheduledExecutor { Thread(it, this.javaClass.simpleName) } + /** Acts as a cache */ + @Volatile + private var loggedUsers = ConcurrentSet() + + private val dayInMs = TimeUnit.DAYS.toMillis(1) + private val currentDay: Long get() = System.currentTimeMillis() / dayInMs + private val tomorrow: Long get() = System.currentTimeMillis() / dayInMs + 1 + private fun Long.toMs() = this * dayInMs + /** Add some artificial latency to make sure we don't trigger the daily task early */ + private val latency = 60000L + private var stats: Stats? = null + + init { + executor.scheduleAtFixedRate( + ::dailyTask, + tomorrow.toMs() - System.currentTimeMillis() + latency, + dayInMs, + TimeUnit.MILLISECONDS + ) + } + + fun logListener(member: Member) { + if (member.isBot) return + val id = member.id + val day = currentDay.toInt() + if (!loggedUsers.add(id)) return + repo.findById(id) + .switchIfEmpty(Mono.from { Activity(id, day) }) + .subscribe { + // Only save if we make any changes + if (it.listenerDays.contains(day)) return@subscribe + repo.save(Activity(id, it.listenerDays.toMutableList().apply { add(day) })) + .subscribe() + } + } + + fun dailyTask() { + val day = currentDay.toInt() - 1 + val week = (day - 6) until day + val month = (day - 29) until day + var dailyCount = 0L + var weeklyCount = 0L + var monthlyCount = 0L + + repo.findAll().doOnNext { ac -> + when { + ac.listenerDays.contains(day) -> { + dailyCount++ + weeklyCount++ + monthlyCount++ + } + ac.listenerDays.any { week.contains(it) } -> { + weeklyCount++ + monthlyCount++ + } + ac.listenerDays.any { month.contains(it) } -> monthlyCount++ + } + }.count().subscribe { + log.info("Iterated {} activity records", it) + stats = Stats(dailyCount.toDouble(), weeklyCount.toDouble(), monthlyCount.toDouble()) + } + } + + fun acquireStats(): Stats? { + val tmp = stats + stats = null + return tmp + } + + data class Stats(val dau: Double, val wau: Double, val mau: Double) + +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt index 9d678fc42..63f0db6a2 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt @@ -5,11 +5,13 @@ import fredboat.audio.queue.SplitAudioTrackContext import lavalink.client.LavalinkUtil import org.slf4j.Logger import org.slf4j.LoggerFactory +import org.springframework.data.annotation.Id import org.springframework.data.repository.reactive.ReactiveCrudRepository import reactor.core.publisher.Flux import reactor.core.publisher.Mono interface PlayerRepository : ReactiveCrudRepository +interface ActivityRepository : ReactiveCrudRepository private val log: Logger = LoggerFactory.getLogger(PlayerRepository::class.java) @@ -46,3 +48,13 @@ private fun GuildPlayer.toMongo() = MongoPlayer( } } ) + +class Activity( + @Id + val id: Long, + val listenerDays: List +) { + constructor(id: Long, day: Int) : this(id, listOf(day)) +} + + diff --git a/FredBoat/src/main/java/fredboat/event/AudioEventHandler.kt b/FredBoat/src/main/java/fredboat/event/AudioEventHandler.kt index bc6a13675..9583ed1e4 100644 --- a/FredBoat/src/main/java/fredboat/event/AudioEventHandler.kt +++ b/FredBoat/src/main/java/fredboat/event/AudioEventHandler.kt @@ -20,8 +20,13 @@ class AudioEventHandler( override fun onVoiceJoin(channel: VoiceChannel, member: Member) { checkForAutoResume(channel, member) - if (!member.isUs) return - getLink(channel).setChannel(channel.id.toString()) + + if (member.isUs) { + getLink(channel).setChannel(channel.id.toString()) + } else if (member.guild.guildPlayer?.isPlaying == true && + member.guild.guildPlayer?.currentVoiceChannel == member.voiceChannel) { + lavalink.activityMetrics.logListener(member) + } } override fun onVoiceLeave(channel: VoiceChannel, member: Member) { diff --git a/FredBoat/src/main/java/fredboat/feature/metrics/collectors/FredBoatCollector.kt b/FredBoat/src/main/java/fredboat/feature/metrics/collectors/FredBoatCollector.kt index fed5d1558..e11271ff4 100644 --- a/FredBoat/src/main/java/fredboat/feature/metrics/collectors/FredBoatCollector.kt +++ b/FredBoat/src/main/java/fredboat/feature/metrics/collectors/FredBoatCollector.kt @@ -26,6 +26,7 @@ package fredboat.feature.metrics.collectors import fredboat.audio.lavalink.SentinelLavalink +import fredboat.db.mongo.ActivityMetricsController import fredboat.feature.metrics.BotMetrics import fredboat.sentinel.GuildCache import io.prometheus.client.Collector @@ -44,7 +45,8 @@ import java.util.* class FredBoatCollector( private val botMetrics: BotMetrics, private val ll: SentinelLavalink, - private val guildCache: GuildCache + private val guildCache: GuildCache, + private val activityMetricsController: ActivityMetricsController ) : Collector() { private var lastEntityCountHash = 0 @@ -76,6 +78,10 @@ class FredBoatCollector( "Number of subscribed guilds", listOf("total")) mfs.add(guildCacheSize) + val listenerActivity = GaugeMetricFamily("fredboat_active_listeners", + "Users on a daily, weekly, or monthly basis", listOf("duration")) + mfs.add(listenerActivity) + //global jda entity stats if (botMetrics.entityCounts != null && botMetrics.entityCounts?.hashCode() != lastEntityCountHash) { val countsPair = botMetrics.entityCounts!! @@ -112,6 +118,12 @@ class FredBoatCollector( guildCacheSize.addMetric(listOf("total"), guildCache.cache.size.toDouble()) + activityMetricsController.acquireStats()?.apply { + listenerActivity.addMetric(listOf("daily"), dau) + listenerActivity.addMetric(listOf("weekly"), wau) + listenerActivity.addMetric(listOf("monthly"), mau) + } + return mfs } } From cee37b2c52a0aa3d259d7a37b627a77aef629d68 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Thu, 27 Dec 2018 17:40:31 +0100 Subject: [PATCH 037/172] Add INSTRUMENT_ACTIVE_USERS feature flag --- .../main/java/fredboat/db/mongo/ActivityMetricsController.kt | 4 ++++ .../src/main/java/fredboat/feature/togglz/FeatureFlags.java | 3 +++ 2 files changed, 7 insertions(+) diff --git a/FredBoat/src/main/java/fredboat/db/mongo/ActivityMetricsController.kt b/FredBoat/src/main/java/fredboat/db/mongo/ActivityMetricsController.kt index 531b26815..3b01e063d 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/ActivityMetricsController.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/ActivityMetricsController.kt @@ -1,5 +1,6 @@ package fredboat.db.mongo +import fredboat.feature.togglz.FeatureFlags import fredboat.sentinel.Member import io.netty.util.internal.ConcurrentSet import org.slf4j.Logger @@ -39,6 +40,7 @@ class ActivityMetricsController(val repo: ActivityRepository) { } fun logListener(member: Member) { + if (!FeatureFlags.INSTRUMENT_ACTIVE_USERS.isActive) return if (member.isBot) return val id = member.id val day = currentDay.toInt() @@ -54,6 +56,8 @@ class ActivityMetricsController(val repo: ActivityRepository) { } fun dailyTask() { + if (!FeatureFlags.INSTRUMENT_ACTIVE_USERS.isActive) return + val day = currentDay.toInt() - 1 val week = (day - 6) until day val month = (day - 29) until day diff --git a/FredBoat/src/main/java/fredboat/feature/togglz/FeatureFlags.java b/FredBoat/src/main/java/fredboat/feature/togglz/FeatureFlags.java index eea9874e7..e25f25dc3 100644 --- a/FredBoat/src/main/java/fredboat/feature/togglz/FeatureFlags.java +++ b/FredBoat/src/main/java/fredboat/feature/togglz/FeatureFlags.java @@ -41,6 +41,9 @@ public enum FeatureFlags implements Feature { @Label("Force soundcloud search instead of youtube") FORCE_SOUNDCLOUD_SEARCH, + @Label("Count active users on a monthly, weekly, and daily basis") + INSTRUMENT_ACTIVE_USERS + ; public boolean isActive() { From 8aafd435703f924272b6f8faa09c67fc9cbebacb Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Thu, 27 Dec 2018 17:53:46 +0100 Subject: [PATCH 038/172] Fix test compile error --- FredBoat/src/test/java/fredboat/audio/PlayerRegistryTest.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/FredBoat/src/test/java/fredboat/audio/PlayerRegistryTest.kt b/FredBoat/src/test/java/fredboat/audio/PlayerRegistryTest.kt index 8dc0b6230..15514b065 100644 --- a/FredBoat/src/test/java/fredboat/audio/PlayerRegistryTest.kt +++ b/FredBoat/src/test/java/fredboat/audio/PlayerRegistryTest.kt @@ -5,8 +5,8 @@ import fredboat.audio.player.PlayerRegistry import fredboat.sentinel.getGuildMono import fredboat.testutil.IntegrationTest import fredboat.testutil.sentinel.Raws -import kotlinx.coroutines.experimental.reactive.awaitFirst -import kotlinx.coroutines.experimental.runBlocking +import kotlinx.coroutines.reactive.awaitFirst +import kotlinx.coroutines.runBlocking import org.junit.Test import org.mockito.Mockito import reactor.test.StepVerifier @@ -23,7 +23,6 @@ class PlayerRegistryTest : IntegrationTest() { } @Test - @Suppress("BlockingMethodInNonBlockingContext") fun testLazyMono(playerRegistry: PlayerRegistry) { val guild = runBlocking { getGuildMono(Raws.guild.id).awaitFirst()!! From 84a25214a462462a92ef1a3cd63f703d1187cc5e Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Thu, 27 Dec 2018 22:05:14 +0100 Subject: [PATCH 039/172] Merge AbstractPlayer into GuildPlayer --- .../fredboat/audio/player/AbstractPlayer.kt | 288 ------------------ .../java/fredboat/audio/player/GuildPlayer.kt | 240 ++++++++++++++- .../audio/queue/TrackEndMarkerHandler.kt | 4 +- 3 files changed, 238 insertions(+), 294 deletions(-) delete mode 100644 FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt diff --git a/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt deleted file mode 100644 index 7d9a1e8f8..000000000 --- a/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt +++ /dev/null @@ -1,288 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package fredboat.audio.player - -import com.google.common.collect.Lists -import com.sedmelluq.discord.lavaplayer.track.AudioTrack -import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason -import com.sedmelluq.discord.lavaplayer.track.TrackMarker -import fredboat.audio.lavalink.SentinelLavalink -import fredboat.audio.queue.AudioTrackContext -import fredboat.audio.queue.ITrackProvider -import fredboat.audio.queue.SplitAudioTrackContext -import fredboat.audio.queue.TrackEndMarkerHandler -import fredboat.commandmeta.MessagingException -import fredboat.sentinel.Guild -import fredboat.util.TextUtils -import lavalink.client.player.IPlayer -import lavalink.client.player.LavalinkPlayer -import lavalink.client.player.event.PlayerEventListenerAdapter -import org.slf4j.LoggerFactory -import java.util.* -import java.util.concurrent.ConcurrentLinkedQueue -import java.util.function.Consumer - -abstract class AbstractPlayer internal constructor( - lavalink: SentinelLavalink, - internal val audioTrackProvider: ITrackProvider, - guild: Guild -) : PlayerEventListenerAdapter() { - - val player: LavalinkPlayer = lavalink.getLink(guild.id.toString()).player - var internalContext: AudioTrackContext? = null - - internal var onPlayHook: Consumer? = null - internal var onErrorHook: Consumer? = null - @Volatile - private var lastLoadedTrack: AudioTrackContext? = null - private val historyQueue = ConcurrentLinkedQueue() - - companion object { - private val log = LoggerFactory.getLogger(AbstractPlayer::class.java) - private const val MAX_HISTORY_SIZE = 20 - } - - val isQueueEmpty: Boolean - get() { - log.trace("isQueueEmpty()") - - return player.playingTrack == null && audioTrackProvider.isEmpty - } - - val trackCountInHistory: Int - get() = historyQueue.size - - val isHistoryQueueEmpty: Boolean - get() = historyQueue.isEmpty() - - val playingTrack: AudioTrackContext? - get() { - log.trace("getPlayingTrack()") - - return if (player.playingTrack == null && internalContext == null) { - audioTrackProvider.peek() - } else internalContext - } - - //the unshuffled playlist - //Includes currently playing track, which comes first - val remainingTracks: List - get() { - log.trace("getRemainingTracks()") - val list = ArrayList() - val atc = playingTrack - if (atc != null) { - list.add(atc) - } - - list.addAll(audioTrackProvider.asList) - return list - } - - var volume: Float - get() = player.volume.toFloat() / 100 - set(vol) { - player.volume = (vol * 100).toInt() - } - - val isPlaying: Boolean - get() = player.playingTrack != null && !player.isPaused - - val isPaused: Boolean - get() = player.isPaused - - val position: Long - get() = player.trackPosition - - init { - @Suppress("LeakingThis") - player.addListener(this) - } - - fun play() { - log.trace("play()") - - if (player.isPaused) { - player.isPaused = false - } - if (player.playingTrack == null) { - logListeners() - loadAndPlay() - } - - } - - fun setPause(pause: Boolean) { - log.trace("setPause({})", pause) - - if (pause) { - player.isPaused = true - } else { - player.isPaused = false - play() - } - } - - /** - * Pause the player - */ - fun pause() { - log.trace("pause()") - - player.isPaused = true - } - - /** - * Clear the tracklist and stop the current track - */ - fun stop() { - log.trace("stop()") - - audioTrackProvider.clear() - stopTrack() - } - - /** - * Skip the current track - */ - fun skip() { - log.trace("skip()") - - audioTrackProvider.skipped() - stopTrack() - } - - /** - * Stop the current track. - */ - fun stopTrack() { - log.trace("stopTrack()") - - internalContext = null - player.stopTrack() - } - - fun getTracksInHistory(start: Int, end: Int): List { - val start2 = Math.max(start, 0) - val end2 = Math.max(end, start) - val historyList = ArrayList(historyQueue) - - return if (historyList.size >= end2) { - Lists.reverse(ArrayList(historyQueue)).subList(start2, end2) - } else { - ArrayList() - } - } - - override fun onTrackEnd(player: IPlayer?, track: AudioTrack?, endReason: AudioTrackEndReason?) { - log.debug("onTrackEnd({} {} {}) called", track!!.info.title, endReason!!.name, endReason.mayStartNext) - - if (endReason == AudioTrackEndReason.FINISHED || endReason == AudioTrackEndReason.STOPPED) { - updateHistoryQueue() - loadAndPlay() - } else if (endReason == AudioTrackEndReason.CLEANUP) { - log.info("Track " + track.identifier + " was cleaned up") - } else if (endReason == AudioTrackEndReason.LOAD_FAILED) { - if (onErrorHook != null) - onErrorHook!!.accept(MessagingException("Track `" + TextUtils.escapeAndDefuse(track.info.title) + "` failed to load. Skipping...")) - audioTrackProvider.skipped() - loadAndPlay() - } else { - log.warn("Track " + track.identifier + " ended with unexpected reason: " + endReason) - } - } - - //request the next track from the track provider and start playing it - private fun loadAndPlay() { - log.trace("loadAndPlay()") - - val atc = audioTrackProvider.provideAudioTrack() - lastLoadedTrack = atc - atc?.let { playTrack(it) } - } - - private fun updateHistoryQueue() { - if (lastLoadedTrack == null) { - log.warn("No lastLoadedTrack in $this after track end") - return - } - if (historyQueue.size == MAX_HISTORY_SIZE) { - historyQueue.poll() - } - historyQueue.add(lastLoadedTrack) - } - - /** - * Plays the provided track. - * - * - * Silently playing a track will not trigger the onPlayHook (which announces the track usually) - */ - private fun playTrack(trackContext: AudioTrackContext, silent: Boolean = false) { - log.trace("playTrack({})", trackContext.effectiveTitle) - - internalContext = trackContext - player.playTrack(trackContext.track) - trackContext.track.position = trackContext.startPosition - - if (trackContext is SplitAudioTrackContext) { - //Ensure we don't step over our bounds - log.info("Start: ${trackContext.startPosition} End: ${trackContext.startPosition + trackContext.effectiveDuration}") - - trackContext.track.setMarker( - TrackMarker(trackContext.startPosition + trackContext.effectiveDuration, - TrackEndMarkerHandler(this, trackContext))) - } - - if (!silent && onPlayHook != null) onPlayHook!!.accept(trackContext) - } - - internal open fun destroy() { - stop() - player.removeListener(this) - player.link.destroy() - } - - override fun onTrackException(player: IPlayer?, track: AudioTrack, exception: Exception?) { - log.error("Lavaplayer encountered an exception while playing {}", - track.identifier, exception) - } - - override fun onTrackStuck(player: IPlayer?, track: AudioTrack, thresholdMs: Long) { - log.error("Lavaplayer got stuck while playing {}", - track.identifier) - } - - fun seekTo(position: Long) { - if (internalContext!!.track.isSeekable) { - player.seekTo(position) - } else { - throw MessagingException(internalContext!!.i18n("seekDeniedLiveTrack")) - } - } - - abstract fun logListeners() -} diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index d385617dc..4419e591a 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -25,8 +25,11 @@ package fredboat.audio.player +import com.google.common.collect.Lists import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager import com.sedmelluq.discord.lavaplayer.track.AudioTrack +import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason +import com.sedmelluq.discord.lavaplayer.track.TrackMarker import fredboat.audio.lavalink.SentinelLavalink import fredboat.audio.lavalink.SentinelLink import fredboat.audio.queue.* @@ -40,16 +43,20 @@ import fredboat.feature.I18n import fredboat.perms.Permission import fredboat.perms.PermsUtil import fredboat.sentinel.* +import fredboat.util.TextUtils import fredboat.util.extension.escapeAndDefuse import fredboat.util.ratelimit.Ratelimiter import fredboat.util.rest.YoutubeAPI import lavalink.client.player.IPlayer +import lavalink.client.player.LavalinkPlayer +import lavalink.client.player.event.PlayerEventListenerAdapter import org.apache.commons.lang3.tuple.ImmutablePair import org.apache.commons.lang3.tuple.Pair import org.bson.types.ObjectId import org.json.JSONObject import org.slf4j.LoggerFactory import java.util.* +import java.util.concurrent.ConcurrentLinkedQueue import java.util.function.Consumer import kotlin.streams.toList @@ -61,13 +68,23 @@ class GuildPlayer( private val guildConfigService: GuildConfigService, ratelimiter: Ratelimiter, youtubeAPI: YoutubeAPI -) : AbstractPlayer(lavalink, SimpleTrackProvider(), guild) { +) : PlayerEventListenerAdapter() { private val audioLoader: AudioLoader + private val audioTrackProvider: ITrackProvider = SimpleTrackProvider() val guildId = guild.id + val player: LavalinkPlayer = lavalink.getLink(guild.id.toString()).player + var internalContext: AudioTrackContext? = null + + private var onPlayHook: Consumer? = null + private var onErrorHook: Consumer? = null + @Volatile + private var lastLoadedTrack: AudioTrackContext? = null + private val historyQueue = ConcurrentLinkedQueue() companion object { private val log = LoggerFactory.getLogger(GuildPlayer::class.java) + private const val MAX_HISTORY_SIZE = 20 } val trackCount: Int @@ -380,9 +397,11 @@ class GuildPlayer( super.onTrackStart(player, track) } - override fun destroy() { + fun destroy() { audioTrackProvider.clear() - super.destroy() + stop() + player.removeListener(this) + player.link.destroy() log.info("Player for $guildId was destroyed.") } @@ -413,7 +432,220 @@ class GuildPlayer( } } - override fun logListeners() { + val isQueueEmpty: Boolean + get() { + log.trace("isQueueEmpty()") + + return player.playingTrack == null && audioTrackProvider.isEmpty + } + + val trackCountInHistory: Int + get() = historyQueue.size + + val isHistoryQueueEmpty: Boolean + get() = historyQueue.isEmpty() + + val playingTrack: AudioTrackContext? + get() { + log.trace("getPlayingTrack()") + + return if (player.playingTrack == null && internalContext == null) { + audioTrackProvider.peek() + } else internalContext + } + + //the unshuffled playlist + //Includes currently playing track, which comes first + val remainingTracks: List + get() { + log.trace("getRemainingTracks()") + val list = ArrayList() + val atc = playingTrack + if (atc != null) { + list.add(atc) + } + + list.addAll(audioTrackProvider.asList) + return list + } + + var volume: Float + get() = player.volume.toFloat() / 100 + set(vol) { + player.volume = (vol * 100).toInt() + } + + val isPlaying: Boolean + get() = player.playingTrack != null && !player.isPaused + + val isPaused: Boolean + get() = player.isPaused + + val position: Long + get() = player.trackPosition + + init { + @Suppress("LeakingThis") + player.addListener(this) + } + + fun play() { + log.trace("play()") + + if (player.isPaused) { + player.isPaused = false + } + if (player.playingTrack == null) { + logListeners() + loadAndPlay() + } + + } + + fun setPause(pause: Boolean) { + log.trace("setPause({})", pause) + + if (pause) { + player.isPaused = true + } else { + player.isPaused = false + play() + } + } + + /** + * Pause the player + */ + fun pause() { + log.trace("pause()") + + player.isPaused = true + } + + /** + * Clear the tracklist and stop the current track + */ + fun stop() { + log.trace("stop()") + + audioTrackProvider.clear() + stopTrack() + } + + /** + * Skip the current track + */ + fun skip() { + log.trace("skip()") + + audioTrackProvider.skipped() + stopTrack() + } + + /** + * Stop the current track. + */ + fun stopTrack() { + log.trace("stopTrack()") + + internalContext = null + player.stopTrack() + } + + fun getTracksInHistory(start: Int, end: Int): List { + val start2 = Math.max(start, 0) + val end2 = Math.max(end, start) + val historyList = ArrayList(historyQueue) + + return if (historyList.size >= end2) { + Lists.reverse(ArrayList(historyQueue)).subList(start2, end2) + } else { + ArrayList() + } + } + + override fun onTrackEnd(player: IPlayer?, track: AudioTrack?, endReason: AudioTrackEndReason?) { + log.debug("onTrackEnd({} {} {}) called", track!!.info.title, endReason!!.name, endReason.mayStartNext) + + if (endReason == AudioTrackEndReason.FINISHED || endReason == AudioTrackEndReason.STOPPED) { + updateHistoryQueue() + loadAndPlay() + } else if (endReason == AudioTrackEndReason.CLEANUP) { + log.info("Track " + track.identifier + " was cleaned up") + } else if (endReason == AudioTrackEndReason.LOAD_FAILED) { + if (onErrorHook != null) + onErrorHook!!.accept(MessagingException("Track `" + TextUtils.escapeAndDefuse(track.info.title) + "` failed to load. Skipping...")) + audioTrackProvider.skipped() + loadAndPlay() + } else { + log.warn("Track " + track.identifier + " ended with unexpected reason: " + endReason) + } + } + + //request the next track from the track provider and start playing it + private fun loadAndPlay() { + log.trace("loadAndPlay()") + + val atc = audioTrackProvider.provideAudioTrack() + lastLoadedTrack = atc + atc?.let { playTrack(it) } + } + + private fun updateHistoryQueue() { + if (lastLoadedTrack == null) { + log.warn("No lastLoadedTrack in $this after track end") + return + } + if (historyQueue.size == MAX_HISTORY_SIZE) { + historyQueue.poll() + } + historyQueue.add(lastLoadedTrack) + } + + /** + * Plays the provided track. + * + * + * Silently playing a track will not trigger the onPlayHook (which announces the track usually) + */ + private fun playTrack(trackContext: AudioTrackContext, silent: Boolean = false) { + log.trace("playTrack({})", trackContext.effectiveTitle) + + internalContext = trackContext + player.playTrack(trackContext.track) + trackContext.track.position = trackContext.startPosition + + if (trackContext is SplitAudioTrackContext) { + //Ensure we don't step over our bounds + log.info("Start: ${trackContext.startPosition} End: ${trackContext.startPosition + trackContext.effectiveDuration}") + + trackContext.track.setMarker( + TrackMarker(trackContext.startPosition + trackContext.effectiveDuration, + TrackEndMarkerHandler(this, trackContext))) + } + + if (!silent && onPlayHook != null) onPlayHook!!.accept(trackContext) + } + + override fun onTrackException(player: IPlayer?, track: AudioTrack, exception: Exception?) { + log.error("Lavaplayer encountered an exception while playing {}", + track.identifier, exception) + } + + override fun onTrackStuck(player: IPlayer?, track: AudioTrack, thresholdMs: Long) { + log.error("Lavaplayer got stuck while playing {}", + track.identifier) + } + + fun seekTo(position: Long) { + if (internalContext!!.track.isSeekable) { + player.seekTo(position) + } else { + throw MessagingException(internalContext!!.i18n("seekDeniedLiveTrack")) + } + } + + private fun logListeners() { humanUsersInCurrentVC.forEach { lavalink.activityMetrics.logListener(it) } } } diff --git a/FredBoat/src/main/java/fredboat/audio/queue/TrackEndMarkerHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/TrackEndMarkerHandler.kt index 71548909e..d5311ea3a 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/TrackEndMarkerHandler.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/TrackEndMarkerHandler.kt @@ -26,11 +26,11 @@ package fredboat.audio.queue import com.sedmelluq.discord.lavaplayer.track.TrackMarkerHandler -import fredboat.audio.player.AbstractPlayer +import fredboat.audio.player.GuildPlayer import org.slf4j.LoggerFactory class TrackEndMarkerHandler( - private val player: AbstractPlayer, + private val player: GuildPlayer, private val track: AudioTrackContext ) : TrackMarkerHandler { From b665566c48356d97cd638ed27723221934653639 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Thu, 27 Dec 2018 22:13:31 +0100 Subject: [PATCH 040/172] Refactor GuildPlayer --- .../java/fredboat/audio/player/GuildPlayer.kt | 115 +++++++++--------- 1 file changed, 55 insertions(+), 60 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index 4419e591a..d04b37f47 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -70,8 +70,8 @@ class GuildPlayer( youtubeAPI: YoutubeAPI ) : PlayerEventListenerAdapter() { - private val audioLoader: AudioLoader private val audioTrackProvider: ITrackProvider = SimpleTrackProvider() + private val audioLoader = AudioLoader(ratelimiter, audioTrackProvider, audioPlayerManager, this, youtubeAPI) val guildId = guild.id val player: LavalinkPlayer = lavalink.getLink(guild.id.toString()).player var internalContext: AudioTrackContext? = null @@ -168,12 +168,64 @@ class GuildPlayer( return enabled } + val isQueueEmpty: Boolean + get() { + log.trace("isQueueEmpty()") + + return player.playingTrack == null && audioTrackProvider.isEmpty + } + + val trackCountInHistory: Int + get() = historyQueue.size + + val isHistoryQueueEmpty: Boolean + get() = historyQueue.isEmpty() + + val playingTrack: AudioTrackContext? + get() { + log.trace("getPlayingTrack()") + + return if (player.playingTrack == null && internalContext == null) { + audioTrackProvider.peek() + } else internalContext + } + + //the unshuffled playlist + //Includes currently playing track, which comes first + val remainingTracks: List + get() { + log.trace("getRemainingTracks()") + val list = ArrayList() + val atc = playingTrack + if (atc != null) { + list.add(atc) + } + + list.addAll(audioTrackProvider.asList) + return list + } + + var volume: Float + get() = player.volume.toFloat() / 100 + set(vol) { + player.volume = (vol * 100).toInt() + } + + val isPlaying: Boolean + get() = player.playingTrack != null && !player.isPaused + + val isPaused: Boolean + get() = player.isPaused + + val position: Long + get() = player.trackPosition + init { log.debug("Constructing GuildPlayer({})", guild) onPlayHook = Consumer { this.announceTrack(it) } onErrorHook = Consumer { this.handleError(it) } - - audioLoader = AudioLoader(ratelimiter, audioTrackProvider, audioPlayerManager, this, youtubeAPI) + @Suppress("LeakingThis") + player.addListener(this) } private fun announceTrack(atc: AudioTrackContext) { @@ -432,63 +484,6 @@ class GuildPlayer( } } - val isQueueEmpty: Boolean - get() { - log.trace("isQueueEmpty()") - - return player.playingTrack == null && audioTrackProvider.isEmpty - } - - val trackCountInHistory: Int - get() = historyQueue.size - - val isHistoryQueueEmpty: Boolean - get() = historyQueue.isEmpty() - - val playingTrack: AudioTrackContext? - get() { - log.trace("getPlayingTrack()") - - return if (player.playingTrack == null && internalContext == null) { - audioTrackProvider.peek() - } else internalContext - } - - //the unshuffled playlist - //Includes currently playing track, which comes first - val remainingTracks: List - get() { - log.trace("getRemainingTracks()") - val list = ArrayList() - val atc = playingTrack - if (atc != null) { - list.add(atc) - } - - list.addAll(audioTrackProvider.asList) - return list - } - - var volume: Float - get() = player.volume.toFloat() / 100 - set(vol) { - player.volume = (vol * 100).toInt() - } - - val isPlaying: Boolean - get() = player.playingTrack != null && !player.isPaused - - val isPaused: Boolean - get() = player.isPaused - - val position: Long - get() = player.trackPosition - - init { - @Suppress("LeakingThis") - player.addListener(this) - } - fun play() { log.trace("play()") From 204c6c7dba50b58c63fc8148661bae71c1520166 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Thu, 27 Dec 2018 22:51:33 +0100 Subject: [PATCH 041/172] Extract GuildPlayer members to extensions --- .../java/fredboat/audio/player/GuildPlayer.kt | 220 +----------------- .../fredboat/audio/player/guildPlayerUtils.kt | 216 +++++++++++++++++ .../java/fredboat/audio/queue/audioLoading.kt | 1 + .../fredboat/command/admin/EvalCommand.kt | 3 +- .../fredboat/command/info/DebugCommand.kt | 5 +- .../command/music/control/JoinCommand.kt | 1 + .../command/music/control/PauseCommand.kt | 1 + .../command/music/control/PlayCommand.kt | 4 +- .../command/music/control/SkipCommand.kt | 2 +- .../command/music/control/UnpauseCommand.kt | 2 + .../command/music/control/VoteSkipCommand.kt | 2 + .../java/fredboat/db/mongo/repositories.kt | 3 +- .../java/fredboat/event/AudioEventHandler.kt | 7 +- .../fredboat/event/MusicPersistenceHandler.kt | 4 +- .../fredboat/event/ShardLifecycleHandler.kt | 3 +- 15 files changed, 249 insertions(+), 225 deletions(-) create mode 100644 FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index d04b37f47..fe04e493d 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -25,7 +25,6 @@ package fredboat.audio.player -import com.google.common.collect.Lists import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager import com.sedmelluq.discord.lavaplayer.track.AudioTrack import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason @@ -37,12 +36,10 @@ import fredboat.command.music.control.VoteSkipCommand import fredboat.commandmeta.MessagingException import fredboat.commandmeta.abs.CommandContext import fredboat.db.api.GuildConfigService -import fredboat.definitions.PermissionLevel import fredboat.definitions.RepeatMode -import fredboat.feature.I18n -import fredboat.perms.Permission -import fredboat.perms.PermsUtil -import fredboat.sentinel.* +import fredboat.sentinel.Guild +import fredboat.sentinel.InternalGuild +import fredboat.sentinel.TextChannel import fredboat.util.TextUtils import fredboat.util.extension.escapeAndDefuse import fredboat.util.ratelimit.Ratelimiter @@ -50,15 +47,12 @@ import fredboat.util.rest.YoutubeAPI import lavalink.client.player.IPlayer import lavalink.client.player.LavalinkPlayer import lavalink.client.player.event.PlayerEventListenerAdapter -import org.apache.commons.lang3.tuple.ImmutablePair -import org.apache.commons.lang3.tuple.Pair import org.bson.types.ObjectId import org.json.JSONObject import org.slf4j.LoggerFactory import java.util.* import java.util.concurrent.ConcurrentLinkedQueue import java.util.function.Consumer -import kotlin.streams.toList class GuildPlayer( val lavalink: SentinelLavalink, @@ -70,7 +64,7 @@ class GuildPlayer( youtubeAPI: YoutubeAPI ) : PlayerEventListenerAdapter() { - private val audioTrackProvider: ITrackProvider = SimpleTrackProvider() + val audioTrackProvider: ITrackProvider = SimpleTrackProvider() private val audioLoader = AudioLoader(ratelimiter, audioTrackProvider, audioPlayerManager, this, youtubeAPI) val guildId = guild.id val player: LavalinkPlayer = lavalink.getLink(guild.id.toString()).player @@ -80,45 +74,13 @@ class GuildPlayer( private var onErrorHook: Consumer? = null @Volatile private var lastLoadedTrack: AudioTrackContext? = null - private val historyQueue = ConcurrentLinkedQueue() + val historyQueue = ConcurrentLinkedQueue() companion object { private val log = LoggerFactory.getLogger(GuildPlayer::class.java) private const val MAX_HISTORY_SIZE = 20 } - val trackCount: Int - get() { - var trackCount = audioTrackProvider.size() - if (player.playingTrack != null) trackCount++ - return trackCount - } - - //Live streams are considered to have a length of 0 - val totalRemainingMusicTimeMillis: Long - get() { - var millis = audioTrackProvider.durationMillis - - val currentTrack = if (player.playingTrack != null) internalContext else null - if (currentTrack != null && !currentTrack.track.info.isStream) { - millis += Math.max(0, currentTrack.effectiveDuration - position) - } - return millis - } - - - val streamsCount: Long - get() { - var streams = audioTrackProvider.streamsCount().toLong() - val atc = if (player.playingTrack != null) internalContext else null - if (atc != null && atc.track.info.isStream) streams++ - return streams - } - - - val currentVoiceChannel: VoiceChannel? - get() = guild.selfMember.voiceChannel - /** * @return The text channel currently used for music commands. * @@ -130,12 +92,6 @@ class GuildPlayer( return musicTextChannelProvider.getMusicTextChannel(guild) } - /** - * @return Users who are not bots - */ - val humanUsersInCurrentVC: List - get() = getHumanUsersInVC(currentVoiceChannel) - var repeatMode: RepeatMode get() = if (audioTrackProvider is AbstractTrackProvider) audioTrackProvider.repeatMode @@ -168,23 +124,8 @@ class GuildPlayer( return enabled } - val isQueueEmpty: Boolean - get() { - log.trace("isQueueEmpty()") - - return player.playingTrack == null && audioTrackProvider.isEmpty - } - - val trackCountInHistory: Int - get() = historyQueue.size - - val isHistoryQueueEmpty: Boolean - get() = historyQueue.isEmpty() - val playingTrack: AudioTrackContext? get() { - log.trace("getPlayingTrack()") - return if (player.playingTrack == null && internalContext == null) { audioTrackProvider.peek() } else internalContext @@ -247,65 +188,6 @@ class GuildPlayer( activeTextChannel?.send("Something went wrong!\n${t.message}")?.subscribe() } - fun joinChannel(usr: Member) { - val targetChannel = usr.voiceChannel - joinChannel(targetChannel) - } - - fun joinChannel(targetChannel: VoiceChannel?) { - if (targetChannel == null) { - throw MessagingException(I18n.get(guild).getString("playerUserNotInChannel")) - } - if (targetChannel == currentVoiceChannel) { - // already connected to the channel - return - } - - val guild = targetChannel.guild - val permissions = targetChannel.ourEffectivePermissions - - if (permissions hasNot Permission.VIEW_CHANNEL) { - val i18n = I18n.get(guild).getString("permissionMissingBot") - throw MessagingException("$i18n ${Permission.VIEW_CHANNEL.uiName}") - } - - if (permissions hasNot Permission.VOICE_CONNECT && guild.selfMember.voiceChannel != targetChannel) { - throw MessagingException(I18n.get(guild).getString("playerJoinConnectDenied")) - } - - if (permissions hasNot Permission.VOICE_SPEAK) { - throw MessagingException(I18n.get(guild).getString("playerJoinSpeakDenied")) - } - - if (targetChannel.userLimit > 0 - && targetChannel.userLimit <= targetChannel.members.size - && permissions hasNot Permission.VOICE_MOVE_OTHERS) { - throw MessagingException(String.format("The channel you want me to join is full!" + - " Please free up some space, or give me the permission to **%s** to bypass the limit.", //todo i18n - Permission.VOICE_MOVE_OTHERS.uiName)) - } - - try { - lavalink.getLink(guild).connect(targetChannel) - log.info("Connected to voice channel $targetChannel") - } catch (e: Exception) { - log.error("Failed to join voice channel {}", targetChannel, e) - } - - } - - fun leaveVoiceChannelRequest(commandContext: CommandContext, silent: Boolean) { - if (!silent) { - val currentVc = commandContext.guild.selfMember.voiceChannel - if (currentVc == null) { - commandContext.reply(commandContext.i18n("playerNotInChannel")) - } else { - commandContext.reply(commandContext.i18nFormat("playerLeftChannel", currentVc.name)) - } - } - lavalink.getLink(guild).disconnect() - } - fun queue(identifier: String, context: CommandContext) { val ic = IdentifierContext(identifier, context.textChannel, context.member) @@ -331,50 +213,11 @@ class GuildPlayer( play() } - //add a bunch of tracks to the track provider + /** Add a bunch of tracks to the track provider */ fun loadAll(tracks: Collection) { audioTrackProvider.addAll(tracks) } - @Suppress("LocalVariableName") - fun getTracksInRange(start: Int, end: Int): List { - // Make mutable - var start_ = start - var end_ = end - - val result = ArrayList() - - //adjust args for whether there is a track playing or not - - if (player.playingTrack != null) { - if (start_ <= 0) { - result.add(internalContext!!) - end_--//shorten the requested range by 1, but still start at 0, since that's the way the trackprovider counts its tracks - } else { - //dont add the currently playing track, drop the args by one since the "first" track is currently playing - start_-- - end_-- - } - } else { - //nothing to do here, args are fine to pass on - } - - result.addAll(audioTrackProvider.getTracksInRange(start_, end_)) - return result - } - - /** Similar to [getTracksInRange], but only gets the trackIds */ - fun getTrackIdsInRange(start: Int, end: Int): List = getTracksInRange(start, end).stream() - .map { it.trackId } - .toList() - - fun getHumanUsersInVC(vc: VoiceChannel?): List { - vc ?: return emptyList() - return vc.members.stream() - .filter { !it.isBot } - .toList() - } - override fun toString(): String { return "[GP:$guildId]" } @@ -387,44 +230,6 @@ class GuildPlayer( } } - //Success, fail message - private suspend fun canMemberSkipTracks(member: Member, trackIds: Collection): Pair { - if (PermsUtil.checkPerms(PermissionLevel.DJ, member)) { - return ImmutablePair(true, null) - } else { - //We are not a mod - val userId = member.id - - //if there is a currently playing track, and the track is requested to be skipped, but not owned by the - // requesting user, then currentTrackSkippable should be false - var currentTrackSkippable = true - val playingTrack = playingTrack - if (playingTrack != null - && trackIds.contains(playingTrack.trackId) - && playingTrack.userId != userId) { - - currentTrackSkippable = false - } - - return if (currentTrackSkippable && audioTrackProvider.isUserTrackOwner(userId, trackIds)) { //check ownership of the queued tracks - ImmutablePair(true, null) - } else { - ImmutablePair(false, I18n.get(guild).getString("skipDeniedTooManyTracks")) - } - } - } - - suspend fun skipTracksForMemberPerms(context: CommandContext, trackIds: Collection, successMessage: String) { - val pair = canMemberSkipTracks(context.member, trackIds) - - if (pair.left) { - context.reply(successMessage) - skipTracks(trackIds) - } else { - context.replyWithName(pair.right) - } - } - fun skipTracks(trackIds: Collection) { var skipCurrentTrack = false @@ -547,18 +352,6 @@ class GuildPlayer( player.stopTrack() } - fun getTracksInHistory(start: Int, end: Int): List { - val start2 = Math.max(start, 0) - val end2 = Math.max(end, start) - val historyList = ArrayList(historyQueue) - - return if (historyList.size >= end2) { - Lists.reverse(ArrayList(historyQueue)).subList(start2, end2) - } else { - ArrayList() - } - } - override fun onTrackEnd(player: IPlayer?, track: AudioTrack?, endReason: AudioTrackEndReason?) { log.debug("onTrackEnd({} {} {}) called", track!!.info.title, endReason!!.name, endReason.mayStartNext) @@ -600,7 +393,6 @@ class GuildPlayer( /** * Plays the provided track. * - * * Silently playing a track will not trigger the onPlayHook (which announces the track usually) */ private fun playTrack(trackContext: AudioTrackContext, silent: Boolean = false) { diff --git a/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt b/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt new file mode 100644 index 000000000..ef3a0768b --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt @@ -0,0 +1,216 @@ +package fredboat.audio.player + +import com.google.common.collect.Lists +import fredboat.audio.queue.AudioTrackContext +import fredboat.commandmeta.MessagingException +import fredboat.commandmeta.abs.CommandContext +import fredboat.definitions.PermissionLevel +import fredboat.feature.I18n +import fredboat.perms.Permission +import fredboat.perms.PermsUtil +import fredboat.sentinel.Member +import fredboat.sentinel.VoiceChannel +import org.apache.commons.lang3.tuple.ImmutablePair +import org.apache.commons.lang3.tuple.Pair +import org.bson.types.ObjectId +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.util.* +import kotlin.streams.toList + +private val log: Logger = LoggerFactory.getLogger(GuildPlayer::class.java) + +val GuildPlayer.trackCount: Int + get() { + var trackCount = audioTrackProvider.size() + if (player.playingTrack != null) trackCount++ + return trackCount + } + +/** Live streams are considered to have a length of 0 */ +val GuildPlayer.totalRemainingMusicTimeMillis: Long + get() { + var millis = audioTrackProvider.durationMillis + + val currentTrack = if (player.playingTrack != null) internalContext else null + if (currentTrack != null && !currentTrack.track.info.isStream) { + millis += Math.max(0, currentTrack.effectiveDuration - position) + } + return millis + } + +val GuildPlayer.streamsCount: Long + get() { + var streams = audioTrackProvider.streamsCount().toLong() + val atc = if (player.playingTrack != null) internalContext else null + if (atc != null && atc.track.info.isStream) streams++ + return streams + } + +val GuildPlayer.voiceChannel: VoiceChannel? + get() = guild.selfMember.voiceChannel + +/** + * @return Users who are not bots + */ +val GuildPlayer.humanUsersInCurrentVC: List + get() = getHumanUsersInVC(voiceChannel) + +val GuildPlayer.isQueueEmpty: Boolean + get() { + log.trace("isQueueEmpty()") + + return player.playingTrack == null && audioTrackProvider.isEmpty + } + +val GuildPlayer.trackCountInHistory: Int + get() = historyQueue.size + +val GuildPlayer.isHistoryQueueEmpty: Boolean + get() = historyQueue.isEmpty() + +fun GuildPlayer.joinChannel(usr: Member) { + joinChannel(usr.voiceChannel) +} + +fun GuildPlayer.joinChannel(targetChannel: VoiceChannel?) { + if (targetChannel == null) { + throw MessagingException(I18n.get(guild).getString("playerUserNotInChannel")) + } + if (targetChannel == voiceChannel) { + // already connected to the channel + return + } + + val guild = targetChannel.guild + val permissions = targetChannel.ourEffectivePermissions + + if (permissions hasNot Permission.VIEW_CHANNEL) { + val i18n = I18n.get(guild).getString("permissionMissingBot") + throw MessagingException("$i18n ${Permission.VIEW_CHANNEL.uiName}") + } + + if (permissions hasNot Permission.VOICE_CONNECT && guild.selfMember.voiceChannel != targetChannel) { + throw MessagingException(I18n.get(guild).getString("playerJoinConnectDenied")) + } + + if (permissions hasNot Permission.VOICE_SPEAK) { + throw MessagingException(I18n.get(guild).getString("playerJoinSpeakDenied")) + } + + if (targetChannel.userLimit > 0 + && targetChannel.userLimit <= targetChannel.members.size + && permissions hasNot Permission.VOICE_MOVE_OTHERS) { + throw MessagingException(String.format("The channel you want me to join is full!" + + " Please free up some space, or give me the permission to **%s** to bypass the limit.", //todo i18n + Permission.VOICE_MOVE_OTHERS.uiName)) + } + + try { + lavalink.getLink(guild).connect(targetChannel) + log.info("Connected to voice channel $targetChannel") + } catch (e: Exception) { + log.error("Failed to join voice channel {}", targetChannel, e) + } +} + +fun GuildPlayer.leaveVoiceChannelRequest(commandContext: CommandContext, silent: Boolean) { + if (!silent) { + val currentVc = commandContext.guild.selfMember.voiceChannel + if (currentVc == null) { + commandContext.reply(commandContext.i18n("playerNotInChannel")) + } else { + commandContext.reply(commandContext.i18nFormat("playerLeftChannel", currentVc.name)) + } + } + lavalink.getLink(guild).disconnect() +} + +@Suppress("LocalVariableName") +fun GuildPlayer.getTracksInRange(start: Int, end: Int): List { + // Make mutable + var start_ = start + var end_ = end + + val result = ArrayList() + + //adjust args for whether there is a track playing or not + + if (player.playingTrack != null) { + if (start_ <= 0) { + result.add(internalContext!!) + end_--//shorten the requested range by 1, but still start at 0, since that's the way the trackprovider counts its tracks + } else { + //dont add the currently playing track, drop the args by one since the "first" track is currently playing + start_-- + end_-- + } + } else { + //nothing to do here, args are fine to pass on + } + + result.addAll(audioTrackProvider.getTracksInRange(start_, end_)) + return result +} + +/** Similar to [getTracksInRange], but only gets the trackIds */ +fun GuildPlayer.getTrackIdsInRange(start: Int, end: Int): List = getTracksInRange(start, end).stream() + .map { it.trackId } + .toList() + +fun GuildPlayer.getHumanUsersInVC(vc: VoiceChannel?): List { + vc ?: return emptyList() + return vc.members.stream() + .filter { !it.isBot } + .toList() +} + +//Success, fail message +private suspend fun GuildPlayer.canMemberSkipTracks(member: Member, trackIds: Collection): Pair { + if (PermsUtil.checkPerms(PermissionLevel.DJ, member)) { + return ImmutablePair(true, null) + } else { + //We are not a mod + val userId = member.id + + //if there is a currently playing track, and the track is requested to be skipped, but not owned by the + // requesting user, then currentTrackSkippable should be false + var currentTrackSkippable = true + val playingTrack = playingTrack + if (playingTrack != null + && trackIds.contains(playingTrack.trackId) + && playingTrack.userId != userId) { + + currentTrackSkippable = false + } + + return if (currentTrackSkippable && audioTrackProvider.isUserTrackOwner(userId, trackIds)) { //check ownership of the queued tracks + ImmutablePair(true, null) + } else { + ImmutablePair(false, I18n.get(guild).getString("skipDeniedTooManyTracks")) + } + } +} + +suspend fun GuildPlayer.skipTracksForMemberPerms(context: CommandContext, trackIds: Collection, successMessage: String) { + val pair = canMemberSkipTracks(context.member, trackIds) + + if (pair.left) { + context.reply(successMessage) + skipTracks(trackIds) + } else { + context.replyWithName(pair.right) + } +} + +fun GuildPlayer.getTracksInHistory(start: Int, end: Int): List { + val start2 = Math.max(start, 0) + val end2 = Math.max(end, start) + val historyList = ArrayList(historyQueue) + + return if (historyList.size >= end2) { + Lists.reverse(ArrayList(historyQueue)).subList(start2, end2) + } else { + ArrayList() + } +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt b/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt index 8f9f35d60..c5f07f421 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt @@ -32,6 +32,7 @@ import com.sedmelluq.discord.lavaplayer.tools.FriendlyException import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist import com.sedmelluq.discord.lavaplayer.track.AudioTrack import fredboat.audio.player.GuildPlayer +import fredboat.audio.player.trackCount import fredboat.audio.source.PlaylistImportSourceManager import fredboat.audio.source.PlaylistImporter import fredboat.audio.source.SpotifyPlaylistSourceManager diff --git a/FredBoat/src/main/java/fredboat/command/admin/EvalCommand.kt b/FredBoat/src/main/java/fredboat/command/admin/EvalCommand.kt index 038aa1313..285a06597 100644 --- a/FredBoat/src/main/java/fredboat/command/admin/EvalCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/admin/EvalCommand.kt @@ -25,6 +25,7 @@ package fredboat.command.admin +import fredboat.audio.player.voiceChannel import fredboat.commandmeta.abs.Command import fredboat.commandmeta.abs.CommandContext import fredboat.commandmeta.abs.ICommandRestricted @@ -101,7 +102,7 @@ class EvalCommand( engine.put("players", Launcher.botController.playerRegistry) engine.put("link", (player.player as LavalinkPlayer?)?.link) engine.put("lavalink", player.lavalink) - engine.put("vc", player.currentVoiceChannel) + engine.put("vc", player.voiceChannel) engine.put("author", member) engine.put("invoker", member) engine.put("bot", guild.selfMember) diff --git a/FredBoat/src/main/java/fredboat/command/info/DebugCommand.kt b/FredBoat/src/main/java/fredboat/command/info/DebugCommand.kt index 353a69ee7..b6c8fb13a 100644 --- a/FredBoat/src/main/java/fredboat/command/info/DebugCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/info/DebugCommand.kt @@ -29,6 +29,9 @@ import com.fredboat.sentinel.entities.Embed import com.fredboat.sentinel.entities.coloredEmbed import com.fredboat.sentinel.entities.field import fredboat.audio.player.GuildPlayer +import fredboat.audio.player.humanUsersInCurrentVC +import fredboat.audio.player.trackCount +import fredboat.audio.player.voiceChannel import fredboat.commandmeta.abs.Command import fredboat.commandmeta.abs.CommandContext import fredboat.commandmeta.abs.ICommandRestricted @@ -88,7 +91,7 @@ class DebugCommand(name: String, vararg aliases: String) : Command(name, *aliase title = "**VoiceChannel Debug**" body = "Current vc: null" - val vc = player.currentVoiceChannel + val vc = player.voiceChannel if (vc != null) { val vcUsers = player.humanUsersInCurrentVC val str = StringBuilder() diff --git a/FredBoat/src/main/java/fredboat/command/music/control/JoinCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/JoinCommand.kt index ea396450c..7b204b19f 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/JoinCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/JoinCommand.kt @@ -25,6 +25,7 @@ package fredboat.command.music.control +import fredboat.audio.player.joinChannel import fredboat.commandmeta.abs.Command import fredboat.commandmeta.abs.CommandContext import fredboat.commandmeta.abs.ICommandRestricted diff --git a/FredBoat/src/main/java/fredboat/command/music/control/PauseCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/PauseCommand.kt index 66afcf051..e44e0196a 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/PauseCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/PauseCommand.kt @@ -25,6 +25,7 @@ package fredboat.command.music.control +import fredboat.audio.player.isQueueEmpty import fredboat.commandmeta.abs.Command import fredboat.commandmeta.abs.CommandContext import fredboat.commandmeta.abs.ICommandRestricted diff --git a/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt index f0630d1a3..a0dc771f2 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt @@ -26,9 +26,7 @@ package fredboat.command.music.control import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist -import fredboat.audio.player.GuildPlayer -import fredboat.audio.player.PlayerLimiter -import fredboat.audio.player.VideoSelectionCache +import fredboat.audio.player.* import fredboat.commandmeta.abs.Command import fredboat.commandmeta.abs.CommandContext import fredboat.commandmeta.abs.ICommandRestricted diff --git a/FredBoat/src/main/java/fredboat/command/music/control/SkipCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/SkipCommand.kt index 12a5fa334..deb4b5757 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/SkipCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/SkipCommand.kt @@ -25,7 +25,7 @@ package fredboat.command.music.control -import fredboat.audio.player.GuildPlayer +import fredboat.audio.player.* import fredboat.command.info.HelpCommand import fredboat.commandmeta.abs.Command import fredboat.commandmeta.abs.CommandContext diff --git a/FredBoat/src/main/java/fredboat/command/music/control/UnpauseCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/UnpauseCommand.kt index 584b31578..e41125f75 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/UnpauseCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/UnpauseCommand.kt @@ -25,6 +25,8 @@ package fredboat.command.music.control +import fredboat.audio.player.humanUsersInCurrentVC +import fredboat.audio.player.isQueueEmpty import fredboat.commandmeta.abs.Command import fredboat.commandmeta.abs.CommandContext import fredboat.commandmeta.abs.ICommandRestricted diff --git a/FredBoat/src/main/java/fredboat/command/music/control/VoteSkipCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/VoteSkipCommand.kt index ec8f90f06..14857fc0f 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/VoteSkipCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/VoteSkipCommand.kt @@ -3,6 +3,8 @@ package fredboat.command.music.control import com.fredboat.sentinel.entities.coloredEmbed import com.fredboat.sentinel.entities.field import fredboat.audio.player.GuildPlayer +import fredboat.audio.player.humanUsersInCurrentVC +import fredboat.audio.player.isQueueEmpty import fredboat.command.info.HelpCommand import fredboat.commandmeta.abs.Command import fredboat.commandmeta.abs.CommandContext diff --git a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt index 63f0db6a2..745d9f57c 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt @@ -1,6 +1,7 @@ package fredboat.db.mongo import fredboat.audio.player.GuildPlayer +import fredboat.audio.player.voiceChannel import fredboat.audio.queue.SplitAudioTrackContext import lavalink.client.LavalinkUtil import org.slf4j.Logger @@ -39,7 +40,7 @@ private fun GuildPlayer.toMongo() = MongoPlayer( repeatMode.ordinal.toByte(), volume, playingTrack?.track?.position, - currentVoiceChannel?.id, + voiceChannel?.id, remainingTracks.map { if (it is SplitAudioTrackContext) { MongoTrack(it.trackId, LavalinkUtil.toBinary(it.track), it.member.id, it.startPosition, it.endPosition, it.effectiveTitle) diff --git a/FredBoat/src/main/java/fredboat/event/AudioEventHandler.kt b/FredBoat/src/main/java/fredboat/event/AudioEventHandler.kt index 9583ed1e4..af0b2908c 100644 --- a/FredBoat/src/main/java/fredboat/event/AudioEventHandler.kt +++ b/FredBoat/src/main/java/fredboat/event/AudioEventHandler.kt @@ -3,6 +3,9 @@ package fredboat.event import com.fredboat.sentinel.entities.VoiceServerUpdate import fredboat.audio.lavalink.SentinelLavalink import fredboat.audio.player.PlayerRegistry +import fredboat.audio.player.getHumanUsersInVC +import fredboat.audio.player.humanUsersInCurrentVC +import fredboat.audio.player.voiceChannel import fredboat.config.property.AppConfig import fredboat.db.api.GuildConfigService import fredboat.feature.I18n @@ -24,7 +27,7 @@ class AudioEventHandler( if (member.isUs) { getLink(channel).setChannel(channel.id.toString()) } else if (member.guild.guildPlayer?.isPlaying == true && - member.guild.guildPlayer?.currentVoiceChannel == member.voiceChannel) { + member.guild.guildPlayer?.voiceChannel == member.voiceChannel) { lavalink.activityMetrics.logListener(member) } } @@ -53,7 +56,7 @@ class AudioEventHandler( val player = playerRegistry.getExisting(channelLeft.guild.id) ?: return //are we in the channel that someone left from? - val currentVc = player.currentVoiceChannel + val currentVc = player.voiceChannel if (currentVc != null && currentVc.id != channelLeft.id) { return } diff --git a/FredBoat/src/main/java/fredboat/event/MusicPersistenceHandler.kt b/FredBoat/src/main/java/fredboat/event/MusicPersistenceHandler.kt index 2c6bea1e1..442425e94 100644 --- a/FredBoat/src/main/java/fredboat/event/MusicPersistenceHandler.kt +++ b/FredBoat/src/main/java/fredboat/event/MusicPersistenceHandler.kt @@ -34,6 +34,8 @@ import com.sedmelluq.discord.lavaplayer.tools.io.MessageOutput import com.sedmelluq.discord.lavaplayer.track.AudioTrack import fredboat.audio.player.MusicTextChannelProvider import fredboat.audio.player.PlayerRegistry +import fredboat.audio.player.joinChannel +import fredboat.audio.player.voiceChannel import fredboat.audio.queue.AudioTrackContext import fredboat.audio.queue.SplitAudioTrackContext import fredboat.config.property.AppConfig @@ -127,7 +129,7 @@ class MusicPersistenceHandler(private val playerRegistry: PlayerRegistry, privat } val data = JSONObject() - val vc = player.currentVoiceChannel + val vc = player.voiceChannel data.put("vc", vc?.id ?: 0) data.put("tc", activeTextChannel?.id ?: 0) data.put("isPaused", player.isPaused) diff --git a/FredBoat/src/main/java/fredboat/event/ShardLifecycleHandler.kt b/FredBoat/src/main/java/fredboat/event/ShardLifecycleHandler.kt index 88f849fb5..dd1bb1960 100644 --- a/FredBoat/src/main/java/fredboat/event/ShardLifecycleHandler.kt +++ b/FredBoat/src/main/java/fredboat/event/ShardLifecycleHandler.kt @@ -28,6 +28,7 @@ import com.fredboat.sentinel.entities.LifecycleEventEnum.* import com.fredboat.sentinel.entities.ShardLifecycleEvent import fredboat.agent.GuildCacheInvalidationAgent import fredboat.audio.player.PlayerRegistry +import fredboat.audio.player.voiceChannel import fredboat.config.property.AppConfig import fredboat.sentinel.Guild import fredboat.sentinel.GuildCache @@ -105,7 +106,7 @@ class ShardLifecycleHandler( channelsToRejoin[shardId] = playerRegistry.playingPlayers.stream() .filter { DiscordUtil.getShardId(it.guildId, appConfig) == shardId } .flatMap { - val channel = it.currentVoiceChannel ?: return@flatMap Stream.empty() + val channel = it.voiceChannel ?: return@flatMap Stream.empty() return@flatMap Stream.of(ChannelReference(channel.guild, channel.id)) }.toList().toMutableList() } catch (ex: Exception) { From 08ecf8a4c08e4d11a49a368ac0113bd0e109b4e3 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Thu, 27 Dec 2018 23:05:26 +0100 Subject: [PATCH 042/172] Migrate classes to kotlin for extension support --- .../command/music/control/LeaveCommand.java | 77 --------- .../command/music/control/LeaveCommand.kt | 67 ++++++++ .../command/music/control/StopCommand.java | 81 --------- .../command/music/control/StopCommand.kt | 63 +++++++ .../command/music/info/ExportCommand.java | 81 --------- .../command/music/info/ExportCommand.kt | 73 ++++++++ .../command/music/info/HistoryCommand.java | 120 ------------- .../command/music/info/HistoryCommand.kt | 111 ++++++++++++ .../command/music/info/ListCommand.java | 161 ------------------ .../command/music/info/ListCommand.kt | 158 +++++++++++++++++ .../command/music/seeking/ForwardCommand.java | 97 ----------- .../command/music/seeking/ForwardCommand.kt | 82 +++++++++ .../command/music/seeking/RestartCommand.java | 74 -------- .../command/music/seeking/RestartCommand.kt | 61 +++++++ .../command/music/seeking/RewindCommand.java | 94 ---------- .../command/music/seeking/RewindCommand.kt | 81 +++++++++ .../command/music/seeking/SeekCommand.java | 95 ----------- .../command/music/seeking/SeekCommand.kt | 81 +++++++++ 18 files changed, 777 insertions(+), 880 deletions(-) delete mode 100644 FredBoat/src/main/java/fredboat/command/music/control/LeaveCommand.java create mode 100644 FredBoat/src/main/java/fredboat/command/music/control/LeaveCommand.kt delete mode 100644 FredBoat/src/main/java/fredboat/command/music/control/StopCommand.java create mode 100644 FredBoat/src/main/java/fredboat/command/music/control/StopCommand.kt delete mode 100644 FredBoat/src/main/java/fredboat/command/music/info/ExportCommand.java create mode 100644 FredBoat/src/main/java/fredboat/command/music/info/ExportCommand.kt delete mode 100644 FredBoat/src/main/java/fredboat/command/music/info/HistoryCommand.java create mode 100644 FredBoat/src/main/java/fredboat/command/music/info/HistoryCommand.kt delete mode 100644 FredBoat/src/main/java/fredboat/command/music/info/ListCommand.java create mode 100644 FredBoat/src/main/java/fredboat/command/music/info/ListCommand.kt delete mode 100644 FredBoat/src/main/java/fredboat/command/music/seeking/ForwardCommand.java create mode 100644 FredBoat/src/main/java/fredboat/command/music/seeking/ForwardCommand.kt delete mode 100644 FredBoat/src/main/java/fredboat/command/music/seeking/RestartCommand.java create mode 100644 FredBoat/src/main/java/fredboat/command/music/seeking/RestartCommand.kt delete mode 100644 FredBoat/src/main/java/fredboat/command/music/seeking/RewindCommand.java create mode 100644 FredBoat/src/main/java/fredboat/command/music/seeking/RewindCommand.kt delete mode 100644 FredBoat/src/main/java/fredboat/command/music/seeking/SeekCommand.java create mode 100644 FredBoat/src/main/java/fredboat/command/music/seeking/SeekCommand.kt diff --git a/FredBoat/src/main/java/fredboat/command/music/control/LeaveCommand.java b/FredBoat/src/main/java/fredboat/command/music/control/LeaveCommand.java deleted file mode 100644 index 7c32c9fde..000000000 --- a/FredBoat/src/main/java/fredboat/command/music/control/LeaveCommand.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package fredboat.command.music.control; - -import fredboat.audio.player.GuildPlayer; -import fredboat.commandmeta.abs.CommandContext; -import fredboat.commandmeta.abs.ICommandRestricted; -import fredboat.commandmeta.abs.IMusicCommand; -import fredboat.commandmeta.abs.JCommand; -import fredboat.definitions.PermissionLevel; -import fredboat.messaging.internal.Context; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nonnull; - -import static fredboat.main.LauncherKt.getBotController; - -public class LeaveCommand extends JCommand implements IMusicCommand, ICommandRestricted { - - private static final Logger log = LoggerFactory.getLogger(LeaveCommand.class); - - public LeaveCommand(String name, String... aliases) { - super(name, aliases); - } - - @Override - public void onInvoke(@Nonnull CommandContext context) { - GuildPlayer player = getBotController().getPlayerRegistry().getExisting(context.getGuild()); - - if (player != null) { - try { - player.pause(); - player.leaveVoiceChannelRequest(context, false); - } catch (Exception e) { - log.error("Something caused us to not properly leave a voice channel!", e); // TODO: Check if this is still a problem - player.leaveVoiceChannelRequest(context, true); - } - } - - } - - @Nonnull - @Override - public String help(@Nonnull Context context) { - return "{0}{1}\n#" + context.i18n("helpLeaveCommand"); - } - - @Nonnull - @Override - public PermissionLevel getMinimumPerms() { - return PermissionLevel.DJ; - } -} diff --git a/FredBoat/src/main/java/fredboat/command/music/control/LeaveCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/LeaveCommand.kt new file mode 100644 index 000000000..8a1e898f5 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/command/music/control/LeaveCommand.kt @@ -0,0 +1,67 @@ +/* + * MIT License + * + * Copyright (c) 2017 Frederik Ar. Mikkelsen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package fredboat.command.music.control + +import fredboat.audio.player.leaveVoiceChannelRequest +import fredboat.commandmeta.abs.Command +import fredboat.commandmeta.abs.CommandContext +import fredboat.commandmeta.abs.ICommandRestricted +import fredboat.commandmeta.abs.IMusicCommand +import fredboat.definitions.PermissionLevel +import fredboat.main.getBotController +import fredboat.messaging.internal.Context +import org.slf4j.LoggerFactory + +class LeaveCommand(name: String, vararg aliases: String) : Command(name, *aliases), IMusicCommand, ICommandRestricted { + + override val minimumPerms: PermissionLevel + get() = PermissionLevel.DJ + + override suspend fun invoke(context: CommandContext) { + val player = getBotController().playerRegistry.getExisting(context.guild) + + if (player != null) { + try { + player.pause() + player.leaveVoiceChannelRequest(context, false) + } catch (e: Exception) { + log.error("Something caused us to not properly leave a voice channel!", e) // TODO: Check if this is still a problem + player.leaveVoiceChannelRequest(context, true) + } + + } + + } + + override fun help(context: Context): String { + return "{0}{1}\n#" + context.i18n("helpLeaveCommand") + } + + companion object { + + private val log = LoggerFactory.getLogger(LeaveCommand::class.java) + } +} diff --git a/FredBoat/src/main/java/fredboat/command/music/control/StopCommand.java b/FredBoat/src/main/java/fredboat/command/music/control/StopCommand.java deleted file mode 100644 index 6e7d4227e..000000000 --- a/FredBoat/src/main/java/fredboat/command/music/control/StopCommand.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package fredboat.command.music.control; - -import fredboat.audio.player.GuildPlayer; -import fredboat.commandmeta.abs.CommandContext; -import fredboat.commandmeta.abs.ICommandRestricted; -import fredboat.commandmeta.abs.IMusicCommand; -import fredboat.commandmeta.abs.JCommand; -import fredboat.definitions.PermissionLevel; -import fredboat.messaging.internal.Context; - -import javax.annotation.Nonnull; - -import static fredboat.main.LauncherKt.getBotController; - -public class StopCommand extends JCommand implements IMusicCommand, ICommandRestricted { - - public StopCommand(String name, String... aliases) { - super(name, aliases); - } - - @Override - public void onInvoke(@Nonnull CommandContext context) { - GuildPlayer player = getBotController().getPlayerRegistry().getExisting(context.getGuild()); - int tracksCount = 0; - if (player != null) { - tracksCount = player.getTrackCount(); - player.pause(); - player.stop(); - player.leaveVoiceChannelRequest(context, true); - } - - switch (tracksCount) { - case 0: - context.reply(context.i18n("stopAlreadyEmpty")); - break; - case 1: - context.reply(context.i18n("stopEmptyOne")); - break; - default: - context.reply(context.i18nFormat("stopEmptySeveral", tracksCount)); - break; - } - } - - @Nonnull - @Override - public String help(@Nonnull Context context) { - return "{0}{1}\n#" + context.i18n("helpStopCommand"); - } - - @Nonnull - @Override - public PermissionLevel getMinimumPerms() { - return PermissionLevel.DJ; - } -} diff --git a/FredBoat/src/main/java/fredboat/command/music/control/StopCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/StopCommand.kt new file mode 100644 index 000000000..baafb0a42 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/command/music/control/StopCommand.kt @@ -0,0 +1,63 @@ +/* + * MIT License + * + * Copyright (c) 2017 Frederik Ar. Mikkelsen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package fredboat.command.music.control + +import fredboat.audio.player.leaveVoiceChannelRequest +import fredboat.audio.player.trackCount +import fredboat.commandmeta.abs.Command +import fredboat.commandmeta.abs.CommandContext +import fredboat.commandmeta.abs.ICommandRestricted +import fredboat.commandmeta.abs.IMusicCommand +import fredboat.definitions.PermissionLevel +import fredboat.main.getBotController +import fredboat.messaging.internal.Context + +class StopCommand(name: String, vararg aliases: String) : Command(name, *aliases), IMusicCommand, ICommandRestricted { + + override val minimumPerms: PermissionLevel + get() = PermissionLevel.DJ + + override suspend fun invoke(context: CommandContext) { + val player = getBotController().playerRegistry.getExisting(context.guild) + var tracksCount = 0 + if (player != null) { + tracksCount = player.trackCount + player.pause() + player.stop() + player.leaveVoiceChannelRequest(context, true) + } + + when (tracksCount) { + 0 -> context.reply(context.i18n("stopAlreadyEmpty")) + 1 -> context.reply(context.i18n("stopEmptyOne")) + else -> context.reply(context.i18nFormat("stopEmptySeveral", tracksCount)) + } + } + + override fun help(context: Context): String { + return "{0}{1}\n#" + context.i18n("helpStopCommand") + } +} diff --git a/FredBoat/src/main/java/fredboat/command/music/info/ExportCommand.java b/FredBoat/src/main/java/fredboat/command/music/info/ExportCommand.java deleted file mode 100644 index ec9d1c4fd..000000000 --- a/FredBoat/src/main/java/fredboat/command/music/info/ExportCommand.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package fredboat.command.music.info; - -import fredboat.audio.player.GuildPlayer; -import fredboat.commandmeta.MessagingException; -import fredboat.commandmeta.abs.CommandContext; -import fredboat.commandmeta.abs.IMusicCommand; -import fredboat.commandmeta.abs.JCommand; -import fredboat.messaging.internal.Context; -import fredboat.util.TextUtils; - -import javax.annotation.Nonnull; -import java.util.stream.Collectors; - -import static fredboat.main.LauncherKt.getBotController; - -public class ExportCommand extends JCommand implements IMusicCommand { - - public ExportCommand(String name, String... aliases) { - super(name, aliases); - } - - @Override - public void onInvoke(@Nonnull CommandContext context) { - GuildPlayer player = getBotController().getPlayerRegistry().getExisting(context.getGuild()); - - if (player == null || player.isQueueEmpty()) { - throw new MessagingException(context.i18n("exportEmpty")); - } - - String out = player.getRemainingTracks().stream() - .map(atc -> atc.getTrack().getInfo().uri) - .collect(Collectors.joining("\n")); - - TextUtils.postToPasteService(out) - .thenApply(pasteUrl -> { - if (pasteUrl.isPresent()) { - String url = pasteUrl.get() + ".fredboat"; - return context.i18nFormat("exportPlaylistResulted", url); - } else { - return context.i18n("exportPlaylistFail") + "\n" + context.i18n("tryLater"); - } - }) - .thenAccept(context::reply) - .whenComplete((ignored, t) -> { - if (t != null) { - TextUtils.handleException("Failed to export to any paste service", t, context); - } - }); - } - - @Nonnull - @Override - public String help(@Nonnull Context context) { - return "{0}{1}\n#" + context.i18n("helpExportCommand"); - } -} diff --git a/FredBoat/src/main/java/fredboat/command/music/info/ExportCommand.kt b/FredBoat/src/main/java/fredboat/command/music/info/ExportCommand.kt new file mode 100644 index 000000000..503d63dde --- /dev/null +++ b/FredBoat/src/main/java/fredboat/command/music/info/ExportCommand.kt @@ -0,0 +1,73 @@ +/* + * MIT License + * + * Copyright (c) 2017 Frederik Ar. Mikkelsen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package fredboat.command.music.info + +import fredboat.audio.player.isQueueEmpty +import fredboat.commandmeta.MessagingException +import fredboat.commandmeta.abs.Command +import fredboat.commandmeta.abs.CommandContext +import fredboat.commandmeta.abs.IMusicCommand +import fredboat.main.getBotController +import fredboat.messaging.internal.Context +import fredboat.util.TextUtils +import java.util.stream.Collectors + + + +class ExportCommand(name: String, vararg aliases: String) : Command(name, *aliases), IMusicCommand { + + override suspend fun invoke(context: CommandContext) { + val player = getBotController().playerRegistry.getExisting(context.guild) + + if (player == null || player.isQueueEmpty) { + throw MessagingException(context.i18n("exportEmpty")) + } + + val out = player.remainingTracks.stream() + .map { atc -> atc.track.info.uri } + .collect(Collectors.joining("\n")) + + TextUtils.postToPasteService(out) + .thenApply { pasteUrl -> + if (pasteUrl.isPresent) { + val url = pasteUrl.get() + ".fredboat" + context.i18nFormat("exportPlaylistResulted", url) + } else { + context.i18n("exportPlaylistFail") + "\n" + context.i18n("tryLater") + } + } + .thenAccept { context.reply(it) } + .whenComplete { _, t -> + if (t != null) { + TextUtils.handleException("Failed to export to any paste service", t, context) + } + } + } + + override fun help(context: Context): String { + return "{0}{1}\n#" + context.i18n("helpExportCommand") + } +} diff --git a/FredBoat/src/main/java/fredboat/command/music/info/HistoryCommand.java b/FredBoat/src/main/java/fredboat/command/music/info/HistoryCommand.java deleted file mode 100644 index db6cea9be..000000000 --- a/FredBoat/src/main/java/fredboat/command/music/info/HistoryCommand.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package fredboat.command.music.info; - -import fredboat.audio.player.GuildPlayer; -import fredboat.audio.queue.AudioTrackContext; -import fredboat.commandmeta.abs.CommandContext; -import fredboat.commandmeta.abs.IMusicCommand; -import fredboat.commandmeta.abs.JCommand; -import fredboat.messaging.internal.Context; -import fredboat.sentinel.Member; -import fredboat.util.MessageBuilder; -import fredboat.util.TextUtils; - -import javax.annotation.Nonnull; -import java.text.MessageFormat; -import java.util.List; - -import static fredboat.main.LauncherKt.getBotController; -import static fredboat.util.MessageBuilderKt.localMessageBuilder; - -public class HistoryCommand extends JCommand implements IMusicCommand { - - private static final int PAGE_SIZE = 10; - - public HistoryCommand(String name, String... aliases) { - super(name, aliases); - } - - @Override - public void onInvoke(@Nonnull CommandContext context) { - GuildPlayer player = getBotController().getPlayerRegistry().getExisting(context.getGuild()); - - if (player == null || player.isHistoryQueueEmpty()) { - context.reply(context.i18n("npNotInHistory")); - return; - } - - int page = 1; - if (context.hasArguments()) { - try { - page = Integer.valueOf(context.getArgs()[0]); - } catch (NumberFormatException ignored) { - } - } - - int tracksCount = player.getTrackCountInHistory(); - int maxPages = (int) Math.ceil(((double) tracksCount - 1d)) / PAGE_SIZE + 1; - - page = Math.max(page, 1); - page = Math.min(page, maxPages); - - int i = (page - 1) * PAGE_SIZE; - int listEnd = (page - 1) * PAGE_SIZE + PAGE_SIZE; - listEnd = Math.min(listEnd, tracksCount); - - int numberLength = Integer.toString(listEnd).length(); - - List sublist = player.getTracksInHistory(i, listEnd); - - MessageBuilder mb = localMessageBuilder() - .append(context.i18n("listShowHistory")) - .append("\n") - .append(MessageFormat.format(context.i18n("listPageNum"), page, maxPages)) - .append("\n") - .append("\n"); - - for (AudioTrackContext atc : sublist) { - String status = " "; - - Member member = atc.getMember(); - String username = member.getEffectiveName(); - mb.code("[" + - TextUtils.forceNDigits(i + 1, numberLength) - + "]") - .append(status) - .append(context.i18nFormat("listAddedBy", TextUtils.escapeAndDefuse(atc.getEffectiveTitle()), - TextUtils.escapeAndDefuse(username), TextUtils.formatTime(atc.getEffectiveDuration()))) - .append("\n"); - - if (i == listEnd) { - break; - } - - i++; - } - - context.reply(mb.build()); - } - - @Nonnull - @Override - public String help(@Nonnull Context context) { - String usage = "{0}{1} (page)\n#"; - return usage + context.i18n("helpHistoryCommand"); - } -} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/command/music/info/HistoryCommand.kt b/FredBoat/src/main/java/fredboat/command/music/info/HistoryCommand.kt new file mode 100644 index 000000000..e695fa136 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/command/music/info/HistoryCommand.kt @@ -0,0 +1,111 @@ +/* + * MIT License + * + * Copyright (c) 2017 Frederik Ar. Mikkelsen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package fredboat.command.music.info + +import fredboat.audio.player.getTracksInHistory + +import fredboat.audio.player.isHistoryQueueEmpty +import fredboat.audio.player.trackCountInHistory +import fredboat.commandmeta.abs.Command +import fredboat.commandmeta.abs.CommandContext +import fredboat.commandmeta.abs.IMusicCommand +import fredboat.main.getBotController +import fredboat.messaging.internal.Context +import fredboat.util.TextUtils +import fredboat.util.localMessageBuilder +import java.text.MessageFormat +class HistoryCommand(name: String, vararg aliases: String) : Command(name, *aliases), IMusicCommand { + + companion object { + private const val PAGE_SIZE = 10 + } + + override suspend fun invoke(context: CommandContext) { + val player = getBotController().playerRegistry.getExisting(context.guild) + + if (player == null || player.isHistoryQueueEmpty) { + context.reply(context.i18n("npNotInHistory")) + return + } + + var page = 1 + if (context.hasArguments()) { + try { + page = Integer.valueOf(context.args[0]) + } catch (ignored: NumberFormatException) { + } + + } + + val tracksCount = player.trackCountInHistory + val maxPages = Math.ceil(tracksCount.toDouble() - 1.0).toInt() / PAGE_SIZE + 1 + + page = Math.max(page, 1) + page = Math.min(page, maxPages) + + var i = (page - 1) * PAGE_SIZE + var listEnd = (page - 1) * PAGE_SIZE + PAGE_SIZE + listEnd = Math.min(listEnd, tracksCount) + + val numberLength = Integer.toString(listEnd).length + + val sublist = player.getTracksInHistory(i, listEnd) + + val mb = localMessageBuilder() + .append(context.i18n("listShowHistory")) + .append("\n") + .append(MessageFormat.format(context.i18n("listPageNum"), page, maxPages)) + .append("\n") + .append("\n") + + for (atc in sublist) { + val status = " " + + val member = atc.member + val username = member.effectiveName + mb.code("[" + + TextUtils.forceNDigits(i + 1, numberLength) + + "]") + .append(status) + .append(context.i18nFormat("listAddedBy", TextUtils.escapeAndDefuse(atc.effectiveTitle), + TextUtils.escapeAndDefuse(username), TextUtils.formatTime(atc.effectiveDuration))) + .append("\n") + + if (i == listEnd) { + break + } + + i++ + } + + context.reply(mb.build()) + } + + override fun help(context: Context): String { + val usage = "{0}{1} (page)\n#" + return usage + context.i18n("helpHistoryCommand") + } +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/command/music/info/ListCommand.java b/FredBoat/src/main/java/fredboat/command/music/info/ListCommand.java deleted file mode 100644 index 83178ada9..000000000 --- a/FredBoat/src/main/java/fredboat/command/music/info/ListCommand.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package fredboat.command.music.info; - -import fredboat.audio.player.GuildPlayer; -import fredboat.audio.queue.AudioTrackContext; -import fredboat.commandmeta.abs.CommandContext; -import fredboat.commandmeta.abs.IMusicCommand; -import fredboat.commandmeta.abs.JCommand; -import fredboat.definitions.RepeatMode; -import fredboat.messaging.internal.Context; -import fredboat.sentinel.Member; -import fredboat.util.MessageBuilder; -import fredboat.util.TextUtils; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nonnull; -import java.util.List; - -import static fredboat.main.LauncherKt.getBotController; -import static fredboat.util.MessageBuilderKt.localMessageBuilder; - -public class ListCommand extends JCommand implements IMusicCommand { - - private static final org.slf4j.Logger log = LoggerFactory.getLogger(ListCommand.class); - - private static final int PAGE_SIZE = 10; - - public ListCommand(String name, String... aliases) { - super(name, aliases); - } - - @Override - public void onInvoke(@Nonnull CommandContext context) { - GuildPlayer player = getBotController().getPlayerRegistry().getExisting(context.getGuild()); - - if (player == null || player.isQueueEmpty()) { - context.reply(context.i18n("npNotPlaying")); - return; - } - - MessageBuilder mb = localMessageBuilder(); - - int page = 1; - if (context.hasArguments()) { - try { - page = Integer.valueOf(context.getArgs()[0]); - } catch (NumberFormatException ignored) {} - } - - int tracksCount = player.getTrackCount(); - int maxPages = (int) Math.ceil(((double) tracksCount - 1d)) / PAGE_SIZE + 1; - - page = Math.max(page, 1); - page = Math.min(page, maxPages); - - int i = (page - 1) * PAGE_SIZE; - int listEnd = (page - 1) * PAGE_SIZE + PAGE_SIZE; - listEnd = Math.min(listEnd, tracksCount); - - int numberLength = Integer.toString(listEnd).length(); - - List sublist = player.getTracksInRange(i, listEnd); - - if (player.isShuffle()) { - mb.append(context.i18n("listShowShuffled")); - mb.append("\n"); - if (player.getRepeatMode() == RepeatMode.OFF) - mb.append("\n"); - } - if (player.getRepeatMode() == RepeatMode.SINGLE) { - mb.append(context.i18n("listShowRepeatSingle")); - mb.append("\n"); - } else if (player.getRepeatMode() == RepeatMode.ALL) { - mb.append(context.i18n("listShowRepeatAll")); - mb.append("\n"); - } - - mb.append(context.i18nFormat("listPageNum", page, maxPages)); - mb.append("\n"); - mb.append("\n"); - - for (AudioTrackContext atc : sublist) { - String status = " "; - if (i == 0) { - status = player.isPlaying() ? " \\▶" : " \\\u23F8"; //Escaped play and pause emojis - } - Member member = atc.getMember(); - String username = member.getEffectiveName(); - mb.code("[" + - TextUtils.forceNDigits(i + 1, numberLength) - + "]") - .append(status) - .append(context.i18nFormat("listAddedBy", TextUtils.escapeAndDefuse(atc.getEffectiveTitle()), - TextUtils.escapeAndDefuse(username), TextUtils.formatTime(atc.getEffectiveDuration()))) - .append("\n"); - - if (i == listEnd) { - break; - } - - i++; - } - - //Now add a timestamp for how much is remaining - String timestamp = TextUtils.formatTime(player.getTotalRemainingMusicTimeMillis()); - - long streams = player.getStreamsCount(); - long numTracks = tracksCount - streams; - - String desc; - - if (numTracks == 0) { - //We are only listening to streams - desc = context.i18nFormat(streams == 1 ? "listStreamsOnlySingle" : "listStreamsOnlyMultiple", - streams, streams == 1 ? - context.i18n("streamSingular") : context.i18n("streamPlural")); - } else { - - desc = context.i18nFormat(numTracks == 1 ? "listStreamsOrTracksSingle" : "listStreamsOrTracksMultiple", - numTracks, numTracks == 1 ? - context.i18n("trackSingular") : context.i18n("trackPlural"), timestamp, streams == 0 - ? "" : context.i18nFormat("listAsWellAsLiveStreams", streams, streams == 1 - ? context.i18n("streamSingular") : context.i18n("streamPlural"))); - } - - mb.append("\n").append(desc); - - context.reply(mb.build()); - - } - - @Nonnull - @Override - public String help(@Nonnull Context context) { - return "{0}{1} (page)\n#" + context.i18n("helpListCommand"); - } -} diff --git a/FredBoat/src/main/java/fredboat/command/music/info/ListCommand.kt b/FredBoat/src/main/java/fredboat/command/music/info/ListCommand.kt new file mode 100644 index 000000000..2c0fa1e92 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/command/music/info/ListCommand.kt @@ -0,0 +1,158 @@ +/* + * MIT License + * + * Copyright (c) 2017 Frederik Ar. Mikkelsen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package fredboat.command.music.info + +import fredboat.audio.player.* +import fredboat.commandmeta.abs.CommandContext +import fredboat.commandmeta.abs.IMusicCommand +import fredboat.commandmeta.abs.JCommand +import fredboat.definitions.RepeatMode +import fredboat.main.getBotController +import fredboat.messaging.internal.Context +import fredboat.util.TextUtils +import fredboat.util.localMessageBuilder +import org.slf4j.LoggerFactory + +class ListCommand(name: String, vararg aliases: String) : JCommand(name, *aliases), IMusicCommand { + + companion object { + private val log = LoggerFactory.getLogger(ListCommand::class.java) + private const val PAGE_SIZE = 10 + } + + override fun onInvoke(context: CommandContext) { + val player = getBotController().playerRegistry.getExisting(context.guild) + + if (player == null || player.isQueueEmpty) { + context.reply(context.i18n("npNotPlaying")) + return + } + + val mb = localMessageBuilder() + + var page = 1 + if (context.hasArguments()) { + try { + page = Integer.valueOf(context.args[0]) + } catch (ignored: NumberFormatException) { + } + + } + + val tracksCount = player.trackCount + val maxPages = Math.ceil(tracksCount.toDouble() - 1.0).toInt() / PAGE_SIZE + 1 + + page = Math.max(page, 1) + page = Math.min(page, maxPages) + + var i = (page - 1) * PAGE_SIZE + var listEnd = (page - 1) * PAGE_SIZE + PAGE_SIZE + listEnd = Math.min(listEnd, tracksCount) + + val numberLength = Integer.toString(listEnd).length + + val sublist = player.getTracksInRange(i, listEnd) + + if (player.isShuffle) { + mb.append(context.i18n("listShowShuffled")) + mb.append("\n") + if (player.repeatMode == RepeatMode.OFF) + mb.append("\n") + } + if (player.repeatMode == RepeatMode.SINGLE) { + mb.append(context.i18n("listShowRepeatSingle")) + mb.append("\n") + } else if (player.repeatMode == RepeatMode.ALL) { + mb.append(context.i18n("listShowRepeatAll")) + mb.append("\n") + } + + mb.append(context.i18nFormat("listPageNum", page, maxPages)) + mb.append("\n") + mb.append("\n") + + for (atc in sublist) { + var status = " " + if (i == 0) { + status = if (player.isPlaying) " \\▶" else " \\\u23F8" //Escaped play and pause emojis + } + val member = atc.member + val username = member.effectiveName + mb.code("[" + + TextUtils.forceNDigits(i + 1, numberLength) + + "]") + .append(status) + .append(context.i18nFormat("listAddedBy", TextUtils.escapeAndDefuse(atc.effectiveTitle), + TextUtils.escapeAndDefuse(username), TextUtils.formatTime(atc.effectiveDuration))) + .append("\n") + + if (i == listEnd) { + break + } + + i++ + } + + //Now add a timestamp for how much is remaining + val timestamp = TextUtils.formatTime(player.totalRemainingMusicTimeMillis) + + val streams = player.streamsCount + val numTracks = tracksCount - streams + + val desc: String + + if (numTracks == 0L) { + //We are only listening to streams + desc = context.i18nFormat(if (streams == 1L) "listStreamsOnlySingle" else "listStreamsOnlyMultiple", + streams, if (streams == 1L) + context.i18n("streamSingular") + else + context.i18n("streamPlural")) + } else { + + desc = context.i18nFormat(if (numTracks == 1L) "listStreamsOrTracksSingle" else "listStreamsOrTracksMultiple", + numTracks, if (numTracks == 1L) + context.i18n("trackSingular") + else + context.i18n("trackPlural"), timestamp, if (streams == 0L) + "" + else + context.i18nFormat("listAsWellAsLiveStreams", streams, if (streams == 1L) + context.i18n("streamSingular") + else + context.i18n("streamPlural"))) + } + + mb.append("\n").append(desc) + + context.reply(mb.build()) + + } + + override fun help(context: Context): String { + return "{0}{1} (page)\n#" + context.i18n("helpListCommand") + } +} diff --git a/FredBoat/src/main/java/fredboat/command/music/seeking/ForwardCommand.java b/FredBoat/src/main/java/fredboat/command/music/seeking/ForwardCommand.java deleted file mode 100644 index fad2895ef..000000000 --- a/FredBoat/src/main/java/fredboat/command/music/seeking/ForwardCommand.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package fredboat.command.music.seeking; - -import com.sedmelluq.discord.lavaplayer.track.AudioTrack; -import fredboat.audio.player.GuildPlayer; -import fredboat.audio.queue.AudioTrackContext; -import fredboat.command.info.HelpCommand; -import fredboat.commandmeta.abs.CommandContext; -import fredboat.commandmeta.abs.ICommandRestricted; -import fredboat.commandmeta.abs.IMusicCommand; -import fredboat.commandmeta.abs.JCommand; -import fredboat.definitions.PermissionLevel; -import fredboat.messaging.internal.Context; -import fredboat.util.TextUtils; - -import javax.annotation.Nonnull; - -import static fredboat.main.LauncherKt.getBotController; - -public class ForwardCommand extends JCommand implements IMusicCommand, ICommandRestricted { - - public ForwardCommand(String name, String... aliases) { - super(name, aliases); - } - - @Override - public void onInvoke(@Nonnull CommandContext context) { - GuildPlayer player = getBotController().getPlayerRegistry().getExisting(context.getGuild()); - - if(player == null || player.isQueueEmpty()) { - context.replyWithName(context.i18n("unpauseQueueEmpty")); - return; - } - - if (!context.hasArguments()) { - HelpCommand.sendFormattedCommandHelp(context); - return; - } - - long t; - try { - t = TextUtils.parseTimeString(context.getArgs()[0]); - } catch (IllegalStateException e){ - HelpCommand.sendFormattedCommandHelp(context); - return; - } - - AudioTrackContext atc = player.getPlayingTrack(); - AudioTrack at = atc.getTrack(); - - //Ensure bounds - t = Math.max(0, t); - t = Math.min(atc.getEffectiveDuration(), t); - - player.seekTo(player.getPosition() + t); - context.reply(context.i18nFormat("fwdSuccess", - TextUtils.escapeAndDefuse(atc.getEffectiveTitle()), TextUtils.formatTime(t))); - } - - @Nonnull - @Override - public String help(@Nonnull Context context) { - String usage = "{0}{1} [[hh:]mm:]ss\n#"; - String example = " {0}{1} 2:30"; - return usage + context.i18n("helpForwardCommand") + example; - } - - @Nonnull - @Override - public PermissionLevel getMinimumPerms() { - return PermissionLevel.DJ; - } -} diff --git a/FredBoat/src/main/java/fredboat/command/music/seeking/ForwardCommand.kt b/FredBoat/src/main/java/fredboat/command/music/seeking/ForwardCommand.kt new file mode 100644 index 000000000..6fc32562f --- /dev/null +++ b/FredBoat/src/main/java/fredboat/command/music/seeking/ForwardCommand.kt @@ -0,0 +1,82 @@ +/* + * MIT License + * + * Copyright (c) 2017 Frederik Ar. Mikkelsen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package fredboat.command.music.seeking + +import fredboat.audio.player.isQueueEmpty +import fredboat.command.info.HelpCommand +import fredboat.commandmeta.abs.Command +import fredboat.commandmeta.abs.CommandContext +import fredboat.commandmeta.abs.ICommandRestricted +import fredboat.commandmeta.abs.IMusicCommand +import fredboat.definitions.PermissionLevel +import fredboat.main.getBotController +import fredboat.messaging.internal.Context +import fredboat.util.TextUtils + +class ForwardCommand(name: String, vararg aliases: String) : Command(name, *aliases), IMusicCommand, ICommandRestricted { + + override val minimumPerms: PermissionLevel + get() = PermissionLevel.DJ + + override suspend fun invoke(context: CommandContext) { + val player = getBotController().playerRegistry.getExisting(context.guild) + + if (player == null || player.isQueueEmpty) { + context.replyWithName(context.i18n("unpauseQueueEmpty")) + return + } + + if (!context.hasArguments()) { + HelpCommand.sendFormattedCommandHelp(context) + return + } + + var t: Long + try { + t = TextUtils.parseTimeString(context.args[0]) + } catch (e: IllegalStateException) { + HelpCommand.sendFormattedCommandHelp(context) + return + } + + val atc = player.playingTrack + val at = atc!!.track + + //Ensure bounds + t = Math.max(0, t) + t = Math.min(atc.effectiveDuration, t) + + player.seekTo(player.position + t) + context.reply(context.i18nFormat("fwdSuccess", + TextUtils.escapeAndDefuse(atc.effectiveTitle), TextUtils.formatTime(t))) + } + + override fun help(context: Context): String { + val usage = "{0}{1} [[hh:]mm:]ss\n#" + val example = " {0}{1} 2:30" + return usage + context.i18n("helpForwardCommand") + example + } +} diff --git a/FredBoat/src/main/java/fredboat/command/music/seeking/RestartCommand.java b/FredBoat/src/main/java/fredboat/command/music/seeking/RestartCommand.java deleted file mode 100644 index f961b9725..000000000 --- a/FredBoat/src/main/java/fredboat/command/music/seeking/RestartCommand.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package fredboat.command.music.seeking; - -import fredboat.audio.player.GuildPlayer; -import fredboat.commandmeta.abs.CommandContext; -import fredboat.commandmeta.abs.ICommandRestricted; -import fredboat.commandmeta.abs.IMusicCommand; -import fredboat.commandmeta.abs.JCommand; -import fredboat.definitions.PermissionLevel; -import fredboat.messaging.internal.Context; -import fredboat.util.TextUtils; - -import javax.annotation.Nonnull; - -import static fredboat.main.LauncherKt.getBotController; - -public class RestartCommand extends JCommand implements IMusicCommand, ICommandRestricted { - - public RestartCommand(String name, String... aliases) { - super(name, aliases); - } - - @Override - public void onInvoke(@Nonnull CommandContext context) { - GuildPlayer player = getBotController().getPlayerRegistry().getExisting(context.getGuild()); - - if (player != null && !player.isQueueEmpty()) { - if (player.getPlayingTrack() == null) { - player.play(); - } - player.seekTo(player.getPlayingTrack().getStartPosition()); - context.reply(context.i18nFormat("restartSuccess", - TextUtils.escapeAndDefuse(player.getPlayingTrack().getEffectiveTitle()))); - } else { - context.replyWithName(context.i18n("queueEmpty")); - } - } - - @Nonnull - @Override - public String help(@Nonnull Context context) { - return "{0}{1}\n#" + context.i18n("helpRestartCommand"); - } - - @Nonnull - @Override - public PermissionLevel getMinimumPerms() { - return PermissionLevel.DJ; - } -} diff --git a/FredBoat/src/main/java/fredboat/command/music/seeking/RestartCommand.kt b/FredBoat/src/main/java/fredboat/command/music/seeking/RestartCommand.kt new file mode 100644 index 000000000..83eb1c419 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/command/music/seeking/RestartCommand.kt @@ -0,0 +1,61 @@ +/* + * MIT License + * + * Copyright (c) 2017 Frederik Ar. Mikkelsen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package fredboat.command.music.seeking + +import fredboat.audio.player.isQueueEmpty +import fredboat.commandmeta.abs.Command +import fredboat.commandmeta.abs.CommandContext +import fredboat.commandmeta.abs.ICommandRestricted +import fredboat.commandmeta.abs.IMusicCommand +import fredboat.definitions.PermissionLevel +import fredboat.main.getBotController +import fredboat.messaging.internal.Context +import fredboat.util.TextUtils + +class RestartCommand(name: String, vararg aliases: String) : Command(name, *aliases), IMusicCommand, ICommandRestricted { + + override val minimumPerms: PermissionLevel + get() = PermissionLevel.DJ + + override suspend fun invoke(context: CommandContext) { + val player = getBotController().playerRegistry.getExisting(context.guild) + + if (player != null && !player.isQueueEmpty) { + if (player.playingTrack == null) { + player.play() + } + player.seekTo(player.playingTrack!!.startPosition) + context.reply(context.i18nFormat("restartSuccess", + TextUtils.escapeAndDefuse(player.playingTrack!!.effectiveTitle))) + } else { + context.replyWithName(context.i18n("queueEmpty")) + } + } + + override fun help(context: Context): String { + return "{0}{1}\n#" + context.i18n("helpRestartCommand") + } +} diff --git a/FredBoat/src/main/java/fredboat/command/music/seeking/RewindCommand.java b/FredBoat/src/main/java/fredboat/command/music/seeking/RewindCommand.java deleted file mode 100644 index 7b8800067..000000000 --- a/FredBoat/src/main/java/fredboat/command/music/seeking/RewindCommand.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package fredboat.command.music.seeking; - -import fredboat.audio.player.GuildPlayer; -import fredboat.command.info.HelpCommand; -import fredboat.commandmeta.abs.CommandContext; -import fredboat.commandmeta.abs.ICommandRestricted; -import fredboat.commandmeta.abs.IMusicCommand; -import fredboat.commandmeta.abs.JCommand; -import fredboat.definitions.PermissionLevel; -import fredboat.messaging.internal.Context; -import fredboat.util.TextUtils; - -import javax.annotation.Nonnull; - -import static fredboat.main.LauncherKt.getBotController; - -public class RewindCommand extends JCommand implements IMusicCommand, ICommandRestricted { - - public RewindCommand(String name, String... aliases) { - super(name, aliases); - } - - @Override - public void onInvoke(@Nonnull CommandContext context) { - GuildPlayer player = getBotController().getPlayerRegistry().getExisting(context.getGuild()); - - if(player == null || player.isQueueEmpty()) { - context.replyWithName(context.i18n("queueEmpty")); - return; - } - - if (!context.hasArguments()) { - HelpCommand.sendFormattedCommandHelp(context); - return; - } - - long t; - try { - t = TextUtils.parseTimeString(context.getArgs()[0]); - } catch (IllegalStateException e){ - HelpCommand.sendFormattedCommandHelp(context); - return; - } - - long currentPosition = player.getPosition(); - //Ensure bounds - t = Math.max(0, t); - t = Math.min(currentPosition, t); - - player.seekTo(currentPosition - t); - context.reply(context.i18nFormat("rewSuccess", - TextUtils.escapeAndDefuse(player.getPlayingTrack().getEffectiveTitle()), TextUtils.formatTime(t))); - } - - @Nonnull - @Override - public String help(@Nonnull Context context) { - String usage = "{0}{1} [[hh:]mm:]ss\n#"; - String example = " {0}{1} 30"; - return usage + context.i18n("helpRewindCommand") + example; - } - - @Nonnull - @Override - public PermissionLevel getMinimumPerms() { - return PermissionLevel.DJ; - } - -} diff --git a/FredBoat/src/main/java/fredboat/command/music/seeking/RewindCommand.kt b/FredBoat/src/main/java/fredboat/command/music/seeking/RewindCommand.kt new file mode 100644 index 000000000..8a453d811 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/command/music/seeking/RewindCommand.kt @@ -0,0 +1,81 @@ +/* + * MIT License + * + * Copyright (c) 2017 Frederik Ar. Mikkelsen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package fredboat.command.music.seeking + +import fredboat.audio.player.isQueueEmpty +import fredboat.command.info.HelpCommand +import fredboat.commandmeta.abs.Command +import fredboat.commandmeta.abs.CommandContext +import fredboat.commandmeta.abs.ICommandRestricted +import fredboat.commandmeta.abs.IMusicCommand +import fredboat.definitions.PermissionLevel +import fredboat.main.getBotController +import fredboat.messaging.internal.Context +import fredboat.util.TextUtils + +class RewindCommand(name: String, vararg aliases: String) : Command(name, *aliases), IMusicCommand, ICommandRestricted { + + override val minimumPerms: PermissionLevel + get() = PermissionLevel.DJ + + override suspend fun invoke(context: CommandContext) { + val player = getBotController().playerRegistry.getExisting(context.guild) + + if (player == null || player.isQueueEmpty) { + context.replyWithName(context.i18n("queueEmpty")) + return + } + + if (!context.hasArguments()) { + HelpCommand.sendFormattedCommandHelp(context) + return + } + + var t: Long + try { + t = TextUtils.parseTimeString(context.args[0]) + } catch (e: IllegalStateException) { + HelpCommand.sendFormattedCommandHelp(context) + return + } + + val currentPosition = player.position + //Ensure bounds + t = Math.max(0, t) + t = Math.min(currentPosition, t) + + player.seekTo(currentPosition - t) + context.reply(context.i18nFormat("rewSuccess", + TextUtils.escapeAndDefuse(player.playingTrack!!.effectiveTitle), TextUtils.formatTime(t))) + } + + override fun help(context: Context): String { + val usage = "{0}{1} [[hh:]mm:]ss\n#" + val example = " {0}{1} 30" + return usage + context.i18n("helpRewindCommand") + example + } + +} diff --git a/FredBoat/src/main/java/fredboat/command/music/seeking/SeekCommand.java b/FredBoat/src/main/java/fredboat/command/music/seeking/SeekCommand.java deleted file mode 100644 index 61ca2a57e..000000000 --- a/FredBoat/src/main/java/fredboat/command/music/seeking/SeekCommand.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package fredboat.command.music.seeking; - -import fredboat.audio.player.GuildPlayer; -import fredboat.audio.queue.AudioTrackContext; -import fredboat.command.info.HelpCommand; -import fredboat.commandmeta.abs.CommandContext; -import fredboat.commandmeta.abs.ICommandRestricted; -import fredboat.commandmeta.abs.IMusicCommand; -import fredboat.commandmeta.abs.JCommand; -import fredboat.definitions.PermissionLevel; -import fredboat.messaging.internal.Context; -import fredboat.util.TextUtils; - -import javax.annotation.Nonnull; - -import static fredboat.main.LauncherKt.getBotController; - -public class SeekCommand extends JCommand implements IMusicCommand, ICommandRestricted { - - public SeekCommand(String name, String... aliases) { - super(name, aliases); - } - - @Override - public void onInvoke(@Nonnull CommandContext context) { - GuildPlayer player = getBotController().getPlayerRegistry().getExisting(context.getGuild()); - - if(player == null || player.isQueueEmpty()) { - context.replyWithName(context.i18n("queueEmpty")); - return; - } - - if (!context.hasArguments()) { - HelpCommand.sendFormattedCommandHelp(context); - return; - } - - long t; - try { - t = TextUtils.parseTimeString(context.getArgs()[0]); - } catch (IllegalStateException e){ - HelpCommand.sendFormattedCommandHelp(context); - return; - } - - AudioTrackContext atc = player.getPlayingTrack(); - - //Ensure bounds - t = Math.max(0, t); - t = Math.min(atc.getEffectiveDuration(), t); - - player.seekTo(atc.getStartPosition() + t); - context.reply(context.i18nFormat("seekSuccess", - TextUtils.escapeAndDefuse(atc.getEffectiveTitle()), TextUtils.formatTime(t))); - } - - @Nonnull - @Override - public String help(@Nonnull Context context) { - String usage = "{0}{1} [[hh:]mm:]ss\n#"; - String example = " {0}{1} 2:45:00"; - return usage + context.i18n("helpSeekCommand") + example; - } - - @Nonnull - @Override - public PermissionLevel getMinimumPerms() { - return PermissionLevel.DJ; - } -} diff --git a/FredBoat/src/main/java/fredboat/command/music/seeking/SeekCommand.kt b/FredBoat/src/main/java/fredboat/command/music/seeking/SeekCommand.kt new file mode 100644 index 000000000..3c216d3bb --- /dev/null +++ b/FredBoat/src/main/java/fredboat/command/music/seeking/SeekCommand.kt @@ -0,0 +1,81 @@ +/* + * MIT License + * + * Copyright (c) 2017 Frederik Ar. Mikkelsen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package fredboat.command.music.seeking + +import fredboat.audio.player.isQueueEmpty +import fredboat.command.info.HelpCommand +import fredboat.commandmeta.abs.Command +import fredboat.commandmeta.abs.CommandContext +import fredboat.commandmeta.abs.ICommandRestricted +import fredboat.commandmeta.abs.IMusicCommand +import fredboat.definitions.PermissionLevel +import fredboat.main.getBotController +import fredboat.messaging.internal.Context +import fredboat.util.TextUtils + +class SeekCommand(name: String, vararg aliases: String) : Command(name, *aliases), IMusicCommand, ICommandRestricted { + + override val minimumPerms: PermissionLevel + get() = PermissionLevel.DJ + + override suspend fun invoke(context: CommandContext) { + val player = getBotController().playerRegistry.getExisting(context.guild) + + if (player == null || player.isQueueEmpty) { + context.replyWithName(context.i18n("queueEmpty")) + return + } + + if (!context.hasArguments()) { + HelpCommand.sendFormattedCommandHelp(context) + return + } + + var t: Long + try { + t = TextUtils.parseTimeString(context.args[0]) + } catch (e: IllegalStateException) { + HelpCommand.sendFormattedCommandHelp(context) + return + } + + val atc = player.playingTrack + + //Ensure bounds + t = Math.max(0, t) + t = Math.min(atc!!.effectiveDuration, t) + + player.seekTo(atc.startPosition + t) + context.reply(context.i18nFormat("seekSuccess", + TextUtils.escapeAndDefuse(atc.effectiveTitle), TextUtils.formatTime(t))) + } + + override fun help(context: Context): String { + val usage = "{0}{1} [[hh:]mm:]ss\n#" + val example = " {0}{1} 2:45:00" + return usage + context.i18n("helpSeekCommand") + example + } +} From 4ab1da075242657da83732700c013c9c8ca94117 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 28 Dec 2018 17:42:15 +0100 Subject: [PATCH 043/172] Add simple player info server --- FredBoat/build.gradle | 1 + .../audio/lavalink/SentinelLavalink.kt | 2 + .../java/fredboat/audio/player/GuildPlayer.kt | 22 +++++--- .../java/fredboat/ws/HandshakeInterceptor.kt | 52 ++++++++++++++++++ .../src/main/java/fredboat/ws/SocketInfo.kt | 10 ++++ .../java/fredboat/ws/UserSessionHandler.kt | 53 +++++++++++++++++++ FredBoat/src/main/java/fredboat/ws/models.kt | 40 ++++++++++++++ build.gradle | 1 + 8 files changed, 173 insertions(+), 8 deletions(-) create mode 100644 FredBoat/src/main/java/fredboat/ws/HandshakeInterceptor.kt create mode 100644 FredBoat/src/main/java/fredboat/ws/SocketInfo.kt create mode 100644 FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt create mode 100644 FredBoat/src/main/java/fredboat/ws/models.kt diff --git a/FredBoat/build.gradle b/FredBoat/build.gradle index 582821f65..fbc067f1c 100644 --- a/FredBoat/build.gradle +++ b/FredBoat/build.gradle @@ -50,6 +50,7 @@ dependencies { compile group: 'org.springframework.boot', name: 'spring-boot-starter-webflux', version: springBootVersion compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-mongodb-reactive', version: springBootVersion + compile group: 'org.springframework', name: 'spring-websocket', version: springWebSocketVersion compile group: 'it.unimi.dsi', name: 'fastutil', version: fastUtilVersion compile group: 'com.google.code.gson', name: 'gson', version: gsonVersion compile group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlinVersion diff --git a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt index d29bf7204..0b59b810a 100644 --- a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt +++ b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt @@ -10,6 +10,7 @@ import fredboat.sentinel.Guild import fredboat.sentinel.GuildCache import fredboat.sentinel.Sentinel import fredboat.util.DiscordUtil +import fredboat.ws.UserSessionHandler import lavalink.client.io.Lavalink import lavalink.client.io.metrics.LavalinkCollector import org.json.JSONObject @@ -26,6 +27,7 @@ class SentinelLavalink( val guildCache: GuildCache, val appConfig: AppConfig, val activityMetrics: ActivityMetricsController, + val userSessionHandler: UserSessionHandler, lavalinkConfig: LavalinkConfig ) : Lavalink( sentinel.selfUser.idString, diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index fe04e493d..f9a3f1f1c 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -44,6 +44,8 @@ import fredboat.util.TextUtils import fredboat.util.extension.escapeAndDefuse import fredboat.util.ratelimit.Ratelimiter import fredboat.util.rest.YoutubeAPI +import fredboat.ws.emptyPlayerInfo +import fredboat.ws.toPlayerInfo import lavalink.client.player.IPlayer import lavalink.client.player.LavalinkPlayer import lavalink.client.player.event.PlayerEventListenerAdapter @@ -210,6 +212,7 @@ class GuildPlayer( joinChannel(member) } audioTrackProvider.add(atc) + if (isPlaying) updateClients() play() } @@ -225,6 +228,7 @@ class GuildPlayer( fun reshuffle() { if (audioTrackProvider is AbstractTrackProvider) { audioTrackProvider.reshuffle() + updateClients() } else { throw UnsupportedOperationException("Can't reshuffle " + audioTrackProvider.javaClass) } @@ -260,6 +264,7 @@ class GuildPlayer( player.removeListener(this) player.link.destroy() log.info("Player for $guildId was destroyed.") + lavalink.userSessionHandler.sendLazy(guildId) { emptyPlayerInfo } } private fun voteSkipCleanup() { @@ -287,6 +292,7 @@ class GuildPlayer( val vc = slink.getChannel(guild) ?: return slink.connect(vc, skipIfSameChannel = false) } + updateClients() } fun play() { @@ -311,16 +317,10 @@ class GuildPlayer( player.isPaused = false play() } + updateClients() } - /** - * Pause the player - */ - fun pause() { - log.trace("pause()") - - player.isPaused = true - } + fun pause() = setPause(true) /** * Clear the tracklist and stop the current track @@ -377,6 +377,7 @@ class GuildPlayer( val atc = audioTrackProvider.provideAudioTrack() lastLoadedTrack = atc atc?.let { playTrack(it) } + updateClients() } private fun updateHistoryQueue() { @@ -427,6 +428,7 @@ class GuildPlayer( fun seekTo(position: Long) { if (internalContext!!.track.isSeekable) { player.seekTo(position) + updateClients() } else { throw MessagingException(internalContext!!.i18n("seekDeniedLiveTrack")) } @@ -435,4 +437,8 @@ class GuildPlayer( private fun logListeners() { humanUsersInCurrentVC.forEach { lavalink.activityMetrics.logListener(it) } } + + private fun updateClients() { + lavalink.userSessionHandler.sendLazy(guildId) { toPlayerInfo() } + } } diff --git a/FredBoat/src/main/java/fredboat/ws/HandshakeInterceptor.kt b/FredBoat/src/main/java/fredboat/ws/HandshakeInterceptor.kt new file mode 100644 index 000000000..af52f27e6 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/ws/HandshakeInterceptor.kt @@ -0,0 +1,52 @@ +package fredboat.ws + +import fredboat.sentinel.GuildCache +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.http.HttpStatus +import org.springframework.http.server.ServerHttpRequest +import org.springframework.http.server.ServerHttpResponse +import org.springframework.stereotype.Controller +import org.springframework.web.socket.WebSocketHandler +import org.springframework.web.socket.server.HandshakeInterceptor +import java.util.regex.Pattern + +@Controller +class HandshakeInterceptorImpl( + private val guildCache: GuildCache +) : HandshakeInterceptor { + + companion object { + private val log: Logger = LoggerFactory.getLogger(HandshakeInterceptorImpl::class.java) + private val expctedPath = Pattern.compile("/playerinfo/(\\d+)/?") + } + + override fun beforeHandshake( + request: ServerHttpRequest, + response: ServerHttpResponse, + wsHandler: WebSocketHandler, + attributes: MutableMap + ): Boolean { + expctedPath.matcher(request.uri.path).apply { + if (!find()) { + response.setStatusCode(HttpStatus.NOT_FOUND) + log.info("Unexpected path: {}", request.uri.path) + return false + } + val info = SocketInfo(guildCache, group(1).toLong()) + attributes["info"] = info + return true + } + } + + override fun afterHandshake( + request: ServerHttpRequest, + response: ServerHttpResponse, + wsHandler: WebSocketHandler, + exception: Exception? + ) { + if (exception == null) { + log.error("Exception in handshake", exception) + } + } +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/ws/SocketInfo.kt b/FredBoat/src/main/java/fredboat/ws/SocketInfo.kt new file mode 100644 index 000000000..07452c419 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/ws/SocketInfo.kt @@ -0,0 +1,10 @@ +package fredboat.ws + +import fredboat.audio.player.GuildPlayer +import fredboat.sentinel.Guild +import fredboat.sentinel.GuildCache + +data class SocketInfo(private val guildCache: GuildCache, var guildId: Long) { + val guild: Guild? get() = guildCache.getIfCached(guildId) + val player: GuildPlayer? get() = guild?.guildPlayer +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt b/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt new file mode 100644 index 000000000..752eb5414 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt @@ -0,0 +1,53 @@ +package fredboat.ws + +import com.google.gson.Gson +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Controller +import org.springframework.web.socket.CloseStatus +import org.springframework.web.socket.TextMessage +import org.springframework.web.socket.WebSocketSession +import org.springframework.web.socket.handler.TextWebSocketHandler +import java.util.concurrent.ConcurrentHashMap + +@Controller +class UserSessionHandler(val gson: Gson) : TextWebSocketHandler() { + + companion object { + private val log: Logger = LoggerFactory.getLogger(UserSessionHandler::class.java) + val WebSocketSession.info: SocketInfo get() = attributes["info"] as SocketInfo + fun WebSocketSession.send(gson: Gson, payload: Any) { + sendMessage(TextMessage(gson.toJson(payload))) + } + } + + private val sessions = ConcurrentHashMap>() + + operator fun get(guildId: Long): List = sessions[guildId] ?: emptyList() + + override fun afterConnectionEstablished(session: WebSocketSession) { + log.info("Established user connection for guild ${session.info.guildId}") + sessions.computeIfAbsent(session.info.guildId) { mutableListOf() }.add(session) + val info = session.info.player?.toPlayerInfo() ?: emptyPlayerInfo + session.send(gson, info) + } + + override fun afterConnectionClosed(session: WebSocketSession, status: CloseStatus) { + val id = session.info.guildId + log.info("Disconnected user for guild $id with status $status") + val list = sessions[id] ?: return + if (list.size == 1) sessions.remove(id) + else list.remove(session) + } + + final inline fun sendLazy(guildId: Long, producer: () -> Any) { + val sessions = this[guildId] + if (sessions.isEmpty()) return + + val msg = TextMessage(gson.toJson(producer())) + sessions.forEach { + if (it.isOpen) it.sendMessage(msg) + } + } + +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/ws/models.kt b/FredBoat/src/main/java/fredboat/ws/models.kt new file mode 100644 index 000000000..f80c563a4 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/ws/models.kt @@ -0,0 +1,40 @@ +package fredboat.ws + +import fredboat.audio.player.GuildPlayer +import fredboat.audio.player.getTracksInRange +import fredboat.audio.queue.AudioTrackContext +import fredboat.definitions.RepeatMode + +data class PlayerInfo( + val playing: Boolean, + val paused: Boolean, + val shuffled: Boolean, + val repeatMode: Int, + val playingPos: Long?, + val queue: List +) + +data class TrackInfo( + val id: String, + val name: String, + val image: String?, + val duration: Long? +) + +val emptyPlayerInfo = PlayerInfo(false, false, false, RepeatMode.OFF.ordinal, null, emptyList()) + +fun GuildPlayer.toPlayerInfo() = PlayerInfo( + isPlaying, + isPaused, + isShuffle, + repeatMode.ordinal, + playingTrack?.getEffectivePosition(this), + getTracksInRange(0, 10).map { it.toTrackInfo() } +) + +fun AudioTrackContext.toTrackInfo() = TrackInfo( + trackId.toHexString(), + effectiveTitle, + null, //TODO + if (track.info.isStream) null else effectiveDuration +) \ No newline at end of file diff --git a/build.gradle b/build.gradle index 63d11ee6c..609b74a98 100644 --- a/build.gradle +++ b/build.gradle @@ -60,6 +60,7 @@ subprojects { sentinelVersion = 'd8680e86' springBootVersion = springBootVersion + springWebSocketVersion = '5.0.8.RELEASE' kotlinVersion = kotlinVersion //audio deps From cb80fa35b5c3a767176e77d8d9a614667734d0a0 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 28 Dec 2018 18:21:55 +0100 Subject: [PATCH 044/172] Add ConfigWebInfoCommand --- .../command/config/ConfigWebInfoCommand.kt | 52 +++++++++++++++++++ .../commandmeta/CommandInitializer.kt | 21 ++++++-- .../fredboat/config/property/AppConfig.java | 2 + .../config/property/AppConfigProperties.java | 10 ++++ .../java/fredboat/db/mongo/GuildSettings.kt | 11 ++++ .../src/main/java/fredboat/db/mongo/player.kt | 2 - .../java/fredboat/db/mongo/repositories.kt | 1 + .../java/fredboat/ws/HandshakeInterceptor.kt | 12 ++++- 8 files changed, 103 insertions(+), 8 deletions(-) create mode 100644 FredBoat/src/main/java/fredboat/command/config/ConfigWebInfoCommand.kt create mode 100644 FredBoat/src/main/java/fredboat/db/mongo/GuildSettings.kt diff --git a/FredBoat/src/main/java/fredboat/command/config/ConfigWebInfoCommand.kt b/FredBoat/src/main/java/fredboat/command/config/ConfigWebInfoCommand.kt new file mode 100644 index 000000000..6536a1588 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/command/config/ConfigWebInfoCommand.kt @@ -0,0 +1,52 @@ +package fredboat.command.config + +import fredboat.command.info.HelpCommand +import fredboat.commandmeta.abs.Command +import fredboat.commandmeta.abs.CommandContext +import fredboat.commandmeta.abs.ICommandRestricted +import fredboat.commandmeta.abs.IConfigCommand +import fredboat.config.property.AppConfig +import fredboat.db.mongo.GuildSettings +import fredboat.db.mongo.GuildSettingsRepository +import fredboat.definitions.PermissionLevel +import fredboat.messaging.internal.Context +import kotlinx.coroutines.reactive.awaitSingle + +class ConfigWebInfoCommand( + name: String, + vararg aliases: String, + private val repo: GuildSettingsRepository, + private val appConfig: AppConfig +) : Command(name, *aliases), IConfigCommand, ICommandRestricted { + + override val minimumPerms = PermissionLevel.ADMIN + + override suspend fun invoke(context: CommandContext) { + if (appConfig.webInfoBaseUrl.isBlank()) { + context.reply("This bot isn't configured to support web info") + return + } + + val settings = repo.findById(context.guild.id) + .defaultIfEmpty(GuildSettings(context.guild.id)) + .awaitSingle() + + when(context.args.last().trim().toLowerCase()) { + "allow" -> settings.allowPublicPlayerInfo = true + "deny" -> settings.allowPublicPlayerInfo = false + else -> { + HelpCommand.sendFormattedCommandHelp(context) + return + } + } + repo.save(settings).subscribe { + val response = if (it.allowPublicPlayerInfo) + "Online playing status is not available at ${appConfig.webInfoBaseUrl}${context.guild.id}" + else "Online playing status has been disabled." + context.reply(response) + } + } + + override fun help(context: Context) = "{0}{1} allow OR {0}{1} deny" + +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt b/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt index 59e161ab2..deca95e9e 100644 --- a/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt +++ b/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt @@ -44,6 +44,8 @@ import fredboat.command.music.seeking.RewindCommand import fredboat.command.music.seeking.SeekCommand import fredboat.command.util.* import fredboat.config.SentryConfiguration +import fredboat.config.property.AppConfig +import fredboat.db.mongo.GuildSettingsRepository import fredboat.definitions.Module import fredboat.definitions.PermissionLevel import fredboat.definitions.SearchProvider @@ -60,10 +62,20 @@ import java.util.* import java.util.function.Supplier @Service -class CommandInitializer(cacheMetrics: CacheMetricsCollector, weather: Weather, trackSearcher: TrackSearcher, - videoSelectionCache: VideoSelectionCache, sentryConfiguration: SentryConfiguration, - playerLimiter: PlayerLimiter, youtubeAPI: YoutubeAPI, sentinel: Sentinel, - playerRegistry: PlayerRegistry, springContext: Supplier) { +class CommandInitializer( + cacheMetrics: CacheMetricsCollector, + weather: Weather, + trackSearcher: TrackSearcher, + videoSelectionCache: VideoSelectionCache, + sentryConfiguration: SentryConfiguration, + playerLimiter: PlayerLimiter, + youtubeAPI: YoutubeAPI, + sentinel: Sentinel, + playerRegistry: PlayerRegistry, + guildSettingsRepository: GuildSettingsRepository, + appConfig: AppConfig, + springContext: Supplier +) { companion object { /** Used for integration testing */ @@ -130,6 +142,7 @@ class CommandInitializer(cacheMetrics: CacheMetricsCollector, weather: Weather, configModule.registerCommand(LanguageCommand(LANGUAGE_COMM_NAME, "lang")) configModule.registerCommand(ModulesCommand("modules", "module", "mods")) configModule.registerCommand(PrefixCommand(cacheMetrics, PREFIX_COMM_NAME, "pre")) + configModule.registerCommand(ConfigWebInfoCommand("webinfo", repo = guildSettingsRepository, appConfig = appConfig)) /* Perms */ configModule.registerCommand(PermissionsCommand(PermissionLevel.ADMIN, "admin", "admins")) configModule.registerCommand(PermissionsCommand(PermissionLevel.DJ, "dj", "djs")) diff --git a/FredBoat/src/main/java/fredboat/config/property/AppConfig.java b/FredBoat/src/main/java/fredboat/config/property/AppConfig.java index 166227730..dc9564114 100644 --- a/FredBoat/src/main/java/fredboat/config/property/AppConfig.java +++ b/FredBoat/src/main/java/fredboat/config/property/AppConfig.java @@ -82,4 +82,6 @@ default String getStatus() { } boolean getContinuePlayback(); + + String getWebInfoBaseUrl(); } diff --git a/FredBoat/src/main/java/fredboat/config/property/AppConfigProperties.java b/FredBoat/src/main/java/fredboat/config/property/AppConfigProperties.java index fc47bf804..a8b57c963 100644 --- a/FredBoat/src/main/java/fredboat/config/property/AppConfigProperties.java +++ b/FredBoat/src/main/java/fredboat/config/property/AppConfigProperties.java @@ -52,6 +52,7 @@ public class AppConfigProperties implements AppConfig { private String game = ""; private boolean continuePlayback = false; private int shardCount = 1; + private String webInfoBaseUrl = ""; //undocumented private int playerLimit = -1; @@ -106,6 +107,11 @@ public int getShardCount() { return shardCount; } + @Override + public String getWebInfoBaseUrl() { + return webInfoBaseUrl; + } + public void setDevelopment(boolean development) { this.development = development; } @@ -146,4 +152,8 @@ public void setPlayerLimit(int playerLimit) { public void setShardCount(int shardCount) { this.shardCount = shardCount; } + + public void setWebInfoBaseUrl(String webInfoBaseUrl) { + this.webInfoBaseUrl = webInfoBaseUrl; + } } diff --git a/FredBoat/src/main/java/fredboat/db/mongo/GuildSettings.kt b/FredBoat/src/main/java/fredboat/db/mongo/GuildSettings.kt new file mode 100644 index 000000000..2618d816a --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/mongo/GuildSettings.kt @@ -0,0 +1,11 @@ +package fredboat.db.mongo + +import org.springframework.data.annotation.Id +import org.springframework.data.mongodb.core.mapping.Document + +@Document(collection = "GuildSettings") +data class GuildSettings( + @Id + val guildId: Long, + var allowPublicPlayerInfo: Boolean = false +) \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/player.kt b/FredBoat/src/main/java/fredboat/db/mongo/player.kt index a1f8468a7..c401d9186 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/player.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/player.kt @@ -2,7 +2,6 @@ package fredboat.db.mongo import org.bson.types.ObjectId import org.springframework.data.annotation.Id -import org.springframework.data.mongodb.core.index.Indexed import org.springframework.data.mongodb.core.mapping.Document @Document @@ -11,7 +10,6 @@ class MongoPlayer( val gid: Long, /** If the player was playing at the time of saving, * it may automatically be reloaded when we start the bot */ - @Indexed val playing: Boolean, val paused: Boolean, val shuffled: Boolean, diff --git a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt index 745d9f57c..7e7bd11a7 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt @@ -13,6 +13,7 @@ import reactor.core.publisher.Mono interface PlayerRepository : ReactiveCrudRepository interface ActivityRepository : ReactiveCrudRepository +interface GuildSettingsRepository : ReactiveCrudRepository private val log: Logger = LoggerFactory.getLogger(PlayerRepository::class.java) diff --git a/FredBoat/src/main/java/fredboat/ws/HandshakeInterceptor.kt b/FredBoat/src/main/java/fredboat/ws/HandshakeInterceptor.kt index af52f27e6..8e862c354 100644 --- a/FredBoat/src/main/java/fredboat/ws/HandshakeInterceptor.kt +++ b/FredBoat/src/main/java/fredboat/ws/HandshakeInterceptor.kt @@ -1,5 +1,6 @@ package fredboat.ws +import fredboat.db.mongo.GuildSettingsRepository import fredboat.sentinel.GuildCache import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -9,11 +10,13 @@ import org.springframework.http.server.ServerHttpResponse import org.springframework.stereotype.Controller import org.springframework.web.socket.WebSocketHandler import org.springframework.web.socket.server.HandshakeInterceptor +import java.time.Duration import java.util.regex.Pattern @Controller class HandshakeInterceptorImpl( - private val guildCache: GuildCache + private val guildCache: GuildCache, + private val guildSettingsRepository: GuildSettingsRepository ) : HandshakeInterceptor { companion object { @@ -33,7 +36,12 @@ class HandshakeInterceptorImpl( log.info("Unexpected path: {}", request.uri.path) return false } - val info = SocketInfo(guildCache, group(1).toLong()) + val guildId = group(1).toLong() + val settings = guildSettingsRepository.findById(guildId).block(Duration.ofMinutes(1)) + if (settings?.allowPublicPlayerInfo != true) { + throw RuntimeException("This guild does not allow anonymous access to player info.") + } + val info = SocketInfo(guildCache, guildId) attributes["info"] = info return true } From 9aa2d1d18b68a1c67d2fcf6c7c29587c8adb6aed Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 28 Dec 2018 18:42:13 +0100 Subject: [PATCH 045/172] Fix test compile error --- FredBoat/src/test/java/fredboat/testutil/config/MockConfig.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/FredBoat/src/test/java/fredboat/testutil/config/MockConfig.kt b/FredBoat/src/test/java/fredboat/testutil/config/MockConfig.kt index 29f78a162..306a2b4fe 100644 --- a/FredBoat/src/test/java/fredboat/testutil/config/MockConfig.kt +++ b/FredBoat/src/test/java/fredboat/testutil/config/MockConfig.kt @@ -106,6 +106,8 @@ class MockConfig : AppConfig, AudioSourcesConfig, Credentials, EventLoggerConfig override fun getCarbonKey() = "" + override fun getWebInfoBaseUrl() = "" + override fun getQuarterdeck() = object : BackendConfig.Quarterdeck { override fun getHost() = "http://localhost:4269" override fun getUser() = "test" From 0949d077ccf8e92c45d88dfdfd5e21495ec7cbdb Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 28 Dec 2018 18:49:32 +0100 Subject: [PATCH 046/172] Fix integration test --- .../java/fredboat/command/music/control/PlayCommandTest.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/FredBoat/src/test/java/fredboat/command/music/control/PlayCommandTest.kt b/FredBoat/src/test/java/fredboat/command/music/control/PlayCommandTest.kt index ffbe7fb21..e024b21d1 100644 --- a/FredBoat/src/test/java/fredboat/command/music/control/PlayCommandTest.kt +++ b/FredBoat/src/test/java/fredboat/command/music/control/PlayCommandTest.kt @@ -86,7 +86,10 @@ internal class PlayCommandTest( val track = players.getOrCreate(guild).block()?.remainingTracks?.get(1) assertNotNull(track) - assertEquals(url, track?.track?.info?.uri) + assertEquals(url2, track?.track?.info?.uri) + + // Check that the first track is still playing + assertEquals(url, players.getOrCreate(guild).block()?.playingTrack?.track?.info?.uri) } } From 6862c57c80ccfb20a901292156f4b6e2a2b90f28 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Sat, 29 Dec 2018 02:54:39 +0100 Subject: [PATCH 047/172] Redo the websocket system because webflux requires reactive --- FredBoat/build.gradle | 1 + .../java/fredboat/api/MetricsController.java | 4 +- .../java/fredboat/config/WebSocketConfig.kt | 37 ++++++++++ .../java/fredboat/ws/HandshakeInterceptor.kt | 60 ---------------- .../src/main/java/fredboat/ws/SocketInfo.kt | 10 --- .../src/main/java/fredboat/ws/UserSession.kt | 45 ++++++++++++ .../java/fredboat/ws/UserSessionHandler.kt | 72 +++++++++++++------ build.gradle | 2 +- 8 files changed, 136 insertions(+), 95 deletions(-) create mode 100644 FredBoat/src/main/java/fredboat/config/WebSocketConfig.kt delete mode 100644 FredBoat/src/main/java/fredboat/ws/HandshakeInterceptor.kt delete mode 100644 FredBoat/src/main/java/fredboat/ws/SocketInfo.kt create mode 100644 FredBoat/src/main/java/fredboat/ws/UserSession.kt diff --git a/FredBoat/build.gradle b/FredBoat/build.gradle index fbc067f1c..49a01f32f 100644 --- a/FredBoat/build.gradle +++ b/FredBoat/build.gradle @@ -49,6 +49,7 @@ dependencies { } compile group: 'org.springframework.boot', name: 'spring-boot-starter-webflux', version: springBootVersion + compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: springBootVersion // Needed for WS compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-mongodb-reactive', version: springBootVersion compile group: 'org.springframework', name: 'spring-websocket', version: springWebSocketVersion compile group: 'it.unimi.dsi', name: 'fastutil', version: fastUtilVersion diff --git a/FredBoat/src/main/java/fredboat/api/MetricsController.java b/FredBoat/src/main/java/fredboat/api/MetricsController.java index 8e01fad1e..ef63d57a5 100644 --- a/FredBoat/src/main/java/fredboat/api/MetricsController.java +++ b/FredBoat/src/main/java/fredboat/api/MetricsController.java @@ -69,11 +69,9 @@ private String buildAnswer(String[] includedParam) throws IOException { } Writer writer = new StringWriter(); - try { + try (writer) { TextFormat.write004(writer, registry.filteredMetricFamilySamples(params)); writer.flush(); - } finally { - writer.close(); } return writer.toString(); diff --git a/FredBoat/src/main/java/fredboat/config/WebSocketConfig.kt b/FredBoat/src/main/java/fredboat/config/WebSocketConfig.kt new file mode 100644 index 000000000..f5861939c --- /dev/null +++ b/FredBoat/src/main/java/fredboat/config/WebSocketConfig.kt @@ -0,0 +1,37 @@ +package fredboat.config + +import fredboat.ws.UserSessionHandler +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.web.reactive.HandlerAdapter +import org.springframework.web.reactive.HandlerMapping +import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping +import org.springframework.web.reactive.socket.WebSocketHandler +import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter +import org.springframework.web.socket.config.annotation.EnableWebSocket +import java.util.* + + + +@Configuration +@EnableWebSocket +class WebSocketConfig( + private val userSessionHandler: UserSessionHandler +) { + + @Bean + fun webSocketHandlerMapping(): HandlerMapping { + val map = HashMap() + map["/playerinfo/*"] = userSessionHandler + + val handlerMapping = SimpleUrlHandlerMapping() + handlerMapping.order = 1 + handlerMapping.urlMap = map + return handlerMapping + } + + @Bean + fun handlerAdapter(): HandlerAdapter { + return WebSocketHandlerAdapter() + } +} diff --git a/FredBoat/src/main/java/fredboat/ws/HandshakeInterceptor.kt b/FredBoat/src/main/java/fredboat/ws/HandshakeInterceptor.kt deleted file mode 100644 index 8e862c354..000000000 --- a/FredBoat/src/main/java/fredboat/ws/HandshakeInterceptor.kt +++ /dev/null @@ -1,60 +0,0 @@ -package fredboat.ws - -import fredboat.db.mongo.GuildSettingsRepository -import fredboat.sentinel.GuildCache -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.http.HttpStatus -import org.springframework.http.server.ServerHttpRequest -import org.springframework.http.server.ServerHttpResponse -import org.springframework.stereotype.Controller -import org.springframework.web.socket.WebSocketHandler -import org.springframework.web.socket.server.HandshakeInterceptor -import java.time.Duration -import java.util.regex.Pattern - -@Controller -class HandshakeInterceptorImpl( - private val guildCache: GuildCache, - private val guildSettingsRepository: GuildSettingsRepository -) : HandshakeInterceptor { - - companion object { - private val log: Logger = LoggerFactory.getLogger(HandshakeInterceptorImpl::class.java) - private val expctedPath = Pattern.compile("/playerinfo/(\\d+)/?") - } - - override fun beforeHandshake( - request: ServerHttpRequest, - response: ServerHttpResponse, - wsHandler: WebSocketHandler, - attributes: MutableMap - ): Boolean { - expctedPath.matcher(request.uri.path).apply { - if (!find()) { - response.setStatusCode(HttpStatus.NOT_FOUND) - log.info("Unexpected path: {}", request.uri.path) - return false - } - val guildId = group(1).toLong() - val settings = guildSettingsRepository.findById(guildId).block(Duration.ofMinutes(1)) - if (settings?.allowPublicPlayerInfo != true) { - throw RuntimeException("This guild does not allow anonymous access to player info.") - } - val info = SocketInfo(guildCache, guildId) - attributes["info"] = info - return true - } - } - - override fun afterHandshake( - request: ServerHttpRequest, - response: ServerHttpResponse, - wsHandler: WebSocketHandler, - exception: Exception? - ) { - if (exception == null) { - log.error("Exception in handshake", exception) - } - } -} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/ws/SocketInfo.kt b/FredBoat/src/main/java/fredboat/ws/SocketInfo.kt deleted file mode 100644 index 07452c419..000000000 --- a/FredBoat/src/main/java/fredboat/ws/SocketInfo.kt +++ /dev/null @@ -1,10 +0,0 @@ -package fredboat.ws - -import fredboat.audio.player.GuildPlayer -import fredboat.sentinel.Guild -import fredboat.sentinel.GuildCache - -data class SocketInfo(private val guildCache: GuildCache, var guildId: Long) { - val guild: Guild? get() = guildCache.getIfCached(guildId) - val player: GuildPlayer? get() = guild?.guildPlayer -} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/ws/UserSession.kt b/FredBoat/src/main/java/fredboat/ws/UserSession.kt new file mode 100644 index 000000000..ccd9c19eb --- /dev/null +++ b/FredBoat/src/main/java/fredboat/ws/UserSession.kt @@ -0,0 +1,45 @@ +package fredboat.ws + +import com.google.gson.Gson +import fredboat.audio.player.GuildPlayer +import fredboat.sentinel.Guild +import fredboat.sentinel.GuildCache +import org.springframework.web.reactive.socket.WebSocketMessage +import org.springframework.web.reactive.socket.WebSocketSession +import reactor.core.publisher.Flux +import reactor.core.publisher.FluxSink +import reactor.core.publisher.Mono +import java.util.regex.Pattern + +class UserSession( + val session: WebSocketSession, + private val guildCache: GuildCache, + private val gson: Gson +) : WebSocketSession by session { + + companion object { + private val expctedPath = Pattern.compile("/playerinfo/(\\d+)/?") + } + + @Volatile + private lateinit var sink: FluxSink + var isOpen = true + val guildId = expctedPath.matcher(handshakeInfo.uri.path).run { find(); group(1) }.toLong() + val guild: Guild? get() = guildCache.getIfCached(guildId) + val player: GuildPlayer? get() = guild?.guildPlayer + fun sendJson(payload: Any) { + sink.next(textMessage(gson.toJson(payload))) + } + fun send(payload: String) { + sink.next(textMessage(payload)) + } + fun send(message: WebSocketMessage) { + sink.next(message) + } + + /** Grants us a hook to send messages with */ + fun initSendStream(): Mono { + return session.send(Flux.create { sink = it }) + .doFinally { isOpen = false } + } +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt b/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt index 752eb5414..48589912f 100644 --- a/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt +++ b/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt @@ -1,40 +1,70 @@ package fredboat.ws import com.google.gson.Gson +import fredboat.db.mongo.GuildSettings +import fredboat.db.mongo.GuildSettingsRepository +import fredboat.sentinel.GuildCache import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.stereotype.Controller -import org.springframework.web.socket.CloseStatus -import org.springframework.web.socket.TextMessage -import org.springframework.web.socket.WebSocketSession -import org.springframework.web.socket.handler.TextWebSocketHandler +import org.springframework.web.reactive.socket.WebSocketHandler +import org.springframework.web.reactive.socket.WebSocketMessage +import org.springframework.web.reactive.socket.WebSocketSession +import reactor.core.publisher.Mono import java.util.concurrent.ConcurrentHashMap @Controller -class UserSessionHandler(val gson: Gson) : TextWebSocketHandler() { +class UserSessionHandler( + val gson: Gson, + val guildCache: GuildCache, + val repository: GuildSettingsRepository +) : WebSocketHandler { companion object { private val log: Logger = LoggerFactory.getLogger(UserSessionHandler::class.java) - val WebSocketSession.info: SocketInfo get() = attributes["info"] as SocketInfo - fun WebSocketSession.send(gson: Gson, payload: Any) { - sendMessage(TextMessage(gson.toJson(payload))) - } } - private val sessions = ConcurrentHashMap>() + private val sessions = ConcurrentHashMap>() + + override fun handle(rawSession: WebSocketSession): Mono { + val session = UserSession(rawSession, guildCache, gson) + log.info("Established user connection for guild ${session.guildId}") + + val interceptMono = repository.findById(session.guildId) + .defaultIfEmpty(GuildSettings(session.guildId)) + .doOnError { e -> + log.error("Exception while validating privacy setting", e) + session.close() + }.doOnSuccess { settings -> + if (settings?.allowPublicPlayerInfo != true) { + log.info("Closing $session because webinfo is not enabled") + session.close() + return@doOnSuccess + } - operator fun get(guildId: Long): List = sessions[guildId] ?: emptyList() + log.info("Allowed $session to pass as anonymous view is allowed") + sessions.computeIfAbsent(session.guildId) { mutableListOf() }.add(session) + val info = session.player?.toPlayerInfo() ?: emptyPlayerInfo + session.sendJson(info) + } - override fun afterConnectionEstablished(session: WebSocketSession) { - log.info("Established user connection for guild ${session.info.guildId}") - sessions.computeIfAbsent(session.info.guildId) { mutableListOf() }.add(session) - val info = session.info.player?.toPlayerInfo() ?: emptyPlayerInfo - session.send(gson, info) + return session.initSendStream() + .doOnSubscribe { interceptMono.subscribe() } + .and(session.receive().doOnNext { handleMessage(session, it) }) + .doFinally { + afterConnectionClosed(session) + } } - override fun afterConnectionClosed(session: WebSocketSession, status: CloseStatus) { - val id = session.info.guildId - log.info("Disconnected user for guild $id with status $status") + fun handleMessage(session: UserSession, msg: WebSocketMessage) { + log.info("User session: {}", msg.payloadAsText) + } + + operator fun get(guildId: Long): List = sessions[guildId] ?: emptyList() + + fun afterConnectionClosed(session: UserSession) { + val id = session.guildId + log.info("Disconnected user for guild $id") val list = sessions[id] ?: return if (list.size == 1) sessions.remove(id) else list.remove(session) @@ -44,9 +74,9 @@ class UserSessionHandler(val gson: Gson) : TextWebSocketHandler() { val sessions = this[guildId] if (sessions.isEmpty()) return - val msg = TextMessage(gson.toJson(producer())) + val msg = sessions.first().textMessage(gson.toJson(producer())) sessions.forEach { - if (it.isOpen) it.sendMessage(msg) + if (it.isOpen) it.send(msg) } } diff --git a/build.gradle b/build.gradle index 609b74a98..65e954a11 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ subprojects { //plugin versions gradleGitVersion = '1.4.21' shadowVersion = '2.0.3' - springBootVersion = '2.0.1.RELEASE' + springBootVersion = '2.0.5.RELEASE' propDepsVersion = '0.0.9.RELEASE' kotlinVersion = '1.3.10' From 729b727bad646f1dbdf506f1ea886ae3867d4178 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Sat, 29 Dec 2018 12:30:53 +0100 Subject: [PATCH 048/172] Attempt to fix recursion bug --- .../src/main/java/fredboat/audio/player/PlayerRegistry.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index a6e7c3e8d..a1e4d78fb 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -154,8 +154,8 @@ class PlayerRegistry( // GuildPlayer's constructor will indirectly call #createPlayer(). // We can defer the construction to a different thread, to prevent an IllegalStateException, which // would be caused by accessing monoCache recursively - Mono.defer { - GuildPlayer( + Mono.create { + it.success(GuildPlayer( sentinelLavalink, guild, musicTextChannelProvider, @@ -163,7 +163,7 @@ class PlayerRegistry( guildConfigService, ratelimiter, youtubeAPI - ).toMono() + )) }.zipWith(playerRepo.findById(guild.id) .map { // player repo may complete as empty. From f401bc00d1a2155a6719bbcb47f048b72879ac07 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Sat, 29 Dec 2018 12:36:59 +0100 Subject: [PATCH 049/172] Remove Spring web starter again --- FredBoat/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/FredBoat/build.gradle b/FredBoat/build.gradle index 49a01f32f..fbc067f1c 100644 --- a/FredBoat/build.gradle +++ b/FredBoat/build.gradle @@ -49,7 +49,6 @@ dependencies { } compile group: 'org.springframework.boot', name: 'spring-boot-starter-webflux', version: springBootVersion - compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: springBootVersion // Needed for WS compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-mongodb-reactive', version: springBootVersion compile group: 'org.springframework', name: 'spring-websocket', version: springWebSocketVersion compile group: 'it.unimi.dsi', name: 'fastutil', version: fastUtilVersion From 0491bc132a8c08a082660c82752cae99a41eee73 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Sat, 29 Dec 2018 12:44:44 +0100 Subject: [PATCH 050/172] Use scheduler to get around recursion bug --- .../fredboat/audio/player/PlayerRegistry.kt | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index a1e4d78fb..b95f804a0 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -47,6 +47,7 @@ import org.springframework.beans.factory.annotation.Qualifier import org.springframework.stereotype.Component import reactor.core.publisher.Mono import reactor.core.publisher.toMono +import reactor.core.scheduler.Schedulers import java.io.IOException import java.time.Duration import java.util.* @@ -74,6 +75,7 @@ class PlayerRegistry( private val registry = ConcurrentHashMap() private val iteratorLock = Any() //iterators, which are also used by stream(), need to be synced, despite it being a concurrent map private val monoCache = ConcurrentHashMap>() + private val playerCreationScheduler = Schedulers.newElastic("player-creation") /** * @return a copied list of the the playing players of the registry. This may be an expensive operation depending on @@ -164,19 +166,21 @@ class PlayerRegistry( ratelimiter, youtubeAPI )) - }.zipWith(playerRepo.findById(guild.id) - .map { - // player repo may complete as empty. - // The zip operator will cancel the other mono, if we return empty-handed. - // We can deal with this by using Optional - Optional.of(it) - } - .switchIfEmpty(Optional.empty().toMono()) - ).map { pair -> - if (pair.t2.isEmpty) return@map pair.t1 - loadMongoData(pair.t1, pair.t2.get()) - pair.t1 } + .subscribeOn(playerCreationScheduler) + .zipWith(playerRepo.findById(guild.id) + .map { + // player repo may complete as empty. + // The zip operator will cancel the other mono, if we return empty-handed. + // We can deal with this by using Optional + Optional.of(it) + } + .switchIfEmpty(Optional.empty().toMono()) + ).map { pair -> + if (pair.t2.isEmpty) return@map pair.t1 + loadMongoData(pair.t1, pair.t2.get()) + pair.t1 + } }.doOnSuccess { registry[it.guildId] = it it.player.link.releaseHeldEvents() From 1af139435ba52d44b3d35f3220dca04b18480914 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Sat, 29 Dec 2018 12:48:43 +0100 Subject: [PATCH 051/172] Remove servlet annotation --- FredBoat/src/main/java/fredboat/config/WebSocketConfig.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/config/WebSocketConfig.kt b/FredBoat/src/main/java/fredboat/config/WebSocketConfig.kt index f5861939c..c0dae8870 100644 --- a/FredBoat/src/main/java/fredboat/config/WebSocketConfig.kt +++ b/FredBoat/src/main/java/fredboat/config/WebSocketConfig.kt @@ -8,13 +8,11 @@ import org.springframework.web.reactive.HandlerMapping import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping import org.springframework.web.reactive.socket.WebSocketHandler import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter -import org.springframework.web.socket.config.annotation.EnableWebSocket import java.util.* @Configuration -@EnableWebSocket class WebSocketConfig( private val userSessionHandler: UserSessionHandler ) { From b97ebc1c1a6039c9b71248b68a91a4b3470df90e Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Sat, 29 Dec 2018 13:21:49 +0100 Subject: [PATCH 052/172] Add a safeguard against recursion --- .../fredboat/audio/player/PlayerRegistry.kt | 65 ++++++++++++------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index b95f804a0..24ecfcd55 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -47,7 +47,6 @@ import org.springframework.beans.factory.annotation.Qualifier import org.springframework.stereotype.Component import reactor.core.publisher.Mono import reactor.core.publisher.toMono -import reactor.core.scheduler.Schedulers import java.io.IOException import java.time.Duration import java.util.* @@ -75,7 +74,7 @@ class PlayerRegistry( private val registry = ConcurrentHashMap() private val iteratorLock = Any() //iterators, which are also used by stream(), need to be synced, despite it being a concurrent map private val monoCache = ConcurrentHashMap>() - private val playerCreationScheduler = Schedulers.newElastic("player-creation") + private val recursionSafeguard = ThreadLocal>() /** * @return a copied list of the the playing players of the registry. This may be an expensive operation depending on @@ -152,12 +151,43 @@ class PlayerRegistry( * @return a [Mono] with a fully loaded [GuildPlayer] */ @Suppress("RedundantLambdaArrow") - private fun createPlayer(guild: Guild): Mono = monoCache.computeIfAbsent(guild.id) { _ -> + private fun createPlayer(guild: Guild): Mono { + // Checks for recursion, and returns the mono getting dealt with + if (recursionSafeguard.get() != null) return recursionSafeguard.get() + + return monoCache.computeIfAbsent(guild.id) { _ -> + createPlayerMono(guild) + }.doOnSuccess { + registry[it.guildId] = it + it.player.link.releaseHeldEvents() + }.doFinally { + monoCache.remove(guild.id) + } + } + + private fun createPlayerMono(guild: Guild): Mono { + lateinit var player: GuildPlayer + + val mono = playerRepo.findById(guild.id) + .map { + // player repo may complete as empty. + // The zip operator will cancel the other mono, if we return empty-handed. + // We can deal with this by using Optional + Optional.of(it) + } + .switchIfEmpty(Optional.empty().toMono()) + .map { mongo -> + if (mongo.isEmpty) return@map player + loadMongoData(player, mongo.get()) + player + } + // GuildPlayer's constructor will indirectly call #createPlayer(). // We can defer the construction to a different thread, to prevent an IllegalStateException, which // would be caused by accessing monoCache recursively - Mono.create { - it.success(GuildPlayer( + try { + recursionSafeguard.set(mono) + player = GuildPlayer( sentinelLavalink, guild, musicTextChannelProvider, @@ -165,27 +195,12 @@ class PlayerRegistry( guildConfigService, ratelimiter, youtubeAPI - )) + ) + } finally { + recursionSafeguard.remove() } - .subscribeOn(playerCreationScheduler) - .zipWith(playerRepo.findById(guild.id) - .map { - // player repo may complete as empty. - // The zip operator will cancel the other mono, if we return empty-handed. - // We can deal with this by using Optional - Optional.of(it) - } - .switchIfEmpty(Optional.empty().toMono()) - ).map { pair -> - if (pair.t2.isEmpty) return@map pair.t1 - loadMongoData(pair.t1, pair.t2.get()) - pair.t1 - } - }.doOnSuccess { - registry[it.guildId] = it - it.player.link.releaseHeldEvents() - }.doFinally { - monoCache.remove(guild.id) + + return mono } /** From 10e9250a60d66c3e8f8ae5b38aabd6017aea04b7 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Sat, 29 Dec 2018 13:30:09 +0100 Subject: [PATCH 053/172] Add an error check to recursion safeguard --- .../src/main/java/fredboat/audio/player/PlayerRegistry.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index 24ecfcd55..8876778b4 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -153,7 +153,11 @@ class PlayerRegistry( @Suppress("RedundantLambdaArrow") private fun createPlayer(guild: Guild): Mono { // Checks for recursion, and returns the mono getting dealt with - if (recursionSafeguard.get() != null) return recursionSafeguard.get() + if (recursionSafeguard.get() != null) return recursionSafeguard.get().doOnSuccess { + if (guild != it.guild) { + throw IllegalStateException("Recursion safeguard held mono for wrong guild. That shouldn't happen!") + } + } return monoCache.computeIfAbsent(guild.id) { _ -> createPlayerMono(guild) From b629cddb9166d0c1c325c9bcf7c6a7729846852b Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Sat, 29 Dec 2018 14:10:08 +0100 Subject: [PATCH 054/172] Fix typo --- .../main/java/fredboat/command/config/ConfigWebInfoCommand.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FredBoat/src/main/java/fredboat/command/config/ConfigWebInfoCommand.kt b/FredBoat/src/main/java/fredboat/command/config/ConfigWebInfoCommand.kt index 6536a1588..04b8c4e05 100644 --- a/FredBoat/src/main/java/fredboat/command/config/ConfigWebInfoCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/ConfigWebInfoCommand.kt @@ -41,7 +41,7 @@ class ConfigWebInfoCommand( } repo.save(settings).subscribe { val response = if (it.allowPublicPlayerInfo) - "Online playing status is not available at ${appConfig.webInfoBaseUrl}${context.guild.id}" + "Online playing status is now available at ${appConfig.webInfoBaseUrl}${context.guild.id}" else "Online playing status has been disabled." context.reply(response) } From 5c8338ede4c4fed63ed66ce5cd0ef9b13debea55 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Sat, 29 Dec 2018 15:47:41 +0100 Subject: [PATCH 055/172] Send thumbnail with track info --- .../java/fredboat/audio/queue/AudioTrackContext.kt | 7 +++++++ FredBoat/src/main/java/fredboat/ws/models.kt | 10 +++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt b/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt index 7130e5ddb..218b2055c 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt @@ -25,6 +25,7 @@ package fredboat.audio.queue +import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioTrack import com.sedmelluq.discord.lavaplayer.track.AudioTrack import fredboat.audio.player.GuildPlayer import fredboat.feature.I18n @@ -61,6 +62,12 @@ open class AudioTrackContext(val track: AudioTrack, val member: Member) : Compar return guildPlayer?.activeTextChannel } + val thumbnailUrl: String? get() { + return if (track is YoutubeAudioTrack) { + "https://img.youtube.com/vi/${track.info.identifier}/mqdefault.jpg" + } else null + } + init { this.rand = ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE) this.trackId = ObjectId() diff --git a/FredBoat/src/main/java/fredboat/ws/models.kt b/FredBoat/src/main/java/fredboat/ws/models.kt index f80c563a4..b6a4d0d5d 100644 --- a/FredBoat/src/main/java/fredboat/ws/models.kt +++ b/FredBoat/src/main/java/fredboat/ws/models.kt @@ -10,7 +10,7 @@ data class PlayerInfo( val paused: Boolean, val shuffled: Boolean, val repeatMode: Int, - val playingPos: Long?, + val playingPos: Int?, val queue: List ) @@ -18,7 +18,7 @@ data class TrackInfo( val id: String, val name: String, val image: String?, - val duration: Long? + val duration: Int? ) val emptyPlayerInfo = PlayerInfo(false, false, false, RepeatMode.OFF.ordinal, null, emptyList()) @@ -28,13 +28,13 @@ fun GuildPlayer.toPlayerInfo() = PlayerInfo( isPaused, isShuffle, repeatMode.ordinal, - playingTrack?.getEffectivePosition(this), + playingTrack?.getEffectivePosition(this)?.toInt(), getTracksInRange(0, 10).map { it.toTrackInfo() } ) fun AudioTrackContext.toTrackInfo() = TrackInfo( trackId.toHexString(), effectiveTitle, - null, //TODO - if (track.info.isStream) null else effectiveDuration + thumbnailUrl, + if (track.info.isStream) null else effectiveDuration.toInt() ) \ No newline at end of file From 6e05b81dc82e80ff75e0ae65c90d08ec73b9820c Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Sat, 29 Dec 2018 16:49:28 +0100 Subject: [PATCH 056/172] Fix out of bounds error --- .../main/java/fredboat/command/config/ConfigWebInfoCommand.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FredBoat/src/main/java/fredboat/command/config/ConfigWebInfoCommand.kt b/FredBoat/src/main/java/fredboat/command/config/ConfigWebInfoCommand.kt index 04b8c4e05..9e718cd94 100644 --- a/FredBoat/src/main/java/fredboat/command/config/ConfigWebInfoCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/ConfigWebInfoCommand.kt @@ -31,7 +31,7 @@ class ConfigWebInfoCommand( .defaultIfEmpty(GuildSettings(context.guild.id)) .awaitSingle() - when(context.args.last().trim().toLowerCase()) { + when(context.args.getOrNull(0)?.trim()?.toLowerCase()) { "allow" -> settings.allowPublicPlayerInfo = true "deny" -> settings.allowPublicPlayerInfo = false else -> { From 0c61160e11bd28c92ecac910b787dfc0a207b7e3 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Sat, 29 Dec 2018 17:29:43 +0100 Subject: [PATCH 057/172] Also run link post-process after GP init --- FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index f9a3f1f1c..0a7bfb47b 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -169,6 +169,7 @@ class GuildPlayer( onErrorHook = Consumer { this.handleError(it) } @Suppress("LeakingThis") player.addListener(this) + linkPostProcess() } private fun announceTrack(atc: AudioTrackContext) { From 5ba1845938553210d3d0299b8c013c2f9338dd80 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Tue, 8 Jan 2019 17:15:37 +0100 Subject: [PATCH 058/172] Fix track duplication bug --- .../src/main/java/fredboat/audio/player/PlayerRegistry.kt | 6 ++++-- build.gradle | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index 8876778b4..64b9fbca3 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -184,7 +184,7 @@ class PlayerRegistry( if (mongo.isEmpty) return@map player loadMongoData(player, mongo.get()) player - } + }.cache() // Cache avoids loading the mongo data twice // GuildPlayer's constructor will indirectly call #createPlayer(). // We can defer the construction to a different thread, to prevent an IllegalStateException, which @@ -235,10 +235,12 @@ class PlayerRegistry( } } + log.info("Restoring ${queue.size} loaded tracks for $guild") + if (queue.isNotEmpty()) { queue = queue.toMutableList() val atc = queue.removeAt(0) - player.player.explicitlySetTrack(atc.track) + player.player.playTrack(atc.track, true) player.internalContext = atc // Optionally set current track position if (mongo.position != null) atc.track.position = mongo.position diff --git a/build.gradle b/build.gradle index 65e954a11..d96c2996a 100644 --- a/build.gradle +++ b/build.gradle @@ -65,7 +65,7 @@ subprojects { //audio deps lavaplayerVersion = '1.3.9' - lavalinkVersion = 'e9dc6511' + lavalinkVersion = 'ba83b59d' //utility deps jsonOrgVersion = '20180130' From 6c94a173565569d36f679ec97807c1cea04cf28e Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 11 Jan 2019 13:16:29 +0100 Subject: [PATCH 059/172] Persist active text channel --- .../src/main/java/fredboat/audio/player/PlayerRegistry.kt | 3 +++ FredBoat/src/main/java/fredboat/db/mongo/player.kt | 4 +++- FredBoat/src/main/java/fredboat/db/mongo/repositories.kt | 1 + build.gradle | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index 64b9fbca3..e96bf8074 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -246,6 +246,9 @@ class PlayerRegistry( if (mongo.position != null) atc.track.position = mongo.position } + val channel = mongo.textChannel?.let { guild.getTextChannel(it) } + if (channel != null) musicTextChannelProvider.setMusicChannel(channel) + player.loadAll(queue) } diff --git a/FredBoat/src/main/java/fredboat/db/mongo/player.kt b/FredBoat/src/main/java/fredboat/db/mongo/player.kt index c401d9186..fb749a9c9 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/player.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/player.kt @@ -4,7 +4,7 @@ import org.bson.types.ObjectId import org.springframework.data.annotation.Id import org.springframework.data.mongodb.core.mapping.Document -@Document +@Document(collection = "MongoPlayer") class MongoPlayer( @Id val gid: Long, @@ -21,6 +21,8 @@ class MongoPlayer( val position: Long?, /** Voice channel we were playing in if interrupted */ val voiceChannel: Long?, + /** Channel to send event messages in */ + val textChannel: Long?, val queue: List ) diff --git a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt index 7e7bd11a7..a36380e58 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt @@ -42,6 +42,7 @@ private fun GuildPlayer.toMongo() = MongoPlayer( volume, playingTrack?.track?.position, voiceChannel?.id, + activeTextChannel?.id, remainingTracks.map { if (it is SplitAudioTrackContext) { MongoTrack(it.trackId, LavalinkUtil.toBinary(it.track), it.member.id, it.startPosition, it.endPosition, it.effectiveTitle) diff --git a/build.gradle b/build.gradle index d96c2996a..c94150b9e 100644 --- a/build.gradle +++ b/build.gradle @@ -65,7 +65,7 @@ subprojects { //audio deps lavaplayerVersion = '1.3.9' - lavalinkVersion = 'ba83b59d' + lavalinkVersion = 'fef3095d' //utility deps jsonOrgVersion = '20180130' From 6307617e2c74cffc4ecd49546a201dd66d861c75 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Sun, 13 Jan 2019 23:21:03 +0100 Subject: [PATCH 060/172] Update LLC --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c94150b9e..1ef79a99e 100644 --- a/build.gradle +++ b/build.gradle @@ -65,7 +65,7 @@ subprojects { //audio deps lavaplayerVersion = '1.3.9' - lavalinkVersion = 'fef3095d' + lavalinkVersion = 'b53ab694' //utility deps jsonOrgVersion = '20180130' From 9ace476c12c45817372d59e945f08672d6ddf3bb Mon Sep 17 00:00:00 2001 From: Marlon Haenen Date: Thu, 24 Jan 2019 16:15:10 +0100 Subject: [PATCH 061/172] First implementation for caching mongo results (DUMP) --- .../fredboat/command/config/ConfigCommand.kt | 25 ++++++++++++++++--- .../commandmeta/CommandInitializer.kt | 2 +- .../java/fredboat/db/mongo/repositories.kt | 15 +++++++++++ 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt b/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt index 373627e14..11fd8c9d8 100644 --- a/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt @@ -30,6 +30,9 @@ import fredboat.commandmeta.abs.Command import fredboat.commandmeta.abs.CommandContext import fredboat.commandmeta.abs.ICommandRestricted import fredboat.commandmeta.abs.IConfigCommand +import fredboat.db.mongo.GuildSettings +import fredboat.db.mongo.GuildSettingsRepository +import fredboat.db.mongo.test import fredboat.db.transfer.GuildConfig import fredboat.definitions.PermissionLevel import fredboat.main.Launcher @@ -37,8 +40,11 @@ import fredboat.messaging.internal.Context import fredboat.perms.PermsUtil import fredboat.util.extension.escapeAndDefuse import fredboat.util.localMessageBuilder +import reactor.core.publisher.BaseSubscriber +import kotlin.system.measureNanoTime +import kotlin.system.measureTimeMillis -class ConfigCommand(name: String, vararg aliases: String) : Command(name, *aliases), IConfigCommand, ICommandRestricted { +class ConfigCommand(name: String, val repo: GuildSettingsRepository, vararg aliases: String) : Command(name, *aliases), IConfigCommand, ICommandRestricted { override val minimumPerms: PermissionLevel get() = PermissionLevel.BASE @@ -77,11 +83,22 @@ class ConfigCommand(name: String, vararg aliases: String) : Command(name, *alias val key = context.args[0] val `val` = context.args[1] + System.out.println("total: " + measureTimeMillis { + System.out.println("elapsed: " + repo.test(context.guild.id).log().elapsed().block().t1) + }) + + System.out.println("old: " + measureTimeMillis { + Launcher.botController.guildConfigService.fetchGuildConfig(context.guild.id) + }) + if (key == "track_announce") { if (`val`.equals("true", ignoreCase = true) or `val`.equals("false", ignoreCase = true)) { - Launcher.botController.guildConfigService.transformGuildConfig(context.guild.id) { gc: GuildConfig -> - gc.setTrackAnnounce(java.lang.Boolean.valueOf(`val`)) - } + System.out.println("old update: " + measureTimeMillis { + Launcher.botController.guildConfigService.transformGuildConfig(context.guild.id) { gc: GuildConfig -> + gc.setTrackAnnounce(java.lang.Boolean.valueOf(`val`)) + } + }) + context.replyWithName("`track_announce` " + context.i18nFormat("configSetTo", `val`)) } else { context.reply(context.i18nFormat("configMustBeBoolean", invoker.effectiveName.escapeAndDefuse())) diff --git a/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt b/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt index deca95e9e..a1e2e5e05 100644 --- a/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt +++ b/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt @@ -138,7 +138,7 @@ class CommandInitializer( // Configurational stuff - always on val configModule = CommandRegistry(Module.CONFIG) - configModule.registerCommand(ConfigCommand(CONFIG_COMM_NAME, "cfg")) + configModule.registerCommand(ConfigCommand(CONFIG_COMM_NAME, guildSettingsRepository, "cfg")) configModule.registerCommand(LanguageCommand(LANGUAGE_COMM_NAME, "lang")) configModule.registerCommand(ModulesCommand("modules", "module", "mods")) configModule.registerCommand(PrefixCommand(cacheMetrics, PREFIX_COMM_NAME, "pre")) diff --git a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt index a36380e58..ac6b09aec 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt @@ -1,5 +1,7 @@ package fredboat.db.mongo +import com.google.common.cache.Cache +import com.google.common.cache.CacheBuilder import fredboat.audio.player.GuildPlayer import fredboat.audio.player.voiceChannel import fredboat.audio.queue.SplitAudioTrackContext @@ -10,11 +12,24 @@ import org.springframework.data.annotation.Id import org.springframework.data.repository.reactive.ReactiveCrudRepository import reactor.core.publisher.Flux import reactor.core.publisher.Mono +import java.util.concurrent.TimeUnit + interface PlayerRepository : ReactiveCrudRepository interface ActivityRepository : ReactiveCrudRepository interface GuildSettingsRepository : ReactiveCrudRepository +private val cache: Cache = CacheBuilder + .newBuilder() + .maximumSize(10000) + .expireAfterWrite(5, TimeUnit.MINUTES) + .build() + +fun GuildSettingsRepository.test(guildId: Long): Mono =Mono.justOrEmpty(cache.getIfPresent(guildId)) + .switchIfEmpty(findById(guildId)) + .defaultIfEmpty(GuildSettings(guildId)) + .doOnNext { cache.put(guildId, it) } + private val log: Logger = LoggerFactory.getLogger(PlayerRepository::class.java) fun PlayerRepository.convertAndSave(player: GuildPlayer): Mono { From 6937a3386054402283997e11228c6f58ee728378 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Tue, 5 Feb 2019 12:56:59 +0100 Subject: [PATCH 062/172] mongodb cache implementation dump --- .../java/fredboat/audio/player/GuildPlayer.kt | 9 +- .../fredboat/audio/player/PlayerRegistry.kt | 5 +- .../fredboat/command/config/ConfigCommand.kt | 83 +++++++++---------- .../command/config/ConfigWebInfoCommand.kt | 4 +- .../commandmeta/CommandInitializer.kt | 8 +- .../fredboat/db/mongo/CachedRepository.kt | 20 +++++ .../java/fredboat/db/mongo/GuildSettings.kt | 5 +- .../db/mongo/GuildSettingsDelegate.kt | 26 ++++++ .../java/fredboat/db/mongo/repositories.kt | 14 ---- .../java/fredboat/event/AudioEventHandler.kt | 15 ++-- .../src/main/java/fredboat/feature/I18n.java | 9 +- .../main/java/fredboat/main/BotController.kt | 3 +- .../java/fredboat/ws/UserSessionHandler.kt | 4 +- 13 files changed, 119 insertions(+), 86 deletions(-) create mode 100644 FredBoat/src/main/java/fredboat/db/mongo/CachedRepository.kt create mode 100644 FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsDelegate.kt diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index 0a7bfb47b..42a4f8547 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -35,7 +35,7 @@ import fredboat.audio.queue.* import fredboat.command.music.control.VoteSkipCommand import fredboat.commandmeta.MessagingException import fredboat.commandmeta.abs.CommandContext -import fredboat.db.api.GuildConfigService +import fredboat.db.mongo.GuildSettingsDelegate import fredboat.definitions.RepeatMode import fredboat.sentinel.Guild import fredboat.sentinel.InternalGuild @@ -61,7 +61,7 @@ class GuildPlayer( var guild: Guild, private val musicTextChannelProvider: MusicTextChannelProvider, audioPlayerManager: AudioPlayerManager, - private val guildConfigService: GuildConfigService, + private val guildSettingsDelegate: GuildSettingsDelegate, ratelimiter: Ratelimiter, youtubeAPI: YoutubeAPI ) : PlayerEventListenerAdapter() { @@ -116,12 +116,9 @@ class GuildPlayer( private val isTrackAnnounceEnabled: Boolean get() { var enabled = false - try { if (guild.selfPresent) { - enabled = guildConfigService.fetchGuildConfig(guild.id).isTrackAnnounce + guildSettingsDelegate.findByCacheWithDefault(guild.id).subscribe { enabled = it.trackAnnounce } } - } catch (ignored: Exception) { - } return enabled } diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index e96bf8074..3c3a79260 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -31,6 +31,7 @@ import fredboat.audio.queue.AudioTrackContext import fredboat.audio.queue.SplitAudioTrackContext import fredboat.config.property.AppConfig import fredboat.db.api.GuildConfigService +import fredboat.db.mongo.GuildSettingsDelegate import fredboat.db.mongo.MongoPlayer import fredboat.db.mongo.PlayerRepository import fredboat.db.mongo.convertAndSaveAll @@ -58,7 +59,7 @@ import kotlin.streams.toList @Component class PlayerRegistry( private val musicTextChannelProvider: MusicTextChannelProvider, - private val guildConfigService: GuildConfigService, + private val guildSettingsDelegate: GuildSettingsDelegate, private val sentinelLavalink: SentinelLavalink, @param:Qualifier("loadAudioPlayerManager") val audioPlayerManager: AudioPlayerManager, private val ratelimiter: Ratelimiter, @@ -196,7 +197,7 @@ class PlayerRegistry( guild, musicTextChannelProvider, audioPlayerManager, - guildConfigService, + guildSettingsDelegate, ratelimiter, youtubeAPI ) diff --git a/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt b/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt index 11fd8c9d8..9abf1db8b 100644 --- a/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt @@ -31,20 +31,15 @@ import fredboat.commandmeta.abs.CommandContext import fredboat.commandmeta.abs.ICommandRestricted import fredboat.commandmeta.abs.IConfigCommand import fredboat.db.mongo.GuildSettings -import fredboat.db.mongo.GuildSettingsRepository -import fredboat.db.mongo.test -import fredboat.db.transfer.GuildConfig +import fredboat.db.mongo.GuildSettingsDelegate import fredboat.definitions.PermissionLevel -import fredboat.main.Launcher import fredboat.messaging.internal.Context import fredboat.perms.PermsUtil import fredboat.util.extension.escapeAndDefuse import fredboat.util.localMessageBuilder -import reactor.core.publisher.BaseSubscriber -import kotlin.system.measureNanoTime -import kotlin.system.measureTimeMillis +import kotlinx.coroutines.reactive.awaitSingle -class ConfigCommand(name: String, val repo: GuildSettingsRepository, vararg aliases: String) : Command(name, *aliases), IConfigCommand, ICommandRestricted { +class ConfigCommand(name: String, private val repo: GuildSettingsDelegate, vararg aliases: String) : Command(name, *aliases), IConfigCommand, ICommandRestricted { override val minimumPerms: PermissionLevel get() = PermissionLevel.BASE @@ -58,15 +53,16 @@ class ConfigCommand(name: String, val repo: GuildSettingsRepository, vararg alia } private fun printConfig(context: CommandContext) { - val gc = Launcher.botController.guildConfigService.fetchGuildConfig(context.guild.id) - - val mb = localMessageBuilder() - .append(context.i18nFormat("configNoArgs", context.guild.name)).append("\n") - .append("track_announce = ${gc.isTrackAnnounce}\n") - .append("auto_resume = ${gc.isAutoResume}\n") - .append("```") //opening ``` is part of the configNoArgs language string - - context.reply(mb.build()) + repo.findByCacheWithDefault(context.guild.id) + .subscribe { + val mb = localMessageBuilder() + .append(context.i18nFormat("configNoArgs", context.guild.name)).append("\n") + .append("track_announce = ${it.trackAnnounce}\n") + .append("auto_resume = ${it.autoResume}\n") + .append("```") //opening ``` is part of the configNoArgs language string + + context.reply(mb.build()) + } } private suspend fun setConfig(context: CommandContext) { @@ -81,37 +77,32 @@ class ConfigCommand(name: String, val repo: GuildSettingsRepository, vararg alia } val key = context.args[0] - val `val` = context.args[1] - - System.out.println("total: " + measureTimeMillis { - System.out.println("elapsed: " + repo.test(context.guild.id).log().elapsed().block().t1) - }) - - System.out.println("old: " + measureTimeMillis { - Launcher.botController.guildConfigService.fetchGuildConfig(context.guild.id) - }) - - if (key == "track_announce") { - if (`val`.equals("true", ignoreCase = true) or `val`.equals("false", ignoreCase = true)) { - System.out.println("old update: " + measureTimeMillis { - Launcher.botController.guildConfigService.transformGuildConfig(context.guild.id) { gc: GuildConfig -> - gc.setTrackAnnounce(java.lang.Boolean.valueOf(`val`)) - } - }) - - context.replyWithName("`track_announce` " + context.i18nFormat("configSetTo", `val`)) - } else { - context.reply(context.i18nFormat("configMustBeBoolean", invoker.effectiveName.escapeAndDefuse())) + val value = context.args[1] + + if (!(value.equals("true", ignoreCase = true) or value.equals("false", ignoreCase = true))) { + context.reply(context.i18nFormat("configMustBeBoolean", invoker.effectiveName.escapeAndDefuse())) + return + } + + val guildSettings = repo.findByCacheWithDefault(context.guild.id).awaitSingle() + + when (key) { + "track_announce" -> { + guildSettings.trackAnnounce = value.toBoolean() + + repo.saveWithCache(guildSettings).subscribe { + context.replyWithName("`track_announce` " + context.i18nFormat("configSetTo", value)) + } } - } else if (key == "auto_resume") { - if (`val`.equals("true", ignoreCase = true) or `val`.equals("false", ignoreCase = true)) { - Launcher.botController.guildConfigService.transformGuildConfig( - context.guild.id) { gc -> gc.setAutoResume(java.lang.Boolean.valueOf(`val`)) } - context.replyWithName("`auto_resume` " + context.i18nFormat("configSetTo", `val`)) - } else { - context.reply(context.i18nFormat("configMustBeBoolean", invoker.effectiveName.escapeAndDefuse())) + "auto_resume" -> { + guildSettings.autoResume = value.toBoolean() + + repo.saveWithCache(guildSettings).subscribe { + context.replyWithName("`auto_resume` " + context.i18nFormat("configSetTo", value)) + } } - } else context.reply(context.i18nFormat("configUnknownKey", invoker.effectiveName.escapeAndDefuse())) + else -> context.reply(context.i18nFormat("configUnknownKey", invoker.effectiveName.escapeAndDefuse())) + } } override fun help(context: Context): String { diff --git a/FredBoat/src/main/java/fredboat/command/config/ConfigWebInfoCommand.kt b/FredBoat/src/main/java/fredboat/command/config/ConfigWebInfoCommand.kt index 9e718cd94..149223823 100644 --- a/FredBoat/src/main/java/fredboat/command/config/ConfigWebInfoCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/ConfigWebInfoCommand.kt @@ -7,7 +7,7 @@ import fredboat.commandmeta.abs.ICommandRestricted import fredboat.commandmeta.abs.IConfigCommand import fredboat.config.property.AppConfig import fredboat.db.mongo.GuildSettings -import fredboat.db.mongo.GuildSettingsRepository +import fredboat.db.mongo.GuildSettingsDelegate import fredboat.definitions.PermissionLevel import fredboat.messaging.internal.Context import kotlinx.coroutines.reactive.awaitSingle @@ -15,7 +15,7 @@ import kotlinx.coroutines.reactive.awaitSingle class ConfigWebInfoCommand( name: String, vararg aliases: String, - private val repo: GuildSettingsRepository, + private val repo: GuildSettingsDelegate, private val appConfig: AppConfig ) : Command(name, *aliases), IConfigCommand, ICommandRestricted { diff --git a/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt b/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt index a1e2e5e05..ec342e7d5 100644 --- a/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt +++ b/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt @@ -45,7 +45,7 @@ import fredboat.command.music.seeking.SeekCommand import fredboat.command.util.* import fredboat.config.SentryConfiguration import fredboat.config.property.AppConfig -import fredboat.db.mongo.GuildSettingsRepository +import fredboat.db.mongo.GuildSettingsDelegate import fredboat.definitions.Module import fredboat.definitions.PermissionLevel import fredboat.definitions.SearchProvider @@ -72,7 +72,7 @@ class CommandInitializer( youtubeAPI: YoutubeAPI, sentinel: Sentinel, playerRegistry: PlayerRegistry, - guildSettingsRepository: GuildSettingsRepository, + guildSettingsDelegate: GuildSettingsDelegate, appConfig: AppConfig, springContext: Supplier ) { @@ -138,11 +138,11 @@ class CommandInitializer( // Configurational stuff - always on val configModule = CommandRegistry(Module.CONFIG) - configModule.registerCommand(ConfigCommand(CONFIG_COMM_NAME, guildSettingsRepository, "cfg")) + configModule.registerCommand(ConfigCommand(CONFIG_COMM_NAME, guildSettingsDelegate, "cfg")) configModule.registerCommand(LanguageCommand(LANGUAGE_COMM_NAME, "lang")) configModule.registerCommand(ModulesCommand("modules", "module", "mods")) configModule.registerCommand(PrefixCommand(cacheMetrics, PREFIX_COMM_NAME, "pre")) - configModule.registerCommand(ConfigWebInfoCommand("webinfo", repo = guildSettingsRepository, appConfig = appConfig)) + configModule.registerCommand(ConfigWebInfoCommand("webinfo", repo = guildSettingsDelegate, appConfig = appConfig)) /* Perms */ configModule.registerCommand(PermissionsCommand(PermissionLevel.ADMIN, "admin", "admins")) configModule.registerCommand(PermissionsCommand(PermissionLevel.DJ, "dj", "djs")) diff --git a/FredBoat/src/main/java/fredboat/db/mongo/CachedRepository.kt b/FredBoat/src/main/java/fredboat/db/mongo/CachedRepository.kt new file mode 100644 index 000000000..affecc457 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/mongo/CachedRepository.kt @@ -0,0 +1,20 @@ +package fredboat.db.mongo + +import com.google.common.cache.Cache +import com.google.common.cache.CacheBuilder +import com.google.common.cache.CacheLoader +import com.google.common.cache.LoadingCache +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import reactor.core.publisher.Mono + +abstract class CachedRepository : ReactiveCrudRepository { + + protected val cache: Cache = CacheBuilder.newBuilder() + .build() + + abstract fun findByCache(id: ID): Mono + + abstract fun findByCacheWithDefault(id: ID): Mono + + abstract fun saveWithCache(entity: T): Mono +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/GuildSettings.kt b/FredBoat/src/main/java/fredboat/db/mongo/GuildSettings.kt index 2618d816a..ffe4be063 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/GuildSettings.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/GuildSettings.kt @@ -7,5 +7,8 @@ import org.springframework.data.mongodb.core.mapping.Document data class GuildSettings( @Id val guildId: Long, - var allowPublicPlayerInfo: Boolean = false + var trackAnnounce: Boolean = false, + var autoResume: Boolean = false, + var allowPublicPlayerInfo: Boolean = false, + var lang: String = "en_US" ) \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsDelegate.kt b/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsDelegate.kt new file mode 100644 index 000000000..0c2393b89 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsDelegate.kt @@ -0,0 +1,26 @@ +package fredboat.db.mongo + +import reactor.core.publisher.Mono + +//TODO: what do i name this +class GuildSettingsDelegate(repo: GuildSettingsRepository) : GuildSettingsRepository by repo, CachedRepository() { + + override fun findByCache(id: Long): Mono { + return Mono.justOrEmpty(cache.getIfPresent(id)) + .switchIfEmpty(findById(id)) + .doOnNext { cache.put(id, it) } + } + + override fun findByCacheWithDefault(id: Long): Mono { + return Mono.justOrEmpty(cache.getIfPresent(id)) + .switchIfEmpty(findById(id)) + .defaultIfEmpty(GuildSettings(id)) + .doOnNext { cache.put(id, it) } + } + + override fun saveWithCache(entity: GuildSettings): Mono { + cache.put(entity.guildId, entity) + + return save(entity) + } +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt index ac6b09aec..b942ddbee 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt @@ -1,7 +1,5 @@ package fredboat.db.mongo -import com.google.common.cache.Cache -import com.google.common.cache.CacheBuilder import fredboat.audio.player.GuildPlayer import fredboat.audio.player.voiceChannel import fredboat.audio.queue.SplitAudioTrackContext @@ -12,24 +10,12 @@ import org.springframework.data.annotation.Id import org.springframework.data.repository.reactive.ReactiveCrudRepository import reactor.core.publisher.Flux import reactor.core.publisher.Mono -import java.util.concurrent.TimeUnit interface PlayerRepository : ReactiveCrudRepository interface ActivityRepository : ReactiveCrudRepository interface GuildSettingsRepository : ReactiveCrudRepository -private val cache: Cache = CacheBuilder - .newBuilder() - .maximumSize(10000) - .expireAfterWrite(5, TimeUnit.MINUTES) - .build() - -fun GuildSettingsRepository.test(guildId: Long): Mono =Mono.justOrEmpty(cache.getIfPresent(guildId)) - .switchIfEmpty(findById(guildId)) - .defaultIfEmpty(GuildSettings(guildId)) - .doOnNext { cache.put(guildId, it) } - private val log: Logger = LoggerFactory.getLogger(PlayerRepository::class.java) fun PlayerRepository.convertAndSave(player: GuildPlayer): Mono { diff --git a/FredBoat/src/main/java/fredboat/event/AudioEventHandler.kt b/FredBoat/src/main/java/fredboat/event/AudioEventHandler.kt index af0b2908c..18b35d94c 100644 --- a/FredBoat/src/main/java/fredboat/event/AudioEventHandler.kt +++ b/FredBoat/src/main/java/fredboat/event/AudioEventHandler.kt @@ -7,7 +7,7 @@ import fredboat.audio.player.getHumanUsersInVC import fredboat.audio.player.humanUsersInCurrentVC import fredboat.audio.player.voiceChannel import fredboat.config.property.AppConfig -import fredboat.db.api.GuildConfigService +import fredboat.db.mongo.GuildSettingsDelegate import fredboat.feature.I18n import fredboat.sentinel.Member import fredboat.sentinel.VoiceChannel @@ -18,7 +18,7 @@ class AudioEventHandler( private val appConfig: AppConfig, private val playerRegistry: PlayerRegistry, private val lavalink: SentinelLavalink, - private val guildConfigService: GuildConfigService + private val guildSettingsDelegate: GuildSettingsDelegate ) : SentinelEventHandler() { override fun onVoiceJoin(channel: VoiceChannel, member: Member) { @@ -78,10 +78,13 @@ class AudioEventHandler( if (player.isPaused && player.playingTrack != null && joinedChannel.members.contains(guild.selfMember) - && player.humanUsersInCurrentVC.isNotEmpty() - && guildConfigService.fetchGuildConfig(guild.id).isAutoResume) { - player.setPause(false) - player.activeTextChannel?.send(I18n.get(guild).getString("eventAutoResumed"))?.subscribe() + && player.humanUsersInCurrentVC.isNotEmpty()) { + guildSettingsDelegate.findByCacheWithDefault(guild.id).subscribe { + if (it.autoResume) { + player.setPause(false) + player.activeTextChannel?.send(I18n.get(guild).getString("eventAutoResumed"))?.subscribe() + } + } } } diff --git a/FredBoat/src/main/java/fredboat/feature/I18n.java b/FredBoat/src/main/java/fredboat/feature/I18n.java index ebca5c0d3..fb1684918 100644 --- a/FredBoat/src/main/java/fredboat/feature/I18n.java +++ b/FredBoat/src/main/java/fredboat/feature/I18n.java @@ -26,6 +26,7 @@ package fredboat.feature; import fredboat.db.DatabaseNotReadyException; +import fredboat.db.mongo.GuildSettings; import fredboat.definitions.Language; import fredboat.sentinel.Guild; import org.slf4j.Logger; @@ -33,6 +34,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.time.Duration; import java.util.HashMap; import java.util.MissingResourceException; import java.util.ResourceBundle; @@ -72,7 +74,7 @@ public static FredBoatLocale getLocale(@Nonnull Guild guild) { @Nonnull public static FredBoatLocale getLocale(long guild) { try { - return LANGS.getOrDefault(getBotController().getGuildConfigService().fetchGuildConfig(guild).getLang(), DEFAULT); + return LANGS.getOrDefault(getBotController().getGuildSettingsDelegate().findByCache(guild).block(Duration.ofSeconds(10)).getLang(), DEFAULT); } catch (DatabaseNotReadyException e) { //don't log spam the full exceptions or logs return DEFAULT; @@ -86,7 +88,10 @@ public static void set(@Nonnull Guild guild, @Nonnull String lang) throws Langua if (!LANGS.containsKey(lang)) throw new LanguageNotSupportedException("Language not found"); - getBotController().getGuildConfigService().transformGuildConfig(guild.getId(), config -> config.setLang(lang)); + getBotController().getGuildSettingsDelegate().findByCacheWithDefault(guild.getId()).doOnNext(guildSettings -> { + guildSettings.setLang(lang); + getBotController().getGuildSettingsDelegate().saveWithCache(guildSettings).block(); + }).block(); } public static class FredBoatLocale { diff --git a/FredBoat/src/main/java/fredboat/main/BotController.kt b/FredBoat/src/main/java/fredboat/main/BotController.kt index 96d348aa6..8a753ebae 100644 --- a/FredBoat/src/main/java/fredboat/main/BotController.kt +++ b/FredBoat/src/main/java/fredboat/main/BotController.kt @@ -8,6 +8,7 @@ import fredboat.db.api.GuildConfigService import fredboat.db.api.GuildModulesService import fredboat.db.api.GuildPermsService import fredboat.db.api.PrefixService +import fredboat.db.mongo.GuildSettingsDelegate import fredboat.feature.metrics.BotMetrics import fredboat.feature.metrics.Metrics import fredboat.metrics.OkHttpEventMetrics @@ -30,7 +31,7 @@ class BotController(private val configProvider: ConfigPropertiesProvider, val botMetrics: BotMetrics, @param:Qualifier("loadAudioPlayerManager") val audioPlayerManager: AudioPlayerManager, val ratelimiter: Ratelimiter, - val guildConfigService: GuildConfigService, + val guildSettingsDelegate: GuildSettingsDelegate, val guildModulesService: GuildModulesService, val guildPermsService: GuildPermsService, val prefixService: PrefixService, diff --git a/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt b/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt index 48589912f..0b3905ca3 100644 --- a/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt +++ b/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt @@ -2,7 +2,7 @@ package fredboat.ws import com.google.gson.Gson import fredboat.db.mongo.GuildSettings -import fredboat.db.mongo.GuildSettingsRepository +import fredboat.db.mongo.GuildSettingsDelegate import fredboat.sentinel.GuildCache import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap class UserSessionHandler( val gson: Gson, val guildCache: GuildCache, - val repository: GuildSettingsRepository + val repository: GuildSettingsDelegate ) : WebSocketHandler { companion object { From 53a6f04508252bf87d873e59eea677fc434cf767 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Fri, 8 Feb 2019 11:22:45 +0100 Subject: [PATCH 063/172] Implement GuildSettingsRepository --- .../java/fredboat/audio/player/GuildPlayer.kt | 6 +- .../fredboat/audio/player/PlayerRegistry.kt | 7 +- .../fredboat/command/config/ConfigCommand.kt | 28 ++-- .../command/config/ConfigWebInfoCommand.kt | 10 +- .../command/config/PermissionsCommand.kt | 77 ++++++----- .../commandmeta/CommandInitializer.kt | 16 ++- .../config/RepositoriesConfiguration.kt | 24 ++++ .../fredboat/db/api/GuildConfigService.kt | 38 ------ .../fredboat/db/api/GuildPermsService.java | 41 ------ .../db/api/GuildSettingsRepository.kt | 15 +++ .../fredboat/db/mongo/CachedRepository.kt | 20 --- .../db/mongo/GuildSettingsDelegate.kt | 26 ---- .../db/mongo/GuildSettingsRepositoryImpl.kt | 35 +++++ .../java/fredboat/db/mongo/repositories.kt | 6 +- .../db/rest/RestGuildConfigService.kt | 60 --------- .../fredboat/db/rest/RestGuildPermsService.kt | 55 -------- .../fredboat/db/transfer/GuildConfig.java | 76 ----------- .../db/transfer/GuildPermissions.java | 124 ------------------ .../db/{mongo => transfer}/GuildSettings.kt | 2 +- .../java/fredboat/event/AudioEventHandler.kt | 6 +- .../src/main/java/fredboat/feature/I18n.java | 13 +- .../main/java/fredboat/main/BotController.kt | 8 +- .../java/fredboat/ws/UserSessionHandler.kt | 8 +- 23 files changed, 165 insertions(+), 536 deletions(-) create mode 100644 FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt delete mode 100644 FredBoat/src/main/java/fredboat/db/api/GuildConfigService.kt delete mode 100644 FredBoat/src/main/java/fredboat/db/api/GuildPermsService.java create mode 100644 FredBoat/src/main/java/fredboat/db/api/GuildSettingsRepository.kt delete mode 100644 FredBoat/src/main/java/fredboat/db/mongo/CachedRepository.kt delete mode 100644 FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsDelegate.kt create mode 100644 FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsRepositoryImpl.kt delete mode 100644 FredBoat/src/main/java/fredboat/db/rest/RestGuildConfigService.kt delete mode 100644 FredBoat/src/main/java/fredboat/db/rest/RestGuildPermsService.kt delete mode 100644 FredBoat/src/main/java/fredboat/db/transfer/GuildConfig.java delete mode 100644 FredBoat/src/main/java/fredboat/db/transfer/GuildPermissions.java rename FredBoat/src/main/java/fredboat/db/{mongo => transfer}/GuildSettings.kt (93%) diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index 42a4f8547..62c65a429 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -35,7 +35,7 @@ import fredboat.audio.queue.* import fredboat.command.music.control.VoteSkipCommand import fredboat.commandmeta.MessagingException import fredboat.commandmeta.abs.CommandContext -import fredboat.db.mongo.GuildSettingsDelegate +import fredboat.db.api.GuildSettingsRepository import fredboat.definitions.RepeatMode import fredboat.sentinel.Guild import fredboat.sentinel.InternalGuild @@ -61,7 +61,7 @@ class GuildPlayer( var guild: Guild, private val musicTextChannelProvider: MusicTextChannelProvider, audioPlayerManager: AudioPlayerManager, - private val guildSettingsDelegate: GuildSettingsDelegate, + private val guildSettingsRepository: GuildSettingsRepository, ratelimiter: Ratelimiter, youtubeAPI: YoutubeAPI ) : PlayerEventListenerAdapter() { @@ -117,7 +117,7 @@ class GuildPlayer( get() { var enabled = false if (guild.selfPresent) { - guildSettingsDelegate.findByCacheWithDefault(guild.id).subscribe { enabled = it.trackAnnounce } + guildSettingsRepository.fetch(guild.id).subscribe { enabled = it.trackAnnounce } } return enabled diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index 3c3a79260..304618942 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -30,8 +30,7 @@ import fredboat.audio.lavalink.SentinelLavalink import fredboat.audio.queue.AudioTrackContext import fredboat.audio.queue.SplitAudioTrackContext import fredboat.config.property.AppConfig -import fredboat.db.api.GuildConfigService -import fredboat.db.mongo.GuildSettingsDelegate +import fredboat.db.api.GuildSettingsRepository import fredboat.db.mongo.MongoPlayer import fredboat.db.mongo.PlayerRepository import fredboat.db.mongo.convertAndSaveAll @@ -59,7 +58,7 @@ import kotlin.streams.toList @Component class PlayerRegistry( private val musicTextChannelProvider: MusicTextChannelProvider, - private val guildSettingsDelegate: GuildSettingsDelegate, + private val guildSettingsRepository: GuildSettingsRepository, private val sentinelLavalink: SentinelLavalink, @param:Qualifier("loadAudioPlayerManager") val audioPlayerManager: AudioPlayerManager, private val ratelimiter: Ratelimiter, @@ -197,7 +196,7 @@ class PlayerRegistry( guild, musicTextChannelProvider, audioPlayerManager, - guildSettingsDelegate, + guildSettingsRepository, ratelimiter, youtubeAPI ) diff --git a/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt b/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt index 9abf1db8b..d9f27a04b 100644 --- a/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt @@ -30,16 +30,14 @@ import fredboat.commandmeta.abs.Command import fredboat.commandmeta.abs.CommandContext import fredboat.commandmeta.abs.ICommandRestricted import fredboat.commandmeta.abs.IConfigCommand -import fredboat.db.mongo.GuildSettings -import fredboat.db.mongo.GuildSettingsDelegate +import fredboat.db.api.GuildSettingsRepository import fredboat.definitions.PermissionLevel import fredboat.messaging.internal.Context import fredboat.perms.PermsUtil import fredboat.util.extension.escapeAndDefuse import fredboat.util.localMessageBuilder -import kotlinx.coroutines.reactive.awaitSingle -class ConfigCommand(name: String, private val repo: GuildSettingsDelegate, vararg aliases: String) : Command(name, *aliases), IConfigCommand, ICommandRestricted { +class ConfigCommand(name: String, private val repo: GuildSettingsRepository, vararg aliases: String) : Command(name, *aliases), IConfigCommand, ICommandRestricted { override val minimumPerms: PermissionLevel get() = PermissionLevel.BASE @@ -53,7 +51,7 @@ class ConfigCommand(name: String, private val repo: GuildSettingsDelegate, varar } private fun printConfig(context: CommandContext) { - repo.findByCacheWithDefault(context.guild.id) + repo.fetch(context.guild.id) .subscribe { val mb = localMessageBuilder() .append(context.i18nFormat("configNoArgs", context.guild.name)).append("\n") @@ -84,22 +82,18 @@ class ConfigCommand(name: String, private val repo: GuildSettingsDelegate, varar return } - val guildSettings = repo.findByCacheWithDefault(context.guild.id).awaitSingle() - when (key) { "track_announce" -> { - guildSettings.trackAnnounce = value.toBoolean() - - repo.saveWithCache(guildSettings).subscribe { - context.replyWithName("`track_announce` " + context.i18nFormat("configSetTo", value)) - } + repo.fetch(context.guild.id) + .doOnSuccess { it.trackAnnounce = value.toBoolean() } + .let { repo.update(it) } + .subscribe { context.replyWithName("`track_announce` " + context.i18nFormat("configSetTo", value)) } } "auto_resume" -> { - guildSettings.autoResume = value.toBoolean() - - repo.saveWithCache(guildSettings).subscribe { - context.replyWithName("`auto_resume` " + context.i18nFormat("configSetTo", value)) - } + repo.fetch(context.guild.id) + .doOnSuccess { it.autoResume = value.toBoolean() } + .let { repo.update(it) } + .subscribe { context.replyWithName("`auto_resume` " + context.i18nFormat("configSetTo", value)) } } else -> context.reply(context.i18nFormat("configUnknownKey", invoker.effectiveName.escapeAndDefuse())) } diff --git a/FredBoat/src/main/java/fredboat/command/config/ConfigWebInfoCommand.kt b/FredBoat/src/main/java/fredboat/command/config/ConfigWebInfoCommand.kt index 149223823..77ad0da77 100644 --- a/FredBoat/src/main/java/fredboat/command/config/ConfigWebInfoCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/ConfigWebInfoCommand.kt @@ -6,8 +6,8 @@ import fredboat.commandmeta.abs.CommandContext import fredboat.commandmeta.abs.ICommandRestricted import fredboat.commandmeta.abs.IConfigCommand import fredboat.config.property.AppConfig -import fredboat.db.mongo.GuildSettings -import fredboat.db.mongo.GuildSettingsDelegate +import fredboat.db.api.GuildSettingsRepository +import fredboat.db.transfer.GuildSettings import fredboat.definitions.PermissionLevel import fredboat.messaging.internal.Context import kotlinx.coroutines.reactive.awaitSingle @@ -15,7 +15,7 @@ import kotlinx.coroutines.reactive.awaitSingle class ConfigWebInfoCommand( name: String, vararg aliases: String, - private val repo: GuildSettingsDelegate, + private val repo: GuildSettingsRepository, private val appConfig: AppConfig ) : Command(name, *aliases), IConfigCommand, ICommandRestricted { @@ -27,7 +27,7 @@ class ConfigWebInfoCommand( return } - val settings = repo.findById(context.guild.id) + val settings = repo.fetch(context.guild.id) .defaultIfEmpty(GuildSettings(context.guild.id)) .awaitSingle() @@ -39,7 +39,7 @@ class ConfigWebInfoCommand( return } } - repo.save(settings).subscribe { + repo.update(settings).subscribe { val response = if (it.allowPublicPlayerInfo) "Online playing status is now available at ${appConfig.webInfoBaseUrl}${context.guild.id}" else "Online playing status has been disabled." diff --git a/FredBoat/src/main/java/fredboat/command/config/PermissionsCommand.kt b/FredBoat/src/main/java/fredboat/command/config/PermissionsCommand.kt index 90b3e0297..d33af68c8 100644 --- a/FredBoat/src/main/java/fredboat/command/config/PermissionsCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/PermissionsCommand.kt @@ -32,8 +32,8 @@ import fredboat.command.info.HelpCommand import fredboat.commandmeta.abs.Command import fredboat.commandmeta.abs.CommandContext import fredboat.commandmeta.abs.IConfigCommand +import fredboat.db.api.GuildPermissionsRepository import fredboat.definitions.PermissionLevel -import fredboat.main.Launcher import fredboat.messaging.internal.Context import fredboat.perms.Permission import fredboat.perms.PermsUtil @@ -50,6 +50,7 @@ import java.util.* class PermissionsCommand( private val permissionLevel: PermissionLevel, + private val repo: GuildPermissionsRepository, name: String, vararg aliases: String ) : Command(name, *aliases), IConfigCommand { @@ -99,29 +100,31 @@ class PermissionsCommand( val selected = ArgumentUtil.checkSingleFuzzySearchResult(search, context, term) ?: return val discordPerms = invoker.getPermissions(channel = null).awaitFirst() - Launcher.botController.guildPermsService.transformGuildPerms(context.guild) { gp -> - if (!gp.getFromEnum(permissionLevel).contains(mentionableToId(selected))) { - context.replyWithName(context.i18nFormat("permsNotAdded", "`" + mentionableToName(selected) + "`", "`$permissionLevel`")) - return@transformGuildPerms gp - } + val gp = repo.fetch(context.guild.id).awaitSingle() - val newList = gp.getFromEnum(permissionLevel).toMutableList() - newList.remove(mentionableToId(selected)) + if (!gp.fromEnum(permissionLevel).contains(mentionableToId(selected))) { + context.replyWithName(context.i18nFormat("permsNotAdded", "`" + mentionableToName(selected) + "`", "`$permissionLevel`")) + return + } - if (permissionLevel == PermissionLevel.ADMIN - && PermissionLevel.BOT_ADMIN.level > permissionLevel.level - && discordPerms hasNot Permission.ADMINISTRATOR - && !PermsUtil.checkList(newList, invoker)) { - context.replyWithName(context.i18n("permsFailSelfDemotion")) - return@transformGuildPerms gp - } + val newList = gp.fromEnum(permissionLevel).toMutableList() + newList.remove(mentionableToId(selected)) - context.replyWithName(context.i18nFormat("permsRemoved", mentionableToName(selected), permissionLevel)) - gp.setFromEnum(permissionLevel, newList) + if (permissionLevel == PermissionLevel.ADMIN + && permissionLevel < PermissionLevel.BOT_ADMIN + && discordPerms hasNot Permission.ADMINISTRATOR + && !PermsUtil.checkList(newList, invoker)) { + context.replyWithName(context.i18n("permsFailSelfDemotion")) + return } + + context.replyWithName(context.i18nFormat("permsRemoved", mentionableToName(selected), permissionLevel)) + + gp.fromEnum(permissionLevel, newList) + repo.update(gp).subscribe() } - fun add(context: CommandContext) { + suspend fun add(context: CommandContext) { val guild = context.guild //remove the first argument aka add / remove etc to get a nice search term val term = context.rawArgs.replaceFirst(context.args[0].toRegex(), "").trim { it <= ' ' } @@ -132,29 +135,27 @@ class PermissionsCommand( val selected = ArgumentUtil.checkSingleFuzzySearchResult(list, context, term) ?: return - Launcher.botController.guildPermsService.transformGuildPerms(context.guild) { gp -> - if (gp.getFromEnum(permissionLevel).contains(mentionableToId(selected))) { - context.replyWithName(context.i18nFormat("permsAlreadyAdded", - "`" + TextUtils.escapeMarkdown(mentionableToName(selected)) + "`", - "`$permissionLevel`")) - return@transformGuildPerms gp - } + val gp = repo.fetch(context.guild.id).awaitSingle() + if (gp.fromEnum(permissionLevel).contains(mentionableToId(selected))) { + context.replyWithName(context.i18nFormat("permsAlreadyAdded", "`" + TextUtils.escapeMarkdown(mentionableToName(selected)) + "`", "`$permissionLevel`")) + return + } - val newList = ArrayList(gp.getFromEnum(permissionLevel)) - newList.add(mentionableToId(selected)) + val newList = gp.fromEnum(permissionLevel).toMutableList() + newList.add(mentionableToId(selected)) - context.replyWithName(context.i18nFormat("permsAdded", - TextUtils.escapeMarkdown(mentionableToName(selected)), permissionLevel)) - gp.setFromEnum(permissionLevel, newList) - } + context.replyWithName(context.i18nFormat("permsAdded", TextUtils.escapeMarkdown(mentionableToName(selected)), permissionLevel)) + + gp.fromEnum(permissionLevel, newList) + repo.update(gp).subscribe() } suspend fun list(context: CommandContext) { val guild = context.guild val invoker = context.member - val gp = Launcher.botController.guildPermsService.fetchGuildPermissions(guild) + val gp = repo.fetch(context.guild.id).awaitSingle() - val mentionables = idsToMentionables(guild, gp.getFromEnum(permissionLevel)) + val mentionables = idsToMentionables(guild, gp.fromEnum(permissionLevel)) var roleMentions = "" var memberMentions = "" @@ -203,9 +204,9 @@ class PermissionsCommand( context.reply(embed) } - private fun mentionableToId(mentionable: IMentionable): String { + private fun mentionableToId(mentionable: IMentionable): Long { return if (mentionable is SentinelEntity) { - mentionable.id.toString() + mentionable.id } else { throw IllegalArgumentException() } @@ -219,10 +220,8 @@ class PermissionsCommand( } } - private fun idsToMentionables(guild: Guild, list: List): List = - list.flatMap { idStr -> - if (idStr == "") return@flatMap emptyList() - val id = idStr.toLong() + private fun idsToMentionables(guild: Guild, list: List): List = + list.flatMap { id -> guild.getRole(id)?.apply { return@flatMap listOf(this) } diff --git a/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt b/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt index ec342e7d5..62457cd63 100644 --- a/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt +++ b/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt @@ -45,7 +45,8 @@ import fredboat.command.music.seeking.SeekCommand import fredboat.command.util.* import fredboat.config.SentryConfiguration import fredboat.config.property.AppConfig -import fredboat.db.mongo.GuildSettingsDelegate +import fredboat.db.api.GuildPermissionsRepository +import fredboat.db.api.GuildSettingsRepository import fredboat.definitions.Module import fredboat.definitions.PermissionLevel import fredboat.definitions.SearchProvider @@ -72,7 +73,8 @@ class CommandInitializer( youtubeAPI: YoutubeAPI, sentinel: Sentinel, playerRegistry: PlayerRegistry, - guildSettingsDelegate: GuildSettingsDelegate, + guildSettingsRepository: GuildSettingsRepository, + guildPermissionsRepository: GuildPermissionsRepository, appConfig: AppConfig, springContext: Supplier ) { @@ -138,15 +140,15 @@ class CommandInitializer( // Configurational stuff - always on val configModule = CommandRegistry(Module.CONFIG) - configModule.registerCommand(ConfigCommand(CONFIG_COMM_NAME, guildSettingsDelegate, "cfg")) + configModule.registerCommand(ConfigCommand(CONFIG_COMM_NAME, guildSettingsRepository, "cfg")) configModule.registerCommand(LanguageCommand(LANGUAGE_COMM_NAME, "lang")) configModule.registerCommand(ModulesCommand("modules", "module", "mods")) configModule.registerCommand(PrefixCommand(cacheMetrics, PREFIX_COMM_NAME, "pre")) - configModule.registerCommand(ConfigWebInfoCommand("webinfo", repo = guildSettingsDelegate, appConfig = appConfig)) + configModule.registerCommand(ConfigWebInfoCommand("webinfo", repo = guildSettingsRepository, appConfig = appConfig)) /* Perms */ - configModule.registerCommand(PermissionsCommand(PermissionLevel.ADMIN, "admin", "admins")) - configModule.registerCommand(PermissionsCommand(PermissionLevel.DJ, "dj", "djs")) - configModule.registerCommand(PermissionsCommand(PermissionLevel.USER, "user", "users")) + configModule.registerCommand(PermissionsCommand(PermissionLevel.ADMIN, guildPermissionsRepository, "admin", "admins")) + configModule.registerCommand(PermissionsCommand(PermissionLevel.DJ, guildPermissionsRepository, "dj", "djs")) + configModule.registerCommand(PermissionsCommand(PermissionLevel.USER, guildPermissionsRepository, "user", "users")) // Moderation Module - Anything related to managing Discord guilds diff --git a/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt b/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt new file mode 100644 index 000000000..b5c4e1a53 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt @@ -0,0 +1,24 @@ +package fredboat.config + +import fredboat.db.api.GuildPermissionsRepository +import fredboat.db.api.GuildSettingsRepository +import fredboat.db.mongo.GuildPermissionsRepositoryImpl +import fredboat.db.mongo.GuildSettingsRepositoryImpl +import fredboat.db.mongo.InternalGuildPermissionRepository +import fredboat.db.mongo.InternalGuildSettingsRepository +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class RepositoriesConfiguration { + + @Bean + fun guildSettingsRepository(repo: InternalGuildSettingsRepository): GuildSettingsRepository { + return GuildSettingsRepositoryImpl(repo) + } + + @Bean + fun guildPermissionsRepository(repo: InternalGuildPermissionRepository): GuildPermissionsRepository { + return GuildPermissionsRepositoryImpl(repo) + } +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/api/GuildConfigService.kt b/FredBoat/src/main/java/fredboat/db/api/GuildConfigService.kt deleted file mode 100644 index d14b50b85..000000000 --- a/FredBoat/src/main/java/fredboat/db/api/GuildConfigService.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.api - -import fredboat.db.transfer.GuildConfig - -/** - * Created by napster on 07.02.18. - */ -interface GuildConfigService { - - fun fetchGuildConfig(guild: Long): GuildConfig - - fun transformGuildConfig(guild: Long, transformation: (GuildConfig) -> GuildConfig): GuildConfig -} diff --git a/FredBoat/src/main/java/fredboat/db/api/GuildPermsService.java b/FredBoat/src/main/java/fredboat/db/api/GuildPermsService.java deleted file mode 100644 index dad40b7e5..000000000 --- a/FredBoat/src/main/java/fredboat/db/api/GuildPermsService.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.api; - -import fredboat.db.transfer.GuildPermissions; -import fredboat.sentinel.Guild; - -import java.util.function.Function; - -/** - * Created by napster on 07.02.18. - */ -public interface GuildPermsService { - - GuildPermissions fetchGuildPermissions(Guild guild); - - GuildPermissions transformGuildPerms(Guild guild, Function transformation); -} diff --git a/FredBoat/src/main/java/fredboat/db/api/GuildSettingsRepository.kt b/FredBoat/src/main/java/fredboat/db/api/GuildSettingsRepository.kt new file mode 100644 index 000000000..93de6a9a4 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/api/GuildSettingsRepository.kt @@ -0,0 +1,15 @@ +package fredboat.db.api + +import fredboat.db.transfer.GuildSettings +import reactor.core.publisher.Mono + +interface GuildSettingsRepository { + + fun fetch(guild: Long): Mono + + fun update(settings: Mono): Mono + + fun update(settings: GuildSettings): Mono + + fun delete(guild: Long): Mono +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/CachedRepository.kt b/FredBoat/src/main/java/fredboat/db/mongo/CachedRepository.kt deleted file mode 100644 index affecc457..000000000 --- a/FredBoat/src/main/java/fredboat/db/mongo/CachedRepository.kt +++ /dev/null @@ -1,20 +0,0 @@ -package fredboat.db.mongo - -import com.google.common.cache.Cache -import com.google.common.cache.CacheBuilder -import com.google.common.cache.CacheLoader -import com.google.common.cache.LoadingCache -import org.springframework.data.repository.reactive.ReactiveCrudRepository -import reactor.core.publisher.Mono - -abstract class CachedRepository : ReactiveCrudRepository { - - protected val cache: Cache = CacheBuilder.newBuilder() - .build() - - abstract fun findByCache(id: ID): Mono - - abstract fun findByCacheWithDefault(id: ID): Mono - - abstract fun saveWithCache(entity: T): Mono -} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsDelegate.kt b/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsDelegate.kt deleted file mode 100644 index 0c2393b89..000000000 --- a/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsDelegate.kt +++ /dev/null @@ -1,26 +0,0 @@ -package fredboat.db.mongo - -import reactor.core.publisher.Mono - -//TODO: what do i name this -class GuildSettingsDelegate(repo: GuildSettingsRepository) : GuildSettingsRepository by repo, CachedRepository() { - - override fun findByCache(id: Long): Mono { - return Mono.justOrEmpty(cache.getIfPresent(id)) - .switchIfEmpty(findById(id)) - .doOnNext { cache.put(id, it) } - } - - override fun findByCacheWithDefault(id: Long): Mono { - return Mono.justOrEmpty(cache.getIfPresent(id)) - .switchIfEmpty(findById(id)) - .defaultIfEmpty(GuildSettings(id)) - .doOnNext { cache.put(id, it) } - } - - override fun saveWithCache(entity: GuildSettings): Mono { - cache.put(entity.guildId, entity) - - return save(entity) - } -} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsRepositoryImpl.kt b/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsRepositoryImpl.kt new file mode 100644 index 000000000..47dbba281 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsRepositoryImpl.kt @@ -0,0 +1,35 @@ +package fredboat.db.mongo + +import com.google.common.cache.Cache +import com.google.common.cache.CacheBuilder +import fredboat.db.api.GuildSettingsRepository +import fredboat.db.transfer.GuildSettings +import reactor.core.publisher.Mono +import java.util.concurrent.TimeUnit + +class GuildSettingsRepositoryImpl(private val repo: InternalGuildSettingsRepository) : InternalGuildSettingsRepository by repo, GuildSettingsRepository { + + private val cache: Cache = CacheBuilder.newBuilder() + .expireAfterAccess(60, TimeUnit.SECONDS) + .expireAfterWrite(120, TimeUnit.SECONDS) + .build() + + override fun fetch(guild: Long): Mono { + return Mono.justOrEmpty(cache.getIfPresent(guild)) + .switchIfEmpty(repo.findById(guild)) + .defaultIfEmpty(GuildSettings(guild)) + .doOnSuccess { cache.put(guild, it) } + } + + override fun update(settings: Mono):Mono { + return settings.flatMap { cache.put(it.guildId, it); save(it) } + } + + override fun update(settings: GuildSettings): Mono { + return repo.save(settings).doOnSuccess { cache.put(settings.guildId, settings) } + } + + override fun delete(guild: Long): Mono { + return repo.deleteById(guild) + } +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt index b942ddbee..aace6bf48 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt @@ -3,6 +3,8 @@ package fredboat.db.mongo import fredboat.audio.player.GuildPlayer import fredboat.audio.player.voiceChannel import fredboat.audio.queue.SplitAudioTrackContext +import fredboat.db.transfer.GuildPermissions +import fredboat.db.transfer.GuildSettings import lavalink.client.LavalinkUtil import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -14,7 +16,9 @@ import reactor.core.publisher.Mono interface PlayerRepository : ReactiveCrudRepository interface ActivityRepository : ReactiveCrudRepository -interface GuildSettingsRepository : ReactiveCrudRepository +interface InternalGuildSettingsRepository : ReactiveCrudRepository +interface InternalGuildPermissionRepository: ReactiveCrudRepository +interface private val log: Logger = LoggerFactory.getLogger(PlayerRepository::class.java) diff --git a/FredBoat/src/main/java/fredboat/db/rest/RestGuildConfigService.kt b/FredBoat/src/main/java/fredboat/db/rest/RestGuildConfigService.kt deleted file mode 100644 index d47335a29..000000000 --- a/FredBoat/src/main/java/fredboat/db/rest/RestGuildConfigService.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.rest - -import fredboat.config.property.BackendConfig -import fredboat.db.FriendlyEntityService.fetchUserFriendly -import fredboat.db.api.GuildConfigService -import fredboat.db.transfer.GuildConfig -import io.prometheus.client.guava.cache.CacheMetricsCollector -import org.springframework.stereotype.Component -import org.springframework.web.client.RestTemplate - -/** - * Created by napster on 17.02.18. - */ -@Component -class RestGuildConfigService(backendConfig: BackendConfig, quarterdeckRestTemplate: RestTemplate, - cacheMetrics: CacheMetricsCollector) - : CachedRestService( - backendConfig.quarterdeck.host + RestService.VERSION_PATH + PATH, - GuildConfig::class.java, - quarterdeckRestTemplate, - cacheMetrics, - RestGuildConfigService::class.java.simpleName -), GuildConfigService { - - companion object { - const val PATH = "guildconfig/" - } - - override fun fetchGuildConfig(guild: Long): GuildConfig { - return fetchUserFriendly { fetch(guild.toString()) } - } - - override fun transformGuildConfig(guild: Long, transformation: (GuildConfig) -> GuildConfig): GuildConfig { - return fetchUserFriendly { merge(transformation(fetchGuildConfig(guild))) } - } -} diff --git a/FredBoat/src/main/java/fredboat/db/rest/RestGuildPermsService.kt b/FredBoat/src/main/java/fredboat/db/rest/RestGuildPermsService.kt deleted file mode 100644 index ca2bdabae..000000000 --- a/FredBoat/src/main/java/fredboat/db/rest/RestGuildPermsService.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.rest - -import fredboat.config.property.BackendConfig -import fredboat.db.FriendlyEntityService.fetchUserFriendly -import fredboat.db.api.GuildPermsService -import fredboat.db.transfer.GuildPermissions -import fredboat.sentinel.Guild -import io.prometheus.client.guava.cache.CacheMetricsCollector -import org.springframework.stereotype.Component -import org.springframework.web.client.RestTemplate -import java.util.function.Function - -/** - * Created by napster on 17.02.18. - */ -@Component -class RestGuildPermsService(backendConfig: BackendConfig, quarterdeckRestTemplate: RestTemplate, - cacheMetrics: CacheMetricsCollector) : CachedRestService(backendConfig.quarterdeck.host + RestService.VERSION_PATH + PATH, GuildPermissions::class.java, quarterdeckRestTemplate, cacheMetrics, RestGuildPermsService::class.java.simpleName), GuildPermsService { - - companion object { - const val PATH = "guildperms/" - } - - override fun fetchGuildPermissions(guild: Guild): GuildPermissions { - return fetchUserFriendly { fetch(guild.id.toString()) } - } - - override fun transformGuildPerms(guild: Guild, transformation: Function): GuildPermissions { - return fetchUserFriendly { merge(transformation.apply(fetchGuildPermissions(guild))) } - } -} diff --git a/FredBoat/src/main/java/fredboat/db/transfer/GuildConfig.java b/FredBoat/src/main/java/fredboat/db/transfer/GuildConfig.java deleted file mode 100644 index 1eb4b2c70..000000000 --- a/FredBoat/src/main/java/fredboat/db/transfer/GuildConfig.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.transfer; - -/** - * Created by napster on 20.03.18. - *

- * Transfer object for the {@link fredboat.db.entity.main.GuildConfig} - */ -//todo move ""business"" logic to the backend -public class GuildConfig implements TransferObject { - - private String guildId = ""; - private boolean trackAnnounce = false; - private boolean autoResume = false; - private String lang = "en_US"; - - @Override - public void setId(String id) { - this.guildId = id; - } - - @Override - public String getId() { - return this.guildId; - } - - public boolean isTrackAnnounce() { - return trackAnnounce; - } - - public GuildConfig setTrackAnnounce(boolean trackAnnounce) { - this.trackAnnounce = trackAnnounce; - return this; - } - - public boolean isAutoResume() { - return autoResume; - } - - public GuildConfig setAutoResume(boolean autoplay) { - this.autoResume = autoplay; - return this; - } - - public String getLang() { - return lang; - } - - public GuildConfig setLang(String lang) { - this.lang = lang; - return this; - } -} diff --git a/FredBoat/src/main/java/fredboat/db/transfer/GuildPermissions.java b/FredBoat/src/main/java/fredboat/db/transfer/GuildPermissions.java deleted file mode 100644 index 6efb0b8eb..000000000 --- a/FredBoat/src/main/java/fredboat/db/transfer/GuildPermissions.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.transfer; - -import fredboat.definitions.PermissionLevel; - -import java.util.Arrays; -import java.util.List; - -/** - * Created by napster on 20.03.18. - *

- * Transfer object for the {@link fredboat.db.entity.main.GuildPermissions} - */ -//todo move ""business"" logic to the backend -public class GuildPermissions implements TransferObject { - - // Guild ID - private String id; - private String adminList = ""; - private String djList = ""; - private String userList = ""; - - @Override - public void setId(String id) { - this.id = id; - } - - @Override - public String getId() { - return id; - } - - public List getAdminList() { - return Arrays.asList(adminList.split(" ")); - } - - public GuildPermissions setAdminList(List list) { - StringBuilder str = new StringBuilder(); - for (String item : list) { - str.append(item).append(" "); - } - - adminList = str.toString().trim(); - return this; - } - - public List getDjList() { - return Arrays.asList(djList.split(" ")); - } - - public GuildPermissions setDjList(List list) { - StringBuilder str = new StringBuilder(); - for (String item : list) { - str.append(item).append(" "); - } - - djList = str.toString().trim(); - return this; - } - - public List getUserList() { - return Arrays.asList(userList.split(" ")); - } - - public GuildPermissions setUserList(List list) { - StringBuilder str = new StringBuilder(); - for (String item : list) { - str.append(item).append(" "); - } - - userList = str.toString().trim(); - return this; - } - - public List getFromEnum(PermissionLevel level) { - switch (level) { - case ADMIN: - return getAdminList(); - case DJ: - return getDjList(); - case USER: - return getUserList(); - default: - throw new IllegalArgumentException("Unexpected enum " + level); - } - } - - public GuildPermissions setFromEnum(PermissionLevel level, List list) { - switch (level) { - case ADMIN: - return setAdminList(list); - case DJ: - return setDjList(list); - case USER: - return setUserList(list); - default: - throw new IllegalArgumentException("Unexpected enum " + level); - } - } - -} diff --git a/FredBoat/src/main/java/fredboat/db/mongo/GuildSettings.kt b/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt similarity index 93% rename from FredBoat/src/main/java/fredboat/db/mongo/GuildSettings.kt rename to FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt index ffe4be063..786675b78 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/GuildSettings.kt +++ b/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt @@ -1,4 +1,4 @@ -package fredboat.db.mongo +package fredboat.db.transfer import org.springframework.data.annotation.Id import org.springframework.data.mongodb.core.mapping.Document diff --git a/FredBoat/src/main/java/fredboat/event/AudioEventHandler.kt b/FredBoat/src/main/java/fredboat/event/AudioEventHandler.kt index 18b35d94c..09d6985f8 100644 --- a/FredBoat/src/main/java/fredboat/event/AudioEventHandler.kt +++ b/FredBoat/src/main/java/fredboat/event/AudioEventHandler.kt @@ -7,7 +7,7 @@ import fredboat.audio.player.getHumanUsersInVC import fredboat.audio.player.humanUsersInCurrentVC import fredboat.audio.player.voiceChannel import fredboat.config.property.AppConfig -import fredboat.db.mongo.GuildSettingsDelegate +import fredboat.db.api.GuildSettingsRepository import fredboat.feature.I18n import fredboat.sentinel.Member import fredboat.sentinel.VoiceChannel @@ -18,7 +18,7 @@ class AudioEventHandler( private val appConfig: AppConfig, private val playerRegistry: PlayerRegistry, private val lavalink: SentinelLavalink, - private val guildSettingsDelegate: GuildSettingsDelegate + private val guildSettingsRepository: GuildSettingsRepository ) : SentinelEventHandler() { override fun onVoiceJoin(channel: VoiceChannel, member: Member) { @@ -79,7 +79,7 @@ class AudioEventHandler( && player.playingTrack != null && joinedChannel.members.contains(guild.selfMember) && player.humanUsersInCurrentVC.isNotEmpty()) { - guildSettingsDelegate.findByCacheWithDefault(guild.id).subscribe { + guildSettingsRepository.fetch(guild.id).subscribe { if (it.autoResume) { player.setPause(false) player.activeTextChannel?.send(I18n.get(guild).getString("eventAutoResumed"))?.subscribe() diff --git a/FredBoat/src/main/java/fredboat/feature/I18n.java b/FredBoat/src/main/java/fredboat/feature/I18n.java index fb1684918..f2a35995a 100644 --- a/FredBoat/src/main/java/fredboat/feature/I18n.java +++ b/FredBoat/src/main/java/fredboat/feature/I18n.java @@ -26,11 +26,12 @@ package fredboat.feature; import fredboat.db.DatabaseNotReadyException; -import fredboat.db.mongo.GuildSettings; +import fredboat.db.transfer.GuildSettings; import fredboat.definitions.Language; import fredboat.sentinel.Guild; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import reactor.core.publisher.Mono; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -74,7 +75,7 @@ public static FredBoatLocale getLocale(@Nonnull Guild guild) { @Nonnull public static FredBoatLocale getLocale(long guild) { try { - return LANGS.getOrDefault(getBotController().getGuildSettingsDelegate().findByCache(guild).block(Duration.ofSeconds(10)).getLang(), DEFAULT); + return LANGS.getOrDefault(getBotController().getGuildSettingsRepository().fetch(guild).block(Duration.ofSeconds(10)).getLang(), DEFAULT); } catch (DatabaseNotReadyException e) { //don't log spam the full exceptions or logs return DEFAULT; @@ -88,10 +89,10 @@ public static void set(@Nonnull Guild guild, @Nonnull String lang) throws Langua if (!LANGS.containsKey(lang)) throw new LanguageNotSupportedException("Language not found"); - getBotController().getGuildSettingsDelegate().findByCacheWithDefault(guild.getId()).doOnNext(guildSettings -> { - guildSettings.setLang(lang); - getBotController().getGuildSettingsDelegate().saveWithCache(guildSettings).block(); - }).block(); + Mono settings = getBotController().getGuildSettingsRepository().fetch(guild.getId()) + .doOnSuccess(guildSettings -> guildSettings.setLang(lang)); + + getBotController().getGuildSettingsRepository().update(settings).subscribe(); } public static class FredBoatLocale { diff --git a/FredBoat/src/main/java/fredboat/main/BotController.kt b/FredBoat/src/main/java/fredboat/main/BotController.kt index 8a753ebae..d29e15bd3 100644 --- a/FredBoat/src/main/java/fredboat/main/BotController.kt +++ b/FredBoat/src/main/java/fredboat/main/BotController.kt @@ -4,11 +4,7 @@ import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager import fredboat.agent.FredBoatAgent import fredboat.audio.player.PlayerRegistry import fredboat.config.property.* -import fredboat.db.api.GuildConfigService -import fredboat.db.api.GuildModulesService -import fredboat.db.api.GuildPermsService -import fredboat.db.api.PrefixService -import fredboat.db.mongo.GuildSettingsDelegate +import fredboat.db.api.* import fredboat.feature.metrics.BotMetrics import fredboat.feature.metrics.Metrics import fredboat.metrics.OkHttpEventMetrics @@ -31,7 +27,7 @@ class BotController(private val configProvider: ConfigPropertiesProvider, val botMetrics: BotMetrics, @param:Qualifier("loadAudioPlayerManager") val audioPlayerManager: AudioPlayerManager, val ratelimiter: Ratelimiter, - val guildSettingsDelegate: GuildSettingsDelegate, + val guildSettingsRepository: GuildSettingsRepository, val guildModulesService: GuildModulesService, val guildPermsService: GuildPermsService, val prefixService: PrefixService, diff --git a/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt b/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt index 0b3905ca3..5da6aad99 100644 --- a/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt +++ b/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt @@ -1,8 +1,8 @@ package fredboat.ws import com.google.gson.Gson -import fredboat.db.mongo.GuildSettings -import fredboat.db.mongo.GuildSettingsDelegate +import fredboat.db.api.GuildSettingsRepository +import fredboat.db.transfer.GuildSettings import fredboat.sentinel.GuildCache import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap class UserSessionHandler( val gson: Gson, val guildCache: GuildCache, - val repository: GuildSettingsDelegate + val repository: GuildSettingsRepository ) : WebSocketHandler { companion object { @@ -30,7 +30,7 @@ class UserSessionHandler( val session = UserSession(rawSession, guildCache, gson) log.info("Established user connection for guild ${session.guildId}") - val interceptMono = repository.findById(session.guildId) + val interceptMono = repository.fetch(session.guildId) .defaultIfEmpty(GuildSettings(session.guildId)) .doOnError { e -> log.error("Exception while validating privacy setting", e) From 3648957f936d8e23704faf11c293685c8d8bf52a Mon Sep 17 00:00:00 2001 From: Nanabell Date: Fri, 8 Feb 2019 11:23:14 +0100 Subject: [PATCH 064/172] Implement GuildPermissionsRepository --- .../db/api/GuildPermissionsRepository.kt | 15 ++++++++ .../mongo/GuildPermissionsRepositoryImpl.kt | 35 +++++++++++++++++++ .../java/fredboat/db/mongo/repositories.kt | 1 - .../fredboat/db/transfer/GuildPermissions.kt | 31 ++++++++++++++++ .../main/java/fredboat/main/BotController.kt | 2 +- .../src/main/java/fredboat/perms/PermsUtil.kt | 10 +++--- .../test/java/fredboat/perms/PermsUtilTest.kt | 21 ++--------- .../testutil/util/MockGuildPermsService.kt | 27 ++++++++------ 8 files changed, 106 insertions(+), 36 deletions(-) create mode 100644 FredBoat/src/main/java/fredboat/db/api/GuildPermissionsRepository.kt create mode 100644 FredBoat/src/main/java/fredboat/db/mongo/GuildPermissionsRepositoryImpl.kt create mode 100644 FredBoat/src/main/java/fredboat/db/transfer/GuildPermissions.kt diff --git a/FredBoat/src/main/java/fredboat/db/api/GuildPermissionsRepository.kt b/FredBoat/src/main/java/fredboat/db/api/GuildPermissionsRepository.kt new file mode 100644 index 000000000..01f606aff --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/api/GuildPermissionsRepository.kt @@ -0,0 +1,15 @@ +package fredboat.db.api + +import fredboat.db.transfer.GuildPermissions +import reactor.core.publisher.Mono + +interface GuildPermissionsRepository { + + fun fetch(guild: Long): Mono + + fun update(mono: Mono): Mono + + fun update(permissions: GuildPermissions): Mono + + fun delete(guild: Long): Mono +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/GuildPermissionsRepositoryImpl.kt b/FredBoat/src/main/java/fredboat/db/mongo/GuildPermissionsRepositoryImpl.kt new file mode 100644 index 000000000..84c5e0cad --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/mongo/GuildPermissionsRepositoryImpl.kt @@ -0,0 +1,35 @@ +package fredboat.db.mongo + +import com.google.common.cache.Cache +import com.google.common.cache.CacheBuilder +import fredboat.db.api.GuildPermissionsRepository +import fredboat.db.transfer.GuildPermissions +import reactor.core.publisher.Mono +import java.util.concurrent.TimeUnit + +class GuildPermissionsRepositoryImpl(private val repo: InternalGuildPermissionRepository) : InternalGuildPermissionRepository by repo, GuildPermissionsRepository { + + private val cache: Cache = CacheBuilder.newBuilder() + .expireAfterAccess(60, TimeUnit.SECONDS) + .expireAfterWrite(120, TimeUnit.SECONDS) + .build() + + override fun fetch(guild: Long): Mono { + return Mono.justOrEmpty(cache.getIfPresent(guild)) + .switchIfEmpty(repo.findById(guild)) + .defaultIfEmpty(GuildPermissions(guild)) + .doOnSuccess { cache.put(guild, it) } + } + + override fun update(mono: Mono): Mono { + return mono.flatMap { cache.put(it.guildId, it); save(it) } + } + + override fun update(permissions: GuildPermissions): Mono { + return repo.save(permissions).doOnSuccess { cache.put(permissions.guildId, permissions) } + } + + override fun delete(guild: Long): Mono { + return repo.deleteById(guild) + } +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt index aace6bf48..6f567b005 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt @@ -18,7 +18,6 @@ interface PlayerRepository : ReactiveCrudRepository interface ActivityRepository : ReactiveCrudRepository interface InternalGuildSettingsRepository : ReactiveCrudRepository interface InternalGuildPermissionRepository: ReactiveCrudRepository -interface private val log: Logger = LoggerFactory.getLogger(PlayerRepository::class.java) diff --git a/FredBoat/src/main/java/fredboat/db/transfer/GuildPermissions.kt b/FredBoat/src/main/java/fredboat/db/transfer/GuildPermissions.kt new file mode 100644 index 000000000..6ae3a139a --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/transfer/GuildPermissions.kt @@ -0,0 +1,31 @@ +package fredboat.db.transfer + +import fredboat.definitions.PermissionLevel +import org.springframework.data.annotation.Id +import org.springframework.data.mongodb.core.mapping.Document + +@Document(collection = "GuildPermissions") +data class GuildPermissions( + @Id val guildId: Long, + var adminList: List = emptyList(), + var djList: List = emptyList(), + var userList: List = emptyList() +) { + fun fromEnum(level: PermissionLevel): List { + return when (level) { + PermissionLevel.ADMIN -> adminList + PermissionLevel.DJ -> djList + PermissionLevel.USER -> userList + else -> throw IllegalArgumentException("Unexpected enum $level") + } + } + + fun fromEnum(level: PermissionLevel, newList: List) { + when (level) { + PermissionLevel.ADMIN -> adminList = newList + PermissionLevel.DJ -> djList = newList + PermissionLevel.USER -> userList = newList + else -> throw IllegalArgumentException("Unexpected enum $level") + } + } +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/main/BotController.kt b/FredBoat/src/main/java/fredboat/main/BotController.kt index d29e15bd3..ab72ddc43 100644 --- a/FredBoat/src/main/java/fredboat/main/BotController.kt +++ b/FredBoat/src/main/java/fredboat/main/BotController.kt @@ -29,7 +29,7 @@ class BotController(private val configProvider: ConfigPropertiesProvider, val ratelimiter: Ratelimiter, val guildSettingsRepository: GuildSettingsRepository, val guildModulesService: GuildModulesService, - val guildPermsService: GuildPermsService, + val guildPermissionsRepository: GuildPermissionsRepository, val prefixService: PrefixService, val sentinel: Sentinel, val sentinelCountingService: SentinelCountingService) { diff --git a/FredBoat/src/main/java/fredboat/perms/PermsUtil.kt b/FredBoat/src/main/java/fredboat/perms/PermsUtil.kt index 0ca115548..ba93e853e 100644 --- a/FredBoat/src/main/java/fredboat/perms/PermsUtil.kt +++ b/FredBoat/src/main/java/fredboat/perms/PermsUtil.kt @@ -45,7 +45,7 @@ object PermsUtil { member.hasPermission(Permission.ADMINISTRATOR).awaitSingle() -> PermissionLevel.ADMIN else -> { - val gp = Launcher.botController.guildPermsService.fetchGuildPermissions(member.guild) + val gp = Launcher.botController.guildPermissionsRepository.fetch(member.guild.id).awaitSingle() when { checkList(gp.adminList, member) -> PermissionLevel.ADMIN @@ -107,13 +107,11 @@ object PermsUtil { /** * Checks if [member] matches any of the IDs of [list], or if it has any of the roles of [list] */ - fun checkList(list: List, member: Member): Boolean { + fun checkList(list: List, member: Member): Boolean { for (id in list) { - if (id.isEmpty()) continue + if (id == member.id) return true - if (id == member.id.toString()) return true - - val role = member.guild.getRole(id.toLong()) + val role = member.guild.getRole(id) if (role != null && (role.isPublicRole || member.roles.contains(role))) return true } diff --git a/FredBoat/src/test/java/fredboat/perms/PermsUtilTest.kt b/FredBoat/src/test/java/fredboat/perms/PermsUtilTest.kt index be6231d42..dd614b1dc 100644 --- a/FredBoat/src/test/java/fredboat/perms/PermsUtilTest.kt +++ b/FredBoat/src/test/java/fredboat/perms/PermsUtilTest.kt @@ -33,34 +33,19 @@ internal class PermsUtilTest : IntegrationTest() { @Test fun testAdmin(permsService: MockGuildPermsService) { - permsService.factory = { - GuildPermissions().apply { - id = it.id.toString() - adminList = listOf(Raws.adminRole.id.toString()) - } - } + permsService.factory = { GuildPermissions(it, adminList = listOf(Raws.adminRole.id)) } assertEquals(PermissionLevel.ADMIN, Raws.napster.level) } @Test fun testDj(permsService: MockGuildPermsService) { - permsService.factory = { - GuildPermissions().apply { - id = it.id.toString() - djList = listOf(Raws.adminRole.id.toString()) - } - } + permsService.factory = { GuildPermissions(it, djList = listOf(Raws.adminRole.id)) } assertEquals(PermissionLevel.DJ, Raws.napster.level) } @Test fun testUser(permsService: MockGuildPermsService) { - permsService.factory = { - GuildPermissions().apply { - id = it.id.toString() - userList = listOf(Raws.adminRole.id.toString()) - } - } + permsService.factory = { GuildPermissions(it, userList = listOf(Raws.adminRole.id)) } assertEquals(PermissionLevel.USER, Raws.napster.level) } diff --git a/FredBoat/src/test/java/fredboat/testutil/util/MockGuildPermsService.kt b/FredBoat/src/test/java/fredboat/testutil/util/MockGuildPermsService.kt index cb795c13c..848d64b0c 100644 --- a/FredBoat/src/test/java/fredboat/testutil/util/MockGuildPermsService.kt +++ b/FredBoat/src/test/java/fredboat/testutil/util/MockGuildPermsService.kt @@ -1,24 +1,31 @@ package fredboat.testutil.util -import fredboat.db.api.GuildPermsService +import fredboat.db.api.GuildPermissionsRepository import fredboat.db.transfer.GuildPermissions -import fredboat.sentinel.Guild import org.springframework.context.annotation.Primary import org.springframework.stereotype.Service -import java.util.function.Function +import reactor.core.publisher.Mono @Service @Primary -class MockGuildPermsService : GuildPermsService { +class MockGuildPermsService : GuildPermissionsRepository { - final val default: (guild: Guild) -> GuildPermissions = { guild -> - GuildPermissions().apply { id = guild.id.toString() } - } + final val default: (guild: Long) -> GuildPermissions = { GuildPermissions(it) } var factory = default - override fun fetchGuildPermissions(guild: Guild) = factory(guild) + override fun fetch(guild: Long): Mono { + return Mono.just(factory(guild)) + } + + override fun update(mono: Mono): Mono { + return mono.flatMap { Mono.just(factory(it.guildId)) } + } - override fun transformGuildPerms(guild: Guild, transformation: Function) - = transformation.apply(factory(guild)) + override fun update(permissions: GuildPermissions): Mono { + return Mono.just(permissions) + } + override fun delete(guild: Long): Mono { + return Mono.empty() + } } \ No newline at end of file From 82b6ade7d6f2fb88b27c181e52be016e07e7e971 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Fri, 8 Feb 2019 12:19:04 +0100 Subject: [PATCH 065/172] Put common repository methods in Base-Class --- .../java/fredboat/db/api/BaseRepository.kt | 16 +++++ .../db/api/GuildPermissionsRepository.kt | 12 +--- .../db/api/GuildSettingsRepository.kt | 11 +--- .../fredboat/db/mongo/BaseRepositoryImpl.kt | 36 +++++++++++ .../mongo/GuildPermissionsRepositoryImpl.kt | 29 +-------- .../db/mongo/GuildSettingsRepositoryImpl.kt | 29 +-------- .../db/rest/RestBlacklistService.java | 64 ------------------- .../fredboat/db/transfer/GuildPermissions.kt | 5 +- .../fredboat/db/transfer/GuildSettings.kt | 4 +- .../java/fredboat/db/transfer/MongoEntry.kt | 5 ++ .../testutil/util/MockGuildPermsService.kt | 6 +- 11 files changed, 75 insertions(+), 142 deletions(-) create mode 100644 FredBoat/src/main/java/fredboat/db/api/BaseRepository.kt create mode 100644 FredBoat/src/main/java/fredboat/db/mongo/BaseRepositoryImpl.kt delete mode 100644 FredBoat/src/main/java/fredboat/db/rest/RestBlacklistService.java create mode 100644 FredBoat/src/main/java/fredboat/db/transfer/MongoEntry.kt diff --git a/FredBoat/src/main/java/fredboat/db/api/BaseRepository.kt b/FredBoat/src/main/java/fredboat/db/api/BaseRepository.kt new file mode 100644 index 000000000..b3bb57bea --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/api/BaseRepository.kt @@ -0,0 +1,16 @@ +package fredboat.db.api + +import reactor.core.publisher.Mono + +interface BaseRepository { + + fun fetch(id: Long): Mono + + fun update(mono: Mono): Mono + + fun update(target: T): Mono + + fun delete(id: Long): Mono + + fun default(id: Long): T +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/api/GuildPermissionsRepository.kt b/FredBoat/src/main/java/fredboat/db/api/GuildPermissionsRepository.kt index 01f606aff..311dd9e69 100644 --- a/FredBoat/src/main/java/fredboat/db/api/GuildPermissionsRepository.kt +++ b/FredBoat/src/main/java/fredboat/db/api/GuildPermissionsRepository.kt @@ -1,15 +1,5 @@ package fredboat.db.api import fredboat.db.transfer.GuildPermissions -import reactor.core.publisher.Mono -interface GuildPermissionsRepository { - - fun fetch(guild: Long): Mono - - fun update(mono: Mono): Mono - - fun update(permissions: GuildPermissions): Mono - - fun delete(guild: Long): Mono -} \ No newline at end of file +interface GuildPermissionsRepository : BaseRepository \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/api/GuildSettingsRepository.kt b/FredBoat/src/main/java/fredboat/db/api/GuildSettingsRepository.kt index 93de6a9a4..bfe8d0551 100644 --- a/FredBoat/src/main/java/fredboat/db/api/GuildSettingsRepository.kt +++ b/FredBoat/src/main/java/fredboat/db/api/GuildSettingsRepository.kt @@ -3,13 +3,4 @@ package fredboat.db.api import fredboat.db.transfer.GuildSettings import reactor.core.publisher.Mono -interface GuildSettingsRepository { - - fun fetch(guild: Long): Mono - - fun update(settings: Mono): Mono - - fun update(settings: GuildSettings): Mono - - fun delete(guild: Long): Mono -} \ No newline at end of file +interface GuildSettingsRepository : BaseRepository \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/BaseRepositoryImpl.kt b/FredBoat/src/main/java/fredboat/db/mongo/BaseRepositoryImpl.kt new file mode 100644 index 000000000..38a7695f3 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/mongo/BaseRepositoryImpl.kt @@ -0,0 +1,36 @@ +package fredboat.db.mongo + +import com.google.common.cache.Cache +import com.google.common.cache.CacheBuilder +import fredboat.db.api.BaseRepository +import fredboat.db.transfer.MongoEntry +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import reactor.core.publisher.Mono +import java.util.concurrent.TimeUnit + +abstract class BaseRepositoryImpl(private val repo: ReactiveCrudRepository) : ReactiveCrudRepository by repo, BaseRepository { + + private val cache: Cache = CacheBuilder.newBuilder() + .expireAfterAccess(60, TimeUnit.SECONDS) + .expireAfterWrite(120, TimeUnit.SECONDS) + .build() + + override fun fetch(id: Long): Mono { + return Mono.justOrEmpty(cache.getIfPresent(id)) + .switchIfEmpty(repo.findById(id)) + .defaultIfEmpty(default(id)) + .doOnSuccess { cache.put(id, it) } + } + + override fun update(mono: Mono): Mono { + return mono.flatMap { cache.put(it.id, it); save(it) } + } + + override fun update(target: T): Mono { + return repo.save(target).doOnSuccess { cache.put(target.id, target) } + } + + override fun delete(id: Long): Mono { + return repo.deleteById(id) + } +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/GuildPermissionsRepositoryImpl.kt b/FredBoat/src/main/java/fredboat/db/mongo/GuildPermissionsRepositoryImpl.kt index 84c5e0cad..d4c7c22fb 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/GuildPermissionsRepositoryImpl.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/GuildPermissionsRepositoryImpl.kt @@ -1,35 +1,12 @@ package fredboat.db.mongo -import com.google.common.cache.Cache -import com.google.common.cache.CacheBuilder import fredboat.db.api.GuildPermissionsRepository import fredboat.db.transfer.GuildPermissions -import reactor.core.publisher.Mono -import java.util.concurrent.TimeUnit -class GuildPermissionsRepositoryImpl(private val repo: InternalGuildPermissionRepository) : InternalGuildPermissionRepository by repo, GuildPermissionsRepository { +class GuildPermissionsRepositoryImpl(repo: InternalGuildPermissionRepository) : BaseRepositoryImpl(repo), GuildPermissionsRepository { - private val cache: Cache = CacheBuilder.newBuilder() - .expireAfterAccess(60, TimeUnit.SECONDS) - .expireAfterWrite(120, TimeUnit.SECONDS) - .build() - - override fun fetch(guild: Long): Mono { - return Mono.justOrEmpty(cache.getIfPresent(guild)) - .switchIfEmpty(repo.findById(guild)) - .defaultIfEmpty(GuildPermissions(guild)) - .doOnSuccess { cache.put(guild, it) } - } - - override fun update(mono: Mono): Mono { - return mono.flatMap { cache.put(it.guildId, it); save(it) } + override fun default(id: Long): GuildPermissions { + return GuildPermissions(id) } - override fun update(permissions: GuildPermissions): Mono { - return repo.save(permissions).doOnSuccess { cache.put(permissions.guildId, permissions) } - } - - override fun delete(guild: Long): Mono { - return repo.deleteById(guild) - } } \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsRepositoryImpl.kt b/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsRepositoryImpl.kt index 47dbba281..4e55f0252 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsRepositoryImpl.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsRepositoryImpl.kt @@ -1,35 +1,12 @@ package fredboat.db.mongo -import com.google.common.cache.Cache -import com.google.common.cache.CacheBuilder import fredboat.db.api.GuildSettingsRepository import fredboat.db.transfer.GuildSettings -import reactor.core.publisher.Mono -import java.util.concurrent.TimeUnit -class GuildSettingsRepositoryImpl(private val repo: InternalGuildSettingsRepository) : InternalGuildSettingsRepository by repo, GuildSettingsRepository { +class GuildSettingsRepositoryImpl(repo: InternalGuildSettingsRepository) : BaseRepositoryImpl(repo), GuildSettingsRepository { - private val cache: Cache = CacheBuilder.newBuilder() - .expireAfterAccess(60, TimeUnit.SECONDS) - .expireAfterWrite(120, TimeUnit.SECONDS) - .build() - - override fun fetch(guild: Long): Mono { - return Mono.justOrEmpty(cache.getIfPresent(guild)) - .switchIfEmpty(repo.findById(guild)) - .defaultIfEmpty(GuildSettings(guild)) - .doOnSuccess { cache.put(guild, it) } - } - - override fun update(settings: Mono):Mono { - return settings.flatMap { cache.put(it.guildId, it); save(it) } + override fun default(id: Long): GuildSettings { + return GuildSettings(id) } - override fun update(settings: GuildSettings): Mono { - return repo.save(settings).doOnSuccess { cache.put(settings.guildId, settings) } - } - - override fun delete(guild: Long): Mono { - return repo.deleteById(guild) - } } \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/rest/RestBlacklistService.java b/FredBoat/src/main/java/fredboat/db/rest/RestBlacklistService.java deleted file mode 100644 index 3d4d998d4..000000000 --- a/FredBoat/src/main/java/fredboat/db/rest/RestBlacklistService.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.rest; - -import fredboat.config.property.BackendConfig; -import fredboat.db.api.BlacklistService; -import fredboat.db.transfer.BlacklistEntry; -import io.prometheus.client.guava.cache.CacheMetricsCollector; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; - -import static fredboat.db.FriendlyEntityService.doUserFriendly; -import static fredboat.db.FriendlyEntityService.fetchUserFriendly; - -/** - * Created by napster on 17.02.18. - */ -@Component -public class RestBlacklistService extends CachedRestService implements BlacklistService { - - public static final String PATH = "blacklist/"; - - public RestBlacklistService(BackendConfig backendConfig, RestTemplate quarterdeckRestTemplate, CacheMetricsCollector cacheMetrics) { - super(backendConfig.getQuarterdeck().getHost() + VERSION_PATH + PATH, BlacklistEntry.class, - quarterdeckRestTemplate, cacheMetrics, RestBlacklistService.class.getSimpleName()); - } - - @Override - public BlacklistEntry fetchBlacklistEntry(long id) { - return fetchUserFriendly(() -> fetch(id)); - } - - @Override - public BlacklistEntry mergeBlacklistEntry(BlacklistEntry entry) { - return fetchUserFriendly(() -> merge(entry)); - } - - @Override - public void deleteBlacklistEntry(long id) { - doUserFriendly(() -> delete(id)); - } -} diff --git a/FredBoat/src/main/java/fredboat/db/transfer/GuildPermissions.kt b/FredBoat/src/main/java/fredboat/db/transfer/GuildPermissions.kt index 6ae3a139a..c643a9428 100644 --- a/FredBoat/src/main/java/fredboat/db/transfer/GuildPermissions.kt +++ b/FredBoat/src/main/java/fredboat/db/transfer/GuildPermissions.kt @@ -6,11 +6,12 @@ import org.springframework.data.mongodb.core.mapping.Document @Document(collection = "GuildPermissions") data class GuildPermissions( - @Id val guildId: Long, + @Id + override val id: Long, var adminList: List = emptyList(), var djList: List = emptyList(), var userList: List = emptyList() -) { +) : MongoEntry { fun fromEnum(level: PermissionLevel): List { return when (level) { PermissionLevel.ADMIN -> adminList diff --git a/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt b/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt index 786675b78..0a9ef995d 100644 --- a/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt +++ b/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt @@ -6,9 +6,9 @@ import org.springframework.data.mongodb.core.mapping.Document @Document(collection = "GuildSettings") data class GuildSettings( @Id - val guildId: Long, + override val id: Long, var trackAnnounce: Boolean = false, var autoResume: Boolean = false, var allowPublicPlayerInfo: Boolean = false, var lang: String = "en_US" -) \ No newline at end of file +) : MongoEntry \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/transfer/MongoEntry.kt b/FredBoat/src/main/java/fredboat/db/transfer/MongoEntry.kt new file mode 100644 index 000000000..53919300f --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/transfer/MongoEntry.kt @@ -0,0 +1,5 @@ +package fredboat.db.transfer + +interface MongoEntry { + val id: Long +} \ No newline at end of file diff --git a/FredBoat/src/test/java/fredboat/testutil/util/MockGuildPermsService.kt b/FredBoat/src/test/java/fredboat/testutil/util/MockGuildPermsService.kt index 848d64b0c..e3cfb3d68 100644 --- a/FredBoat/src/test/java/fredboat/testutil/util/MockGuildPermsService.kt +++ b/FredBoat/src/test/java/fredboat/testutil/util/MockGuildPermsService.kt @@ -10,6 +10,10 @@ import reactor.core.publisher.Mono @Primary class MockGuildPermsService : GuildPermissionsRepository { + override fun default(id: Long): GuildPermissions { + return GuildPermissions(id) + } + final val default: (guild: Long) -> GuildPermissions = { GuildPermissions(it) } var factory = default @@ -18,7 +22,7 @@ class MockGuildPermsService : GuildPermissionsRepository { } override fun update(mono: Mono): Mono { - return mono.flatMap { Mono.just(factory(it.guildId)) } + return mono.flatMap { Mono.just(factory(it.id)) } } override fun update(permissions: GuildPermissions): Mono { From 995345ec65beffc0e77f7a9e9b7450b2afd3d15f Mon Sep 17 00:00:00 2001 From: Nanabell Date: Fri, 8 Feb 2019 12:19:30 +0100 Subject: [PATCH 066/172] Implement BlacklistRepository --- .../config/RepositoriesConfiguration.kt | 11 +- .../fredboat/db/api/BlacklistRepository.kt | 5 + .../fredboat/db/api/BlacklistService.java | 41 ------- .../db/mongo/BlacklistRepositoryImpl.kt | 11 ++ .../java/fredboat/db/mongo/repositories.kt | 2 + .../fredboat/db/transfer/BlacklistEntry.java | 109 ------------------ .../fredboat/db/transfer/BlacklistEntry.kt | 22 ++++ .../fredboat/util/ratelimit/Blacklist.java | 34 +++--- .../fredboat/util/ratelimit/Ratelimiter.java | 7 +- 9 files changed, 68 insertions(+), 174 deletions(-) create mode 100644 FredBoat/src/main/java/fredboat/db/api/BlacklistRepository.kt delete mode 100644 FredBoat/src/main/java/fredboat/db/api/BlacklistService.java create mode 100644 FredBoat/src/main/java/fredboat/db/mongo/BlacklistRepositoryImpl.kt delete mode 100644 FredBoat/src/main/java/fredboat/db/transfer/BlacklistEntry.java create mode 100644 FredBoat/src/main/java/fredboat/db/transfer/BlacklistEntry.kt diff --git a/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt b/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt index b5c4e1a53..de4070692 100644 --- a/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt +++ b/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt @@ -1,11 +1,9 @@ package fredboat.config +import fredboat.db.api.BlacklistRepository import fredboat.db.api.GuildPermissionsRepository import fredboat.db.api.GuildSettingsRepository -import fredboat.db.mongo.GuildPermissionsRepositoryImpl -import fredboat.db.mongo.GuildSettingsRepositoryImpl -import fredboat.db.mongo.InternalGuildPermissionRepository -import fredboat.db.mongo.InternalGuildSettingsRepository +import fredboat.db.mongo.* import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -21,4 +19,9 @@ class RepositoriesConfiguration { fun guildPermissionsRepository(repo: InternalGuildPermissionRepository): GuildPermissionsRepository { return GuildPermissionsRepositoryImpl(repo) } + + @Bean + fun blacklistRepository(repo: InternalBlacklistRepository): BlacklistRepository { + return BlacklistRepositoryImpl(repo) + } } \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/api/BlacklistRepository.kt b/FredBoat/src/main/java/fredboat/db/api/BlacklistRepository.kt new file mode 100644 index 000000000..0ce60490f --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/api/BlacklistRepository.kt @@ -0,0 +1,5 @@ +package fredboat.db.api + +import fredboat.db.transfer.BlacklistEntry + +interface BlacklistRepository : BaseRepository \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/api/BlacklistService.java b/FredBoat/src/main/java/fredboat/db/api/BlacklistService.java deleted file mode 100644 index 968992fca..000000000 --- a/FredBoat/src/main/java/fredboat/db/api/BlacklistService.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.api; - - -import fredboat.db.rest.BackendException; -import fredboat.db.transfer.BlacklistEntry; - -/** - * Created by napster on 07.02.18. - */ -public interface BlacklistService { - - BlacklistEntry fetchBlacklistEntry(long id) throws BackendException; - - BlacklistEntry mergeBlacklistEntry(BlacklistEntry entry) throws BackendException; - - void deleteBlacklistEntry(long id) throws BackendException; -} diff --git a/FredBoat/src/main/java/fredboat/db/mongo/BlacklistRepositoryImpl.kt b/FredBoat/src/main/java/fredboat/db/mongo/BlacklistRepositoryImpl.kt new file mode 100644 index 000000000..0526183e9 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/mongo/BlacklistRepositoryImpl.kt @@ -0,0 +1,11 @@ +package fredboat.db.mongo + +import fredboat.db.api.BlacklistRepository +import fredboat.db.transfer.BlacklistEntry + +class BlacklistRepositoryImpl(repo: InternalBlacklistRepository) : BaseRepositoryImpl(repo), BlacklistRepository { + + override fun default(id: Long): BlacklistEntry { + return BlacklistEntry(id) + } +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt index 6f567b005..382d36314 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt @@ -3,6 +3,7 @@ package fredboat.db.mongo import fredboat.audio.player.GuildPlayer import fredboat.audio.player.voiceChannel import fredboat.audio.queue.SplitAudioTrackContext +import fredboat.db.transfer.BlacklistEntry import fredboat.db.transfer.GuildPermissions import fredboat.db.transfer.GuildSettings import lavalink.client.LavalinkUtil @@ -18,6 +19,7 @@ interface PlayerRepository : ReactiveCrudRepository interface ActivityRepository : ReactiveCrudRepository interface InternalGuildSettingsRepository : ReactiveCrudRepository interface InternalGuildPermissionRepository: ReactiveCrudRepository +interface InternalBlacklistRepository: ReactiveCrudRepository private val log: Logger = LoggerFactory.getLogger(PlayerRepository::class.java) diff --git a/FredBoat/src/main/java/fredboat/db/transfer/BlacklistEntry.java b/FredBoat/src/main/java/fredboat/db/transfer/BlacklistEntry.java deleted file mode 100644 index a68b11a90..000000000 --- a/FredBoat/src/main/java/fredboat/db/transfer/BlacklistEntry.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.transfer; - -/** - * Created by napster on 20.03.18. - *

- * Transfer object for the {@link fredboat.db.entity.main.BlacklistEntry} - */ -//todo move ""business"" logic to the backend -public class BlacklistEntry implements TransferObject { - - //id of the user or guild that this blacklist entry belongs to - private long id; - - //blacklist level that the user or guild is on - private int level = -1; - - //keeps track of how many times a user or guild reached the rate limit on the current blacklist level - private int rateLimitReached; - - //when was the ratelimit hit the last time? - private long rateLimitReachedTimestamp; - - //time when the id was blacklisted - private long blacklistedTimestamp; - - @Override - public void setId(Long id) { - this.id = id; - } - - @Override - public Long getId() { - return id; - } - - @Override - public int hashCode() { - return Long.hashCode(id); - } - - @Override - public boolean equals(Object other) { - return (other instanceof BlacklistEntry) && ((BlacklistEntry) other).id == this.id; - } - - public int getLevel() { - return level; - } - - public void incLevel() { - level++; - } - - public void setLevel(int level) { - this.level = level; - } - - public int getRateLimitReached() { - return rateLimitReached; - } - - public void incRateLimitReached() { - rateLimitReached++; - } - - public void setRateLimitReached(int rateLimitReached) { - this.rateLimitReached = rateLimitReached; - } - - public long getRateLimitReachedTimestamp() { - return rateLimitReachedTimestamp; - } - - public void setRateLimitReachedTimestamp(long rateLimitReachedTimestamp) { - this.rateLimitReachedTimestamp = rateLimitReachedTimestamp; - } - - public long getBlacklistedTimestamp() { - return blacklistedTimestamp; - } - - public void setBlacklistedTimestamp(long blacklistedTimestamp) { - this.blacklistedTimestamp = blacklistedTimestamp; - } -} diff --git a/FredBoat/src/main/java/fredboat/db/transfer/BlacklistEntry.kt b/FredBoat/src/main/java/fredboat/db/transfer/BlacklistEntry.kt new file mode 100644 index 000000000..098b5832e --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/transfer/BlacklistEntry.kt @@ -0,0 +1,22 @@ +package fredboat.db.transfer + +import org.springframework.data.annotation.Id +import org.springframework.data.mongodb.core.mapping.Document + +@Document(collection = "Blacklist") +data class BlacklistEntry( + @Id override val id: Long, + var level: Int = -1, + var hitCount: Int = 0, + var lastHitTime: Long = 0, + var blacklistTime: Long = 0 +) : MongoEntry { + + fun incLevel() { + level++ + } + + fun incHitCount() { + hitCount++ + } +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/util/ratelimit/Blacklist.java b/FredBoat/src/main/java/fredboat/util/ratelimit/Blacklist.java index 6829fa22a..4ba5a4a11 100644 --- a/FredBoat/src/main/java/fredboat/util/ratelimit/Blacklist.java +++ b/FredBoat/src/main/java/fredboat/util/ratelimit/Blacklist.java @@ -24,7 +24,7 @@ package fredboat.util.ratelimit; -import fredboat.db.api.BlacklistService; +import fredboat.db.api.BlacklistRepository; import fredboat.db.transfer.BlacklistEntry; import fredboat.feature.metrics.Metrics; @@ -36,7 +36,7 @@ * Created by napster on 17.04.17. *

* Provides a forgiving blacklist with progressively increasing blacklist lengths - * + *

* In an environment where shards are running in different containers and not inside a single jar this class will need * some help in keeping bans up to date, that is, reading them from the database, either on changes (rethinkDB?) or * through an agent in regular periods @@ -61,11 +61,11 @@ public class Blacklist { //users that can never be blacklisted private final Set userWhiteList; - private final BlacklistService blacklistService; //implementation as a RestRepo includes a cache + private final BlacklistRepository repository; //implementation as a RestRepo includes a cache - public Blacklist(BlacklistService blacklistService, Set userWhiteList, long rateLimitHitsBeforeBlacklist) { - this.blacklistService = blacklistService; + public Blacklist(BlacklistRepository repository, Set userWhiteList, long rateLimitHitsBeforeBlacklist) { + this.repository = repository; this.rateLimitHitsBeforeBlacklist = rateLimitHitsBeforeBlacklist; this.userWhiteList = Collections.unmodifiableSet(userWhiteList); } @@ -82,13 +82,13 @@ public boolean isBlacklisted(long id) { //first of all, ppl that can never get blacklisted no matter what if (userWhiteList.contains(id)) return false; - BlacklistEntry blEntry = blacklistService.fetchBlacklistEntry(id); + BlacklistEntry blEntry = repository.fetch(id).block(); if (blEntry.getLevel() < 0) return false; //blacklist entry exists, but id hasn't actually been blacklisted yet //id was a blacklisted, but it has run out //noinspection RedundantIfStatement - if (System.currentTimeMillis() > blEntry.getBlacklistedTimestamp() + (getBlacklistTimeLength(blEntry.getLevel()))) { + if (System.currentTimeMillis() > blEntry.getBlacklistTime() + (getBlacklistTimeLength(blEntry.getLevel()))) { return false; } @@ -102,7 +102,7 @@ public boolean isBlacklisted(long id) { public long hitRateLimit(long id) { //update blacklist entry of this id long blacklistingLength = 0; - BlacklistEntry blEntry = blacklistService.fetchBlacklistEntry(id); + BlacklistEntry blEntry = repository.fetch(id).block(); //synchronize on the individual blacklist entries since we are about to change and convertAndSave them // we can use these to synchronize because they are backed by a cache, subsequent calls to fetch them @@ -112,24 +112,24 @@ public long hitRateLimit(long id) { long now = System.currentTimeMillis(); //is the last ratelimit hit a long time away (1 hour)? then reset the ratelimit hits - if (now - blEntry.getRateLimitReachedTimestamp() > 60 * 60 * 1000) { - blEntry.setRateLimitReached(0); + if (now - blEntry.getLastHitTime() > 60 * 60 * 1000) { + blEntry.setHitCount(0); } - blEntry.incRateLimitReached(); - blEntry.setRateLimitReachedTimestamp(now); - if (blEntry.getRateLimitReached() >= rateLimitHitsBeforeBlacklist) { + blEntry.incHitCount(); + blEntry.setLastHitTime(now); + if (blEntry.getHitCount() >= rateLimitHitsBeforeBlacklist) { //issue blacklist incident blEntry.incLevel(); if (blEntry.getLevel() < 0) blEntry.setLevel(0); Metrics.autoBlacklistsIssued.labels(Integer.toString(blEntry.getLevel())).inc(); - blEntry.setBlacklistedTimestamp(now); - blEntry.setRateLimitReached(0); //reset these for the next time + blEntry.setBlacklistTime(now); + blEntry.setHitCount(0); //reset these for the next time blacklistingLength = getBlacklistTimeLength(blEntry.getLevel()); } //persist it //if this turns up to be a performance bottleneck, have an agent run that persists the blacklist occasionally - blacklistService.mergeBlacklistEntry(blEntry); + repository.update(blEntry); return blacklistingLength; } } @@ -138,7 +138,7 @@ public long hitRateLimit(long id) { * completely resets a blacklist for an id */ public void liftBlacklist(long id) { - blacklistService.deleteBlacklistEntry(id); + repository.delete(id); } /** diff --git a/FredBoat/src/main/java/fredboat/util/ratelimit/Ratelimiter.java b/FredBoat/src/main/java/fredboat/util/ratelimit/Ratelimiter.java index 2ea2ad425..16a488a31 100644 --- a/FredBoat/src/main/java/fredboat/util/ratelimit/Ratelimiter.java +++ b/FredBoat/src/main/java/fredboat/util/ratelimit/Ratelimiter.java @@ -32,7 +32,7 @@ import fredboat.commandmeta.CommandInitializer; import fredboat.commandmeta.abs.JCommand; import fredboat.config.property.AppConfig; -import fredboat.db.api.BlacklistService; +import fredboat.db.api.BlacklistRepository; import fredboat.feature.metrics.Metrics; import fredboat.messaging.internal.Context; import fredboat.util.TextUtils; @@ -63,18 +63,19 @@ public class Ratelimiter { @Nullable private final Blacklist autoBlacklist; - public Ratelimiter(AppConfig appConfig, ExecutorService executor, BlacklistService blacklistService, + public Ratelimiter(AppConfig appConfig, ExecutorService executor, BlacklistRepository repository, CacheMetricsCollector cacheMetrics) { Set whitelist = ConcurrentHashMap.newKeySet(); //only works for those admins who are added with their userId and not through a roleId + //TODO: Add ownerIds (needs merge with dev) whitelist.addAll(appConfig.getAdminIds()); //Create all the rate limiters we want ratelimits = new ArrayList<>(); if (appConfig.useAutoBlacklist()) { - autoBlacklist = new Blacklist(blacklistService, whitelist, RATE_LIMIT_HITS_BEFORE_BLACKLIST); + autoBlacklist = new Blacklist(repository, whitelist, RATE_LIMIT_HITS_BEFORE_BLACKLIST); } else { autoBlacklist = null; } From 3afb17131128e3a2262c9c350c71cf3331ec0103 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Fri, 8 Feb 2019 12:36:17 +0100 Subject: [PATCH 067/172] Implement PrefixRepository --- .../fredboat/command/config/PrefixCommand.kt | 17 +-- .../config/RepositoriesConfiguration.kt | 6 + .../java/fredboat/db/api/PrefixRepository.kt | 10 ++ .../java/fredboat/db/api/PrefixService.java | 42 ------ .../fredboat/db/mongo/PrefixRepositoryImpl.kt | 17 +++ .../java/fredboat/db/mongo/repositories.kt | 2 + .../fredboat/db/rest/RestPrefixService.kt | 77 ----------- .../java/fredboat/db/transfer/Prefix.java | 121 ------------------ .../main/java/fredboat/db/transfer/Prefix.kt | 11 ++ .../main/java/fredboat/main/BotController.kt | 2 +- 10 files changed, 54 insertions(+), 251 deletions(-) create mode 100644 FredBoat/src/main/java/fredboat/db/api/PrefixRepository.kt delete mode 100644 FredBoat/src/main/java/fredboat/db/api/PrefixService.java create mode 100644 FredBoat/src/main/java/fredboat/db/mongo/PrefixRepositoryImpl.kt delete mode 100644 FredBoat/src/main/java/fredboat/db/rest/RestPrefixService.kt delete mode 100644 FredBoat/src/main/java/fredboat/db/transfer/Prefix.java create mode 100644 FredBoat/src/main/java/fredboat/db/transfer/Prefix.kt diff --git a/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt b/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt index 6f59552b9..6c00fb130 100644 --- a/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt @@ -61,13 +61,9 @@ class PrefixCommand(cacheMetrics: CacheMetricsCollector, name: String, vararg al .refreshAfterWrite(1, TimeUnit.MINUTES) //NOTE: never use refreshing without async reloading, because Guavas cache uses the thread calling it to do cleanup tasks (including refreshing) .expireAfterAccess(1, TimeUnit.MINUTES) //evict inactive guilds .concurrencyLevel(Launcher.botController.appConfig.shardCount) //each shard has a thread (main JDA thread) accessing this cache many times - .build(CacheLoader.asyncReloading(CacheLoader.from> { - guildId -> Launcher.botController.prefixService.getPrefix(Prefix.GuildBotId( - guildId!!, - botId - )) - }, - Launcher.botController.executor))!! + .build(CacheLoader.asyncReloading(CacheLoader.from> { guildId -> + Launcher.botController.prefixRepository.getOptional(guildId!!) + }, Launcher.botController.executor))!! fun giefPrefix(guildId: Long) = CacheUtil.getUncheckedUnwrapped(CUSTOM_PREFIXES, guildId) .orElse(Launcher.botController.appConfig.prefix) @@ -103,9 +99,10 @@ class PrefixCommand(cacheMetrics: CacheMetricsCollector, name: String, vararg al newPrefix = context.rawArgs } - Launcher.botController.prefixService.transformPrefix(context.guild, { - prefixEntity -> prefixEntity.setPrefix(newPrefix) - }) + Launcher.botController.prefixRepository.fetch(context.guild.id) + .doOnSuccess { it.prefix = newPrefix } + .let { Launcher.botController.prefixRepository.update(it) } + .subscribe() //we could do a put instead of invalidate here and probably safe one lookup, but that undermines the database // as being the single source of truth for prefixes diff --git a/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt b/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt index de4070692..9f55a337c 100644 --- a/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt +++ b/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt @@ -3,6 +3,7 @@ package fredboat.config import fredboat.db.api.BlacklistRepository import fredboat.db.api.GuildPermissionsRepository import fredboat.db.api.GuildSettingsRepository +import fredboat.db.api.PrefixRepository import fredboat.db.mongo.* import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -24,4 +25,9 @@ class RepositoriesConfiguration { fun blacklistRepository(repo: InternalBlacklistRepository): BlacklistRepository { return BlacklistRepositoryImpl(repo) } + + @Bean + fun prefixRepository(repo: InternalPrefixRepository): PrefixRepository { + return PrefixRepositoryImpl(repo) + } } \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/api/PrefixRepository.kt b/FredBoat/src/main/java/fredboat/db/api/PrefixRepository.kt new file mode 100644 index 000000000..50d5aeaa5 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/api/PrefixRepository.kt @@ -0,0 +1,10 @@ +package fredboat.db.api + +import fredboat.db.transfer.Prefix +import java.util.* + +interface PrefixRepository : BaseRepository { + + fun getOptional(id: Long): Optional + +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/api/PrefixService.java b/FredBoat/src/main/java/fredboat/db/api/PrefixService.java deleted file mode 100644 index 6715b4b9c..000000000 --- a/FredBoat/src/main/java/fredboat/db/api/PrefixService.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.api; - -import fredboat.db.transfer.Prefix; -import fredboat.sentinel.Guild; - -import java.util.Optional; -import java.util.function.Function; - -/** - * Created by napster on 07.02.18. - */ -public interface PrefixService { - - Prefix transformPrefix(Guild guild, Function transformation); - - Optional getPrefix(Prefix.GuildBotId id); -} diff --git a/FredBoat/src/main/java/fredboat/db/mongo/PrefixRepositoryImpl.kt b/FredBoat/src/main/java/fredboat/db/mongo/PrefixRepositoryImpl.kt new file mode 100644 index 000000000..7b5faac75 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/mongo/PrefixRepositoryImpl.kt @@ -0,0 +1,17 @@ +package fredboat.db.mongo + +import fredboat.db.api.PrefixRepository +import fredboat.db.transfer.Prefix +import java.util.* + +class PrefixRepositoryImpl(repo: InternalPrefixRepository): BaseRepositoryImpl(repo), PrefixRepository { + + override fun default(id: Long): Prefix { + return Prefix(id) + } + + override fun getOptional(id: Long): Optional { + return Optional.ofNullable(fetch(id).block()?.prefix) + } + +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt index 382d36314..0e107d0c1 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt @@ -6,6 +6,7 @@ import fredboat.audio.queue.SplitAudioTrackContext import fredboat.db.transfer.BlacklistEntry import fredboat.db.transfer.GuildPermissions import fredboat.db.transfer.GuildSettings +import fredboat.db.transfer.Prefix import lavalink.client.LavalinkUtil import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -20,6 +21,7 @@ interface ActivityRepository : ReactiveCrudRepository interface InternalGuildSettingsRepository : ReactiveCrudRepository interface InternalGuildPermissionRepository: ReactiveCrudRepository interface InternalBlacklistRepository: ReactiveCrudRepository +interface InternalPrefixRepository: ReactiveCrudRepository private val log: Logger = LoggerFactory.getLogger(PlayerRepository::class.java) diff --git a/FredBoat/src/main/java/fredboat/db/rest/RestPrefixService.kt b/FredBoat/src/main/java/fredboat/db/rest/RestPrefixService.kt deleted file mode 100644 index 5b56bdda1..000000000 --- a/FredBoat/src/main/java/fredboat/db/rest/RestPrefixService.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.rest - -import fredboat.config.property.BackendConfig -import fredboat.db.FriendlyEntityService.fetchUserFriendly -import fredboat.db.api.PrefixService -import fredboat.db.transfer.Prefix -import fredboat.sentinel.Guild -import fredboat.sentinel.RawUser -import io.prometheus.client.guava.cache.CacheMetricsCollector -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.stereotype.Component -import org.springframework.web.client.RestClientException -import org.springframework.web.client.RestTemplate -import java.util.* -import java.util.function.Function - - -/** - * Created by napster on 17.02.18. - */ -@Component -class RestPrefixService( - @param:Qualifier("selfUser") - private val selfUser: RawUser, - backendConfig: BackendConfig, - quarterdeckRestTemplate: RestTemplate, - cacheMetrics: CacheMetricsCollector -) : CachedRestService( - backendConfig.quarterdeck.host + RestService.VERSION_PATH + PATH, - Prefix::class.java, - quarterdeckRestTemplate, - cacheMetrics, - RestPrefixService::class.java.simpleName -), PrefixService { - - companion object { - const val PATH = "prefix/" - } - - override fun transformPrefix(guild: Guild, transformation: Function): Prefix { - val prefix = fetchUserFriendly { fetch(Prefix.GuildBotId(guild, selfUser.id)) } - return fetchUserFriendly { merge(transformation.apply(prefix)) } - } - - override fun getPrefix(id: Prefix.GuildBotId): Optional { - try { - return Optional.ofNullable(backendRestTemplate.postForObject(path + "getraw", id, String::class.java)) - } catch (e: RestClientException) { - throw BackendException("Could not get prefix for guild " + id.guildId, e) - } - - } -} diff --git a/FredBoat/src/main/java/fredboat/db/transfer/Prefix.java b/FredBoat/src/main/java/fredboat/db/transfer/Prefix.java deleted file mode 100644 index 66b749ad5..000000000 --- a/FredBoat/src/main/java/fredboat/db/transfer/Prefix.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.transfer; - -import fredboat.sentinel.Guild; - -import javax.annotation.CheckReturnValue; -import javax.annotation.Nullable; -import java.io.Serializable; -import java.util.Objects; - -/** - * Created by napster on 20.03.18. - *

- * Transfer object for the {@link fredboat.db.entity.main.Prefix} - */ -//todo move ""business"" logic to the backend -public class Prefix implements TransferObject { - - private GuildBotId id; - @Nullable - private String prefix; - - public Prefix(GuildBotId id, @Nullable String prefix) { - this.id = id; - this.prefix = prefix; - } - - @Override - public void setId(GuildBotId id) { - this.id = id; - } - - @Override - public GuildBotId getId() { - return this.id; - } - - @Nullable - public String getPrefix() { - return this.prefix; - } - - @CheckReturnValue - public Prefix setPrefix(@Nullable String prefix) { - this.prefix = prefix; - return this; - } - - public static class GuildBotId implements Serializable { - private static final long serialVersionUID = -7996104189406039666L; - private long guildId; - private long botId; - - public GuildBotId(Guild guild, long botId) { - this(guild.getId(), botId); - } - - public GuildBotId(long guildId, long botId) { - this.guildId = guildId; - this.botId = botId; - } - - public long getGuildId() { - return guildId; - } - - public void setGuildId(long guildId) { - this.guildId = guildId; - } - - public long getBotId() { - return botId; - } - - public void setBotId(long botId) { - this.botId = botId; - } - - @Override - public int hashCode() { - return Objects.hash(guildId, botId); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof GuildBotId)) return false; - GuildBotId other = (GuildBotId) o; - return this.guildId == other.guildId && this.botId == other.botId; - } - - - @Override - public String toString() { - return GuildBotId.class.getSimpleName() + String.format("(G %s, B %s)", guildId, botId); - } - } -} diff --git a/FredBoat/src/main/java/fredboat/db/transfer/Prefix.kt b/FredBoat/src/main/java/fredboat/db/transfer/Prefix.kt new file mode 100644 index 000000000..843fa704b --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/transfer/Prefix.kt @@ -0,0 +1,11 @@ +package fredboat.db.transfer + +import org.springframework.data.annotation.Id +import org.springframework.data.mongodb.core.mapping.Document + +@Document(collection = "Prefix") +class Prefix( + @Id + override val id: Long, + var prefix: String? = null +) : MongoEntry \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/main/BotController.kt b/FredBoat/src/main/java/fredboat/main/BotController.kt index ab72ddc43..21e046059 100644 --- a/FredBoat/src/main/java/fredboat/main/BotController.kt +++ b/FredBoat/src/main/java/fredboat/main/BotController.kt @@ -30,7 +30,7 @@ class BotController(private val configProvider: ConfigPropertiesProvider, val guildSettingsRepository: GuildSettingsRepository, val guildModulesService: GuildModulesService, val guildPermissionsRepository: GuildPermissionsRepository, - val prefixService: PrefixService, + val prefixRepository: PrefixRepository, val sentinel: Sentinel, val sentinelCountingService: SentinelCountingService) { From 9f402170a72dba150f4f2d05db7dd62b21e2ca0d Mon Sep 17 00:00:00 2001 From: Nanabell Date: Fri, 8 Feb 2019 16:55:39 +0100 Subject: [PATCH 068/172] Combine Module, Permissions & Prefix into GuildSettings --- .../fredboat/command/config/ModulesCommand.kt | 37 ++-- .../command/config/PermissionsCommand.kt | 18 +- .../fredboat/command/config/PrefixCommand.kt | 14 +- .../commandmeta/CommandInitializer.kt | 12 +- .../commandmeta/abs/CommandContext.kt | 5 +- .../config/RepositoriesConfiguration.kt | 15 +- .../fredboat/db/api/GuildModulesService.java | 42 ----- .../db/api/GuildPermissionsRepository.kt | 5 - .../db/api/GuildSettingsRepository.kt | 1 + .../java/fredboat/db/api/PrefixRepository.kt | 10 -- .../mongo/GuildPermissionsRepositoryImpl.kt | 12 -- .../db/mongo/GuildSettingsRepositoryImpl.kt | 1 + .../fredboat/db/mongo/PrefixRepositoryImpl.kt | 17 -- .../java/fredboat/db/mongo/repositories.kt | 7 +- .../db/rest/RestGuildModulesService.java | 62 ------- .../fredboat/db/transfer/GuildModules.java | 169 ------------------ .../fredboat/db/transfer/GuildSettings.kt | 13 +- ...uildPermissions.kt => PermissionEntity.kt} | 9 +- .../main/java/fredboat/db/transfer/Prefix.kt | 11 -- .../main/java/fredboat/main/BotController.kt | 3 - .../src/main/java/fredboat/perms/PermsUtil.kt | 2 +- .../test/java/fredboat/perms/PermsUtilTest.kt | 9 +- .../testutil/util/MockGuildPermsService.kt | 24 +-- 23 files changed, 79 insertions(+), 419 deletions(-) delete mode 100644 FredBoat/src/main/java/fredboat/db/api/GuildModulesService.java delete mode 100644 FredBoat/src/main/java/fredboat/db/api/GuildPermissionsRepository.kt delete mode 100644 FredBoat/src/main/java/fredboat/db/api/PrefixRepository.kt delete mode 100644 FredBoat/src/main/java/fredboat/db/mongo/GuildPermissionsRepositoryImpl.kt delete mode 100644 FredBoat/src/main/java/fredboat/db/mongo/PrefixRepositoryImpl.kt delete mode 100644 FredBoat/src/main/java/fredboat/db/rest/RestGuildModulesService.java delete mode 100644 FredBoat/src/main/java/fredboat/db/transfer/GuildModules.java rename FredBoat/src/main/java/fredboat/db/transfer/{GuildPermissions.kt => PermissionEntity.kt} (78%) delete mode 100644 FredBoat/src/main/java/fredboat/db/transfer/Prefix.kt diff --git a/FredBoat/src/main/java/fredboat/command/config/ModulesCommand.kt b/FredBoat/src/main/java/fredboat/command/config/ModulesCommand.kt index 9606dad17..1b1ca468a 100644 --- a/FredBoat/src/main/java/fredboat/command/config/ModulesCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/ModulesCommand.kt @@ -33,22 +33,21 @@ import fredboat.commandmeta.CommandRegistry import fredboat.commandmeta.abs.Command import fredboat.commandmeta.abs.CommandContext import fredboat.commandmeta.abs.IConfigCommand -import fredboat.db.transfer.GuildModules +import fredboat.db.api.GuildSettingsRepository import fredboat.definitions.Module import fredboat.definitions.PermissionLevel -import fredboat.main.Launcher import fredboat.messaging.internal.Context import fredboat.perms.PermsUtil import fredboat.util.AsciiArtConstant.MAGICAL_LENNY import fredboat.util.Emojis -import java.util.function.Function +import kotlinx.coroutines.reactive.awaitSingle /** * Created by napster on 09.11.17. * * Turn modules on and off */ -class ModulesCommand(name: String, vararg aliases: String) : Command(name, *aliases), IConfigCommand { +class ModulesCommand(name: String, private val repo: GuildSettingsRepository, vararg aliases: String) : Command(name, *aliases), IConfigCommand { companion object { private const val ENABLE = "enable" @@ -88,27 +87,25 @@ class ModulesCommand(name: String, vararg aliases: String) : Command(name, *alia return } - val transform: Function - val output: String - if (enable) { - transform = Function { gm -> gm.enableModule(module) } - output = (context.i18nFormat("moduleEnable", "**${context.i18n(module.translationKey)}**") - + "\n" + context.i18nFormat("moduleShowCommands", - "`" + context.prefix + CommandInitializer.COMMANDS_COMM_NAME - + " " + context.i18n(module.translationKey) + "`")) - } else { - transform = Function { gm -> gm.disableModule(module) } - output = context.i18nFormat("moduleDisable", "**${context.i18n(module.translationKey)}**") - } + repo.fetch(context.guild.id).doOnSuccess { + it.get(module).enabled = enable + + if (enable) { + context.reply((context.i18nFormat("moduleEnable", "**${context.i18n(module.translationKey)}**") + + "\n" + context.i18nFormat("moduleShowCommands", + "`" + context.prefix + CommandInitializer.COMMANDS_COMM_NAME + + " " + context.i18n(module.translationKey) + "`"))) + } else { + context.reply(context.i18nFormat("moduleDisable", "**${context.i18n(module.translationKey)}**")) + } - Launcher.botController.guildModulesService.transformGuildModules(context.guild, transform) - context.reply(output)//if the transaction right above this line fails, it won't be reached, which is intended + }.let { repo.update(it) }.subscribe() } private suspend fun displayModuleStatus(context: CommandContext) { - val gm = Launcher.botController.guildModulesService.fetchGuildModules(context.guild) + val settings = repo.fetch(context.guild.id).awaitSingle() val moduleStatusFormatter = { module: Module -> - val goodOrBad = if (gm.isModuleEnabled(module, module.isEnabledByDefault)) Emojis.OK else Emojis.BAD + val goodOrBad = if (settings.get(module).enabled) Emojis.OK else Emojis.BAD goodOrBad + module.emoji + " " + context.i18n(module.translationKey) } var moduleStatus = "" diff --git a/FredBoat/src/main/java/fredboat/command/config/PermissionsCommand.kt b/FredBoat/src/main/java/fredboat/command/config/PermissionsCommand.kt index d33af68c8..facaa3a4f 100644 --- a/FredBoat/src/main/java/fredboat/command/config/PermissionsCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/PermissionsCommand.kt @@ -32,7 +32,7 @@ import fredboat.command.info.HelpCommand import fredboat.commandmeta.abs.Command import fredboat.commandmeta.abs.CommandContext import fredboat.commandmeta.abs.IConfigCommand -import fredboat.db.api.GuildPermissionsRepository +import fredboat.db.api.GuildSettingsRepository import fredboat.definitions.PermissionLevel import fredboat.messaging.internal.Context import fredboat.perms.Permission @@ -50,7 +50,7 @@ import java.util.* class PermissionsCommand( private val permissionLevel: PermissionLevel, - private val repo: GuildPermissionsRepository, + private val repo: GuildSettingsRepository, name: String, vararg aliases: String ) : Command(name, *aliases), IConfigCommand { @@ -100,7 +100,8 @@ class PermissionsCommand( val selected = ArgumentUtil.checkSingleFuzzySearchResult(search, context, term) ?: return val discordPerms = invoker.getPermissions(channel = null).awaitFirst() - val gp = repo.fetch(context.guild.id).awaitSingle() + val settings = repo.fetch(context.guild.id).awaitSingle() + val gp = settings.permissions if (!gp.fromEnum(permissionLevel).contains(mentionableToId(selected))) { context.replyWithName(context.i18nFormat("permsNotAdded", "`" + mentionableToName(selected) + "`", "`$permissionLevel`")) @@ -121,7 +122,7 @@ class PermissionsCommand( context.replyWithName(context.i18nFormat("permsRemoved", mentionableToName(selected), permissionLevel)) gp.fromEnum(permissionLevel, newList) - repo.update(gp).subscribe() + repo.update(settings).subscribe() } suspend fun add(context: CommandContext) { @@ -135,7 +136,8 @@ class PermissionsCommand( val selected = ArgumentUtil.checkSingleFuzzySearchResult(list, context, term) ?: return - val gp = repo.fetch(context.guild.id).awaitSingle() + val settings = repo.fetch(context.guild.id).awaitSingle() + val gp = settings.permissions if (gp.fromEnum(permissionLevel).contains(mentionableToId(selected))) { context.replyWithName(context.i18nFormat("permsAlreadyAdded", "`" + TextUtils.escapeMarkdown(mentionableToName(selected)) + "`", "`$permissionLevel`")) return @@ -147,15 +149,15 @@ class PermissionsCommand( context.replyWithName(context.i18nFormat("permsAdded", TextUtils.escapeMarkdown(mentionableToName(selected)), permissionLevel)) gp.fromEnum(permissionLevel, newList) - repo.update(gp).subscribe() + repo.update(settings).subscribe() } suspend fun list(context: CommandContext) { val guild = context.guild val invoker = context.member - val gp = repo.fetch(context.guild.id).awaitSingle() + val settings = repo.fetch(context.guild.id).awaitSingle() - val mentionables = idsToMentionables(guild, gp.fromEnum(permissionLevel)) + val mentionables = idsToMentionables(guild, settings.permissions.fromEnum(permissionLevel)) var roleMentions = "" var memberMentions = "" diff --git a/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt b/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt index 6c00fb130..ec641d967 100644 --- a/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt @@ -30,7 +30,7 @@ import com.google.common.cache.CacheLoader import fredboat.commandmeta.abs.Command import fredboat.commandmeta.abs.CommandContext import fredboat.commandmeta.abs.IConfigCommand -import fredboat.db.transfer.Prefix +import fredboat.db.api.GuildSettingsRepository import fredboat.definitions.PermissionLevel import fredboat.main.Launcher import fredboat.messaging.internal.Context @@ -44,14 +44,16 @@ import java.util.concurrent.TimeUnit /** * Created by napster on 19.10.17. */ -class PrefixCommand(cacheMetrics: CacheMetricsCollector, name: String, vararg aliases: String) : Command(name, *aliases), IConfigCommand { +class PrefixCommand(cacheMetrics: CacheMetricsCollector, + private val repo: GuildSettingsRepository, + name: String, vararg aliases: String) + : Command(name, *aliases), IConfigCommand { init { cacheMetrics.addCache("customPrefixes", CUSTOM_PREFIXES) } companion object { - val botId = Launcher.botController.sentinel.selfUser.id val CUSTOM_PREFIXES = CacheBuilder.newBuilder() //it is fine to check the db for updates occasionally, as we currently dont have any use case where we change //the value saved there through other means. in case we add such a thing (like a dashboard), consider lowering @@ -62,7 +64,7 @@ class PrefixCommand(cacheMetrics: CacheMetricsCollector, name: String, vararg al .expireAfterAccess(1, TimeUnit.MINUTES) //evict inactive guilds .concurrencyLevel(Launcher.botController.appConfig.shardCount) //each shard has a thread (main JDA thread) accessing this cache many times .build(CacheLoader.asyncReloading(CacheLoader.from> { guildId -> - Launcher.botController.prefixRepository.getOptional(guildId!!) + Optional.ofNullable(Launcher.botController.guildSettingsRepository.fetch(guildId!!).block()?.prefix) }, Launcher.botController.executor))!! fun giefPrefix(guildId: Long) = CacheUtil.getUncheckedUnwrapped(CUSTOM_PREFIXES, guildId) @@ -99,9 +101,9 @@ class PrefixCommand(cacheMetrics: CacheMetricsCollector, name: String, vararg al newPrefix = context.rawArgs } - Launcher.botController.prefixRepository.fetch(context.guild.id) + repo.fetch(context.guild.id) .doOnSuccess { it.prefix = newPrefix } - .let { Launcher.botController.prefixRepository.update(it) } + .let { repo.update(it) } .subscribe() //we could do a put instead of invalidate here and probably safe one lookup, but that undermines the database diff --git a/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt b/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt index 62457cd63..3959ec82a 100644 --- a/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt +++ b/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt @@ -45,7 +45,6 @@ import fredboat.command.music.seeking.SeekCommand import fredboat.command.util.* import fredboat.config.SentryConfiguration import fredboat.config.property.AppConfig -import fredboat.db.api.GuildPermissionsRepository import fredboat.db.api.GuildSettingsRepository import fredboat.definitions.Module import fredboat.definitions.PermissionLevel @@ -74,7 +73,6 @@ class CommandInitializer( sentinel: Sentinel, playerRegistry: PlayerRegistry, guildSettingsRepository: GuildSettingsRepository, - guildPermissionsRepository: GuildPermissionsRepository, appConfig: AppConfig, springContext: Supplier ) { @@ -142,13 +140,13 @@ class CommandInitializer( val configModule = CommandRegistry(Module.CONFIG) configModule.registerCommand(ConfigCommand(CONFIG_COMM_NAME, guildSettingsRepository, "cfg")) configModule.registerCommand(LanguageCommand(LANGUAGE_COMM_NAME, "lang")) - configModule.registerCommand(ModulesCommand("modules", "module", "mods")) - configModule.registerCommand(PrefixCommand(cacheMetrics, PREFIX_COMM_NAME, "pre")) + configModule.registerCommand(ModulesCommand("modules", guildSettingsRepository, "module", "mods")) + configModule.registerCommand(PrefixCommand(cacheMetrics, guildSettingsRepository, PREFIX_COMM_NAME, "pre")) configModule.registerCommand(ConfigWebInfoCommand("webinfo", repo = guildSettingsRepository, appConfig = appConfig)) /* Perms */ - configModule.registerCommand(PermissionsCommand(PermissionLevel.ADMIN, guildPermissionsRepository, "admin", "admins")) - configModule.registerCommand(PermissionsCommand(PermissionLevel.DJ, guildPermissionsRepository, "dj", "djs")) - configModule.registerCommand(PermissionsCommand(PermissionLevel.USER, guildPermissionsRepository, "user", "users")) + configModule.registerCommand(PermissionsCommand(PermissionLevel.ADMIN, guildSettingsRepository, "admin", "admins")) + configModule.registerCommand(PermissionsCommand(PermissionLevel.DJ, guildSettingsRepository, "dj", "djs")) + configModule.registerCommand(PermissionsCommand(PermissionLevel.USER, guildSettingsRepository, "user", "users")) // Moderation Module - Anything related to managing Discord guilds diff --git a/FredBoat/src/main/java/fredboat/commandmeta/abs/CommandContext.kt b/FredBoat/src/main/java/fredboat/commandmeta/abs/CommandContext.kt index 873177c99..995e71504 100644 --- a/FredBoat/src/main/java/fredboat/commandmeta/abs/CommandContext.kt +++ b/FredBoat/src/main/java/fredboat/commandmeta/abs/CommandContext.kt @@ -29,6 +29,7 @@ import fredboat.definitions.Module import fredboat.main.Launcher import fredboat.messaging.internal.Context import fredboat.sentinel.* +import kotlinx.coroutines.reactive.awaitFirst /** * Convenience container for values associated with an issued command @@ -66,7 +67,9 @@ class CommandContext( } val enabledModules: Collection - get() = Launcher.botController.guildModulesService.fetchGuildModules(this.guild).enabledModules + get() = Launcher.botController.guildSettingsRepository.fetch(guild.id).block()!! + .modules.filter { it.enabled } + .map { it.module } override val user: User get() = member.user diff --git a/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt b/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt index 9f55a337c..2c0ad67e2 100644 --- a/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt +++ b/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt @@ -1,9 +1,6 @@ package fredboat.config -import fredboat.db.api.BlacklistRepository -import fredboat.db.api.GuildPermissionsRepository -import fredboat.db.api.GuildSettingsRepository -import fredboat.db.api.PrefixRepository +import fredboat.db.api.* import fredboat.db.mongo.* import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -16,18 +13,8 @@ class RepositoriesConfiguration { return GuildSettingsRepositoryImpl(repo) } - @Bean - fun guildPermissionsRepository(repo: InternalGuildPermissionRepository): GuildPermissionsRepository { - return GuildPermissionsRepositoryImpl(repo) - } - @Bean fun blacklistRepository(repo: InternalBlacklistRepository): BlacklistRepository { return BlacklistRepositoryImpl(repo) } - - @Bean - fun prefixRepository(repo: InternalPrefixRepository): PrefixRepository { - return PrefixRepositoryImpl(repo) - } } \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/api/GuildModulesService.java b/FredBoat/src/main/java/fredboat/db/api/GuildModulesService.java deleted file mode 100644 index 417c621e8..000000000 --- a/FredBoat/src/main/java/fredboat/db/api/GuildModulesService.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.api; - -import fredboat.db.transfer.GuildModules; -import fredboat.sentinel.Guild; - -import java.util.function.Function; - -/** - * Created by napster on 07.02.18. - */ -public interface GuildModulesService { - - GuildModules fetchGuildModules(Guild guild); - - GuildModules transformGuildModules(Guild guild, Function transformation); - -} diff --git a/FredBoat/src/main/java/fredboat/db/api/GuildPermissionsRepository.kt b/FredBoat/src/main/java/fredboat/db/api/GuildPermissionsRepository.kt deleted file mode 100644 index 311dd9e69..000000000 --- a/FredBoat/src/main/java/fredboat/db/api/GuildPermissionsRepository.kt +++ /dev/null @@ -1,5 +0,0 @@ -package fredboat.db.api - -import fredboat.db.transfer.GuildPermissions - -interface GuildPermissionsRepository : BaseRepository \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/api/GuildSettingsRepository.kt b/FredBoat/src/main/java/fredboat/db/api/GuildSettingsRepository.kt index bfe8d0551..4e5fb3230 100644 --- a/FredBoat/src/main/java/fredboat/db/api/GuildSettingsRepository.kt +++ b/FredBoat/src/main/java/fredboat/db/api/GuildSettingsRepository.kt @@ -2,5 +2,6 @@ package fredboat.db.api import fredboat.db.transfer.GuildSettings import reactor.core.publisher.Mono +import java.util.* interface GuildSettingsRepository : BaseRepository \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/api/PrefixRepository.kt b/FredBoat/src/main/java/fredboat/db/api/PrefixRepository.kt deleted file mode 100644 index 50d5aeaa5..000000000 --- a/FredBoat/src/main/java/fredboat/db/api/PrefixRepository.kt +++ /dev/null @@ -1,10 +0,0 @@ -package fredboat.db.api - -import fredboat.db.transfer.Prefix -import java.util.* - -interface PrefixRepository : BaseRepository { - - fun getOptional(id: Long): Optional - -} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/GuildPermissionsRepositoryImpl.kt b/FredBoat/src/main/java/fredboat/db/mongo/GuildPermissionsRepositoryImpl.kt deleted file mode 100644 index d4c7c22fb..000000000 --- a/FredBoat/src/main/java/fredboat/db/mongo/GuildPermissionsRepositoryImpl.kt +++ /dev/null @@ -1,12 +0,0 @@ -package fredboat.db.mongo - -import fredboat.db.api.GuildPermissionsRepository -import fredboat.db.transfer.GuildPermissions - -class GuildPermissionsRepositoryImpl(repo: InternalGuildPermissionRepository) : BaseRepositoryImpl(repo), GuildPermissionsRepository { - - override fun default(id: Long): GuildPermissions { - return GuildPermissions(id) - } - -} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsRepositoryImpl.kt b/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsRepositoryImpl.kt index 4e55f0252..fffa7c605 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsRepositoryImpl.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsRepositoryImpl.kt @@ -2,6 +2,7 @@ package fredboat.db.mongo import fredboat.db.api.GuildSettingsRepository import fredboat.db.transfer.GuildSettings +import java.util.* class GuildSettingsRepositoryImpl(repo: InternalGuildSettingsRepository) : BaseRepositoryImpl(repo), GuildSettingsRepository { diff --git a/FredBoat/src/main/java/fredboat/db/mongo/PrefixRepositoryImpl.kt b/FredBoat/src/main/java/fredboat/db/mongo/PrefixRepositoryImpl.kt deleted file mode 100644 index 7b5faac75..000000000 --- a/FredBoat/src/main/java/fredboat/db/mongo/PrefixRepositoryImpl.kt +++ /dev/null @@ -1,17 +0,0 @@ -package fredboat.db.mongo - -import fredboat.db.api.PrefixRepository -import fredboat.db.transfer.Prefix -import java.util.* - -class PrefixRepositoryImpl(repo: InternalPrefixRepository): BaseRepositoryImpl(repo), PrefixRepository { - - override fun default(id: Long): Prefix { - return Prefix(id) - } - - override fun getOptional(id: Long): Optional { - return Optional.ofNullable(fetch(id).block()?.prefix) - } - -} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt index 0e107d0c1..2322df2aa 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt @@ -3,10 +3,7 @@ package fredboat.db.mongo import fredboat.audio.player.GuildPlayer import fredboat.audio.player.voiceChannel import fredboat.audio.queue.SplitAudioTrackContext -import fredboat.db.transfer.BlacklistEntry -import fredboat.db.transfer.GuildPermissions -import fredboat.db.transfer.GuildSettings -import fredboat.db.transfer.Prefix +import fredboat.db.transfer.* import lavalink.client.LavalinkUtil import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -19,9 +16,7 @@ import reactor.core.publisher.Mono interface PlayerRepository : ReactiveCrudRepository interface ActivityRepository : ReactiveCrudRepository interface InternalGuildSettingsRepository : ReactiveCrudRepository -interface InternalGuildPermissionRepository: ReactiveCrudRepository interface InternalBlacklistRepository: ReactiveCrudRepository -interface InternalPrefixRepository: ReactiveCrudRepository private val log: Logger = LoggerFactory.getLogger(PlayerRepository::class.java) diff --git a/FredBoat/src/main/java/fredboat/db/rest/RestGuildModulesService.java b/FredBoat/src/main/java/fredboat/db/rest/RestGuildModulesService.java deleted file mode 100644 index 22ff472b4..000000000 --- a/FredBoat/src/main/java/fredboat/db/rest/RestGuildModulesService.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.rest; - -import fredboat.config.property.BackendConfig; -import fredboat.db.api.GuildModulesService; -import fredboat.db.transfer.GuildModules; -import fredboat.sentinel.Guild; -import io.prometheus.client.guava.cache.CacheMetricsCollector; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; - -import java.util.function.Function; - -import static fredboat.db.FriendlyEntityService.fetchUserFriendly; - -/** - * Created by napster on 17.02.18. - */ -@Component -public class RestGuildModulesService extends CachedRestService implements GuildModulesService { - - public static final String PATH = "guildmodules/"; - - public RestGuildModulesService(BackendConfig backendConfig, RestTemplate quarterdeckRestTemplate, - CacheMetricsCollector cacheMetrics) { - super(backendConfig.getQuarterdeck().getHost() + VERSION_PATH + PATH, GuildModules.class, - quarterdeckRestTemplate, cacheMetrics, RestGuildModulesService.class.getSimpleName()); - } - - @Override - public GuildModules fetchGuildModules(Guild guild) { - return fetchUserFriendly(() -> fetch(guild.getId())); - } - - @Override - public GuildModules transformGuildModules(Guild guild, Function transformation) { - return fetchUserFriendly(() -> merge(transformation.apply(fetchGuildModules(guild)))); - } -} diff --git a/FredBoat/src/main/java/fredboat/db/transfer/GuildModules.java b/FredBoat/src/main/java/fredboat/db/transfer/GuildModules.java deleted file mode 100644 index f897f27db..000000000 --- a/FredBoat/src/main/java/fredboat/db/transfer/GuildModules.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.transfer; - -import fredboat.definitions.Module; - -import javax.annotation.CheckReturnValue; -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * Created by napster on 20.03.18. - *

- * Transfer object for the {@link fredboat.db.entity.main.GuildModules} - */ -//todo move ""business"" logic to the backend -public class GuildModules implements TransferObject { - - private long guildId; - @Nullable - private Boolean adminModule; - @Nullable - private Boolean infoModule; - @Nullable - private Boolean configModule; - @Nullable - private Boolean musicModule; - @Nullable - private Boolean modModule; - @Nullable - private Boolean utilModule; - @Nullable - private Boolean funModule; - - @Override - public void setId(Long guildId) { - this.guildId = guildId; - } - - @Override - public Long getId() { - return guildId; - } - - @Override - public int hashCode() { - return Objects.hashCode(this.guildId); - } - - @Override - public boolean equals(final Object obj) { - return (obj instanceof GuildModules) && ((GuildModules) obj).guildId == this.guildId; - } - - @CheckReturnValue - public GuildModules enableModule(Module module) { - return setModule(module, true); - } - - @CheckReturnValue - public GuildModules disableModule(Module module) { - return setModule(module, false); - } - - - @CheckReturnValue - public GuildModules resetModule(Module module) { - return setModule(module, null); - } - - @CheckReturnValue - private GuildModules setModule(Module module, @Nullable Boolean enabled) { - switch (module) { - case ADMIN: - adminModule = enabled; - break; - case INFO: - infoModule = enabled; - break; - case CONFIG: - configModule = enabled; - break; - case MUSIC: - musicModule = enabled; - break; - case MOD: - modModule = enabled; - break; - case UTIL: - utilModule = enabled; - break; - case FUN: - funModule = enabled; - break; - default: - throw new RuntimeException("Unknown Module " + module.name()); - } - return this; - } - - @Nullable - public Boolean isModuleEnabled(Module module) { - switch (module) { - case ADMIN: - return adminModule; - case INFO: - return infoModule; - case CONFIG: - return configModule; - case MUSIC: - return musicModule; - case MOD: - return modModule; - case UTIL: - return utilModule; - case FUN: - return funModule; - default: - throw new RuntimeException("Unknown Module " + module.name()); - } - } - - /** - * @return true if the provided module is enabled, false if not. If no value has been specified, return the provide - * default value. - */ - public boolean isModuleEnabled(Module module, boolean def) { - Boolean enabled = isModuleEnabled(module); - if (enabled != null) { - return enabled; - } else { - return def; - } - } - - public List getEnabledModules() { - List enabledModules = new ArrayList<>(); - for (Module module : Module.values()) { - if (isModuleEnabled(module, module.isEnabledByDefault())) { - enabledModules.add(module); - } - } - return enabledModules; - } -} diff --git a/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt b/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt index 0a9ef995d..16f020c28 100644 --- a/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt +++ b/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt @@ -1,5 +1,6 @@ package fredboat.db.transfer +import fredboat.definitions.Module import org.springframework.data.annotation.Id import org.springframework.data.mongodb.core.mapping.Document @@ -10,5 +11,13 @@ data class GuildSettings( var trackAnnounce: Boolean = false, var autoResume: Boolean = false, var allowPublicPlayerInfo: Boolean = false, - var lang: String = "en_US" -) : MongoEntry \ No newline at end of file + var lang: String = "en_US", + var prefix: String? = null, + var modules: List = Module.values().toList().map { ModuleEntity(it, true) }, + var permissions: PermissionEntity = PermissionEntity() +) : MongoEntry { + + fun get(module: Module): ModuleEntity { + return modules.first { it.module.name == module.name } + } +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/transfer/GuildPermissions.kt b/FredBoat/src/main/java/fredboat/db/transfer/PermissionEntity.kt similarity index 78% rename from FredBoat/src/main/java/fredboat/db/transfer/GuildPermissions.kt rename to FredBoat/src/main/java/fredboat/db/transfer/PermissionEntity.kt index c643a9428..afb349024 100644 --- a/FredBoat/src/main/java/fredboat/db/transfer/GuildPermissions.kt +++ b/FredBoat/src/main/java/fredboat/db/transfer/PermissionEntity.kt @@ -1,17 +1,12 @@ package fredboat.db.transfer import fredboat.definitions.PermissionLevel -import org.springframework.data.annotation.Id -import org.springframework.data.mongodb.core.mapping.Document -@Document(collection = "GuildPermissions") -data class GuildPermissions( - @Id - override val id: Long, +data class PermissionEntity( var adminList: List = emptyList(), var djList: List = emptyList(), var userList: List = emptyList() -) : MongoEntry { +) { fun fromEnum(level: PermissionLevel): List { return when (level) { PermissionLevel.ADMIN -> adminList diff --git a/FredBoat/src/main/java/fredboat/db/transfer/Prefix.kt b/FredBoat/src/main/java/fredboat/db/transfer/Prefix.kt deleted file mode 100644 index 843fa704b..000000000 --- a/FredBoat/src/main/java/fredboat/db/transfer/Prefix.kt +++ /dev/null @@ -1,11 +0,0 @@ -package fredboat.db.transfer - -import org.springframework.data.annotation.Id -import org.springframework.data.mongodb.core.mapping.Document - -@Document(collection = "Prefix") -class Prefix( - @Id - override val id: Long, - var prefix: String? = null -) : MongoEntry \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/main/BotController.kt b/FredBoat/src/main/java/fredboat/main/BotController.kt index 21e046059..2f5d394b0 100644 --- a/FredBoat/src/main/java/fredboat/main/BotController.kt +++ b/FredBoat/src/main/java/fredboat/main/BotController.kt @@ -28,9 +28,6 @@ class BotController(private val configProvider: ConfigPropertiesProvider, @param:Qualifier("loadAudioPlayerManager") val audioPlayerManager: AudioPlayerManager, val ratelimiter: Ratelimiter, val guildSettingsRepository: GuildSettingsRepository, - val guildModulesService: GuildModulesService, - val guildPermissionsRepository: GuildPermissionsRepository, - val prefixRepository: PrefixRepository, val sentinel: Sentinel, val sentinelCountingService: SentinelCountingService) { diff --git a/FredBoat/src/main/java/fredboat/perms/PermsUtil.kt b/FredBoat/src/main/java/fredboat/perms/PermsUtil.kt index ba93e853e..fc0df0d77 100644 --- a/FredBoat/src/main/java/fredboat/perms/PermsUtil.kt +++ b/FredBoat/src/main/java/fredboat/perms/PermsUtil.kt @@ -45,7 +45,7 @@ object PermsUtil { member.hasPermission(Permission.ADMINISTRATOR).awaitSingle() -> PermissionLevel.ADMIN else -> { - val gp = Launcher.botController.guildPermissionsRepository.fetch(member.guild.id).awaitSingle() + val gp = Launcher.botController.guildSettingsRepository.fetch(member.guild.id).awaitSingle().permissions when { checkList(gp.adminList, member) -> PermissionLevel.ADMIN diff --git a/FredBoat/src/test/java/fredboat/perms/PermsUtilTest.kt b/FredBoat/src/test/java/fredboat/perms/PermsUtilTest.kt index dd614b1dc..1d6cf7e20 100644 --- a/FredBoat/src/test/java/fredboat/perms/PermsUtilTest.kt +++ b/FredBoat/src/test/java/fredboat/perms/PermsUtilTest.kt @@ -1,6 +1,7 @@ package fredboat.perms -import fredboat.db.transfer.GuildPermissions +import fredboat.db.transfer.PermissionEntity +import fredboat.db.transfer.GuildSettings import fredboat.definitions.PermissionLevel import fredboat.sentinel.RawMember import fredboat.sentinel.getGuild @@ -33,19 +34,19 @@ internal class PermsUtilTest : IntegrationTest() { @Test fun testAdmin(permsService: MockGuildPermsService) { - permsService.factory = { GuildPermissions(it, adminList = listOf(Raws.adminRole.id)) } + permsService.factory = { GuildSettings(it, permissions = PermissionEntity(adminList = listOf(Raws.adminRole.id))) } assertEquals(PermissionLevel.ADMIN, Raws.napster.level) } @Test fun testDj(permsService: MockGuildPermsService) { - permsService.factory = { GuildPermissions(it, djList = listOf(Raws.adminRole.id)) } + permsService.factory = { GuildSettings(it, permissions = PermissionEntity(djList = listOf(Raws.adminRole.id))) } assertEquals(PermissionLevel.DJ, Raws.napster.level) } @Test fun testUser(permsService: MockGuildPermsService) { - permsService.factory = { GuildPermissions(it, userList = listOf(Raws.adminRole.id)) } + permsService.factory = { GuildSettings(it, permissions = PermissionEntity(userList = listOf(Raws.adminRole.id))) } assertEquals(PermissionLevel.USER, Raws.napster.level) } diff --git a/FredBoat/src/test/java/fredboat/testutil/util/MockGuildPermsService.kt b/FredBoat/src/test/java/fredboat/testutil/util/MockGuildPermsService.kt index e3cfb3d68..8ff269384 100644 --- a/FredBoat/src/test/java/fredboat/testutil/util/MockGuildPermsService.kt +++ b/FredBoat/src/test/java/fredboat/testutil/util/MockGuildPermsService.kt @@ -1,35 +1,35 @@ package fredboat.testutil.util -import fredboat.db.api.GuildPermissionsRepository -import fredboat.db.transfer.GuildPermissions +import fredboat.db.api.GuildSettingsRepository +import fredboat.db.transfer.GuildSettings import org.springframework.context.annotation.Primary import org.springframework.stereotype.Service import reactor.core.publisher.Mono @Service @Primary -class MockGuildPermsService : GuildPermissionsRepository { +class MockGuildPermsService : GuildSettingsRepository { - override fun default(id: Long): GuildPermissions { - return GuildPermissions(id) + override fun default(id: Long): GuildSettings { + return GuildSettings(id) } - final val default: (guild: Long) -> GuildPermissions = { GuildPermissions(it) } + final val default: (guild: Long) -> GuildSettings = { GuildSettings(it) } var factory = default - override fun fetch(guild: Long): Mono { - return Mono.just(factory(guild)) + override fun fetch(id: Long): Mono { + return Mono.just(factory(id)) } - override fun update(mono: Mono): Mono { + override fun update(mono: Mono): Mono { return mono.flatMap { Mono.just(factory(it.id)) } } - override fun update(permissions: GuildPermissions): Mono { - return Mono.just(permissions) + override fun update(target: GuildSettings): Mono { + return Mono.just(target) } - override fun delete(guild: Long): Mono { + override fun delete(id: Long): Mono { return Mono.empty() } } \ No newline at end of file From aecacfd0ad005f75469823bc751cbb069988098c Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sat, 9 Feb 2019 12:17:24 +0100 Subject: [PATCH 069/172] Final Cleanup & Implement SearchResultRepository --- .../config/RepositoriesConfiguration.kt | 7 + .../db/DatabaseNotReadyException.java | 53 ---- .../fredboat/db/FriendlyEntityService.java | 85 ------ .../java/fredboat/db/api/BaseRepository.kt | 8 +- .../fredboat/db/api/BlacklistRepository.kt | 4 +- .../fredboat/db/api/GuildDataService.java | 42 --- .../db/api/GuildSettingsRepository.kt | 4 +- .../fredboat/db/api/SearchResultRepository.kt | 6 + .../fredboat/db/api/SearchResultService.java | 51 ---- .../java/fredboat/db/api/package-info.java | 29 -- .../fredboat/db/mongo/BaseRepositoryImpl.kt | 26 +- .../db/mongo/BlacklistRepositoryImpl.kt | 8 +- .../db/mongo/GuildSettingsRepositoryImpl.kt | 4 +- .../db/mongo/SearchResultRepositoryImpl.kt | 13 + .../src/main/java/fredboat/db/mongo/player.kt | 5 +- .../java/fredboat/db/mongo/repositories.kt | 3 +- .../main/java/fredboat/db/package-info.java | 29 -- .../fredboat/db/rest/BackendException.java | 50 ---- .../fredboat/db/rest/CachedRestService.java | 85 ------ .../db/rest/RestGuildDataService.java | 61 ----- .../db/rest/RestSearchResultService.java | 80 ------ .../java/fredboat/db/rest/RestService.java | 97 ------- .../java/fredboat/db/rest/package-info.java | 28 -- .../{BlacklistEntry.kt => BlacklistEntity.kt} | 4 +- .../java/fredboat/db/transfer/GuildData.java | 59 ----- .../fredboat/db/transfer/GuildSettings.kt | 3 +- .../java/fredboat/db/transfer/ModuleEntity.kt | 8 + .../java/fredboat/db/transfer/MongoEntity.kt | 7 + .../java/fredboat/db/transfer/MongoEntry.kt | 5 - .../fredboat/db/transfer/SearchResult.java | 250 ------------------ .../java/fredboat/db/transfer/SearchResult.kt | 73 +++++ .../fredboat/db/transfer/TransferObject.java | 39 --- .../fredboat/db/transfer/package-info.java | 28 -- .../java/fredboat/event/GuildEventHandler.kt | 47 ++-- .../src/main/java/fredboat/feature/I18n.java | 4 - .../fredboat/util/ratelimit/Blacklist.java | 8 +- .../fredboat/util/rest/TrackSearcher.java | 40 +-- 37 files changed, 201 insertions(+), 1152 deletions(-) delete mode 100644 FredBoat/src/main/java/fredboat/db/DatabaseNotReadyException.java delete mode 100644 FredBoat/src/main/java/fredboat/db/FriendlyEntityService.java delete mode 100644 FredBoat/src/main/java/fredboat/db/api/GuildDataService.java create mode 100644 FredBoat/src/main/java/fredboat/db/api/SearchResultRepository.kt delete mode 100644 FredBoat/src/main/java/fredboat/db/api/SearchResultService.java delete mode 100644 FredBoat/src/main/java/fredboat/db/api/package-info.java create mode 100644 FredBoat/src/main/java/fredboat/db/mongo/SearchResultRepositoryImpl.kt delete mode 100644 FredBoat/src/main/java/fredboat/db/package-info.java delete mode 100644 FredBoat/src/main/java/fredboat/db/rest/BackendException.java delete mode 100644 FredBoat/src/main/java/fredboat/db/rest/CachedRestService.java delete mode 100644 FredBoat/src/main/java/fredboat/db/rest/RestGuildDataService.java delete mode 100644 FredBoat/src/main/java/fredboat/db/rest/RestSearchResultService.java delete mode 100644 FredBoat/src/main/java/fredboat/db/rest/RestService.java delete mode 100644 FredBoat/src/main/java/fredboat/db/rest/package-info.java rename FredBoat/src/main/java/fredboat/db/transfer/{BlacklistEntry.kt => BlacklistEntity.kt} (89%) delete mode 100644 FredBoat/src/main/java/fredboat/db/transfer/GuildData.java create mode 100644 FredBoat/src/main/java/fredboat/db/transfer/ModuleEntity.kt create mode 100644 FredBoat/src/main/java/fredboat/db/transfer/MongoEntity.kt delete mode 100644 FredBoat/src/main/java/fredboat/db/transfer/MongoEntry.kt delete mode 100644 FredBoat/src/main/java/fredboat/db/transfer/SearchResult.java create mode 100644 FredBoat/src/main/java/fredboat/db/transfer/SearchResult.kt delete mode 100644 FredBoat/src/main/java/fredboat/db/transfer/TransferObject.java delete mode 100644 FredBoat/src/main/java/fredboat/db/transfer/package-info.java diff --git a/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt b/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt index 2c0ad67e2..486b46fdd 100644 --- a/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt +++ b/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt @@ -1,9 +1,11 @@ package fredboat.config +import com.google.common.cache.CacheBuilder import fredboat.db.api.* import fredboat.db.mongo.* import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import java.util.concurrent.TimeUnit @Configuration class RepositoriesConfiguration { @@ -17,4 +19,9 @@ class RepositoriesConfiguration { fun blacklistRepository(repo: InternalBlacklistRepository): BlacklistRepository { return BlacklistRepositoryImpl(repo) } + + @Bean + fun searchResultRepository(repo: InternalSearchResultRepository): SearchResultRepository { + return SearchResultRepositoryImpl(repo) + } } \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/DatabaseNotReadyException.java b/FredBoat/src/main/java/fredboat/db/DatabaseNotReadyException.java deleted file mode 100644 index 54803981e..000000000 --- a/FredBoat/src/main/java/fredboat/db/DatabaseNotReadyException.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db; - -import fredboat.commandmeta.MessagingException; -import fredboat.feature.metrics.Metrics; - -public class DatabaseNotReadyException extends MessagingException { - private static final long serialVersionUID = -3320905078677229733L; - - private static final String DEFAULT_MESSAGE = "The database is not available currently. Please try again in a moment."; - - DatabaseNotReadyException(String str, Throwable cause) { - super(str, cause); - Metrics.databaseExceptionsCreated.inc(); - } - - DatabaseNotReadyException(String str) { - super(str); - Metrics.databaseExceptionsCreated.inc(); - } - - public DatabaseNotReadyException(Throwable cause) { - this(DEFAULT_MESSAGE, cause); - } - - public DatabaseNotReadyException() { - this(DEFAULT_MESSAGE); - } -} diff --git a/FredBoat/src/main/java/fredboat/db/FriendlyEntityService.java b/FredBoat/src/main/java/fredboat/db/FriendlyEntityService.java deleted file mode 100644 index 3d5a5976f..000000000 --- a/FredBoat/src/main/java/fredboat/db/FriendlyEntityService.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db; - -import fredboat.db.rest.BackendException; -import fredboat.util.func.NonnullSupplier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nullable; -import java.util.function.Supplier; - -/** - * Conversion of exceptions from communicating with the backend to userfriendly ones - */ -public class FriendlyEntityService { - - private static final Logger log = LoggerFactory.getLogger(FriendlyEntityService.class); - - //this is a utility class - private FriendlyEntityService() { - } - - /** - * Wrap an operation that throws a database exception so that it gets rethrown as one of our user friendly - * MessagingExceptions. MessagingExceptions or their causes are currently not expected to be logged further up, - * that's why we log the cause of it at this place. - */ - public static T fetchUserFriendly(NonnullSupplier operation) { - try { - return operation.get(); - } catch (BackendException e) { - log.error("EntityService database operation failed", e); - throw new DatabaseNotReadyException(e); - } - } - - /** - * Same as {@link FriendlyEntityService#fetchUserFriendly(NonnullSupplier)}, just with a nullable return. - */ - @Nullable - public static T getUserFriendly(Supplier operation) { - try { - return operation.get(); - } catch (BackendException e) { - log.error("EntityService database operation failed", e); - throw new DatabaseNotReadyException(e); - } - } - - /** - * Same as {@link FriendlyEntityService#fetchUserFriendly(NonnullSupplier)}, just without returning anything - */ - public static void doUserFriendly(Runnable operation) { - try { - operation.run(); - } catch (BackendException e) { - log.error("EntityService database operation failed", e); - throw new DatabaseNotReadyException(e); - } - } -} diff --git a/FredBoat/src/main/java/fredboat/db/api/BaseRepository.kt b/FredBoat/src/main/java/fredboat/db/api/BaseRepository.kt index b3bb57bea..8d931ad39 100644 --- a/FredBoat/src/main/java/fredboat/db/api/BaseRepository.kt +++ b/FredBoat/src/main/java/fredboat/db/api/BaseRepository.kt @@ -2,15 +2,15 @@ package fredboat.db.api import reactor.core.publisher.Mono -interface BaseRepository { +interface BaseRepository { - fun fetch(id: Long): Mono + fun fetch(id: ID): Mono fun update(mono: Mono): Mono fun update(target: T): Mono - fun delete(id: Long): Mono + fun remove(id: ID): Mono - fun default(id: Long): T + fun default(id: ID): T? } \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/api/BlacklistRepository.kt b/FredBoat/src/main/java/fredboat/db/api/BlacklistRepository.kt index 0ce60490f..77fba564b 100644 --- a/FredBoat/src/main/java/fredboat/db/api/BlacklistRepository.kt +++ b/FredBoat/src/main/java/fredboat/db/api/BlacklistRepository.kt @@ -1,5 +1,5 @@ package fredboat.db.api -import fredboat.db.transfer.BlacklistEntry +import fredboat.db.transfer.BlacklistEntity -interface BlacklistRepository : BaseRepository \ No newline at end of file +interface BlacklistRepository : BaseRepository \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/api/GuildDataService.java b/FredBoat/src/main/java/fredboat/db/api/GuildDataService.java deleted file mode 100644 index cf79fb3b8..000000000 --- a/FredBoat/src/main/java/fredboat/db/api/GuildDataService.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.api; - -import fredboat.db.transfer.GuildData; -import fredboat.sentinel.Guild; - -import java.util.function.Function; - -/** - * Created by napster on 07.02.18. - */ -public interface GuildDataService { - - GuildData fetchGuildData(Guild guild); - - GuildData transformGuildData(Guild guild, Function transformation); - -} diff --git a/FredBoat/src/main/java/fredboat/db/api/GuildSettingsRepository.kt b/FredBoat/src/main/java/fredboat/db/api/GuildSettingsRepository.kt index 4e5fb3230..d6105b713 100644 --- a/FredBoat/src/main/java/fredboat/db/api/GuildSettingsRepository.kt +++ b/FredBoat/src/main/java/fredboat/db/api/GuildSettingsRepository.kt @@ -1,7 +1,5 @@ package fredboat.db.api import fredboat.db.transfer.GuildSettings -import reactor.core.publisher.Mono -import java.util.* -interface GuildSettingsRepository : BaseRepository \ No newline at end of file +interface GuildSettingsRepository : BaseRepository \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/api/SearchResultRepository.kt b/FredBoat/src/main/java/fredboat/db/api/SearchResultRepository.kt new file mode 100644 index 000000000..fff1efdbe --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/api/SearchResultRepository.kt @@ -0,0 +1,6 @@ +package fredboat.db.api + +import fredboat.db.transfer.SearchResult +import fredboat.db.transfer.SearchResultId + +interface SearchResultRepository : BaseRepository \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/api/SearchResultService.java b/FredBoat/src/main/java/fredboat/db/api/SearchResultService.java deleted file mode 100644 index 8b45dcdaf..000000000 --- a/FredBoat/src/main/java/fredboat/db/api/SearchResultService.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.api; - -import fredboat.db.transfer.SearchResult; - -import java.util.Optional; - -/** - * Created by napster on 07.02.18. - */ -public interface SearchResultService { - - /** - * Merge a search result into the database. - * - * @return the merged SearchResult object, or null when there is no cache database - */ - Optional mergeSearchResult(SearchResult searchResult); - - /** - * @param maxAgeMillis the maximum age of the cached search result; provide a negative value for eternal cache - * @return the cached search result; may return null for a non-existing or outdated search, or when there is no - * cache database - */ - Optional getSearchResult(SearchResult.SearchResultId id, long maxAgeMillis); - -} diff --git a/FredBoat/src/main/java/fredboat/db/api/package-info.java b/FredBoat/src/main/java/fredboat/db/api/package-info.java deleted file mode 100644 index 432846658..000000000 --- a/FredBoat/src/main/java/fredboat/db/api/package-info.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -@space.npstr.annotations.FieldsAreNonNullByDefault -@space.npstr.annotations.ParametersAreNonnullByDefault -@space.npstr.annotations.ReturnTypesAreNonNullByDefault -package fredboat.db.api; diff --git a/FredBoat/src/main/java/fredboat/db/mongo/BaseRepositoryImpl.kt b/FredBoat/src/main/java/fredboat/db/mongo/BaseRepositoryImpl.kt index 38a7695f3..5ea836a5d 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/BaseRepositoryImpl.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/BaseRepositoryImpl.kt @@ -3,23 +3,29 @@ package fredboat.db.mongo import com.google.common.cache.Cache import com.google.common.cache.CacheBuilder import fredboat.db.api.BaseRepository -import fredboat.db.transfer.MongoEntry +import fredboat.db.transfer.MongoEntity import org.springframework.data.repository.reactive.ReactiveCrudRepository import reactor.core.publisher.Mono +import java.io.Serializable import java.util.concurrent.TimeUnit -abstract class BaseRepositoryImpl(private val repo: ReactiveCrudRepository) : ReactiveCrudRepository by repo, BaseRepository { +abstract class BaseRepositoryImpl>(private val repo: ReactiveCrudRepository) : ReactiveCrudRepository by repo, BaseRepository { - private val cache: Cache = CacheBuilder.newBuilder() + private val cache: Cache = CacheBuilder.newBuilder() .expireAfterAccess(60, TimeUnit.SECONDS) .expireAfterWrite(120, TimeUnit.SECONDS) - .build() + .build() - override fun fetch(id: Long): Mono { - return Mono.justOrEmpty(cache.getIfPresent(id)) - .switchIfEmpty(repo.findById(id)) - .defaultIfEmpty(default(id)) - .doOnSuccess { cache.put(id, it) } + override fun fetch(id: ID): Mono { + return if (default(id) != null) + Mono.justOrEmpty(cache.getIfPresent(id)) + .switchIfEmpty(repo.findById(id)) + .defaultIfEmpty(default(id)!!) + .doOnSuccess { cache.put(id, it) } + else + Mono.justOrEmpty(cache.getIfPresent(id)) + .switchIfEmpty(repo.findById(id)) + .doOnSuccess { if (it != null) cache.put(id, it) } } override fun update(mono: Mono): Mono { @@ -30,7 +36,7 @@ abstract class BaseRepositoryImpl(private val repo: ReactiveCrud return repo.save(target).doOnSuccess { cache.put(target.id, target) } } - override fun delete(id: Long): Mono { + override fun remove(id: ID): Mono { return repo.deleteById(id) } } \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/BlacklistRepositoryImpl.kt b/FredBoat/src/main/java/fredboat/db/mongo/BlacklistRepositoryImpl.kt index 0526183e9..73a17239f 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/BlacklistRepositoryImpl.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/BlacklistRepositoryImpl.kt @@ -1,11 +1,11 @@ package fredboat.db.mongo import fredboat.db.api.BlacklistRepository -import fredboat.db.transfer.BlacklistEntry +import fredboat.db.transfer.BlacklistEntity -class BlacklistRepositoryImpl(repo: InternalBlacklistRepository) : BaseRepositoryImpl(repo), BlacklistRepository { +class BlacklistRepositoryImpl(repo: InternalBlacklistRepository) : BaseRepositoryImpl(repo), BlacklistRepository { - override fun default(id: Long): BlacklistEntry { - return BlacklistEntry(id) + override fun default(id: Long): BlacklistEntity? { + return BlacklistEntity(id) } } \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsRepositoryImpl.kt b/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsRepositoryImpl.kt index fffa7c605..df97086f9 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsRepositoryImpl.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsRepositoryImpl.kt @@ -4,9 +4,9 @@ import fredboat.db.api.GuildSettingsRepository import fredboat.db.transfer.GuildSettings import java.util.* -class GuildSettingsRepositoryImpl(repo: InternalGuildSettingsRepository) : BaseRepositoryImpl(repo), GuildSettingsRepository { +class GuildSettingsRepositoryImpl(repo: InternalGuildSettingsRepository) : BaseRepositoryImpl(repo), GuildSettingsRepository { - override fun default(id: Long): GuildSettings { + override fun default(id: Long): GuildSettings? { return GuildSettings(id) } diff --git a/FredBoat/src/main/java/fredboat/db/mongo/SearchResultRepositoryImpl.kt b/FredBoat/src/main/java/fredboat/db/mongo/SearchResultRepositoryImpl.kt new file mode 100644 index 000000000..3f322b68f --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/mongo/SearchResultRepositoryImpl.kt @@ -0,0 +1,13 @@ +package fredboat.db.mongo + +import fredboat.db.api.SearchResultRepository +import fredboat.db.transfer.SearchResult +import fredboat.db.transfer.SearchResultId + +class SearchResultRepositoryImpl(repo: InternalSearchResultRepository) : BaseRepositoryImpl(repo), SearchResultRepository { + + override fun default(id: SearchResultId): SearchResult? { + return null + } + +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/player.kt b/FredBoat/src/main/java/fredboat/db/mongo/player.kt index fb749a9c9..ffc1f99fa 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/player.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/player.kt @@ -1,5 +1,6 @@ package fredboat.db.mongo +import fredboat.db.transfer.MongoEntity import org.bson.types.ObjectId import org.springframework.data.annotation.Id import org.springframework.data.mongodb.core.mapping.Document @@ -7,7 +8,7 @@ import org.springframework.data.mongodb.core.mapping.Document @Document(collection = "MongoPlayer") class MongoPlayer( @Id - val gid: Long, + override val id: Long, /** If the player was playing at the time of saving, * it may automatically be reloaded when we start the bot */ val playing: Boolean, @@ -24,7 +25,7 @@ class MongoPlayer( /** Channel to send event messages in */ val textChannel: Long?, val queue: List -) +) : MongoEntity class MongoTrack( val id: ObjectId, diff --git a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt index 2322df2aa..57309c385 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt @@ -16,7 +16,8 @@ import reactor.core.publisher.Mono interface PlayerRepository : ReactiveCrudRepository interface ActivityRepository : ReactiveCrudRepository interface InternalGuildSettingsRepository : ReactiveCrudRepository -interface InternalBlacklistRepository: ReactiveCrudRepository +interface InternalBlacklistRepository: ReactiveCrudRepository +interface InternalSearchResultRepository: ReactiveCrudRepository private val log: Logger = LoggerFactory.getLogger(PlayerRepository::class.java) diff --git a/FredBoat/src/main/java/fredboat/db/package-info.java b/FredBoat/src/main/java/fredboat/db/package-info.java deleted file mode 100644 index 21afb82d8..000000000 --- a/FredBoat/src/main/java/fredboat/db/package-info.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -@space.npstr.annotations.FieldsAreNonNullByDefault -@space.npstr.annotations.ParametersAreNonnullByDefault -@space.npstr.annotations.ReturnTypesAreNonNullByDefault -package fredboat.db; diff --git a/FredBoat/src/main/java/fredboat/db/rest/BackendException.java b/FredBoat/src/main/java/fredboat/db/rest/BackendException.java deleted file mode 100644 index d869e07e7..000000000 --- a/FredBoat/src/main/java/fredboat/db/rest/BackendException.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.rest; - -/** - * Created by napster on 28.02.18. - *

- * Usually wraps another exception so look for the cause. - */ -public class BackendException extends RuntimeException { - - private static final long serialVersionUID = -2936856678286772706L; - - public BackendException() {//force creation with a message / cause - } - - public BackendException(String message) { - super(message); - } - - public BackendException(String message, Throwable cause) { - super(message, cause); - } - - public BackendException(Throwable cause) { - super(cause); - } -} diff --git a/FredBoat/src/main/java/fredboat/db/rest/CachedRestService.java b/FredBoat/src/main/java/fredboat/db/rest/CachedRestService.java deleted file mode 100644 index 4482cb3f9..000000000 --- a/FredBoat/src/main/java/fredboat/db/rest/CachedRestService.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.rest; - -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import fredboat.db.transfer.TransferObject; -import fredboat.util.rest.CacheUtil; -import io.prometheus.client.guava.cache.CacheMetricsCollector; -import org.springframework.web.client.RestTemplate; - -import java.io.Serializable; -import java.util.concurrent.TimeUnit; - -/** - * Created by napster on 18.02.18. - */ -public abstract class CachedRestService> extends RestService { - - protected final LoadingCache cache; - - /** - * Create the CachedRestRepo using a default cache - */ - public CachedRestService(String path, Class entityClass, RestTemplate backendRestTemplate, - CacheMetricsCollector cacheMetrics, String cacheName) { - this(path, entityClass, backendRestTemplate, - CacheBuilder.newBuilder() - .expireAfterAccess(60, TimeUnit.SECONDS) - .expireAfterWrite(120, TimeUnit.SECONDS), - cacheMetrics, cacheName - ); - } - - public CachedRestService(String path, Class entityClass, RestTemplate backendRestTemplate, - CacheBuilder cacheBuilder, CacheMetricsCollector cacheMetrics, String cacheName) { - super(path, entityClass, backendRestTemplate); - this.cache = cacheBuilder.recordStats().build(CacheLoader.from(super::fetch)); - cacheMetrics.addCache(cacheName, cache); - } - - @Override - protected void delete(I id) { - try { - super.delete(id); - } finally { - cache.invalidate(id); - } - } - - @Override - public E fetch(I id) { - return CacheUtil.getUncheckedUnwrapped(cache, id); - } - - @Override - public E merge(E entity) { - E merged = super.merge(entity); - cache.put(merged.getId(), merged); - return merged; - } -} diff --git a/FredBoat/src/main/java/fredboat/db/rest/RestGuildDataService.java b/FredBoat/src/main/java/fredboat/db/rest/RestGuildDataService.java deleted file mode 100644 index 9db9cb3fc..000000000 --- a/FredBoat/src/main/java/fredboat/db/rest/RestGuildDataService.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.rest; - -import fredboat.config.property.BackendConfig; -import fredboat.db.api.GuildDataService; -import fredboat.db.transfer.GuildData; -import fredboat.sentinel.Guild; -import io.prometheus.client.guava.cache.CacheMetricsCollector; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; - -import java.util.function.Function; - -import static fredboat.db.FriendlyEntityService.fetchUserFriendly; -/** - * Created by napster on 17.02.18. - */ -@Component -public class RestGuildDataService extends CachedRestService implements GuildDataService { - - public static final String PATH = "guilddata/"; - - public RestGuildDataService(BackendConfig backendConfig, RestTemplate quarterdeckRestTemplate, - CacheMetricsCollector cacheMetrics) { - super(backendConfig.getQuarterdeck().getHost() + VERSION_PATH + PATH, GuildData.class, quarterdeckRestTemplate, - cacheMetrics, RestGuildDataService.class.getSimpleName()); - } - - @Override - public GuildData fetchGuildData(Guild guild) { - return fetchUserFriendly(() -> fetch(guild.getId())); - } - - @Override - public GuildData transformGuildData(Guild guild, Function transformation) { - return fetchUserFriendly(() -> merge(transformation.apply(fetchGuildData(guild)))); - } -} diff --git a/FredBoat/src/main/java/fredboat/db/rest/RestSearchResultService.java b/FredBoat/src/main/java/fredboat/db/rest/RestSearchResultService.java deleted file mode 100644 index 1d9fd48ec..000000000 --- a/FredBoat/src/main/java/fredboat/db/rest/RestSearchResultService.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.rest; - -import fredboat.config.property.BackendConfig; -import fredboat.db.api.SearchResultService; -import fredboat.db.transfer.SearchResult; -import io.prometheus.client.guava.cache.CacheMetricsCollector; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; - -import java.util.Optional; - -/** - * Created by napster on 17.02.18. - */ -@Component -public class RestSearchResultService extends CachedRestService implements SearchResultService { - - public static final String PATH = "searchresult/"; - - public RestSearchResultService(BackendConfig backendConfig, RestTemplate quarterdeckRestTemplate, - CacheMetricsCollector cacheMetrics) { - super(backendConfig.getQuarterdeck().getHost() + VERSION_PATH + PATH, SearchResult.class, - quarterdeckRestTemplate, cacheMetrics, RestSearchResultService.class.getSimpleName()); - } - - /** - * Merge a search result into the database. - * - * @return the merged SearchResult object, or null when there is no cache database - */ - @Override - public Optional mergeSearchResult(SearchResult searchResult) { - try { - return Optional.of(merge(searchResult)); - } catch (Exception e) { - log.error("Could not merge search result for " + searchResult.getId(), e); - return Optional.empty(); - } - } - - /** - * @param maxAgeMillis the maximum age of the cached search result; provide a negative value for eternal cache - * @return the cached search result; may return null for a non-existing or outdated search, or when there is no - * cache database - */ - @Override - public Optional getSearchResult(SearchResult.SearchResultId id, long maxAgeMillis) { - try { - return Optional.ofNullable(backendRestTemplate.postForObject(path + "getmaxaged?millis={millis}", id, - SearchResult.class, Long.toString(maxAgeMillis))); - } catch (Exception e) { - log.error("Could not get search result for " + id, e); - return Optional.empty(); - } - } -} diff --git a/FredBoat/src/main/java/fredboat/db/rest/RestService.java b/FredBoat/src/main/java/fredboat/db/rest/RestService.java deleted file mode 100644 index 3dc291afb..000000000 --- a/FredBoat/src/main/java/fredboat/db/rest/RestService.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.rest; - -import fredboat.db.transfer.TransferObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.web.client.RestClientException; -import org.springframework.web.client.RestTemplate; - -import java.io.Serializable; - -/** - * Created by napster on 17.02.18. - * - * Counterpart to the EntityController of the Quarterdeck module. - * The calls to methods of this class are expected to be wrapped by the service implementations - */ -public abstract class RestService> { - - protected static final Logger log = LoggerFactory.getLogger(RestService.class); - - public static final int API_VERSION = 0; - public static final String VERSION_PATH = "v" + API_VERSION + "/"; - - protected final String path; - protected final Class entityClass; - protected final RestTemplate backendRestTemplate; - - /** - * @param path base path of this resource, including the version and a trailing slash - * Example: http://quarterdeck:4269/v1/blacklist/ - */ - protected RestService(String path, Class entityClass, RestTemplate backendRestTemplate) { - this.path = path; - this.entityClass = entityClass; - this.backendRestTemplate = backendRestTemplate; - } - - protected Class getEntityClass() { - return entityClass; - } - - protected void delete(I id) { //todo success handling? - try { - backendRestTemplate.postForObject(path + "delete", id, Void.class); - } catch (RestClientException e) { - throw new BackendException(String.format("Could not delete entity with id %s of class %s", id, entityClass), e); - } - } - - protected E fetch(I id) { - try { - E result = backendRestTemplate.postForObject(path + "fetch", id, entityClass); - if (result == null) { - throw new BackendException(String.format("Fetched entity with id %s of class %s is null", id, entityClass)); - } - return result; - } catch (RestClientException e) { - throw new BackendException(String.format("Could not fetch entity with id %s of class %s", id, entityClass), e); - } - } - - protected E merge(E entity) { - try { - E result = backendRestTemplate.postForObject(path + "merge", entity, entityClass); - if (result == null) { - throw new BackendException(String.format("Merged entity with id %s of class %s is null", entity.getId(), entityClass)); - } - return result; - } catch (RestClientException e) { - throw new BackendException(String.format("Could not merge entity with id %s of class %s", entity.getId(), entityClass), e); - } - } -} diff --git a/FredBoat/src/main/java/fredboat/db/rest/package-info.java b/FredBoat/src/main/java/fredboat/db/rest/package-info.java deleted file mode 100644 index 24b77670d..000000000 --- a/FredBoat/src/main/java/fredboat/db/rest/package-info.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -@space.npstr.annotations.FieldsAreNonNullByDefault -@space.npstr.annotations.ParametersAreNonnullByDefault -@space.npstr.annotations.ReturnTypesAreNonNullByDefault -package fredboat.db.rest; diff --git a/FredBoat/src/main/java/fredboat/db/transfer/BlacklistEntry.kt b/FredBoat/src/main/java/fredboat/db/transfer/BlacklistEntity.kt similarity index 89% rename from FredBoat/src/main/java/fredboat/db/transfer/BlacklistEntry.kt rename to FredBoat/src/main/java/fredboat/db/transfer/BlacklistEntity.kt index 098b5832e..ce0d13431 100644 --- a/FredBoat/src/main/java/fredboat/db/transfer/BlacklistEntry.kt +++ b/FredBoat/src/main/java/fredboat/db/transfer/BlacklistEntity.kt @@ -4,13 +4,13 @@ import org.springframework.data.annotation.Id import org.springframework.data.mongodb.core.mapping.Document @Document(collection = "Blacklist") -data class BlacklistEntry( +data class BlacklistEntity( @Id override val id: Long, var level: Int = -1, var hitCount: Int = 0, var lastHitTime: Long = 0, var blacklistTime: Long = 0 -) : MongoEntry { +) : MongoEntity { fun incLevel() { level++ diff --git a/FredBoat/src/main/java/fredboat/db/transfer/GuildData.java b/FredBoat/src/main/java/fredboat/db/transfer/GuildData.java deleted file mode 100644 index c0ff1d2a6..000000000 --- a/FredBoat/src/main/java/fredboat/db/transfer/GuildData.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.transfer; - -import javax.annotation.CheckReturnValue; - -/** - * Created by napster on 20.03.18. - *

- * Transfer object for the {@link fredboat.db.entity.main.GuildData} - */ -//todo move ""business"" logic to the backend -public class GuildData implements TransferObject { - - private long guildId; - private long timestampHelloSent; - - @Override - public void setId(Long guildId) { - this.guildId = guildId; - } - - @Override - public Long getId() { - return guildId; - } - - public long getTimestampHelloSent() { - return timestampHelloSent; - } - - @CheckReturnValue - public GuildData helloSent() { - this.timestampHelloSent = System.currentTimeMillis(); - return this; - } -} diff --git a/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt b/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt index 16f020c28..140594714 100644 --- a/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt +++ b/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt @@ -8,6 +8,7 @@ import org.springframework.data.mongodb.core.mapping.Document data class GuildSettings( @Id override val id: Long, + var helloSent: Boolean = false, var trackAnnounce: Boolean = false, var autoResume: Boolean = false, var allowPublicPlayerInfo: Boolean = false, @@ -15,7 +16,7 @@ data class GuildSettings( var prefix: String? = null, var modules: List = Module.values().toList().map { ModuleEntity(it, true) }, var permissions: PermissionEntity = PermissionEntity() -) : MongoEntry { +) : MongoEntity { fun get(module: Module): ModuleEntity { return modules.first { it.module.name == module.name } diff --git a/FredBoat/src/main/java/fredboat/db/transfer/ModuleEntity.kt b/FredBoat/src/main/java/fredboat/db/transfer/ModuleEntity.kt new file mode 100644 index 000000000..914401283 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/transfer/ModuleEntity.kt @@ -0,0 +1,8 @@ +package fredboat.db.transfer + +import fredboat.definitions.Module + +data class ModuleEntity( + val module: Module, + var enabled: Boolean = true +) \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/transfer/MongoEntity.kt b/FredBoat/src/main/java/fredboat/db/transfer/MongoEntity.kt new file mode 100644 index 000000000..8f44ecb67 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/transfer/MongoEntity.kt @@ -0,0 +1,7 @@ +package fredboat.db.transfer + +import java.io.Serializable + +interface MongoEntity { + val id: ID +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/transfer/MongoEntry.kt b/FredBoat/src/main/java/fredboat/db/transfer/MongoEntry.kt deleted file mode 100644 index 53919300f..000000000 --- a/FredBoat/src/main/java/fredboat/db/transfer/MongoEntry.kt +++ /dev/null @@ -1,5 +0,0 @@ -package fredboat.db.transfer - -interface MongoEntry { - val id: Long -} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/transfer/SearchResult.java b/FredBoat/src/main/java/fredboat/db/transfer/SearchResult.java deleted file mode 100644 index feb917301..000000000 --- a/FredBoat/src/main/java/fredboat/db/transfer/SearchResult.java +++ /dev/null @@ -1,250 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.transfer; - -import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; -import com.sedmelluq.discord.lavaplayer.tools.io.MessageInput; -import com.sedmelluq.discord.lavaplayer.tools.io.MessageOutput; -import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; -import com.sedmelluq.discord.lavaplayer.track.AudioTrack; -import com.sedmelluq.discord.lavaplayer.track.BasicAudioPlaylist; -import fredboat.definitions.SearchProvider; -import org.apache.commons.lang3.SerializationUtils; - -import javax.annotation.Nullable; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * Created by napster on 20.03.18. - *

- * Transfer object for the {@link fredboat.db.entity.main.GuildConfig} - */ -//todo move ""business"" logic to the backend -public class SearchResult implements TransferObject { - - private SearchResultId searchResultId; - private long timestamp; - private byte[] serializedSearchResult; - - @Override - public void setId(SearchResultId id) { - this.searchResultId = id; - } - - public SearchResult(AudioPlayerManager playerManager, SearchProvider provider, String searchTerm, - AudioPlaylist searchResult) { - this.searchResultId = new SearchResultId(provider, searchTerm); - this.timestamp = System.currentTimeMillis(); - this.serializedSearchResult = SerializationUtils.serialize(new SearchResult.SerializableAudioPlaylist(playerManager, searchResult)); - } - - @Override - public SearchResultId getId() { - return searchResultId; - } - - public SearchProvider getProvider() { - return searchResultId.getProvider(); - } - - public void setProvider(SearchProvider provider) { - searchResultId.provider = provider.name(); - } - - public String getSearchTerm() { - return searchResultId.searchTerm; - } - - public void setSearchTerm(String searchTerm) { - this.searchResultId.searchTerm = searchTerm; - } - - public long getTimestamp() { - return timestamp; - } - - public void setTimestamp(long timestamp) { - this.timestamp = timestamp; - } - - public AudioPlaylist getSearchResult(AudioPlayerManager playerManager) { - SerializableAudioPlaylist sap = SerializationUtils.deserialize(serializedSearchResult); - return sap.decode(playerManager); - } - - public void setSearchResult(AudioPlayerManager playerManager, AudioPlaylist searchResult) { - this.serializedSearchResult = SerializationUtils.serialize(new SerializableAudioPlaylist(playerManager, searchResult)); - } - - /** - * Composite primary key for SearchResults - */ - public static class SearchResultId implements Serializable { - - private static final long serialVersionUID = 8969973651938173208L; - - private String provider; - - private String searchTerm; - - public SearchResultId(SearchProvider provider, String searchTerm) { - this.provider = provider.name(); - this.searchTerm = searchTerm; - } - - public SearchProvider getProvider() { - return SearchProvider.valueOf(provider); - } - - public void setProvider(SearchProvider provider) { - this.provider = provider.name(); - } - - public String getSearchTerm() { - return searchTerm; - } - - public void setSearchTerm(String searchTerm) { - this.searchTerm = searchTerm; - } - - @Override - public int hashCode() { - return Objects.hash(provider, searchTerm); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof SearchResultId)) return false; - SearchResultId other = (SearchResultId) o; - return provider.equals(other.provider) && searchTerm.equals(other.searchTerm); - } - - @Override - public String toString() { - return "Search: Provider " + provider + " Term " + searchTerm; - } - } - - - private static class SerializableAudioPlaylist implements Serializable { - private static final long serialVersionUID = -6823555858689776338L; - - @Nullable - private String name; - @SuppressWarnings("NullableProblems") //triggered by the empty no params constructor - private byte[][] tracks; - @Nullable - private byte[] selectedTrack; - private boolean isSearchResult; - - //required for deserialization - @SuppressWarnings("unused") - SerializableAudioPlaylist() { - } - - public SerializableAudioPlaylist(AudioPlayerManager playerManager, AudioPlaylist audioPlaylist) { - this.name = audioPlaylist.getName(); - this.tracks = encodeTracks(playerManager, audioPlaylist.getTracks()); - this.selectedTrack = encodeTrack(playerManager, audioPlaylist.getSelectedTrack()); - this.isSearchResult = audioPlaylist.isSearchResult(); - } - - public AudioPlaylist decode(AudioPlayerManager playerManager) { - return new BasicAudioPlaylist(name, - decodeTracks(playerManager, tracks), - decodeTrack(playerManager, selectedTrack), - isSearchResult); - } - - private static byte[][] encodeTracks(AudioPlayerManager playerManager, List tracks) { - byte[][] encoded = new byte[tracks.size()][]; - int skipped = 0; - for (int i = 0; i < tracks.size(); i++) { - encoded[i] = encodeTrack(playerManager, tracks.get(i)); - if (encoded[i] == null) { - skipped++; - } - } - - byte[][] result = new byte[tracks.size() - skipped][]; - int i = 0; - for (byte[] encodedTrack : encoded) { - if (encodedTrack != null) { - result[i] = encodedTrack; - i++; - } - } - - return result; - } - - //may return null if the encoding fails or the input is null - @Nullable - private static byte[] encodeTrack(AudioPlayerManager playerManager, @Nullable AudioTrack track) { - if (track == null) { - return null; - } - try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { - playerManager.encodeTrack(new MessageOutput(baos), track); - return baos.toByteArray(); - } catch (IOException ignored) { - return null; - } - } - - private static List decodeTracks(AudioPlayerManager playerManager, @Nullable byte[][] input) { - List result = new ArrayList<>(); - if (input == null) return result; - - for (byte[] track : input) { - AudioTrack decoded = decodeTrack(playerManager, track); - if (decoded != null) { - result.add(decoded); - } - } - return result; - } - - //may return null if the decoding fails or the input is null - @Nullable - private static AudioTrack decodeTrack(AudioPlayerManager playerManager, @Nullable byte[] input) { - if (input == null) return null; - ByteArrayInputStream bais = new ByteArrayInputStream(input); - try { - return playerManager.decodeTrack(new MessageInput(bais)).decodedTrack; - } catch (IOException e) { - return null; - } - } - } -} diff --git a/FredBoat/src/main/java/fredboat/db/transfer/SearchResult.kt b/FredBoat/src/main/java/fredboat/db/transfer/SearchResult.kt new file mode 100644 index 000000000..073a28679 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/transfer/SearchResult.kt @@ -0,0 +1,73 @@ +package fredboat.db.transfer + +import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist +import com.sedmelluq.discord.lavaplayer.track.BasicAudioPlaylist +import fredboat.definitions.SearchProvider +import lavalink.client.LavalinkUtil +import org.springframework.data.annotation.Id +import org.springframework.data.mongodb.core.mapping.Document +import java.io.Serializable + +@Document(collection = "SearchResult") +data class SearchResult( + @Id + override val id: SearchResultId, + val timestamp: Long, + val playlist: SerializableAudioPlaylist +) : MongoEntity { + + constructor(provider: SearchProvider, query: String, playlist: AudioPlaylist) + : this(SearchResultId(provider, query), System.currentTimeMillis(), SerializableAudioPlaylist(playlist)) + + fun getSearchResult(): AudioPlaylist { + return BasicAudioPlaylist( + playlist.name, + playlist.tracks.map { LavalinkUtil.toAudioTrack(it) }, + if (playlist.selectedTrack != null) LavalinkUtil.toAudioTrack(playlist.selectedTrack) else null, + playlist.isSearch ?: false) + } +} + +data class SearchResultId(val name: String, val query: String) : Serializable { + constructor(provider: SearchProvider, query: String) : this(provider.name, query) +} + +data class SerializableAudioPlaylist( + val name: String?, + val tracks: List, + val selectedTrack: ByteArray?, + val isSearch: Boolean? +) : Serializable { + + constructor(playlist: AudioPlaylist) : this( + playlist.name, + playlist.tracks.map { LavalinkUtil.toBinary(it) }, + if (playlist.selectedTrack != null) LavalinkUtil.toBinary(playlist.selectedTrack) else null, + playlist.isSearchResult + ) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as SerializableAudioPlaylist + + if (name != other.name) return false + if (tracks != other.tracks) return false + if (selectedTrack != null) { + if (other.selectedTrack == null) return false + if (!selectedTrack.contentEquals(other.selectedTrack)) return false + } else if (other.selectedTrack != null) return false + if (isSearch != other.isSearch) return false + + return true + } + + override fun hashCode(): Int { + var result = name?.hashCode() ?: 0 + result = 31 * result + tracks.hashCode() + result = 31 * result + (selectedTrack?.contentHashCode() ?: 0) + result = 31 * result + isSearch.hashCode() + return result + } +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/transfer/TransferObject.java b/FredBoat/src/main/java/fredboat/db/transfer/TransferObject.java deleted file mode 100644 index dd556ce8d..000000000 --- a/FredBoat/src/main/java/fredboat/db/transfer/TransferObject.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.db.transfer; - -import java.io.Serializable; - -/** - * Created by napster on 18.03.18. - *

- * Base type for our objects used to model the responses of the backend - */ -public interface TransferObject { - - void setId(I id); - - I getId(); -} diff --git a/FredBoat/src/main/java/fredboat/db/transfer/package-info.java b/FredBoat/src/main/java/fredboat/db/transfer/package-info.java deleted file mode 100644 index 343402caf..000000000 --- a/FredBoat/src/main/java/fredboat/db/transfer/package-info.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -@space.npstr.annotations.FieldsAreNonNullByDefault -@space.npstr.annotations.ParametersAreNonnullByDefault -@space.npstr.annotations.ReturnTypesAreNonNullByDefault -package fredboat.db.transfer; diff --git a/FredBoat/src/main/java/fredboat/event/GuildEventHandler.kt b/FredBoat/src/main/java/fredboat/event/GuildEventHandler.kt index 3cc326144..a70feb1dd 100644 --- a/FredBoat/src/main/java/fredboat/event/GuildEventHandler.kt +++ b/FredBoat/src/main/java/fredboat/event/GuildEventHandler.kt @@ -2,7 +2,7 @@ package fredboat.event import fredboat.audio.player.PlayerRegistry import fredboat.command.info.HelloCommand -import fredboat.db.api.GuildDataService +import fredboat.db.api.GuildSettingsRepository import fredboat.feature.metrics.Metrics import fredboat.sentinel.Guild import fredboat.sentinel.TextChannel @@ -13,15 +13,13 @@ import java.time.Instant @Component class GuildEventHandler( - private val guildDataService: GuildDataService, + private val repo: GuildSettingsRepository, private val playerRegistry: PlayerRegistry ) : SentinelEventHandler() { override fun onGuildJoin(guild: Guild) { // Wait a few seconds to allow permissions to be set and applied and propagated - val mono = Mono.create { - sendHelloOnJoin(guild) - } - mono.delaySubscription(Duration.ofSeconds(10)) + Mono.create { sendHelloOnJoin(guild) } + .delaySubscription(Duration.ofSeconds(10)) .subscribe() } @@ -33,28 +31,27 @@ class GuildEventHandler( } private fun sendHelloOnJoin(guild: Guild) { - //filter guilds that already received a hello message - // useful for when discord trolls us with fake guild joins - // or to prevent it send repeatedly due to kick and reinvite - val gd = guildDataService.fetchGuildData(guild) - if (gd.timestampHelloSent > 0) { - return - } + repo.fetch(guild.id).subscribe { gs -> - var channel: TextChannel? = guild.getTextChannel(guild.id) //old public channel - if (channel == null || !channel.canTalk()) { - //find first channel that we can talk in - guild.textChannels.forEach { _, tc -> - if (tc.canTalk()) { - channel = tc - return@forEach + //filter guilds that already received a hello message + // useful for when discord trolls us with fake guild joins + // or to prevent it send repeatedly due to kick and reinvite + if (gs.helloSent) { + return@subscribe + } + + var channel: TextChannel? = guild.getTextChannel(guild.id) //old public channel + if (channel == null || !channel.canTalk()) { + //find first channel that we can talk in + guild.textChannels.forEach { _, tc -> + if (tc.canTalk()) { + channel = tc + return@forEach + } } } + gs.helloSent = true + channel?.send(HelloCommand.getHello(guild))?.doOnSuccess { repo.update(gs) }?.subscribe() } - - //send actual hello message and persist on success - channel?.send(HelloCommand.getHello(guild)) - ?.doOnSuccess { guildDataService.transformGuildData(guild, { it.helloSent() }) } - ?.subscribe() } } \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/feature/I18n.java b/FredBoat/src/main/java/fredboat/feature/I18n.java index f2a35995a..8995aa6b3 100644 --- a/FredBoat/src/main/java/fredboat/feature/I18n.java +++ b/FredBoat/src/main/java/fredboat/feature/I18n.java @@ -25,7 +25,6 @@ package fredboat.feature; -import fredboat.db.DatabaseNotReadyException; import fredboat.db.transfer.GuildSettings; import fredboat.definitions.Language; import fredboat.sentinel.Guild; @@ -76,9 +75,6 @@ public static FredBoatLocale getLocale(@Nonnull Guild guild) { public static FredBoatLocale getLocale(long guild) { try { return LANGS.getOrDefault(getBotController().getGuildSettingsRepository().fetch(guild).block(Duration.ofSeconds(10)).getLang(), DEFAULT); - } catch (DatabaseNotReadyException e) { - //don't log spam the full exceptions or logs - return DEFAULT; } catch (Exception e) { log.error("Error when reading entity", e); return DEFAULT; diff --git a/FredBoat/src/main/java/fredboat/util/ratelimit/Blacklist.java b/FredBoat/src/main/java/fredboat/util/ratelimit/Blacklist.java index 4ba5a4a11..c48484461 100644 --- a/FredBoat/src/main/java/fredboat/util/ratelimit/Blacklist.java +++ b/FredBoat/src/main/java/fredboat/util/ratelimit/Blacklist.java @@ -25,7 +25,7 @@ package fredboat.util.ratelimit; import fredboat.db.api.BlacklistRepository; -import fredboat.db.transfer.BlacklistEntry; +import fredboat.db.transfer.BlacklistEntity; import fredboat.feature.metrics.Metrics; import java.util.Collections; @@ -82,7 +82,7 @@ public boolean isBlacklisted(long id) { //first of all, ppl that can never get blacklisted no matter what if (userWhiteList.contains(id)) return false; - BlacklistEntry blEntry = repository.fetch(id).block(); + BlacklistEntity blEntry = repository.fetch(id).block(); if (blEntry.getLevel() < 0) return false; //blacklist entry exists, but id hasn't actually been blacklisted yet @@ -102,7 +102,7 @@ public boolean isBlacklisted(long id) { public long hitRateLimit(long id) { //update blacklist entry of this id long blacklistingLength = 0; - BlacklistEntry blEntry = repository.fetch(id).block(); + BlacklistEntity blEntry = repository.fetch(id).block(); //synchronize on the individual blacklist entries since we are about to change and convertAndSave them // we can use these to synchronize because they are backed by a cache, subsequent calls to fetch them @@ -138,7 +138,7 @@ public long hitRateLimit(long id) { * completely resets a blacklist for an id */ public void liftBlacklist(long id) { - repository.delete(id); + repository.remove(id); } /** diff --git a/FredBoat/src/main/java/fredboat/util/rest/TrackSearcher.java b/FredBoat/src/main/java/fredboat/util/rest/TrackSearcher.java index c4cc58b37..2b2efc810 100644 --- a/FredBoat/src/main/java/fredboat/util/rest/TrackSearcher.java +++ b/FredBoat/src/main/java/fredboat/util/rest/TrackSearcher.java @@ -33,8 +33,9 @@ import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import com.sedmelluq.discord.lavaplayer.track.BasicAudioPlaylist; import fredboat.config.property.AppConfig; -import fredboat.db.api.SearchResultService; +import fredboat.db.api.SearchResultRepository; import fredboat.db.transfer.SearchResult; +import fredboat.db.transfer.SearchResultId; import fredboat.definitions.SearchProvider; import fredboat.feature.metrics.Metrics; import fredboat.feature.togglz.FeatureFlags; @@ -49,7 +50,6 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -69,18 +69,15 @@ public class TrackSearcher { private final AudioPlayerManager audioPlayerManager; private final YoutubeAPI youtubeAPI; - private final SearchResultService searchResultService; + private final SearchResultRepository repository; private final AppConfig appConfig; - private final ExecutorService executor; public TrackSearcher(@Qualifier("searchAudioPlayerManager") AudioPlayerManager audioPlayerManager, - YoutubeAPI youtubeAPI, SearchResultService searchResultService, AppConfig appConfig, - ExecutorService executor) { + YoutubeAPI youtubeAPI, SearchResultRepository repository, AppConfig appConfig) { this.audioPlayerManager = audioPlayerManager; this.youtubeAPI = youtubeAPI; - this.searchResultService = searchResultService; + this.repository = repository; this.appConfig = appConfig; - this.executor = executor; } public AudioPlaylist searchForTracks(String query, List providers) throws SearchingException { @@ -89,7 +86,6 @@ public AudioPlaylist searchForTracks(String query, List provider /** * @param query The search term - * @param cacheMaxAge Age of acceptable results from cache. * @param timeoutMillis How long to wait for each lavaplayer search to answer * @param providers Providers that shall be used for the search. They will be used in the order they are provided, the * result of the first successful one will be returned @@ -128,8 +124,7 @@ public AudioPlaylist searchForTracks(String query, long cacheMaxAge, int timeout if (!lavaplayerResult.getTracks().isEmpty()) { log.debug("Loaded search result {} {} from lavaplayer", provider, query); // got a search result? cache and return it - executor.execute(() -> searchResultService - .mergeSearchResult(new SearchResult(audioPlayerManager, provider, query, lavaplayerResult))); + repository.update(new SearchResult(provider, query, lavaplayerResult)).subscribe(); Metrics.searchHits.labels("lavaplayer-" + provider.name().toLowerCase()).inc(); return lavaplayerResult; } @@ -152,8 +147,7 @@ public AudioPlaylist searchForTracks(String query, long cacheMaxAge, int timeout if (!youtubeApiResult.getTracks().isEmpty()) { log.debug("Loaded search result {} {} from Youtube API", provider, query); // got a search result? cache and return it - executor.execute(() -> searchResultService - .mergeSearchResult(new SearchResult(audioPlayerManager, provider, query, youtubeApiResult))); + repository.update(new SearchResult(provider, query, youtubeApiResult)).subscribe(); Metrics.searchHits.labels("youtube-api").inc(); return youtubeApiResult; } @@ -180,10 +174,22 @@ public AudioPlaylist searchForTracks(String query, long cacheMaxAge, int timeout @Nullable private AudioPlaylist fromCache(SearchProvider provider, String searchTerm, long cacheMaxAge) { try { - SearchResult.SearchResultId id = new SearchResult.SearchResultId(provider, searchTerm); - return searchResultService.getSearchResult(id, cacheMaxAge) - .map(searchResult -> searchResult.getSearchResult(audioPlayerManager)) - .orElse(null); + SearchResultId id = new SearchResultId(provider, searchTerm); + SearchResult result = repository.fetch(id).block(); + + if (result == null) { + return null; + } + + // If the cache entry is old evict it from DB and return null + if ((result.getTimestamp() + cacheMaxAge) < System.currentTimeMillis()) { + repository.remove(result.getId()).subscribe(); + + return null; + } + + return result.getSearchResult(); + } catch (Exception e) { //could be a database issue, could be a serialization issue. better to catch them all here and "orderly" return log.warn("Could not retrieve cached search result from database.", e); From 0e1d50c421d3e5b701cab4ec95aa7d50ddeb1fbc Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sat, 9 Feb 2019 16:05:50 +0100 Subject: [PATCH 070/172] Remove accidental merge of the java classes --- .../command/music/control/PauseCommand.java | 72 -------- .../command/music/info/ListCommand.java | 163 ------------------ 2 files changed, 235 deletions(-) delete mode 100644 FredBoat/src/main/java/fredboat/command/music/control/PauseCommand.java delete mode 100644 FredBoat/src/main/java/fredboat/command/music/info/ListCommand.java diff --git a/FredBoat/src/main/java/fredboat/command/music/control/PauseCommand.java b/FredBoat/src/main/java/fredboat/command/music/control/PauseCommand.java deleted file mode 100644 index d9201e008..000000000 --- a/FredBoat/src/main/java/fredboat/command/music/control/PauseCommand.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package fredboat.command.music.control; - -import fredboat.audio.player.GuildPlayer; -import fredboat.commandmeta.abs.CommandContext; -import fredboat.commandmeta.abs.ICommandRestricted; -import fredboat.commandmeta.abs.IMusicCommand; -import fredboat.commandmeta.abs.JCommand; -import fredboat.definitions.PermissionLevel; -import fredboat.messaging.internal.Context; -import fredboat.util.TextUtils; - -import javax.annotation.Nonnull; - -import static fredboat.commandmeta.CommandInitializer.PLAY_COMM_NAME; -import static fredboat.main.LauncherKt.getBotController; - -public class PauseCommand extends JCommand implements IMusicCommand, ICommandRestricted { - - public PauseCommand(String name, String... aliases) { - super(name, aliases); - } - - @Override - public void onInvoke(@Nonnull CommandContext context) { - GuildPlayer player = getBotController().getPlayerRegistry().getOrCreate(context.getGuild()); - if (player.isQueueEmpty()) { - context.reply(context.i18n("playQueueEmpty")); - } else if (player.isPaused()) { - context.reply(context.i18n("pauseAlreadyPaused")); - } else { - player.pause(); - context.reply(context.i18nFormat("pauseSuccess", TextUtils.escapeMarkdown(context.getPrefix()))); - } - } - - @Nonnull - @Override - public String help(@Nonnull Context context) { - return "{0}{1}\n#" + context.i18n("helpPauseCommand"); - } - - @Nonnull - @Override - public PermissionLevel getMinimumPerms() { - return PermissionLevel.DJ; - } -} diff --git a/FredBoat/src/main/java/fredboat/command/music/info/ListCommand.java b/FredBoat/src/main/java/fredboat/command/music/info/ListCommand.java deleted file mode 100644 index 39a314849..000000000 --- a/FredBoat/src/main/java/fredboat/command/music/info/ListCommand.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package fredboat.command.music.info; - -import fredboat.audio.player.GuildPlayer; -import fredboat.audio.queue.AudioTrackContext; -import fredboat.commandmeta.abs.CommandContext; -import fredboat.commandmeta.abs.IMusicCommand; -import fredboat.commandmeta.abs.JCommand; -import fredboat.definitions.RepeatMode; -import fredboat.messaging.internal.Context; -import fredboat.sentinel.Member; -import fredboat.util.MessageBuilder; -import fredboat.util.TextUtils; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nonnull; -import java.util.List; - -import static fredboat.main.LauncherKt.getBotController; -import static fredboat.util.MessageBuilderKt.localMessageBuilder; - -public class ListCommand extends JCommand implements IMusicCommand { - - private static final org.slf4j.Logger log = LoggerFactory.getLogger(ListCommand.class); - - private static final int PAGE_SIZE = 10; - - public ListCommand(String name, String... aliases) { - super(name, aliases); - } - - @Override - public void onInvoke(@Nonnull CommandContext context) { - GuildPlayer player = getBotController().getPlayerRegistry().getExisting(context.getGuild()); - - if (player == null || player.isQueueEmpty()) { - context.reply(context.i18n("npNotPlaying")); - return; - } - - MessageBuilder mb = localMessageBuilder(); - - int page = 1; - if (context.hasArguments()) { - try { - page = Integer.valueOf(context.getArgs()[0]); - } catch (NumberFormatException ignored) {} - } - - int tracksCount = player.getTrackCount(); - int maxPages = (int) Math.ceil(((double) tracksCount - 1d)) / PAGE_SIZE + 1; - - page = Math.max(page, 1); - page = Math.min(page, maxPages); - - int i = (page - 1) * PAGE_SIZE; - int listEnd = (page - 1) * PAGE_SIZE + PAGE_SIZE; - listEnd = Math.min(listEnd, tracksCount); - - int numberLength = Integer.toString(listEnd).length(); - - List sublist = player.getTracksInRange(i, listEnd); - - if (player.isShuffle()) { - mb.append(context.i18n("listShowShuffled")); - mb.append("\n"); - if (player.getRepeatMode() == RepeatMode.OFF) - mb.append("\n"); - } - if (player.getRepeatMode() == RepeatMode.SINGLE) { - mb.append(context.i18n("listShowRepeatSingle")); - mb.append("\n"); - } else if (player.getRepeatMode() == RepeatMode.ALL) { - mb.append(context.i18n("listShowRepeatAll")); - mb.append("\n"); - } - - mb.append(context.i18nFormat("listPageNum", page, maxPages)); - mb.append("\n"); - mb.append("\n"); - - for (AudioTrackContext atc : sublist) { - String status = " "; - if (i == 0) { - status = player.isPlaying() ? " \\▶" : " \\\u23F8"; //Escaped play and pause emojis - } - Member member = atc.getMember(); - String username = member.getEffectiveName(); - - mb.code("[" + TextUtils.forceNDigits(i + 1, numberLength) + "]"); - if (atc.isPriority()) { - mb.append(" ").code("⬆"); - } - mb.append(status); - mb.append(context.i18nFormat("listAddedBy", TextUtils.escapeAndDefuse(atc.getEffectiveTitle()), - TextUtils.escapeAndDefuse(username), TextUtils.formatTime(atc.getEffectiveDuration()))); - mb.append("\n"); - - if (i == listEnd) { - break; - } - - i++; - } - - //Now add a timestamp for how much is remaining - String timestamp = TextUtils.formatTime(player.getTotalRemainingMusicTimeMillis()); - - long streams = player.getStreamsCount(); - long numTracks = tracksCount - streams; - - String desc; - - if (numTracks == 0) { - //We are only listening to streams - desc = context.i18nFormat(streams == 1 ? "listStreamsOnlySingle" : "listStreamsOnlyMultiple", - streams, streams == 1 ? - context.i18n("streamSingular") : context.i18n("streamPlural")); - } else { - - desc = context.i18nFormat(numTracks == 1 ? "listStreamsOrTracksSingle" : "listStreamsOrTracksMultiple", - numTracks, numTracks == 1 ? - context.i18n("trackSingular") : context.i18n("trackPlural"), timestamp, streams == 0 - ? "" : context.i18nFormat("listAsWellAsLiveStreams", streams, streams == 1 - ? context.i18n("streamSingular") : context.i18n("streamPlural"))); - } - - mb.append("\n").append(desc); - - context.reply(mb.build()); - - } - - @Nonnull - @Override - public String help(@Nonnull Context context) { - return "{0}{1} (page)\n#" + context.i18n("helpListCommand"); - } -} From 08de9cf203414e734ffae9d811f8fa4dd3aca7a5 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sat, 9 Feb 2019 16:06:11 +0100 Subject: [PATCH 071/172] Add missing imports for PlayCommand.kt --- .../main/java/fredboat/command/music/control/PlayCommand.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt index 45774d730..c6a7295c9 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt @@ -26,9 +26,7 @@ package fredboat.command.music.control import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist -import fredboat.audio.player.GuildPlayer -import fredboat.audio.player.PlayerLimiter -import fredboat.audio.player.VideoSelectionCache +import fredboat.audio.player.* import fredboat.command.info.HelpCommand import fredboat.commandmeta.abs.Command import fredboat.commandmeta.abs.CommandContext From f921b7421681f8577683d466e4df58c59f64adfa Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sat, 9 Feb 2019 16:06:47 +0100 Subject: [PATCH 072/172] GuildPlayer context is now named internalContext --- FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index b6cd8e1bf..052dfd420 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -109,7 +109,7 @@ class GuildPlayer( get() = audioTrackProvider is AbstractTrackProvider && audioTrackProvider.isShuffle set(shuffle) = if (audioTrackProvider is AbstractTrackProvider) { audioTrackProvider.isShuffle = shuffle - context?.isPriority = false + internalContext?.isPriority = false } else { throw UnsupportedOperationException("Can't shuffle " + audioTrackProvider.javaClass) } @@ -229,7 +229,7 @@ class GuildPlayer( fun reshuffle() { if (audioTrackProvider is AbstractTrackProvider) { audioTrackProvider.reshuffle() - context?.isPriority = false + internalContext?.isPriority = false updateClients() } else { throw UnsupportedOperationException("Can't reshuffle " + audioTrackProvider.javaClass) From a40a5a61469bd86fdc4be8482cb2bb602cd2d138 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sat, 9 Feb 2019 16:08:20 +0100 Subject: [PATCH 073/172] Rename Test MockGuildPermsService.delete to .remove --- .../test/java/fredboat/testutil/util/MockGuildPermsService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FredBoat/src/test/java/fredboat/testutil/util/MockGuildPermsService.kt b/FredBoat/src/test/java/fredboat/testutil/util/MockGuildPermsService.kt index 8ff269384..add3dc6b1 100644 --- a/FredBoat/src/test/java/fredboat/testutil/util/MockGuildPermsService.kt +++ b/FredBoat/src/test/java/fredboat/testutil/util/MockGuildPermsService.kt @@ -29,7 +29,7 @@ class MockGuildPermsService : GuildSettingsRepository { return Mono.just(target) } - override fun delete(id: Long): Mono { + override fun remove(id: Long): Mono { return Mono.empty() } } \ No newline at end of file From 1aba4c1ebdccb60c134c7965d7bd7f873b380328 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sat, 9 Feb 2019 16:09:12 +0100 Subject: [PATCH 074/172] Remove QuarterdeckConfiguration.java --- .../config/QuarterdeckConfiguration.java | 91 ------------------- 1 file changed, 91 deletions(-) delete mode 100644 FredBoat/src/main/java/fredboat/config/QuarterdeckConfiguration.java diff --git a/FredBoat/src/main/java/fredboat/config/QuarterdeckConfiguration.java b/FredBoat/src/main/java/fredboat/config/QuarterdeckConfiguration.java deleted file mode 100644 index 4c88415ab..000000000 --- a/FredBoat/src/main/java/fredboat/config/QuarterdeckConfiguration.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.config; - -import fredboat.config.property.BackendConfig; -import fredboat.db.rest.RestService; -import fredboat.main.ShutdownHandler; -import fredboat.shared.constant.ExitCodes; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.client.RestTemplate; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -/** - * Created by napster on 24.02.18. - *

- * Create our entity repositories - */ -@Configuration -public class QuarterdeckConfiguration { - - private static final Logger log = LoggerFactory.getLogger(QuarterdeckConfiguration.class); - - public QuarterdeckConfiguration(BackendConfig backendConfig, ShutdownHandler shutdownHandler, - RestTemplate quarterdeckRestTemplate) - throws InterruptedException { - BackendConfig.Quarterdeck quarterdeck = backendConfig.getQuarterdeck(); - - log.info("Contacting the quarterdeck backend"); - String[] apiVersions = null; - int attempts = 0; - Exception lastException = null; - while ((apiVersions == null || apiVersions.length < 1) && attempts < 100) { //total time is 100 sec - try { - apiVersions = quarterdeckRestTemplate.getForObject(quarterdeck.getHost() + "info/api/versions", String[].class); - } catch (Exception ignored) { - lastException = ignored; - attempts++; - Thread.sleep(1000); - } - } - - if (apiVersions == null || apiVersions.length < 1) { - log.error("Could not contact the quarterdeck backend. Please make sure it is started and configuration values are correct", lastException); - shutdownHandler.shutdown(ExitCodes.EXIT_CODE_ERROR); - return; - } - - List supportedApiVersions = Arrays.stream(apiVersions).map(v -> { - if (!v.startsWith("v")) return "v" + v; - else return v; - }).collect(Collectors.toList()); - log.info("Supported Quarterdeck API versions: {}", String.join(", ", supportedApiVersions)); - - - String ourVersion = Integer.toString(RestService.API_VERSION); - if (supportedApiVersions.contains(ourVersion) - || supportedApiVersions.contains("v" + ourVersion)) { - log.info("Using Quarterdeck API v{}", ourVersion); - } else { - log.error("Quarterdeck API does not support our expected version v{}. Update quarterdeck, or roll back this FredBoat version!", ourVersion); - shutdownHandler.shutdown(ExitCodes.EXIT_CODE_ERROR); - } - } -} From e24fa45318c70561bfa8e6f1d3b25a65e3873585 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 10 Feb 2019 10:45:46 +0100 Subject: [PATCH 075/172] Nitpicks, CodeStyle --- .../fredboat/audio/queue/SimpleTrackProvider.kt | 3 ++- .../fredboat/command/config/ConfigCommand.kt | 17 +++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt b/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt index e50e39610..91af3d2f9 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt @@ -207,7 +207,8 @@ class SimpleTrackProvider : AbstractTrackProvider() { shouldUpdateShuffledQueue = true tracks.reversed().forEach { it.rand = Integer.MIN_VALUE - queue.addFirst(it) } + queue.addFirst(it) + } } override fun clear() { diff --git a/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt b/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt index d9f27a04b..3705bc2ed 100644 --- a/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt @@ -51,16 +51,13 @@ class ConfigCommand(name: String, private val repo: GuildSettingsRepository, var } private fun printConfig(context: CommandContext) { - repo.fetch(context.guild.id) - .subscribe { - val mb = localMessageBuilder() - .append(context.i18nFormat("configNoArgs", context.guild.name)).append("\n") - .append("track_announce = ${it.trackAnnounce}\n") - .append("auto_resume = ${it.autoResume}\n") - .append("```") //opening ``` is part of the configNoArgs language string - - context.reply(mb.build()) - } + repo.fetch(context.guild.id).subscribe { + context.reply(localMessageBuilder() + .append(context.i18nFormat("configNoArgs", context.guild.name)).append("\n") + .append("track_announce = ${it.trackAnnounce}\n") + .append("auto_resume = ${it.autoResume}\n") + .append("```").build()) //opening ``` is part of the configNoArgs language string + } } private suspend fun setConfig(context: CommandContext) { From 0e3464d8699af581751711609cac51ee1dabb082 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 10 Feb 2019 11:07:19 +0100 Subject: [PATCH 076/172] Fix GuildPlayer isTrackAnnounceEnabled always being false --- .../java/fredboat/audio/player/GuildPlayer.kt | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index 052dfd420..bfae0cc42 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -52,6 +52,7 @@ import lavalink.client.player.event.PlayerEventListenerAdapter import org.bson.types.ObjectId import org.json.JSONObject import org.slf4j.LoggerFactory +import reactor.core.publisher.Mono import java.util.* import java.util.concurrent.ConcurrentLinkedQueue import java.util.function.Consumer @@ -114,14 +115,13 @@ class GuildPlayer( throw UnsupportedOperationException("Can't shuffle " + audioTrackProvider.javaClass) } - private val isTrackAnnounceEnabled: Boolean + private val isTrackAnnounceEnabled: Mono get() { - var enabled = false - if (guild.selfPresent) { - guildSettingsRepository.fetch(guild.id).subscribe { enabled = it.trackAnnounce } - } + if (guild.selfPresent) { + return guildSettingsRepository.fetch(guild.id).map { it.trackAnnounce } + } - return enabled + return Mono.just(false) } val playingTrack: AudioTrackContext? @@ -171,14 +171,19 @@ class GuildPlayer( } private fun announceTrack(atc: AudioTrackContext) { - if (repeatMode != RepeatMode.SINGLE && isTrackAnnounceEnabled && !isPaused) { - val activeTextChannel = activeTextChannel - activeTextChannel?.send(atc.i18nFormat( - "trackAnnounce", - atc.effectiveTitle.escapeAndDefuse(), - atc.member.effectiveName.escapeAndDefuse() - ))?.subscribe() - } + val activeTextChannel = activeTextChannel + + isTrackAnnounceEnabled.flatMap { + if (it && !isPaused && repeatMode != RepeatMode.SINGLE) { + activeTextChannel?.send(atc.i18nFormat( + "trackAnnounce", + atc.effectiveTitle.escapeAndDefuse(), + atc.member.effectiveName.escapeAndDefuse() + )) + } else { + Mono.empty() + } + }.subscribe() } private fun handleError(t: Throwable) { From 4b846223cd09d8d9e263b12dff24cc6dee9a6533 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 10 Feb 2019 11:07:54 +0100 Subject: [PATCH 077/172] Fix GuildEventHandler to actually send Hello on first join --- .../java/fredboat/event/GuildEventHandler.kt | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/event/GuildEventHandler.kt b/FredBoat/src/main/java/fredboat/event/GuildEventHandler.kt index a70feb1dd..a8a6c330f 100644 --- a/FredBoat/src/main/java/fredboat/event/GuildEventHandler.kt +++ b/FredBoat/src/main/java/fredboat/event/GuildEventHandler.kt @@ -1,5 +1,6 @@ package fredboat.event +import com.fredboat.sentinel.entities.SendMessageResponse import fredboat.audio.player.PlayerRegistry import fredboat.command.info.HelloCommand import fredboat.db.api.GuildSettingsRepository @@ -17,10 +18,7 @@ class GuildEventHandler( private val playerRegistry: PlayerRegistry ) : SentinelEventHandler() { override fun onGuildJoin(guild: Guild) { - // Wait a few seconds to allow permissions to be set and applied and propagated - Mono.create { sendHelloOnJoin(guild) } - .delaySubscription(Duration.ofSeconds(10)) - .subscribe() + sendHelloOnJoin(guild).subscribe() } override fun onGuildLeave(guildId: Long, joinTime: Instant) { @@ -30,14 +28,14 @@ class GuildEventHandler( Metrics.guildLifespan.observe(lifespan.toDouble()) } - private fun sendHelloOnJoin(guild: Guild) { - repo.fetch(guild.id).subscribe { gs -> + private fun sendHelloOnJoin(guild: Guild): Mono { + return repo.fetch(guild.id).flatMap { gs -> //filter guilds that already received a hello message // useful for when discord trolls us with fake guild joins // or to prevent it send repeatedly due to kick and reinvite if (gs.helloSent) { - return@subscribe + return@flatMap Mono.empty() } var channel: TextChannel? = guild.getTextChannel(guild.id) //old public channel @@ -50,8 +48,11 @@ class GuildEventHandler( } } } + gs.helloSent = true - channel?.send(HelloCommand.getHello(guild))?.doOnSuccess { repo.update(gs) }?.subscribe() + channel?.send(HelloCommand.getHello(guild)) + ?.delaySubscription(Duration.ofSeconds(10)) // Wait a few seconds to allow permissions to be set and applied and propagated + ?.flatMap { repo.update(gs) } ?: Mono.empty() } } -} \ No newline at end of file +} From d3b32e5018f227cdb14c5083e72ebe3f919e9a9a Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 10 Feb 2019 11:25:03 +0100 Subject: [PATCH 078/172] GuildPlayer announceTrack just use 2 subscribes --- .../src/main/java/fredboat/audio/player/GuildPlayer.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index bfae0cc42..5afda8a4e 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -173,17 +173,15 @@ class GuildPlayer( private fun announceTrack(atc: AudioTrackContext) { val activeTextChannel = activeTextChannel - isTrackAnnounceEnabled.flatMap { + isTrackAnnounceEnabled.subscribe { if (it && !isPaused && repeatMode != RepeatMode.SINGLE) { activeTextChannel?.send(atc.i18nFormat( "trackAnnounce", atc.effectiveTitle.escapeAndDefuse(), atc.member.effectiveName.escapeAndDefuse() - )) - } else { - Mono.empty() + ))?.subscribe() } - }.subscribe() + } } private fun handleError(t: Throwable) { From f2684abddb6f3f97ed6df705f9d23f4841651c1f Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 10 Feb 2019 11:26:19 +0100 Subject: [PATCH 079/172] GuildPlayer announceTrack to method and better name --- FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index 5afda8a4e..7a75fa4d9 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -115,8 +115,7 @@ class GuildPlayer( throw UnsupportedOperationException("Can't shuffle " + audioTrackProvider.javaClass) } - private val isTrackAnnounceEnabled: Mono - get() { + private fun getTrackAnnounceStatus(): Mono { if (guild.selfPresent) { return guildSettingsRepository.fetch(guild.id).map { it.trackAnnounce } } @@ -173,7 +172,7 @@ class GuildPlayer( private fun announceTrack(atc: AudioTrackContext) { val activeTextChannel = activeTextChannel - isTrackAnnounceEnabled.subscribe { + getTrackAnnounceStatus().subscribe { if (it && !isPaused && repeatMode != RepeatMode.SINGLE) { activeTextChannel?.send(atc.i18nFormat( "trackAnnounce", From 23a63e8ef9619cd15e7e368ea34454043da0ea41 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 10 Feb 2019 11:53:19 +0100 Subject: [PATCH 080/172] Update blocking Blacklist Methods to return Monos instead --- .../fredboat/command/util/UserInfoCommand.kt | 2 +- .../fredboat/event/MessageEventHandler.kt | 47 +++--- .../fredboat/util/ratelimit/Blacklist.java | 151 ------------------ .../java/fredboat/util/ratelimit/Blacklist.kt | 141 ++++++++++++++++ .../fredboat/util/ratelimit/Ratelimit.java | 18 ++- .../fredboat/util/ratelimit/Ratelimiter.java | 6 +- 6 files changed, 179 insertions(+), 186 deletions(-) delete mode 100644 FredBoat/src/main/java/fredboat/util/ratelimit/Blacklist.java create mode 100644 FredBoat/src/main/java/fredboat/util/ratelimit/Blacklist.kt diff --git a/FredBoat/src/main/java/fredboat/command/util/UserInfoCommand.kt b/FredBoat/src/main/java/fredboat/command/util/UserInfoCommand.kt index cd6c36415..e031ca7e8 100644 --- a/FredBoat/src/main/java/fredboat/command/util/UserInfoCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/util/UserInfoCommand.kt @@ -73,7 +73,7 @@ class UserInfoCommand(name: String, vararg aliases: String) : Command(name, *ali field(context.i18n("userinfoNick"), TextUtils.escapeMarkdown(target.effectiveName), true) field(context.i18n("userinfoJoinDate"), joinTimestamp, true) field(context.i18n("userinfoCreationTime"), target.user.creationTime.format(dtf), true) - field(context.i18n("userinfoBlacklisted"), Launcher.botController.ratelimiter.isBlacklisted(target.user.id).toString(), true) + field(context.i18n("userinfoBlacklisted"), Launcher.botController.ratelimiter.isBlacklisted(target.user.id).awaitSingle().toString(), true) field("Permission Level", PermsUtil.getPerms(target).label, true) //todo i18n }) } diff --git a/FredBoat/src/main/java/fredboat/event/MessageEventHandler.kt b/FredBoat/src/main/java/fredboat/event/MessageEventHandler.kt index b69358988..7e6d8fdfb 100644 --- a/FredBoat/src/main/java/fredboat/event/MessageEventHandler.kt +++ b/FredBoat/src/main/java/fredboat/event/MessageEventHandler.kt @@ -49,6 +49,7 @@ import fredboat.util.ratelimit.Ratelimiter import io.prometheus.client.guava.cache.CacheMetricsCollector import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch +import kotlinx.coroutines.reactive.awaitSingle import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.stereotype.Component @@ -78,12 +79,7 @@ class MessageEventHandler( } override fun onGuildMessage(event: MessageReceivedEvent) { - if (ratelimiter.isBlacklisted(event.author)) { - Metrics.blacklistedMessagesReceived.inc() - return - } - - if (sentinel.selfUser.id == event.author) log.info(if(event.content.isBlank()) "" else event.content) + if (sentinel.selfUser.id == event.author) log.info(if (event.content.isBlank()) "" else event.content) if (event.fromBot) return //Preliminary permission filter to avoid a ton of parsing @@ -94,6 +90,11 @@ class MessageEventHandler( && !event.content.contains(CommandInitializer.HELP_COMM_NAME)) return GlobalScope.launch { + if (ratelimiter.isBlacklisted(event.author).awaitSingle()) { + Metrics.blacklistedMessagesReceived.inc() + return@launch + } + val context = commandContextParser.parse(event) ?: return@launch // Renew the time to prevent invalidation @@ -141,38 +142,36 @@ class MessageEventHandler( } override fun onPrivateMessage(author: User, content: String) { - if (ratelimiter.isBlacklisted(author.id)) { - Metrics.blacklistedMessagesReceived.inc() - return - } - //Technically not possible anymore to receive private messages from bots but better safe than sorry //Also ignores our own messages since we're a bot if (author.isBot) { return } - //quick n dirty bot admin / owner check - if (appConfig.adminIds.contains(author.id) || sentinel.applicationInfo.ownerId == author.id) { + GlobalScope.launch { + if (ratelimiter.isBlacklisted(author.id).awaitSingle()) { + Metrics.blacklistedMessagesReceived.inc() + return@launch + } + + //quick n dirty bot admin / owner check + if (appConfig.adminIds.contains(author.id) || sentinel.applicationInfo.ownerId == author.id) { - //hack in / hardcode some commands; this is not meant to look clean - val lowered = content.toLowerCase() - if (lowered.contains("shard")) { - GlobalScope.launch { + //hack in / hardcode some commands; this is not meant to look clean + val lowered = content.toLowerCase() + if (lowered.contains("shard")) { for (message in ShardsCommand.getShardStatus(author.sentinel, content)) { author.sendPrivate(message).subscribe() } - } - return - } else if (lowered.contains("stats")) { - GlobalScope.launch { + return@launch + } else if (lowered.contains("stats")) { author.sendPrivate(StatsCommand.getStats(null)).subscribe() + return@launch } - return } - } - HelpCommand.sendGeneralHelp(author, content) + HelpCommand.sendGeneralHelp(author, content) + } } override fun onGuildMessageDelete(guildId: Long, channelId: Long, messageId: Long) { diff --git a/FredBoat/src/main/java/fredboat/util/ratelimit/Blacklist.java b/FredBoat/src/main/java/fredboat/util/ratelimit/Blacklist.java deleted file mode 100644 index c48484461..000000000 --- a/FredBoat/src/main/java/fredboat/util/ratelimit/Blacklist.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.util.ratelimit; - -import fredboat.db.api.BlacklistRepository; -import fredboat.db.transfer.BlacklistEntity; -import fredboat.feature.metrics.Metrics; - -import java.util.Collections; -import java.util.List; -import java.util.Set; - -/** - * Created by napster on 17.04.17. - *

- * Provides a forgiving blacklist with progressively increasing blacklist lengths - *

- * In an environment where shards are running in different containers and not inside a single jar this class will need - * some help in keeping bans up to date, that is, reading them from the database, either on changes (rethinkDB?) or - * through an agent in regular periods - */ -public class Blacklist { - - //this holds progressively increasing lengths of blacklisting in milliseconds - private static final List blacklistLevels; - - static { - blacklistLevels = List.of( - 1000L * 60, //one minute - 1000L * 600, //ten minutes - 1000L * 3600, //one hour - 1000L * 3600 * 24, //24 hours - 1000L * 3600 * 24 * 7 //a week - ); - } - - private final long rateLimitHitsBeforeBlacklist; - - //users that can never be blacklisted - private final Set userWhiteList; - - private final BlacklistRepository repository; //implementation as a RestRepo includes a cache - - - public Blacklist(BlacklistRepository repository, Set userWhiteList, long rateLimitHitsBeforeBlacklist) { - this.repository = repository; - this.rateLimitHitsBeforeBlacklist = rateLimitHitsBeforeBlacklist; - this.userWhiteList = Collections.unmodifiableSet(userWhiteList); - } - - /** - * @param id check whether this id is blacklisted - * @return true if the id is blacklisted, false if not - */ - //This will be called really fucking often, should be able to be accessed non-synchronized for performance - // -> don't do any writes in here - // -> don't call expensive methods - public boolean isBlacklisted(long id) { - - //first of all, ppl that can never get blacklisted no matter what - if (userWhiteList.contains(id)) return false; - - BlacklistEntity blEntry = repository.fetch(id).block(); - if (blEntry.getLevel() < 0) return false; //blacklist entry exists, but id hasn't actually been blacklisted yet - - - //id was a blacklisted, but it has run out - //noinspection RedundantIfStatement - if (System.currentTimeMillis() > blEntry.getBlacklistTime() + (getBlacklistTimeLength(blEntry.getLevel()))) { - return false; - } - - //looks like this id is blacklisted ¯\_(ツ)_/¯ - return true; - } - - /** - * @return length if issued blacklisting, 0 if none has been issued - */ - public long hitRateLimit(long id) { - //update blacklist entry of this id - long blacklistingLength = 0; - BlacklistEntity blEntry = repository.fetch(id).block(); - - //synchronize on the individual blacklist entries since we are about to change and convertAndSave them - // we can use these to synchronize because they are backed by a cache, subsequent calls to fetch them - // will return the same object - //noinspection SynchronizationOnLocalVariableOrMethodParameter - synchronized (blEntry) { - long now = System.currentTimeMillis(); - - //is the last ratelimit hit a long time away (1 hour)? then reset the ratelimit hits - if (now - blEntry.getLastHitTime() > 60 * 60 * 1000) { - blEntry.setHitCount(0); - } - blEntry.incHitCount(); - blEntry.setLastHitTime(now); - if (blEntry.getHitCount() >= rateLimitHitsBeforeBlacklist) { - //issue blacklist incident - blEntry.incLevel(); - if (blEntry.getLevel() < 0) blEntry.setLevel(0); - Metrics.autoBlacklistsIssued.labels(Integer.toString(blEntry.getLevel())).inc(); - blEntry.setBlacklistTime(now); - blEntry.setHitCount(0); //reset these for the next time - - blacklistingLength = getBlacklistTimeLength(blEntry.getLevel()); - } - //persist it - //if this turns up to be a performance bottleneck, have an agent run that persists the blacklist occasionally - repository.update(blEntry); - return blacklistingLength; - } - } - - /** - * completely resets a blacklist for an id - */ - public void liftBlacklist(long id) { - repository.remove(id); - } - - /** - * Return length of a blacklist incident in milliseconds depending on the blacklist level - */ - private long getBlacklistTimeLength(int blacklistLevel) { - if (blacklistLevel < 0) return 0; - return blacklistLevel >= blacklistLevels.size() ? blacklistLevels.get(blacklistLevels.size() - 1) : blacklistLevels.get(blacklistLevel); - } -} diff --git a/FredBoat/src/main/java/fredboat/util/ratelimit/Blacklist.kt b/FredBoat/src/main/java/fredboat/util/ratelimit/Blacklist.kt new file mode 100644 index 000000000..3c16272b0 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/util/ratelimit/Blacklist.kt @@ -0,0 +1,141 @@ +/* + * MIT License + * + * Copyright (c) 2017 Frederik Ar. Mikkelsen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package fredboat.util.ratelimit + +import fredboat.db.api.BlacklistRepository +import fredboat.feature.metrics.Metrics +import reactor.core.publisher.Mono +import java.util.* + +/** + * Created by napster on 17.04.17. + * + * + * Provides a forgiving blacklist with progressively increasing blacklist lengths + * + * + * In an environment where shards are running in different containers and not inside a single jar this class will need + * some help in keeping bans up to date, that is, reading them from the database, either on changes (rethinkDB?) or + * through an agent in regular periods + */ +class Blacklist(private val repository: BlacklistRepository, userWhiteList: Set, private val rateLimitHitsBeforeBlacklist: Long) { + + //users that can never be blacklisted + private val userWhiteList: Set = Collections.unmodifiableSet(userWhiteList) + + + /** + * @param id check whether this id is blacklisted + * @return true if the id is blacklisted, false if not + */ + //This will be called really fucking often, should be able to be accessed non-synchronized for performance + // -> don't do any writes in here + // -> don't call expensive methods + fun isBlacklisted(id: Long): Mono { + + //first of all, ppl that can never get blacklisted no matter what + return if (userWhiteList.contains(id)) Mono.just(false) else repository.fetch(id).map { (_, level, _, _, blacklistTime) -> + if (level < 0) return@map false // blacklist entry exists, but id hasn't actually been blacklisted yet + + // id was blacklisted, but it has run out + if (System.currentTimeMillis() > blacklistTime + getBlacklistTimeLength(level)) { + return@map false + } + + // looks like this id is blacklisted ¯\_(ツ)_/¯ + true + } + + } + + /** + * @return length if issued blacklisting, 0 if none has been issued + */ + fun hitRateLimit(id: Long): Mono { + //update blacklist entry of this id + var blacklistingLength: Long = 0 + return repository.fetch(id).map { + + //synchronize on the individual blacklist entries since we are about to change and convertAndSave them + // we can use these to synchronize because they are backed by a cache, subsequent calls to fetch them + // will return the same object + + synchronized(it) { + val now = System.currentTimeMillis() + + //is the last ratelimit hit a long time away (1 hour)? then reset the ratelimit hits + if (now - it!!.lastHitTime > 60 * 60 * 1000) { + it.hitCount = 0 + } + it.incHitCount() + it.lastHitTime = now + if (it.hitCount >= rateLimitHitsBeforeBlacklist) { + //issue blacklist incident + it.incLevel() + if (it.level < 0) it.level = 0 + Metrics.autoBlacklistsIssued.labels(Integer.toString(it.level)).inc() + it.blacklistTime = now + it.hitCount = 0 //reset these for the next time + + blacklistingLength = getBlacklistTimeLength(it.level) + } + //persist it + //if this turns up to be a performance bottleneck, have an agent run that persists the blacklist occasionally + repository.update(it).subscribe() + blacklistingLength + } + } + + + } + + /** + * completely resets a blacklist for an id + */ + fun liftBlacklist(id: Long) { + repository.remove(id) + } + + /** + * Return length of a blacklist incident in milliseconds depending on the blacklist level + */ + private fun getBlacklistTimeLength(blacklistLevel: Int): Long { + if (blacklistLevel < 0) return 0 + return if (blacklistLevel >= blacklistLevels.size) blacklistLevels[blacklistLevels.size - 1] else blacklistLevels[blacklistLevel] + } + + companion object { + + //this holds progressively increasing lengths of blacklisting in milliseconds + private val blacklistLevels: List = listOf( + 1000L * 60, //one minute + 1000L * 600, //ten minutes + 1000L * 3600, //one hour + 1000L * 3600 * 24, //24 hours + 1000L * 3600 * 24 * 7 //a week + ) + + } +} diff --git a/FredBoat/src/main/java/fredboat/util/ratelimit/Ratelimit.java b/FredBoat/src/main/java/fredboat/util/ratelimit/Ratelimit.java index 38617408d..0fce5f985 100644 --- a/FredBoat/src/main/java/fredboat/util/ratelimit/Ratelimit.java +++ b/FredBoat/src/main/java/fredboat/util/ratelimit/Ratelimit.java @@ -178,14 +178,16 @@ public boolean isAllowed(Context context, int weight, @Nullable Blacklist blackl * Best run async as the blacklist might be hitting a database */ private void bannerinoUserino(Context context, Blacklist blacklist) { - long length = blacklist.hitRateLimit(context.getUser().getId()); - if (length <= 0) { - return; //nothing to do here - } - long s = length / 1000; - String duration = String.format("%d:%02d:%02d", s / 3600, (s % 3600) / 60, (s % 60)); - String out = "\uD83D\uDD28 _**BLACKLISTED**_ \uD83D\uDD28 for **" + duration + "**"; - context.replyWithMention(out); + blacklist.hitRateLimit(context.getUser().getId()).subscribe(length -> { + if (length <= 0) { + return; //nothing to do here + } + + long s = length / 1000; + String duration = String.format("%d:%02d:%02d", s / 3600, (s % 3600) / 60, (s % 60)); + String out = "\uD83D\uDD28 _**BLACKLISTED**_ \uD83D\uDD28 for **" + duration + "**"; + context.replyWithMention(out); + }); } /** diff --git a/FredBoat/src/main/java/fredboat/util/ratelimit/Ratelimiter.java b/FredBoat/src/main/java/fredboat/util/ratelimit/Ratelimiter.java index 16a488a31..b18274c53 100644 --- a/FredBoat/src/main/java/fredboat/util/ratelimit/Ratelimiter.java +++ b/FredBoat/src/main/java/fredboat/util/ratelimit/Ratelimiter.java @@ -38,6 +38,7 @@ import fredboat.util.TextUtils; import io.prometheus.client.guava.cache.CacheMetricsCollector; import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; import javax.annotation.Nullable; import java.util.ArrayList; @@ -141,8 +142,8 @@ public boolean isRatelimited(Context context, Object command) { * @param id Id of the object whose blacklist status is to be checked, for example a userId or a guildId * @return true if the id is blacklisted, false if it's not */ - public boolean isBlacklisted(long id) { - return autoBlacklist != null && autoBlacklist.isBlacklisted(id); + public Mono isBlacklisted(long id) { + return (autoBlacklist != null) ? autoBlacklist.isBlacklisted(id) : Mono.just(false); } /** @@ -153,6 +154,7 @@ public void liftLimitAndBlacklist(long id) { ratelimit.liftLimit(id); } if (autoBlacklist != null) + autoBlacklist.liftBlacklist(id); } } From 870cf4060546803e61092e55875f6f2cfc7f97b7 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 10 Feb 2019 12:28:40 +0100 Subject: [PATCH 081/172] Convert I18n.java to Kotlin --- .../fredboat/command/info/HelpCommand.java | 2 +- .../src/main/java/fredboat/feature/I18n.java | 132 ------------------ .../src/main/java/fredboat/feature/I18n.kt | 107 ++++++++++++++ .../test/java/fredboat/feature/I18nTest.java | 6 +- 4 files changed, 111 insertions(+), 136 deletions(-) delete mode 100644 FredBoat/src/main/java/fredboat/feature/I18n.java create mode 100644 FredBoat/src/main/java/fredboat/feature/I18n.kt diff --git a/FredBoat/src/main/java/fredboat/command/info/HelpCommand.java b/FredBoat/src/main/java/fredboat/command/info/HelpCommand.java index 1d22020dc..d1bebc729 100644 --- a/FredBoat/src/main/java/fredboat/command/info/HelpCommand.java +++ b/FredBoat/src/main/java/fredboat/command/info/HelpCommand.java @@ -107,7 +107,7 @@ public static void sendGeneralHelp(User author, String content) { if (HELP_RECEIVED_RECENTLY.getIfPresent(author.getId()) != null) return; HELP_RECEIVED_RECENTLY.put(author.getId(), true); - author.sendPrivate(getHelpDmMsg(I18n.DEFAULT.getProps())).subscribe(); + author.sendPrivate(getHelpDmMsg(I18n.INSTANCE.getDEFAULT().getProps())).subscribe(); } public static String getFormattedCommandHelp(Context context, Command command, String commandOrAlias) { diff --git a/FredBoat/src/main/java/fredboat/feature/I18n.java b/FredBoat/src/main/java/fredboat/feature/I18n.java deleted file mode 100644 index 8995aa6b3..000000000 --- a/FredBoat/src/main/java/fredboat/feature/I18n.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package fredboat.feature; - -import fredboat.db.transfer.GuildSettings; -import fredboat.definitions.Language; -import fredboat.sentinel.Guild; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Mono; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.time.Duration; -import java.util.HashMap; -import java.util.MissingResourceException; -import java.util.ResourceBundle; - -import static fredboat.main.LauncherKt.getBotController; - -public class I18n { - - private static final Logger log = LoggerFactory.getLogger(I18n.class); - - public static FredBoatLocale DEFAULT = new FredBoatLocale(Language.EN_US); - public static final HashMap LANGS = new HashMap<>(); - - public static void start() { - for (Language language : Language.values()) { - LANGS.put(language.getCode(), new FredBoatLocale(language)); - } - log.info("Loaded " + LANGS.size() + " languages: " + LANGS); - } - - @Nonnull - public static ResourceBundle get(@Nullable Guild guild) { - if (guild == null) return DEFAULT.getProps(); - return get(guild.getId()); - } - - @Nonnull - public static ResourceBundle get(long guild) { - return getLocale(guild).getProps(); - } - - @Nonnull - public static FredBoatLocale getLocale(@Nonnull Guild guild) { - return getLocale(guild.getId()); - } - - @Nonnull - public static FredBoatLocale getLocale(long guild) { - try { - return LANGS.getOrDefault(getBotController().getGuildSettingsRepository().fetch(guild).block(Duration.ofSeconds(10)).getLang(), DEFAULT); - } catch (Exception e) { - log.error("Error when reading entity", e); - return DEFAULT; - } - } - - public static void set(@Nonnull Guild guild, @Nonnull String lang) throws LanguageNotSupportedException { - if (!LANGS.containsKey(lang)) - throw new LanguageNotSupportedException("Language not found"); - - Mono settings = getBotController().getGuildSettingsRepository().fetch(guild.getId()) - .doOnSuccess(guildSettings -> guildSettings.setLang(lang)); - - getBotController().getGuildSettingsRepository().update(settings).subscribe(); - } - - public static class FredBoatLocale { - - private final Language language; - private final ResourceBundle props; - - FredBoatLocale(Language language) throws MissingResourceException { - this.language = language; - props = ResourceBundle.getBundle("lang." + language.getCode(), language.getLocale()); - } - - public ResourceBundle getProps() { - return props; - } - - public String getCode() { - return language.getCode(); - } - - public String getNativeName() { - return language.getNativeName(); - } - - public String getEnglishName() { - return language.getEnglishName(); - } - - @Override - public String toString() { - return "[" + getCode() + " " + getNativeName() + "]"; - } - } - - public static class LanguageNotSupportedException extends Exception { - public LanguageNotSupportedException(String message) { - super(message); - } - } - -} diff --git a/FredBoat/src/main/java/fredboat/feature/I18n.kt b/FredBoat/src/main/java/fredboat/feature/I18n.kt new file mode 100644 index 000000000..364d3405a --- /dev/null +++ b/FredBoat/src/main/java/fredboat/feature/I18n.kt @@ -0,0 +1,107 @@ +/* + * MIT License + * + * Copyright (c) 2017 Frederik Ar. Mikkelsen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package fredboat.feature + +import fredboat.definitions.Language +import fredboat.main.getBotController +import fredboat.sentinel.Guild +import org.slf4j.LoggerFactory +import java.time.Duration +import java.util.* + +object I18n { + + private val log = LoggerFactory.getLogger(I18n::class.java) + + var DEFAULT = FredBoatLocale(Language.EN_US) + val LANGS = HashMap() + + fun start() { + for (language in Language.values()) { + LANGS[language.code] = FredBoatLocale(language) + } + log.info("Loaded " + LANGS.size + " languages: " + LANGS) + } + + operator fun get(guild: Guild?): ResourceBundle { + return if (guild == null) DEFAULT.props else get(guild.id) + } + + operator fun get(guild: Long): ResourceBundle { + return getLocale(guild).props + } + + fun getLocale(guild: Guild): FredBoatLocale { + return getLocale(guild.id) + } + + @Deprecated("Convert this to reactive at some point!") + fun getLocale(guild: Long): FredBoatLocale { + return try { + LANGS.getOrDefault(getBotController().guildSettingsRepository.fetch(guild).block(Duration.ofSeconds(5))?.lang, DEFAULT) + } catch (e: Exception) { + log.error("Error when reading entity", e) + DEFAULT + } + + } + + @Throws(LanguageNotSupportedException::class) + operator fun set(guild: Guild, lang: String) { + if (!LANGS.containsKey(lang)) + throw LanguageNotSupportedException("Language not found") + + val settings = getBotController().guildSettingsRepository.fetch(guild.id) + .doOnSuccess { guildSettings -> guildSettings.lang = lang } + + getBotController().guildSettingsRepository.update(settings).subscribe() + } + + class FredBoatLocale @Throws(MissingResourceException::class) + internal constructor(private val language: Language) { + val props: ResourceBundle + + val code: String + get() = language.code + + val nativeName: String + get() = language.nativeName + + val englishName: String + get() = language.englishName + + init { + props = ResourceBundle.getBundle("lang." + language.code, language.locale) + } + + override fun toString(): String { + return "[$code $nativeName]" + } + } + + class LanguageNotSupportedException(message: String) : Exception(message) + +} diff --git a/FredBoat/src/test/java/fredboat/feature/I18nTest.java b/FredBoat/src/test/java/fredboat/feature/I18nTest.java index 4d74310b6..afb91e001 100644 --- a/FredBoat/src/test/java/fredboat/feature/I18nTest.java +++ b/FredBoat/src/test/java/fredboat/feature/I18nTest.java @@ -34,10 +34,10 @@ public class I18nTest extends BaseTest { @Test public void testTranslatedStrings() { - I18n.start(); + I18n.INSTANCE.start(); - ResourceBundle id_ID = I18n.LANGS.get("id_ID").getProps(); - for(String key : I18n.DEFAULT.getProps().keySet()){ + ResourceBundle id_ID = I18n.INSTANCE.getLANGS().get("id_ID").getProps(); + for(String key : I18n.INSTANCE.getDEFAULT().getProps().keySet()){ Assertions.assertNotNull(id_ID.getString(key), () -> key + " prop missing in language files"); } } From 558bff929e514c4979403b0b29b6079ec1dfc078 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Tue, 12 Feb 2019 17:33:35 +0100 Subject: [PATCH 082/172] Add BaseDefaultedRepository --- .../main/java/fredboat/db/api/BaseDefaultedRepository.kt | 6 ++++++ FredBoat/src/main/java/fredboat/db/api/BaseRepository.kt | 2 -- .../src/main/java/fredboat/db/api/BlacklistRepository.kt | 2 +- .../main/java/fredboat/db/api/GuildSettingsRepository.kt | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 FredBoat/src/main/java/fredboat/db/api/BaseDefaultedRepository.kt diff --git a/FredBoat/src/main/java/fredboat/db/api/BaseDefaultedRepository.kt b/FredBoat/src/main/java/fredboat/db/api/BaseDefaultedRepository.kt new file mode 100644 index 000000000..081f7d8e5 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/api/BaseDefaultedRepository.kt @@ -0,0 +1,6 @@ +package fredboat.db.api + +interface BaseDefaultedRepository : BaseRepository { + + fun default(id: ID): T +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/api/BaseRepository.kt b/FredBoat/src/main/java/fredboat/db/api/BaseRepository.kt index 8d931ad39..c9ea29aee 100644 --- a/FredBoat/src/main/java/fredboat/db/api/BaseRepository.kt +++ b/FredBoat/src/main/java/fredboat/db/api/BaseRepository.kt @@ -11,6 +11,4 @@ interface BaseRepository { fun update(target: T): Mono fun remove(id: ID): Mono - - fun default(id: ID): T? } \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/api/BlacklistRepository.kt b/FredBoat/src/main/java/fredboat/db/api/BlacklistRepository.kt index 77fba564b..f07d09570 100644 --- a/FredBoat/src/main/java/fredboat/db/api/BlacklistRepository.kt +++ b/FredBoat/src/main/java/fredboat/db/api/BlacklistRepository.kt @@ -2,4 +2,4 @@ package fredboat.db.api import fredboat.db.transfer.BlacklistEntity -interface BlacklistRepository : BaseRepository \ No newline at end of file +interface BlacklistRepository : BaseDefaultedRepository \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/api/GuildSettingsRepository.kt b/FredBoat/src/main/java/fredboat/db/api/GuildSettingsRepository.kt index d6105b713..181442d83 100644 --- a/FredBoat/src/main/java/fredboat/db/api/GuildSettingsRepository.kt +++ b/FredBoat/src/main/java/fredboat/db/api/GuildSettingsRepository.kt @@ -2,4 +2,4 @@ package fredboat.db.api import fredboat.db.transfer.GuildSettings -interface GuildSettingsRepository : BaseRepository \ No newline at end of file +interface GuildSettingsRepository : BaseDefaultedRepository \ No newline at end of file From 27ea84ed2c99d90c3ba22cf1af4d967dcce7f939 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Tue, 12 Feb 2019 17:34:57 +0100 Subject: [PATCH 083/172] Switch Prefix Cache to Caffeine --- .../fredboat/config/MetricsConfiguration.java | 9 +++-- .../config/RepositoriesConfiguration.kt | 15 ++++---- .../db/mongo/BaseDefaultedRepositoryImpl.kt | 19 ++++++++++ .../fredboat/db/mongo/BaseRepositoryImpl.kt | 37 ++++++++++--------- .../db/mongo/BlacklistRepositoryImpl.kt | 9 ++++- .../db/mongo/GuildSettingsRepositoryImpl.kt | 9 ++++- .../db/mongo/SearchResultRepositoryImpl.kt | 13 +++---- Shared/build.gradle | 4 ++ build.gradle | 1 + 9 files changed, 78 insertions(+), 38 deletions(-) create mode 100644 FredBoat/src/main/java/fredboat/db/mongo/BaseDefaultedRepositoryImpl.kt diff --git a/FredBoat/src/main/java/fredboat/config/MetricsConfiguration.java b/FredBoat/src/main/java/fredboat/config/MetricsConfiguration.java index 70d240980..5cd8cb2ca 100644 --- a/FredBoat/src/main/java/fredboat/config/MetricsConfiguration.java +++ b/FredBoat/src/main/java/fredboat/config/MetricsConfiguration.java @@ -24,7 +24,6 @@ package fredboat.config; -import io.prometheus.client.guava.cache.CacheMetricsCollector; import io.prometheus.client.logback.InstrumentedAppender; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -37,8 +36,12 @@ public class MetricsConfiguration { //guava cache metrics @Bean - public CacheMetricsCollector cacheMetrics() { - return new CacheMetricsCollector().register(); + public io.prometheus.client.guava.cache.CacheMetricsCollector guavaCacheMetrics() { + return new io.prometheus.client.guava.cache.CacheMetricsCollector().register(); + } + + public io.prometheus.client.cache.caffeine.CacheMetricsCollector caffeineCacheMetrics() { + return new io.prometheus.client.cache.caffeine.CacheMetricsCollector().register(); } @Bean diff --git a/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt b/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt index 486b46fdd..afa243a19 100644 --- a/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt +++ b/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt @@ -1,27 +1,28 @@ package fredboat.config -import com.google.common.cache.CacheBuilder -import fredboat.db.api.* +import fredboat.db.api.BlacklistRepository +import fredboat.db.api.GuildSettingsRepository +import fredboat.db.api.SearchResultRepository import fredboat.db.mongo.* +import io.prometheus.client.cache.caffeine.CacheMetricsCollector import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import java.util.concurrent.TimeUnit @Configuration -class RepositoriesConfiguration { +class RepositoriesConfiguration(private val cacheMetrics: CacheMetricsCollector) { @Bean fun guildSettingsRepository(repo: InternalGuildSettingsRepository): GuildSettingsRepository { - return GuildSettingsRepositoryImpl(repo) + return GuildSettingsRepositoryImpl(repo, cacheMetrics, GuildSettingsRepository::class.java.simpleName) } @Bean fun blacklistRepository(repo: InternalBlacklistRepository): BlacklistRepository { - return BlacklistRepositoryImpl(repo) + return BlacklistRepositoryImpl(repo, cacheMetrics, BlacklistRepository::class.java.simpleName) } @Bean fun searchResultRepository(repo: InternalSearchResultRepository): SearchResultRepository { - return SearchResultRepositoryImpl(repo) + return SearchResultRepositoryImpl(repo, cacheMetrics, SearchResultRepository::class.java.simpleName) } } \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/BaseDefaultedRepositoryImpl.kt b/FredBoat/src/main/java/fredboat/db/mongo/BaseDefaultedRepositoryImpl.kt new file mode 100644 index 000000000..4d9b40683 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/mongo/BaseDefaultedRepositoryImpl.kt @@ -0,0 +1,19 @@ +package fredboat.db.mongo + +import fredboat.db.api.BaseDefaultedRepository +import fredboat.db.transfer.MongoEntity +import io.prometheus.client.cache.caffeine.CacheMetricsCollector +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import reactor.core.publisher.Mono +import java.io.Serializable + +abstract class BaseDefaultedRepositoryImpl>( + repo: ReactiveCrudRepository, + cacheMetrics: CacheMetricsCollector, + cacheName: String +) : BaseRepositoryImpl(repo, cacheMetrics, cacheName), BaseDefaultedRepository { + + override fun fetch(id: ID): Mono { + return super.fetch(id).defaultIfEmpty(default(id)).doOnSuccess { super.cache.synchronous().put(it.id, it) } + } +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/BaseRepositoryImpl.kt b/FredBoat/src/main/java/fredboat/db/mongo/BaseRepositoryImpl.kt index 5ea836a5d..78f2b1697 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/BaseRepositoryImpl.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/BaseRepositoryImpl.kt @@ -1,42 +1,45 @@ package fredboat.db.mongo -import com.google.common.cache.Cache -import com.google.common.cache.CacheBuilder +import com.github.benmanes.caffeine.cache.AsyncLoadingCache +import com.github.benmanes.caffeine.cache.Caffeine import fredboat.db.api.BaseRepository import fredboat.db.transfer.MongoEntity +import io.prometheus.client.cache.caffeine.CacheMetricsCollector import org.springframework.data.repository.reactive.ReactiveCrudRepository import reactor.core.publisher.Mono +import reactor.core.publisher.toMono import java.io.Serializable import java.util.concurrent.TimeUnit -abstract class BaseRepositoryImpl>(private val repo: ReactiveCrudRepository) : ReactiveCrudRepository by repo, BaseRepository { +abstract class BaseRepositoryImpl>( + private val repo: ReactiveCrudRepository, + cacheMetrics: CacheMetricsCollector, + cacheName: String +) : ReactiveCrudRepository by repo, BaseRepository { - private val cache: Cache = CacheBuilder.newBuilder() + protected val cache: AsyncLoadingCache = Caffeine.newBuilder() .expireAfterAccess(60, TimeUnit.SECONDS) .expireAfterWrite(120, TimeUnit.SECONDS) - .build() + .recordStats() + .buildAsync { key, _ -> repo.findById(key).toFuture() } + + init { + cacheMetrics.addCache(cacheName, cache) + } override fun fetch(id: ID): Mono { - return if (default(id) != null) - Mono.justOrEmpty(cache.getIfPresent(id)) - .switchIfEmpty(repo.findById(id)) - .defaultIfEmpty(default(id)!!) - .doOnSuccess { cache.put(id, it) } - else - Mono.justOrEmpty(cache.getIfPresent(id)) - .switchIfEmpty(repo.findById(id)) - .doOnSuccess { if (it != null) cache.put(id, it) } + return cache[id].toMono() } override fun update(mono: Mono): Mono { - return mono.flatMap { cache.put(it.id, it); save(it) } + return mono.flatMap { cache.synchronous().put(it.id, it); save(it) } } override fun update(target: T): Mono { - return repo.save(target).doOnSuccess { cache.put(target.id, target) } + return repo.save(target).doOnSuccess { cache.synchronous().put(target.id, target) } } override fun remove(id: ID): Mono { - return repo.deleteById(id) + return repo.deleteById(id).doOnSuccess { cache.synchronous().invalidate(id) } } } \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/BlacklistRepositoryImpl.kt b/FredBoat/src/main/java/fredboat/db/mongo/BlacklistRepositoryImpl.kt index 73a17239f..bf539af0b 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/BlacklistRepositoryImpl.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/BlacklistRepositoryImpl.kt @@ -2,10 +2,15 @@ package fredboat.db.mongo import fredboat.db.api.BlacklistRepository import fredboat.db.transfer.BlacklistEntity +import io.prometheus.client.cache.caffeine.CacheMetricsCollector -class BlacklistRepositoryImpl(repo: InternalBlacklistRepository) : BaseRepositoryImpl(repo), BlacklistRepository { +class BlacklistRepositoryImpl( + repo: InternalBlacklistRepository, + cacheMetrics: CacheMetricsCollector, + cacheName: String +) : BaseDefaultedRepositoryImpl(repo, cacheMetrics, cacheName), BlacklistRepository { - override fun default(id: Long): BlacklistEntity? { + override fun default(id: Long): BlacklistEntity { return BlacklistEntity(id) } } \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsRepositoryImpl.kt b/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsRepositoryImpl.kt index df97086f9..095527397 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsRepositoryImpl.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsRepositoryImpl.kt @@ -2,11 +2,16 @@ package fredboat.db.mongo import fredboat.db.api.GuildSettingsRepository import fredboat.db.transfer.GuildSettings +import io.prometheus.client.cache.caffeine.CacheMetricsCollector import java.util.* -class GuildSettingsRepositoryImpl(repo: InternalGuildSettingsRepository) : BaseRepositoryImpl(repo), GuildSettingsRepository { +class GuildSettingsRepositoryImpl( + repo: InternalGuildSettingsRepository, + cacheMetrics: CacheMetricsCollector, + cacheName: String +) : BaseDefaultedRepositoryImpl(repo, cacheMetrics, cacheName), GuildSettingsRepository { - override fun default(id: Long): GuildSettings? { + override fun default(id: Long): GuildSettings { return GuildSettings(id) } diff --git a/FredBoat/src/main/java/fredboat/db/mongo/SearchResultRepositoryImpl.kt b/FredBoat/src/main/java/fredboat/db/mongo/SearchResultRepositoryImpl.kt index 3f322b68f..33b6481cc 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/SearchResultRepositoryImpl.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/SearchResultRepositoryImpl.kt @@ -3,11 +3,10 @@ package fredboat.db.mongo import fredboat.db.api.SearchResultRepository import fredboat.db.transfer.SearchResult import fredboat.db.transfer.SearchResultId +import io.prometheus.client.cache.caffeine.CacheMetricsCollector -class SearchResultRepositoryImpl(repo: InternalSearchResultRepository) : BaseRepositoryImpl(repo), SearchResultRepository { - - override fun default(id: SearchResultId): SearchResult? { - return null - } - -} \ No newline at end of file +class SearchResultRepositoryImpl( + repo: InternalSearchResultRepository, + cacheMetrics: CacheMetricsCollector, + cacheName: String +) : BaseRepositoryImpl(repo, cacheMetrics, cacheName), SearchResultRepository \ No newline at end of file diff --git a/Shared/build.gradle b/Shared/build.gradle index b01b07598..90c3a0943 100644 --- a/Shared/build.gradle +++ b/Shared/build.gradle @@ -46,12 +46,16 @@ dependencies { compile group: 'com.squareup.okhttp3', name: 'okhttp', version: okhttpVersion compile group: 'com.google.guava', name: 'guava', version: guavaVersion + compile group: 'com.github.ben-manes.caffeine', name: 'caffeine', version: caffeineVersion + compile group: 'com.github.ben-manes.caffeine', name: 'guava', version: caffeineVersion + compile group: 'io.prometheus', name: 'simpleclient', version: prometheusClientVersion compile group: 'io.prometheus', name: 'simpleclient_hotspot', version: prometheusClientVersion compile group: 'io.prometheus', name: 'simpleclient_logback', version: prometheusClientVersion compile group: 'io.prometheus', name: 'simpleclient_hibernate', version: prometheusClientVersion compile group: 'io.prometheus', name: 'simpleclient_guava', version: prometheusClientVersion + compile group: 'io.prometheus', name: 'simpleclient_caffeine', version: prometheusClientVersion compile group: 'io.prometheus', name: 'simpleclient_servlet', version: prometheusClientVersion implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" } diff --git a/build.gradle b/build.gradle index c34f5e628..6ae22496d 100644 --- a/build.gradle +++ b/build.gradle @@ -77,6 +77,7 @@ subprojects { fastUtilVersion = '8.1.1' togglzVersion = '2.6.0.Final' guavaVersion = '24.1-jre' + caffeineVersion = '2.6.2' bucket4jVersion = '3.1.1' gsonVersion = '2.8.2' amqpVersion = "2.0.4.RELEASE" From e6cbc6b470c098aad2775bd5b99c883f5dae6568 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Tue, 12 Feb 2019 17:36:00 +0100 Subject: [PATCH 084/172] Switch Prefix Command to Caffeine cache --- .../fredboat/command/config/PrefixCommand.kt | 22 ++++++++----------- .../commandmeta/CommandInitializer.kt | 3 ++- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt b/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt index ec641d967..7a5f0fc82 100644 --- a/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt @@ -25,20 +25,18 @@ package fredboat.command.config -import com.google.common.cache.CacheBuilder -import com.google.common.cache.CacheLoader +import com.github.benmanes.caffeine.cache.Caffeine import fredboat.commandmeta.abs.Command import fredboat.commandmeta.abs.CommandContext import fredboat.commandmeta.abs.IConfigCommand import fredboat.db.api.GuildSettingsRepository import fredboat.definitions.PermissionLevel import fredboat.main.Launcher +import fredboat.main.getBotController import fredboat.messaging.internal.Context import fredboat.perms.PermsUtil import fredboat.sentinel.Guild -import fredboat.util.rest.CacheUtil -import io.prometheus.client.guava.cache.CacheMetricsCollector -import java.util.* +import io.prometheus.client.cache.caffeine.CacheMetricsCollector import java.util.concurrent.TimeUnit /** @@ -54,7 +52,7 @@ class PrefixCommand(cacheMetrics: CacheMetricsCollector, } companion object { - val CUSTOM_PREFIXES = CacheBuilder.newBuilder() + val CUSTOM_PREFIXES = Caffeine.newBuilder() //it is fine to check the db for updates occasionally, as we currently dont have any use case where we change //the value saved there through other means. in case we add such a thing (like a dashboard), consider lowering //the refresh value to have the changes reflect faster in the bot, or consider implementing a FredBoat wide @@ -62,13 +60,11 @@ class PrefixCommand(cacheMetrics: CacheMetricsCollector, .recordStats() .refreshAfterWrite(1, TimeUnit.MINUTES) //NOTE: never use refreshing without async reloading, because Guavas cache uses the thread calling it to do cleanup tasks (including refreshing) .expireAfterAccess(1, TimeUnit.MINUTES) //evict inactive guilds - .concurrencyLevel(Launcher.botController.appConfig.shardCount) //each shard has a thread (main JDA thread) accessing this cache many times - .build(CacheLoader.asyncReloading(CacheLoader.from> { guildId -> - Optional.ofNullable(Launcher.botController.guildSettingsRepository.fetch(guildId!!).block()?.prefix) - }, Launcher.botController.executor))!! + //.concurrencyLevel(Launcher.botController.appConfig.shardCount) //each shard has a thread (main JDA thread) accessing this cache many times + .buildAsync { key, _ -> getBotController().guildSettingsRepository.fetch(key).map { it.prefix }.toFuture() } - fun giefPrefix(guildId: Long) = CacheUtil.getUncheckedUnwrapped(CUSTOM_PREFIXES, guildId) - .orElse(Launcher.botController.appConfig.prefix) + fun giefPrefix(guildId: Long) = CUSTOM_PREFIXES.synchronous()[guildId] + ?: Launcher.botController.appConfig.prefix fun giefPrefix(guild: Guild) = giefPrefix(guild.id) @@ -108,7 +104,7 @@ class PrefixCommand(cacheMetrics: CacheMetricsCollector, //we could do a put instead of invalidate here and probably safe one lookup, but that undermines the database // as being the single source of truth for prefixes - CUSTOM_PREFIXES.invalidate(context.guild.id) + CUSTOM_PREFIXES.synchronous().invalidate(context.guild.id) showPrefix(context, giefPrefix(context.guild)) } diff --git a/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt b/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt index 7eef037b9..97b550b24 100644 --- a/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt +++ b/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt @@ -64,6 +64,7 @@ import java.util.function.Supplier @Service class CommandInitializer( cacheMetrics: CacheMetricsCollector, + caffeineCacheMetrics: io.prometheus.client.cache.caffeine.CacheMetricsCollector, weather: Weather, trackSearcher: TrackSearcher, videoSelectionCache: VideoSelectionCache, @@ -142,7 +143,7 @@ class CommandInitializer( configModule.registerCommand(ConfigCommand(CONFIG_COMM_NAME, guildSettingsRepository, "cfg")) configModule.registerCommand(LanguageCommand(LANGUAGE_COMM_NAME, "lang")) configModule.registerCommand(ModulesCommand("modules", guildSettingsRepository, "module", "mods")) - configModule.registerCommand(PrefixCommand(cacheMetrics, guildSettingsRepository, PREFIX_COMM_NAME, "pre")) + configModule.registerCommand(PrefixCommand(caffeineCacheMetrics, guildSettingsRepository, PREFIX_COMM_NAME, "pre")) configModule.registerCommand(ConfigWebInfoCommand("webinfo", repo = guildSettingsRepository, appConfig = appConfig)) /* Perms */ configModule.registerCommand(PermissionsCommand(PermissionLevel.ADMIN, guildSettingsRepository, "admin", "admins")) From e3f54a91a195d51069ec34cc038101a2a054e145 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Tue, 12 Feb 2019 17:36:27 +0100 Subject: [PATCH 085/172] Convert TrackSearcher to Kotlin and make cache calls suspend --- .../command/music/control/PlayCommand.kt | 88 +++--- .../fredboat/util/rest/TrackSearcher.java | 294 ------------------ .../java/fredboat/util/rest/TrackSearcher.kt | 267 ++++++++++++++++ 3 files changed, 313 insertions(+), 336 deletions(-) delete mode 100644 FredBoat/src/main/java/fredboat/util/rest/TrackSearcher.java create mode 100644 FredBoat/src/main/java/fredboat/util/rest/TrackSearcher.kt diff --git a/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt index c6a7295c9..b9b987c01 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt @@ -41,6 +41,8 @@ import fredboat.util.TextUtils import fredboat.util.extension.edit import fredboat.util.localMessageBuilder import fredboat.util.rest.TrackSearcher +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import org.apache.commons.lang3.StringUtils import org.slf4j.LoggerFactory @@ -128,49 +130,51 @@ class PlayCommand(private val playerLimiter: PlayerLimiter, private val trackSea val query = context.rawArgs.replace(TrackSearcher.PUNCTUATION_REGEX.toRegex(), "") context.replyMono(context.i18n("playSearching").replace("{q}", query)) - .subscribe{ outMsg -> - val list: AudioPlaylist? - try { - list = trackSearcher.searchForTracks(query, searchProviders) - } catch (e: TrackSearcher.SearchingException) { - context.reply(context.i18n("playYoutubeSearchError")) - log.error("YouTube search exception", e) - return@subscribe - } - - if (list == null || list.tracks.isEmpty()) { - outMsg.edit( - context.textChannel, - context.i18n("playSearchNoResults").replace("{q}", query) - ).subscribe() - - } else { - //Get at most 5 tracks - val selectable = list.tracks.subList(0, Math.min(TrackSearcher.MAX_RESULTS, list.tracks.size)) - - val oldSelection = videoSelectionCache.remove(context.member) - oldSelection?.deleteMessage() - - val builder = localMessageBuilder() - builder.append(context.i18nFormat("playSelectVideo", TextUtils.escapeMarkdown(context.prefix))) - - var i = 1 - for (track in selectable) { - builder.append("\n**") - .append(i.toString()) - .append(":** ") - .append(TextUtils.escapeAndDefuse(track.info.title)) - .append(" (") - .append(TextUtils.formatTime(track.info.length)) - .append(")") - - i++ + .subscribe { outMsg -> + GlobalScope.launch { + val list: AudioPlaylist? + try { + list = trackSearcher.searchForTracks(query, searchProviders) + } catch (e: TrackSearcher.SearchingException) { + context.reply(context.i18n("playYoutubeSearchError")) + log.error("YouTube search exception", e) + return@launch + } + + if (list.tracks.isEmpty()) { + outMsg.edit( + context.textChannel, + context.i18n("playSearchNoResults").replace("{q}", query) + ).subscribe() + + } else { + //Get at most 5 tracks + val selectable = list.tracks.subList(0, Math.min(TrackSearcher.MAX_RESULTS, list.tracks.size)) + + val oldSelection = videoSelectionCache.remove(context.member) + oldSelection?.deleteMessage() + + val builder = localMessageBuilder() + builder.append(context.i18nFormat("playSelectVideo", TextUtils.escapeMarkdown(context.prefix))) + + var i = 1 + for (track in selectable) { + builder.append("\n**") + .append(i.toString()) + .append(":** ") + .append(TextUtils.escapeAndDefuse(track.info.title)) + .append(" (") + .append(TextUtils.formatTime(track.info.length)) + .append(")") + + i++ + } + + outMsg.edit(context.textChannel, builder.build()).subscribe() + videoSelectionCache.put(outMsg.messageId, context, selectable, isPriority) + } + } } - - outMsg.edit(context.textChannel, builder.build()).subscribe() - videoSelectionCache.put(outMsg.messageId, context, selectable, isPriority) - } - } } override fun help(context: Context): String { diff --git a/FredBoat/src/main/java/fredboat/util/rest/TrackSearcher.java b/FredBoat/src/main/java/fredboat/util/rest/TrackSearcher.java deleted file mode 100644 index 2b2efc810..000000000 --- a/FredBoat/src/main/java/fredboat/util/rest/TrackSearcher.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package fredboat.util.rest; - -import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler; -import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; -import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioSourceManager; -import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; -import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; -import com.sedmelluq.discord.lavaplayer.track.AudioTrack; -import com.sedmelluq.discord.lavaplayer.track.BasicAudioPlaylist; -import fredboat.config.property.AppConfig; -import fredboat.db.api.SearchResultRepository; -import fredboat.db.transfer.SearchResult; -import fredboat.db.transfer.SearchResultId; -import fredboat.definitions.SearchProvider; -import fredboat.feature.metrics.Metrics; -import fredboat.feature.togglz.FeatureFlags; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Component; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -@Component -public class TrackSearcher { - - public static final int MAX_RESULTS = 5; - public static final long DEFAULT_CACHE_MAX_AGE = TimeUnit.HOURS.toMillis(48); - public static final String PUNCTUATION_REGEX = "[.,/#!$%^&*;:{}=\\-_`~()\"\']"; - private static final int DEFAULT_TIMEOUT = 3000; - - private static final Logger log = LoggerFactory.getLogger(TrackSearcher.class); - - //give youtube a break if we get flagged and keep getting 503s - private static final long DEFAULT_YOUTUBE_COOLDOWN = TimeUnit.MINUTES.toMillis(10); // 10 minutes - private static long youtubeCooldownUntil; - - private final AudioPlayerManager audioPlayerManager; - private final YoutubeAPI youtubeAPI; - private final SearchResultRepository repository; - private final AppConfig appConfig; - - public TrackSearcher(@Qualifier("searchAudioPlayerManager") AudioPlayerManager audioPlayerManager, - YoutubeAPI youtubeAPI, SearchResultRepository repository, AppConfig appConfig) { - this.audioPlayerManager = audioPlayerManager; - this.youtubeAPI = youtubeAPI; - this.repository = repository; - this.appConfig = appConfig; - } - - public AudioPlaylist searchForTracks(String query, List providers) throws SearchingException { - return searchForTracks(query, DEFAULT_CACHE_MAX_AGE, DEFAULT_TIMEOUT, providers); - } - - /** - * @param query The search term - * @param timeoutMillis How long to wait for each lavaplayer search to answer - * @param providers Providers that shall be used for the search. They will be used in the order they are provided, the - * result of the first successful one will be returned - * @return The result of the search, or an empty list. - * @throws SearchingException If none of the search providers could give us a result, and there was at least one SearchingException thrown by them - */ - public AudioPlaylist searchForTracks(String query, long cacheMaxAge, int timeoutMillis, List providers) - throws SearchingException { - Metrics.searchRequests.inc(); - - List provs = new ArrayList<>(); - if (providers == null || providers.isEmpty()) { - log.warn("No search provider provided, defaulting to youtube -> soundcloud."); - provs.add(SearchProvider.YOUTUBE); - provs.add(SearchProvider.SOUNDCLOUD); - } else { - provs.addAll(providers); - } - - SearchingException searchingException = null; - - for (SearchProvider provider : provs) { - //1. cache - AudioPlaylist cacheResult = fromCache(provider, query, cacheMaxAge); - if (cacheResult != null && !cacheResult.getTracks().isEmpty()) { - log.debug("Loaded search result {} {} from cache", provider, query); - Metrics.searchHits.labels("cache").inc(); - return cacheResult; - } - - //2. lavaplayer todo break up this beautiful construction of ifs and exception handling in a better readable one? - if (provider != SearchProvider.YOUTUBE || System.currentTimeMillis() > youtubeCooldownUntil) { - try { - AudioPlaylist lavaplayerResult = new SearchResultHandler() - .searchSync(audioPlayerManager, provider, query, timeoutMillis); - if (!lavaplayerResult.getTracks().isEmpty()) { - log.debug("Loaded search result {} {} from lavaplayer", provider, query); - // got a search result? cache and return it - repository.update(new SearchResult(provider, query, lavaplayerResult)).subscribe(); - Metrics.searchHits.labels("lavaplayer-" + provider.name().toLowerCase()).inc(); - return lavaplayerResult; - } - } catch (Http503Exception e) { - if (provider == SearchProvider.YOUTUBE) { - log.warn("Got a 503 from Youtube. Not hitting it with searches it for {} minutes", TimeUnit.MILLISECONDS.toMinutes(DEFAULT_YOUTUBE_COOLDOWN)); - youtubeCooldownUntil = System.currentTimeMillis() + DEFAULT_YOUTUBE_COOLDOWN; - } - searchingException = e; - } catch (SearchingException e) { - searchingException = e; - } - } - - //3. optional: youtube api - if (provider == SearchProvider.YOUTUBE - && (appConfig.isPatronDistribution() || appConfig.isDevDistribution())) { - try { - AudioPlaylist youtubeApiResult = youtubeAPI.search(query, MAX_RESULTS, audioPlayerManager.source(YoutubeAudioSourceManager.class)); - if (!youtubeApiResult.getTracks().isEmpty()) { - log.debug("Loaded search result {} {} from Youtube API", provider, query); - // got a search result? cache and return it - repository.update(new SearchResult(provider, query, youtubeApiResult)).subscribe(); - Metrics.searchHits.labels("youtube-api").inc(); - return youtubeApiResult; - } - } catch (SearchingException e) { - searchingException = e; - } - } - } - - //did we run into searching exceptions that made us end up here? - if (searchingException != null) { - Metrics.searchHits.labels("exception").inc(); - throw searchingException; - } - //no result with any of the search providers - Metrics.searchHits.labels("empty").inc(); - return new BasicAudioPlaylist("Search result for: " + query, Collections.emptyList(), null, true); - } - - /** - * @param provider the search provider that shall be used for this search - * @param searchTerm the searchTerm to search for - */ - @Nullable - private AudioPlaylist fromCache(SearchProvider provider, String searchTerm, long cacheMaxAge) { - try { - SearchResultId id = new SearchResultId(provider, searchTerm); - SearchResult result = repository.fetch(id).block(); - - if (result == null) { - return null; - } - - // If the cache entry is old evict it from DB and return null - if ((result.getTimestamp() + cacheMaxAge) < System.currentTimeMillis()) { - repository.remove(result.getId()).subscribe(); - - return null; - } - - return result.getSearchResult(); - - } catch (Exception e) { - //could be a database issue, could be a serialization issue. better to catch them all here and "orderly" return - log.warn("Could not retrieve cached search result from database.", e); - return null; - } - } - - public static class SearchingException extends Exception { - private static final long serialVersionUID = -1020150337258395420L; - - public SearchingException(String message) { - super(message); - } - - public SearchingException(String message, Exception cause) { - super(message, cause); - } - } - - //creative name... - public static class Http503Exception extends SearchingException { - private static final long serialVersionUID = -2698566544845714550L; - - public Http503Exception(String message) { - super(message); - } - - public Http503Exception(String message, Exception cause) { - super(message, cause); - } - } - - private static class SearchResultHandler implements AudioLoadResultHandler { - - Exception exception; - AudioPlaylist result; - - /** - * @return The result of the search (which may be empty but not null). - */ - @Nonnull - AudioPlaylist searchSync(AudioPlayerManager audioPlayerManager, SearchProvider provider, String query, int timeoutMillis) - throws SearchingException { - SearchProvider searchProvider = provider; - if (FeatureFlags.FORCE_SOUNDCLOUD_SEARCH.isActive()) { - searchProvider = SearchProvider.SOUNDCLOUD; - } - - log.debug("Searching {} for {}", searchProvider, query); - try { - audioPlayerManager.loadItem(searchProvider.getPrefix() + query, this) - .get(timeoutMillis, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } catch (ExecutionException e) { - exception = e; - } catch (TimeoutException e) { - throw new SearchingException(String.format("Searching provider %s for %s timed out after %sms", - searchProvider.name(), query, timeoutMillis)); - } - - if (exception != null) { - if (exception instanceof FriendlyException && exception.getCause() != null) { - String messageOfCause = exception.getCause().getMessage(); - if (messageOfCause.contains("java.io.IOException: Invalid status code for search response: 503")) { - throw new Http503Exception("Lavaplayer search returned a 503", exception); - } - } - - String message = String.format("Failed to search provider %s for query %s with exception %s.", - searchProvider, query, exception.getMessage()); - throw new SearchingException(message, exception); - } - - if (result == null) { - throw new SearchingException(String.format("Result from provider %s for query %s is unexpectedly null", searchProvider, query)); - } - - return result; - } - - @Override - public void trackLoaded(AudioTrack audioTrack) { - exception = new UnsupportedOperationException("Can't load a single track when we are expecting a playlist!"); - } - - @Override - public void playlistLoaded(AudioPlaylist audioPlaylist) { - result = audioPlaylist; - } - - @Override - public void noMatches() { - result = new BasicAudioPlaylist("No matches", Collections.emptyList(), null, true); - } - - @Override - public void loadFailed(FriendlyException e) { - exception = e; - } - } -} diff --git a/FredBoat/src/main/java/fredboat/util/rest/TrackSearcher.kt b/FredBoat/src/main/java/fredboat/util/rest/TrackSearcher.kt new file mode 100644 index 000000000..fe30a3654 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/util/rest/TrackSearcher.kt @@ -0,0 +1,267 @@ +/* + * MIT License + * + * Copyright (c) 2017 Frederik Ar. Mikkelsen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +package fredboat.util.rest + +import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler +import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager +import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioSourceManager +import com.sedmelluq.discord.lavaplayer.tools.FriendlyException +import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist +import com.sedmelluq.discord.lavaplayer.track.AudioTrack +import com.sedmelluq.discord.lavaplayer.track.BasicAudioPlaylist +import fredboat.config.property.AppConfig +import fredboat.db.api.SearchResultRepository +import fredboat.db.transfer.SearchResult +import fredboat.db.transfer.SearchResultId +import fredboat.definitions.SearchProvider +import fredboat.feature.metrics.Metrics +import fredboat.feature.togglz.FeatureFlags +import kotlinx.coroutines.reactive.awaitSingle +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.stereotype.Component +import java.util.ArrayList +import java.util.concurrent.ExecutionException +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException + +@Component +class TrackSearcher(@param:Qualifier("searchAudioPlayerManager") private val audioPlayerManager: AudioPlayerManager, + private val youtubeAPI: YoutubeAPI, private val repository: SearchResultRepository, private val appConfig: AppConfig) { + + @Throws(TrackSearcher.SearchingException::class) + suspend fun searchForTracks(query: String, providers: List): AudioPlaylist { + return searchForTracks(query, DEFAULT_CACHE_MAX_AGE, DEFAULT_TIMEOUT, providers) + } + + /** + * @param query The search term + * @param timeoutMillis How long to wait for each lavaplayer search to answer + * @param providers Providers that shall be used for the search. They will be used in the order they are provided, the + * result of the first successful one will be returned + * @return The result of the search, or an empty list. + * @throws SearchingException If none of the search providers could give us a result, and there was at least one SearchingException thrown by them + */ + @Throws(TrackSearcher.SearchingException::class) + suspend fun searchForTracks(query: String, cacheMaxAge: Long, timeoutMillis: Int, providers: List?): AudioPlaylist { + Metrics.searchRequests.inc() + + val provs = ArrayList() + if (providers == null || providers.isEmpty()) { + log.warn("No search provider provided, defaulting to youtube -> soundcloud.") + provs.add(SearchProvider.YOUTUBE) + provs.add(SearchProvider.SOUNDCLOUD) + } else { + provs.addAll(providers) + } + + var searchingException: SearchingException? = null + + for (provider in provs) { + //1. cache + val cacheResult = fromCache(provider, query, cacheMaxAge) + if (cacheResult != null && !cacheResult.tracks.isEmpty()) { + log.debug("Loaded search result {} {} from cache", provider, query) + Metrics.searchHits.labels("cache").inc() + return cacheResult + } + + //2. lavaplayer todo break up this beautiful construction of ifs and exception handling in a better readable one? + if (provider != SearchProvider.YOUTUBE || System.currentTimeMillis() > youtubeCooldownUntil) { + try { + val lavaplayerResult = SearchResultHandler() + .searchSync(audioPlayerManager, provider, query, timeoutMillis) + if (!lavaplayerResult.tracks.isEmpty()) { + log.debug("Loaded search result {} {} from lavaplayer", provider, query) + // got a search result? cache and return it + repository.update(SearchResult(provider, query, lavaplayerResult)).subscribe() + Metrics.searchHits.labels("lavaplayer-" + provider.name.toLowerCase()).inc() + return lavaplayerResult + } + } catch (e: Http503Exception) { + if (provider == SearchProvider.YOUTUBE) { + log.warn("Got a 503 from Youtube. Not hitting it with searches it for {} minutes", TimeUnit.MILLISECONDS.toMinutes(DEFAULT_YOUTUBE_COOLDOWN)) + youtubeCooldownUntil = System.currentTimeMillis() + DEFAULT_YOUTUBE_COOLDOWN + } + searchingException = e + } catch (e: SearchingException) { + searchingException = e + } + + } + + //3. optional: youtube api + if (provider == SearchProvider.YOUTUBE && (appConfig.isPatronDistribution || appConfig.isDevDistribution)) { + try { + val youtubeApiResult = youtubeAPI.search(query, MAX_RESULTS, audioPlayerManager.source(YoutubeAudioSourceManager::class.java)) + if (!youtubeApiResult.tracks.isEmpty()) { + log.debug("Loaded search result {} {} from Youtube API", provider, query) + // got a search result? cache and return it + repository.update(SearchResult(provider, query, youtubeApiResult)).subscribe() + Metrics.searchHits.labels("youtube-api").inc() + return youtubeApiResult + } + } catch (e: SearchingException) { + searchingException = e + } + + } + } + + //did we run into searching exceptions that made us end up here? + if (searchingException != null) { + Metrics.searchHits.labels("exception").inc() + throw searchingException + } + //no result with any of the search providers + Metrics.searchHits.labels("empty").inc() + return BasicAudioPlaylist("Search result for: $query", emptyList(), null, true) + } + + /** + * @param provider the search provider that shall be used for this search + * @param searchTerm the searchTerm to search for + */ + private suspend fun fromCache(provider: SearchProvider, searchTerm: String, cacheMaxAge: Long): AudioPlaylist? { + try { + val id = SearchResultId(provider, searchTerm) + val result = repository.fetch(id).awaitSingle() + + // If the cache entry is old evict it from DB and return null + if (result.timestamp + cacheMaxAge < System.currentTimeMillis()) { + repository.remove(result.id).subscribe() + + return null + } + + return result.getSearchResult() + + } catch (e: Exception) { + //could be a database issue, could be a serialization issue. better to catch them all here and "orderly" return + log.warn("Could not retrieve cached search result from database.", e) + return null + } + + } + + open class SearchingException : Exception { + + constructor(message: String) : super(message) + + constructor(message: String, cause: Exception) : super(message, cause) + + companion object { + private const val serialVersionUID = -1020150337258395420L + } + } + + //creative name... + class Http503Exception(message: String, cause: Exception) : SearchingException(message, cause) { + + companion object { + private const val serialVersionUID = -2698566544845714550L + } + } + + private class SearchResultHandler : AudioLoadResultHandler { + + internal var exception: Exception? = null + internal var result: AudioPlaylist? = null + + /** + * @return The result of the search (which may be empty but not null). + */ + @Throws(TrackSearcher.SearchingException::class) + internal fun searchSync(audioPlayerManager: AudioPlayerManager, provider: SearchProvider, query: String, timeoutMillis: Int): AudioPlaylist { + var searchProvider = provider + if (FeatureFlags.FORCE_SOUNDCLOUD_SEARCH.isActive) { + searchProvider = SearchProvider.SOUNDCLOUD + } + + log.debug("Searching {} for {}", searchProvider, query) + try { + audioPlayerManager.loadItem(searchProvider.prefix + query, this) + .get(timeoutMillis.toLong(), TimeUnit.MILLISECONDS) + } catch (e: InterruptedException) { + Thread.currentThread().interrupt() + } catch (e: ExecutionException) { + exception = e + } catch (e: TimeoutException) { + throw SearchingException(String.format("Searching provider %s for %s timed out after %sms", + searchProvider.name, query, timeoutMillis)) + } + + if (exception != null) { + if (exception is FriendlyException && exception!!.cause != null) { + val messageOfCause = exception!!.cause!!.message + if (messageOfCause!!.contains("java.io.IOException: Invalid status code for search response: 503")) { + throw Http503Exception("Lavaplayer search returned a 503", exception as FriendlyException) + } + } + + val message = String.format("Failed to search provider %s for query %s with exception %s.", + searchProvider, query, exception!!.message) + throw SearchingException(message, exception!!) + } + + if (result == null) { + throw SearchingException(String.format("Result from provider %s for query %s is unexpectedly null", searchProvider, query)) + } + + return result as AudioPlaylist + } + + override fun trackLoaded(audioTrack: AudioTrack) { + exception = UnsupportedOperationException("Can't load a single track when we are expecting a playlist!") + } + + override fun playlistLoaded(audioPlaylist: AudioPlaylist) { + result = audioPlaylist + } + + override fun noMatches() { + result = BasicAudioPlaylist("No matches", emptyList(), null, true) + } + + override fun loadFailed(e: FriendlyException) { + exception = e + } + } + + companion object { + + const val MAX_RESULTS = 5 + val DEFAULT_CACHE_MAX_AGE = TimeUnit.HOURS.toMillis(48) + const val PUNCTUATION_REGEX = "[.,/#!$%^&*;:{}=\\-_`~()\"\']" + private const val DEFAULT_TIMEOUT = 3000 + + private val log = LoggerFactory.getLogger(TrackSearcher::class.java) + + //give youtube a break if we get flagged and keep getting 503s + private val DEFAULT_YOUTUBE_COOLDOWN = TimeUnit.MINUTES.toMillis(10) // 10 minutes + private var youtubeCooldownUntil: Long = 0 + } +} From d50c7aaa9b1e114837f5b875536f41d655086f96 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Tue, 12 Feb 2019 17:36:38 +0100 Subject: [PATCH 086/172] fred help how to? --- config/templates/fredboat.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/config/templates/fredboat.yml b/config/templates/fredboat.yml index bed2de724..ec38950b8 100644 --- a/config/templates/fredboat.yml +++ b/config/templates/fredboat.yml @@ -87,15 +87,7 @@ audio-sources: ################################################################ backend: - quarterdeck: - # Host address of your quarterdeck backend, including port unless you are using a reverse proxy. - # Example: https://such.example.com:4269/ - # No need set the host when running Quarterdeck in Docker. - host: "" - # Credentials used to authenticate to with Quarterdeck. - # By default Quarterdeck uses docker:docker when using our docker-compose files - user: "docker" - pass: "docker" + # TODO: Add Mongo here credentials: From cb50a8ec92e4f87be06c5bb570e53f1645e56cc3 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Tue, 12 Feb 2019 18:33:05 +0100 Subject: [PATCH 087/172] More suspend because its awesome --- .../source/SpotifyPlaylistSourceManager.java | 229 ------------------ .../source/SpotifyPlaylistSourceManager.kt | 220 +++++++++++++++++ .../command/music/control/PlayCommand.kt | 93 ++++--- config/templates/fredboat.yml | 7 +- 4 files changed, 267 insertions(+), 282 deletions(-) delete mode 100644 FredBoat/src/main/java/fredboat/audio/source/SpotifyPlaylistSourceManager.java create mode 100644 FredBoat/src/main/java/fredboat/audio/source/SpotifyPlaylistSourceManager.kt diff --git a/FredBoat/src/main/java/fredboat/audio/source/SpotifyPlaylistSourceManager.java b/FredBoat/src/main/java/fredboat/audio/source/SpotifyPlaylistSourceManager.java deleted file mode 100644 index 1117baea8..000000000 --- a/FredBoat/src/main/java/fredboat/audio/source/SpotifyPlaylistSourceManager.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.audio.source; - -import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager; -import com.sedmelluq.discord.lavaplayer.source.AudioSourceManager; -import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; -import com.sedmelluq.discord.lavaplayer.track.*; -import fredboat.audio.queue.PlaylistInfo; -import fredboat.definitions.SearchProvider; -import fredboat.util.rest.SpotifyAPIWrapper; -import fredboat.util.rest.TrackSearcher; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Created by napster on 08.03.17. - *

- * Loads playlists from Spotify playlist links. - * - * todo bulk load the songs from the search cache (remote db connections are slow when loading one by one) - * - * @author napster - */ -public class SpotifyPlaylistSourceManager implements AudioSourceManager, PlaylistImporter { - - public static long CACHE_DURATION = TimeUnit.DAYS.toMillis(30);// 1 month; - - private static final Logger log = LoggerFactory.getLogger(SpotifyPlaylistSourceManager.class); - - //https://regex101.com/r/AEWyxi/3 - private static final Pattern PLAYLIST_PATTERN = Pattern.compile("https?://.*\\.spotify\\.com/user/(.*)/playlist/([^?/\\s]*)"); - - //Take care when deciding on upping the core pool size: The threads may hog database connections when loading an uncached playlist. - // Upping the threads will also fire search requests more aggressively against Youtube which is probably better avoided. - public static ScheduledExecutorService loader = Executors.newScheduledThreadPool(1); - - private static final List searchProviders - = Arrays.asList(SearchProvider.YOUTUBE, SearchProvider.SOUNDCLOUD); - private final TrackSearcher trackSearcher; - private final SpotifyAPIWrapper spotifyAPIWrapper; - - public SpotifyPlaylistSourceManager(TrackSearcher trackSearcher, SpotifyAPIWrapper spotifyAPIWrapper) { - this.trackSearcher = trackSearcher; - this.spotifyAPIWrapper = spotifyAPIWrapper; - } - - @Override - public String getSourceName() { - return "spotify_playlist_import"; - } - - @Override - public AudioItem loadItem(final DefaultAudioPlayerManager manager, final AudioReference ar) { - - String[] data = parse(ar.identifier); - if (data == null) return null; - final String spotifyUser = data[0]; - final String spotifyListId = data[1]; - - PlaylistInfo plData; - try { - plData = spotifyAPIWrapper.getPlaylistDataBlocking(spotifyUser, spotifyListId); - } catch (Exception e) { - log.warn("Could not retrieve playlist " + spotifyListId + " of user " + spotifyUser, e); - throw new FriendlyException("Couldn't load playlist. Either Spotify is down or the playlist does not exist.", FriendlyException.Severity.COMMON, e); - } - - String playlistName = plData.getName(); - if (playlistName == null || "".equals(playlistName)) playlistName = "Spotify Playlist"; - int tracksTotal = plData.getTotalTracks(); - - final List trackList = new ArrayList<>(); - final List trackListSearchTerms; - - try { - trackListSearchTerms = spotifyAPIWrapper.getPlaylistTracksSearchTermsBlocking(spotifyUser, spotifyListId); - } catch (Exception e) { - log.warn("Could not retrieve tracks for playlist " + spotifyListId + " of user " + spotifyUser, e); - throw new FriendlyException("Couldn't load playlist. Either Spotify is down or the playlist does not exist.", FriendlyException.Severity.COMMON, e); - } - log.info("Retrieved playlist data for " + playlistName + " from Spotify, loading up " + tracksTotal + " tracks"); - - //build a task list - List> taskList = new ArrayList<>(); - for (final String s : trackListSearchTerms) { - //remove all punctuation - final String query = s.replaceAll(TrackSearcher.PUNCTUATION_REGEX, ""); - - CompletableFuture f = CompletableFuture.supplyAsync(() -> searchSingleTrack(query), loader); - taskList.add(f); - } - - //build a tracklist from that task list - for (CompletableFuture futureTrack : taskList) { - try { - final AudioTrack audioItem = futureTrack.get(); - if (audioItem == null) { - continue; //skip the track if we couldn't find it - } - trackList.add(audioItem); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } catch (ExecutionException ignored) { - //this is fine, loop will go for the next item - } - } - return new BasicAudioPlaylist(playlistName, trackList, null, true); - } - - /** - * Searches all available searching sources for a single track. - *

- * Will go Youtube > SoundCloud > return null - * This could probably be moved to SearchUtil - * - * @param query Term that shall be searched - * @return An AudioTrack likely corresponding to the query term or null. - */ - private AudioTrack searchSingleTrack(final String query) { - try { - AudioPlaylist list = trackSearcher.searchForTracks(query, CACHE_DURATION, 60000, searchProviders); - //didn't find anything - if (list == null || list.getTracks().isEmpty()) { - return null; - } - - //pick topmost result, and hope it's what the user wants to listen to - //having users pick tracks like they can do for individual searches would be ridiculous for playlists with - //dozens of tracks. youtube search is probably good enough for this - // - //testcase: Rammstein playlists; high quality Rammstein vids are really rare on Youtube. - // https://open.spotify.com/user/11174036433/playlist/0ePRMvD3Dn3zG31A8y64xX - //result: lots of low quality (covers, pitched up/down, etc) tracks loaded. - //conclusion: there's room for improvement to this whole method - return list.getTracks().get(0); - } catch (TrackSearcher.SearchingException e) { - //youtube & soundcloud not available - return null; - } - } - - @Override - public boolean isTrackEncodable(final AudioTrack track) { - return false; - } - - @Override - public void encodeTrack(final AudioTrack track, final DataOutput output) throws IOException { - throw new UnsupportedOperationException("This source manager is only for loading playlists"); - } - - @Override - public AudioTrack decodeTrack(final AudioTrackInfo trackInfo, final DataInput input) throws IOException { - throw new UnsupportedOperationException("This source manager is only for loading playlists"); - } - - @Override - public void shutdown() { - - } - - /** - * @return null or a string array containing spotifyUser at [0] and playlistId at [1] of the requested playlist - */ - private String[] parse(String identifier) { - String[] result = new String[2]; - final Matcher m = PLAYLIST_PATTERN.matcher(identifier); - - if (!m.find()) { - return null; - } - - result[0] = m.group(1); - result[1] = m.group(2); - - log.debug("matched spotify playlist link. user: " + result[0] + ", listId: " + result[1]); - return result; - } - - @Override - public PlaylistInfo getPlaylistDataBlocking(String identifier) { - - String[] data = parse(identifier); - if (data == null) return null; - final String spotifyUser = data[0]; - final String spotifyListId = data[1]; - - try { - return spotifyAPIWrapper.getPlaylistDataBlocking(spotifyUser, spotifyListId); - } catch (Exception e) { - log.warn("Could not retrieve playlist " + spotifyListId + " of user " + spotifyUser, e); - throw new FriendlyException("Couldn't load playlist. Either Spotify is down or the playlist does not exist.", FriendlyException.Severity.COMMON, e); - } - } -} diff --git a/FredBoat/src/main/java/fredboat/audio/source/SpotifyPlaylistSourceManager.kt b/FredBoat/src/main/java/fredboat/audio/source/SpotifyPlaylistSourceManager.kt new file mode 100644 index 000000000..68a286290 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/source/SpotifyPlaylistSourceManager.kt @@ -0,0 +1,220 @@ +/* + * MIT License + * + * Copyright (c) 2017 Frederik Ar. Mikkelsen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package fredboat.audio.source + +import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager +import com.sedmelluq.discord.lavaplayer.source.AudioSourceManager +import com.sedmelluq.discord.lavaplayer.tools.FriendlyException +import com.sedmelluq.discord.lavaplayer.track.* +import fredboat.audio.queue.PlaylistInfo +import fredboat.definitions.SearchProvider +import fredboat.util.rest.SpotifyAPIWrapper +import fredboat.util.rest.TrackSearcher +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.reactor.mono +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +import java.io.DataInput +import java.io.DataOutput +import java.io.IOException +import java.util.ArrayList +import java.util.Arrays +import java.util.concurrent.* +import java.util.regex.Matcher +import java.util.regex.Pattern + +/** + * Created by napster on 08.03.17. + * + * + * Loads playlists from Spotify playlist links. + * + * todo bulk load the songs from the search cache (remote db connections are slow when loading one by one) + * + * @author napster + */ +class SpotifyPlaylistSourceManager(private val trackSearcher: TrackSearcher, private val spotifyAPIWrapper: SpotifyAPIWrapper) : AudioSourceManager, PlaylistImporter { + + override fun getSourceName(): String { + return "spotify_playlist_import" + } + + override fun loadItem(manager: DefaultAudioPlayerManager, ar: AudioReference): AudioItem? { + + val data = parse(ar.identifier) ?: return null + val spotifyUser = data[0] + val spotifyListId = data[1] + + val plData: PlaylistInfo + try { + plData = spotifyAPIWrapper.getPlaylistDataBlocking(spotifyUser, spotifyListId) + } catch (e: Exception) { + log.warn("Could not retrieve playlist $spotifyListId of user $spotifyUser", e) + throw FriendlyException("Couldn't load playlist. Either Spotify is down or the playlist does not exist.", FriendlyException.Severity.COMMON, e) + } + + var playlistName: String? = plData.name + if (playlistName == null || "" == playlistName) playlistName = "Spotify Playlist" + val tracksTotal = plData.totalTracks + + val trackList = ArrayList() + val trackListSearchTerms: List + + try { + trackListSearchTerms = spotifyAPIWrapper.getPlaylistTracksSearchTermsBlocking(spotifyUser, spotifyListId) + } catch (e: Exception) { + log.warn("Could not retrieve tracks for playlist $spotifyListId of user $spotifyUser", e) + throw FriendlyException("Couldn't load playlist. Either Spotify is down or the playlist does not exist.", FriendlyException.Severity.COMMON, e) + } + + log.info("Retrieved playlist data for $playlistName from Spotify, loading up $tracksTotal tracks") + + //build a task list + val taskList = ArrayList>() + for (s in trackListSearchTerms) { + //remove all punctuation + val query = s.replace(TrackSearcher.PUNCTUATION_REGEX.toRegex(), "") + + taskList.add(GlobalScope.mono { searchSingleTrack(query) }.toFuture()) + } + + //build a tracklist from that task list + for (futureTrack in taskList) { + try { + val audioItem = futureTrack.get() + ?: continue //skip the track if we couldn't find it + trackList.add(audioItem) + } catch (e: InterruptedException) { + Thread.currentThread().interrupt() + break + } catch (ignored: ExecutionException) { + //this is fine, loop will go for the next item + } + + } + return BasicAudioPlaylist(playlistName, trackList, null, true) + } + + /** + * Searches all available searching sources for a single track. + * + * + * Will go Youtube > SoundCloud > return null + * This could probably be moved to SearchUtil + * + * @param query Term that shall be searched + * @return An AudioTrack likely corresponding to the query term or null. + */ + private suspend fun searchSingleTrack(query: String): AudioTrack? { + try { + val list = trackSearcher.searchForTracks(query, CACHE_DURATION, 60000, searchProviders) + //didn't find anything + return if (list == null || list.tracks.isEmpty()) { + null + } else list.tracks[0] + + //pick topmost result, and hope it's what the user wants to listen to + //having users pick tracks like they can do for individual searches would be ridiculous for playlists with + //dozens of tracks. youtube search is probably good enough for this + // + //testcase: Rammstein playlists; high quality Rammstein vids are really rare on Youtube. + // https://open.spotify.com/user/11174036433/playlist/0ePRMvD3Dn3zG31A8y64xX + //result: lots of low quality (covers, pitched up/down, etc) tracks loaded. + //conclusion: there's room for improvement to this whole method + } catch (e: TrackSearcher.SearchingException) { + //youtube & soundcloud not available + return null + } + + } + + override fun isTrackEncodable(track: AudioTrack): Boolean { + return false + } + + @Throws(IOException::class) + override fun encodeTrack(track: AudioTrack, output: DataOutput) { + throw UnsupportedOperationException("This source manager is only for loading playlists") + } + + @Throws(IOException::class) + override fun decodeTrack(trackInfo: AudioTrackInfo, input: DataInput): AudioTrack { + throw UnsupportedOperationException("This source manager is only for loading playlists") + } + + override fun shutdown() { + + } + + /** + * @return null or a string array containing spotifyUser at [0] and playlistId at [1] of the requested playlist + */ + private fun parse(identifier: String): Array? { + val result = arrayOfNulls(2) + val m = PLAYLIST_PATTERN.matcher(identifier) + + if (!m.find()) { + return null + } + + result[0] = m.group(1) + result[1] = m.group(2) + + log.debug("matched spotify playlist link. user: " + result[0] + ", listId: " + result[1]) + return result + } + + override fun getPlaylistDataBlocking(identifier: String): PlaylistInfo? { + + val data = parse(identifier) ?: return null + val spotifyUser = data[0] + val spotifyListId = data[1] + + try { + return spotifyAPIWrapper.getPlaylistDataBlocking(spotifyUser, spotifyListId) + } catch (e: Exception) { + log.warn("Could not retrieve playlist $spotifyListId of user $spotifyUser", e) + throw FriendlyException("Couldn't load playlist. Either Spotify is down or the playlist does not exist.", FriendlyException.Severity.COMMON, e) + } + + } + + companion object { + + var CACHE_DURATION = TimeUnit.DAYS.toMillis(30)// 1 month; + + private val log = LoggerFactory.getLogger(SpotifyPlaylistSourceManager::class.java) + + //https://regex101.com/r/AEWyxi/3 + private val PLAYLIST_PATTERN = Pattern.compile("https?://.*\\.spotify\\.com/user/(.*)/playlist/([^?/\\s]*)") + + //Take care when deciding on upping the core pool size: The threads may hog database connections when loading an uncached playlist. + // Upping the threads will also fire search requests more aggressively against Youtube which is probably better avoided. + var loader = Executors.newScheduledThreadPool(1) + + private val searchProviders = Arrays.asList(SearchProvider.YOUTUBE, SearchProvider.SOUNDCLOUD) + } +} diff --git a/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt index b9b987c01..fe0306336 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt @@ -41,8 +41,7 @@ import fredboat.util.TextUtils import fredboat.util.extension.edit import fredboat.util.localMessageBuilder import fredboat.util.rest.TrackSearcher -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch +import kotlinx.coroutines.reactive.awaitSingle import org.apache.commons.lang3.StringUtils import org.slf4j.LoggerFactory @@ -125,56 +124,52 @@ class PlayCommand(private val playerLimiter: PlayerLimiter, private val trackSea } } - private fun searchForVideos(context: CommandContext) { + private suspend fun searchForVideos(context: CommandContext) { //Now remove all punctuation val query = context.rawArgs.replace(TrackSearcher.PUNCTUATION_REGEX.toRegex(), "") + val outMsg = context.replyMono(context.i18n("playSearching").replace("{q}", query)).awaitSingle() + + val list: AudioPlaylist? + try { + list = trackSearcher.searchForTracks(query, searchProviders) + } catch (e: TrackSearcher.SearchingException) { + context.reply(context.i18n("playYoutubeSearchError")) + log.error("YouTube search exception", e) + return + } - context.replyMono(context.i18n("playSearching").replace("{q}", query)) - .subscribe { outMsg -> - GlobalScope.launch { - val list: AudioPlaylist? - try { - list = trackSearcher.searchForTracks(query, searchProviders) - } catch (e: TrackSearcher.SearchingException) { - context.reply(context.i18n("playYoutubeSearchError")) - log.error("YouTube search exception", e) - return@launch - } - - if (list.tracks.isEmpty()) { - outMsg.edit( - context.textChannel, - context.i18n("playSearchNoResults").replace("{q}", query) - ).subscribe() - - } else { - //Get at most 5 tracks - val selectable = list.tracks.subList(0, Math.min(TrackSearcher.MAX_RESULTS, list.tracks.size)) - - val oldSelection = videoSelectionCache.remove(context.member) - oldSelection?.deleteMessage() - - val builder = localMessageBuilder() - builder.append(context.i18nFormat("playSelectVideo", TextUtils.escapeMarkdown(context.prefix))) - - var i = 1 - for (track in selectable) { - builder.append("\n**") - .append(i.toString()) - .append(":** ") - .append(TextUtils.escapeAndDefuse(track.info.title)) - .append(" (") - .append(TextUtils.formatTime(track.info.length)) - .append(")") - - i++ - } - - outMsg.edit(context.textChannel, builder.build()).subscribe() - videoSelectionCache.put(outMsg.messageId, context, selectable, isPriority) - } - } - } + if (list.tracks.isEmpty()) { + outMsg.edit( + context.textChannel, + context.i18n("playSearchNoResults").replace("{q}", query) + ).subscribe() + + } else { + //Get at most 5 tracks + val selectable = list.tracks.subList(0, Math.min(TrackSearcher.MAX_RESULTS, list.tracks.size)) + + val oldSelection = videoSelectionCache.remove(context.member) + oldSelection?.deleteMessage() + + val builder = localMessageBuilder() + builder.append(context.i18nFormat("playSelectVideo", TextUtils.escapeMarkdown(context.prefix))) + + var i = 1 + for (track in selectable) { + builder.append("\n**") + .append(i.toString()) + .append(":** ") + .append(TextUtils.escapeAndDefuse(track.info.title)) + .append(" (") + .append(TextUtils.formatTime(track.info.length)) + .append(")") + + i++ + } + + outMsg.edit(context.textChannel, builder.build()).subscribe() + videoSelectionCache.put(outMsg.messageId, context, selectable, isPriority) + } } override fun help(context: Context): String { diff --git a/config/templates/fredboat.yml b/config/templates/fredboat.yml index ec38950b8..c4088281a 100644 --- a/config/templates/fredboat.yml +++ b/config/templates/fredboat.yml @@ -68,6 +68,9 @@ spring: output: ansi: enabled: detect # for developers: setting this to "always" will force colored logs in your console + data: + mongodb: + # uri: # URI connection for the mongodb server. Uncomment only if mongodb server is not on localhost with default settings audio-sources: @@ -86,10 +89,6 @@ audio-sources: ### Essential credentials ################################################################ -backend: - # TODO: Add Mongo here - - credentials: # Used by the ;;split and ;;np commands. Must be hooked up to the Youtube Data API. # You can add additional keys in case you are running a big bot From 71a0f941299b6fad5171d50c0498068e85455e0d Mon Sep 17 00:00:00 2001 From: Nanabell Date: Tue, 12 Feb 2019 18:37:27 +0100 Subject: [PATCH 088/172] Simplify If else --- .../command/music/control/PlayCommand.kt | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt index fe0306336..b65d89de9 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt @@ -143,33 +143,33 @@ class PlayCommand(private val playerLimiter: PlayerLimiter, private val trackSea context.textChannel, context.i18n("playSearchNoResults").replace("{q}", query) ).subscribe() + return + } - } else { - //Get at most 5 tracks - val selectable = list.tracks.subList(0, Math.min(TrackSearcher.MAX_RESULTS, list.tracks.size)) - - val oldSelection = videoSelectionCache.remove(context.member) - oldSelection?.deleteMessage() - - val builder = localMessageBuilder() - builder.append(context.i18nFormat("playSelectVideo", TextUtils.escapeMarkdown(context.prefix))) - - var i = 1 - for (track in selectable) { - builder.append("\n**") - .append(i.toString()) - .append(":** ") - .append(TextUtils.escapeAndDefuse(track.info.title)) - .append(" (") - .append(TextUtils.formatTime(track.info.length)) - .append(")") - - i++ - } + //Get at most 5 tracks + val selectable = list.tracks.subList(0, Math.min(TrackSearcher.MAX_RESULTS, list.tracks.size)) - outMsg.edit(context.textChannel, builder.build()).subscribe() - videoSelectionCache.put(outMsg.messageId, context, selectable, isPriority) + val oldSelection = videoSelectionCache.remove(context.member) + oldSelection?.deleteMessage() + + val builder = localMessageBuilder() + builder.append(context.i18nFormat("playSelectVideo", TextUtils.escapeMarkdown(context.prefix))) + + var i = 1 + for (track in selectable) { + builder.append("\n**") + .append(i.toString()) + .append(":** ") + .append(TextUtils.escapeAndDefuse(track.info.title)) + .append(" (") + .append(TextUtils.formatTime(track.info.length)) + .append(")") + + i++ } + + outMsg.edit(context.textChannel, builder.build()).subscribe() + videoSelectionCache.put(outMsg.messageId, context, selectable, isPriority) } override fun help(context: Context): String { From 1736b9983de1577fe39c329a8f3996a12f080ce8 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 15 Feb 2019 13:30:08 +0100 Subject: [PATCH 089/172] Change misleading fromEnum function name --- .../fredboat/command/config/PermissionsCommand.kt | 14 +++++++------- .../java/fredboat/db/transfer/PermissionEntity.kt | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/command/config/PermissionsCommand.kt b/FredBoat/src/main/java/fredboat/command/config/PermissionsCommand.kt index 68b583f39..0b0c6f1f7 100644 --- a/FredBoat/src/main/java/fredboat/command/config/PermissionsCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/PermissionsCommand.kt @@ -103,12 +103,12 @@ class PermissionsCommand( val settings = repo.fetch(context.guild.id).awaitSingle() val gp = settings.permissions - if (!gp.fromEnum(permissionLevel).contains(mentionableToId(selected))) { + if (!gp.getForEnum(permissionLevel).contains(mentionableToId(selected))) { context.replyWithName(context.i18nFormat("permsNotAdded", "`" + mentionableToName(selected) + "`", "`$permissionLevel`")) return } - val newList = gp.fromEnum(permissionLevel).toMutableList() + val newList = gp.getForEnum(permissionLevel).toMutableList() newList.remove(mentionableToId(selected)) if (permissionLevel == PermissionLevel.ADMIN @@ -121,7 +121,7 @@ class PermissionsCommand( context.replyWithName(context.i18nFormat("permsRemoved", mentionableToName(selected), permissionLevel)) - gp.fromEnum(permissionLevel, newList) + gp.setForEnum(permissionLevel, newList) repo.update(settings).subscribe() } @@ -138,17 +138,17 @@ class PermissionsCommand( val settings = repo.fetch(context.guild.id).awaitSingle() val gp = settings.permissions - if (gp.fromEnum(permissionLevel).contains(mentionableToId(selected))) { + if (gp.getForEnum(permissionLevel).contains(mentionableToId(selected))) { context.replyWithName(context.i18nFormat("permsAlreadyAdded", "`" + TextUtils.escapeMarkdown(mentionableToName(selected)) + "`", "`$permissionLevel`")) return } - val newList = gp.fromEnum(permissionLevel).toMutableList() + val newList = gp.getForEnum(permissionLevel).toMutableList() newList.add(mentionableToId(selected)) context.replyWithName(context.i18nFormat("permsAdded", TextUtils.escapeMarkdown(mentionableToName(selected)), permissionLevel)) - gp.fromEnum(permissionLevel, newList) + gp.setForEnum(permissionLevel, newList) repo.update(settings).subscribe() } @@ -157,7 +157,7 @@ class PermissionsCommand( val invoker = context.member val settings = repo.fetch(context.guild.id).awaitSingle() - val mentionables = idsToMentionables(guild, settings.permissions.fromEnum(permissionLevel)) + val mentionables = idsToMentionables(guild, settings.permissions.getForEnum(permissionLevel)) var roleMentions = "" var memberMentions = "" diff --git a/FredBoat/src/main/java/fredboat/db/transfer/PermissionEntity.kt b/FredBoat/src/main/java/fredboat/db/transfer/PermissionEntity.kt index afb349024..f3a28be11 100644 --- a/FredBoat/src/main/java/fredboat/db/transfer/PermissionEntity.kt +++ b/FredBoat/src/main/java/fredboat/db/transfer/PermissionEntity.kt @@ -7,7 +7,7 @@ data class PermissionEntity( var djList: List = emptyList(), var userList: List = emptyList() ) { - fun fromEnum(level: PermissionLevel): List { + fun getForEnum(level: PermissionLevel): List { return when (level) { PermissionLevel.ADMIN -> adminList PermissionLevel.DJ -> djList @@ -16,7 +16,7 @@ data class PermissionEntity( } } - fun fromEnum(level: PermissionLevel, newList: List) { + fun setForEnum(level: PermissionLevel, newList: List) { when (level) { PermissionLevel.ADMIN -> adminList = newList PermissionLevel.DJ -> djList = newList From 244d18d8c9f78c6c607efa3e1bfbde74c764a75d Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 15 Feb 2019 13:44:42 +0100 Subject: [PATCH 090/172] Adapt the configs for no quarterdeck --- config/templates/docker-compose.yml | 90 ++++++---------------- config/templates/quarterdeck.yml | 114 ---------------------------- config/templates/selfhosting.yml | 69 +++-------------- 3 files changed, 35 insertions(+), 238 deletions(-) delete mode 100644 config/templates/quarterdeck.yml diff --git a/config/templates/docker-compose.yml b/config/templates/docker-compose.yml index 2cec0ce68..7ca897ee5 100644 --- a/config/templates/docker-compose.yml +++ b/config/templates/docker-compose.yml @@ -6,7 +6,6 @@ # common.yml - Contains essential configuration for FredBoat and Sentinel # fredboat.yml - FredBoat config # lavalink.yml - Lavalink config -# quarterdeck.yml - Quarterdeck config # ################################################################################ @@ -24,26 +23,6 @@ version: '3' # This is not the FredBoat version, do not touch this line. services: - ################################################################################ - ## Database - ################################################################################ - db: - image: fredboat/postgres:latest - #image: fredboat/postgres:arm64v8 #for (some) arm based machines - #build: ./FredBoat/docker/database/ #useful alternative for developers - restart: always - labels: - - "com.centurylinklabs.watchtower.enable=true" - - # WINDOWS ONLY: if you are running under windows you need to comment out the following two lines: - volumes: - - ./postgres-data:/var/lib/postgresql/data - - # WINDOWS ONLY: if you are running under windows and want to take manual backups of the database - # via a docker volume, uncomment the following two lines and read the snippet at the bottom of this file - #volumes: - #- postgres-data-volume:/var/lib/postgresql/data - ################################################################################ ## RabbitMQ ################################################################################ @@ -51,10 +30,10 @@ services: image: rabbitmq:3-management restart: always ports: - - 127.0.0.1:5672:5672 # The broker - - 127.0.0.1:15672:15672 # Web UI + - 127.0.0.1:5672:5672 # The broker + - 127.0.0.1:15672:15672 # Web UI labels: - - "com.centurylinklabs.watchtower.enable=true" + - "com.centurylinklabs.watchtower.enable=true" ################################################################################ ## Lavalink @@ -63,12 +42,12 @@ services: image: fredboat/lavalink:master restart: always labels: - - "com.centurylinklabs.watchtower.enable=true" + - "com.centurylinklabs.watchtower.enable=true" ports: - - 127.0.01:2333:2333 + - 127.0.01:2333:2333 volumes: - - ./lavalink.yml:/opt/Lavalink/application.yaml - - ./lavalink_logs:/opt/Lavalink/logs + - ./lavalink.yml:/opt/Lavalink/application.yaml + - ./lavalink_logs:/opt/Lavalink/logs # Need a bigger memory size or any other custom JVM args? Edit the line below accordingly entrypoint: java -Xmx128m -jar Lavalink.jar @@ -79,48 +58,17 @@ services: image: fredboat/sentinel:dev restart: on-failure:3 depends_on: - - rabbitmq + - rabbitmq labels: - - "com.centurylinklabs.watchtower.enable=true" + - "com.centurylinklabs.watchtower.enable=true" volumes: - - ./sentinel_logs:/opt/sentinel/logs - - ./common.yml:/opt/sentinel/common.yml + - ./sentinel_logs:/opt/sentinel/logs + - ./common.yml:/opt/sentinel/common.yml environment: - - "SPRING_RABBITMQ_ADDRESSES=amqp://guest:guest@rabbitmq" + - "SPRING_RABBITMQ_ADDRESSES=amqp://guest:guest@rabbitmq" # Need a bigger memory size or any other custom JVM args? Uncomment and edit the line below accordingly #entrypoint: java -Xmx512m -jar sentinel.jar - ################################################################################ - ## Quarterdeck (Database Backend) - ################################################################################ - quarterdeck: - # versions (example: v2) receive continous non-breaking (best effort) updates via watchtower. switching between versions - # (example: going from v2 to v3) usually requires manual migration. what exactly is needed is published on the - # FredBoat selfhosting website and/or the selfhosters channel (see top of this file for how to get to these places) - # Once you use the dev branch, you may not be able to go back to stable without deleting your database. - # To run on (some) arm based machines, prepend the tag with arm64v8-, for example: fredboat/quarterdeck:arm64v8-dev-v1 - #image: fredboat/quarterdeck:stable-v1 - image: fredboat/quarterdeck:dev-v1 - restart: always - labels: - - "com.centurylinklabs.watchtower.enable=true" - depends_on: - - db - ports: - - 127.0.01:4269:4269 - volumes: - - ./quarterdeck.yml:/opt/Quarterdeck/quarterdeck.yaml - - ./quarterdeck_logs:/opt/Quarterdeck/logs - - # Need a bigger memory size or any other custom JVM args? Edit the list below accordingly - entrypoint: - - java - - -Xmx128m - - -jar - - Quarterdeck.jar - - > - --spring.application.json={"security": {"admins": [{"name": "docker", "pass": "docker"}]}} - ################################################################################ ## MongoDB ################################################################################ @@ -128,9 +76,17 @@ services: image: mongo:latest ports: - 127.0.0.1:27017:27017 + restart: always + + # WINDOWS ONLY: if you are running under windows you need to comment out the following two lines: volumes: - ./mongo:/data/db" + # WINDOWS ONLY: if you are running under windows and want to take manual backups of the database + # via a docker volume, uncomment the following two lines and read the snippet at the bottom of this file + #volumes: + #- mongo-data-volume:/data/db + ################################################################################ ## Automatic updates ################################################################################ @@ -152,13 +108,13 @@ services: ## Windows stuff ################################################################################ -# WINDOWS ONLY: If you are running on Windows and want to be able to backup the postgres data volume, run +# WINDOWS ONLY: If you are running on Windows and want to be able to backup the MongoDB data volume, run # -# docker volume create --name postgres-data-volume -d local +# docker volume create --name mongo-data-volume -d local # # and uncomment the following lines: # See also the WINDOWS ONLY hints in the database section #volumes: -# postgres-data-volume: +# mongo-data-volume: # external: true diff --git a/config/templates/quarterdeck.yml b/config/templates/quarterdeck.yml deleted file mode 100644 index 76b185235..000000000 --- a/config/templates/quarterdeck.yml +++ /dev/null @@ -1,114 +0,0 @@ ---- -################################################################ -### *** WARNING *** -################################################################ -### -### ALMOST EVERYTHING REQUESTED IN THIS FILE ARE CONFIDENTIAL CREDENTIALS -### IF YOU POST THIS FILE ONLINE (such as on GitHub) YOUR BOT COULD BE COMPROMISED -### -### -### Use a proper text editor when editing this file, for example Sublime. -### Do not use tab characters in this file, use plain spaces. -### -### Keep at least one space after a colon, like so: -### -### key: value -### -### -### Never edit or add a value to lines that have no default value, like: -### -### credentials: -### -### Just leave them be. A default value may be an empty string like so: "" -### -### -### You can wrap most values into quotation marks, except numbers and booleans: -### -### someUrl: "http://example.com" -### someToken: "123.qwe.456[DFG=" -### somePortNumber: 22 -### useSomeFeature: true -### -### -### You can have a list of things like this: -### -### listOfStrings: ["string1", "string2", "string3"] -### -### or like this: -### -### listOfStrings: -### - "string1" -### - "string2" -### - "string3" -### -### -### More information on correctly formatting yaml files: http://www.yaml.org/start.html - -security: - admins: - # Set a name and a pass. These have to be the same as set up in the fredboat.yaml - # Do not leave any of them blank or empty. - - name: "" - pass: "" - -whitelist: - userIds: # a list of discord user ids that shall never be ratelimited or blacklisted - - 81011298891993088 - - 166604053629894657 - -spring: - main: - banner-mode: log - output: - ansi: - enabled: detect # for developers: setting this to "always" will force colored logs in your console - -server: - port: 4269 # port of the backend - -sentry: - dsn: "" - -# If you are using docker to host the whole FredBoat, you can ignore all database settings below this line. -database: - main: - # FredBoat was written to work with PostgreSQL. - # If you are running with docker-compose then you don't need to change the jdbcUrl here. - # In PostgreSQL, role means user and vice versa. Keep that in mind when reading the following help and the provided links. - # If you are running your own PostgreSQL database, you will need to provide a role and a database belonging to that role. - # The role needs at least the permission to log in. - # All postgres databases used by FredBoat are required to have the Hstore extension enabled. - # Learn more about roles here: https://www.postgresql.org/docs/10/static/database-roles.html - # Learn more about creating databases here: https://www.postgresql.org/docs/10/static/manage-ag-createdb.html - # Learn more about the postgres jdbc url here: https://jdbc.postgresql.org/documentation/head/connect.html - # Learn more about creating extensions here: https://www.postgresql.org/docs/current/static/sql-createextension.html - # Example jdbc: "jdbc:postgresql://localhost:5432/fredboat?user=fredboat&password=youshallnotpass" - jdbcUrl: "" - - cache: - # Database for caching things, see config of main database above for details about the individual values. - # If you are running with docker-compose then you don't need to change the cache jdbcUrl here. - # The main and cache databases can be two databases inside a single postgres instance. - # They CANNOT be the same database due to the way flyway migrations work. - # The main benefit is that you don't have to backup/migrate the cache database, it can just be dropped/recreated - # If you do not provide a jdbc url for the cache database, FredBoat will still work (most likely), but may have a degraded - # performance, especially in high usage environments and when using Spotify playlists. - jdbcUrl: "" - -# tune these according to your needs, or just leave them as is. -logging: - file: - max-history: 30 - max-size: 1GB - path: ./logs/ - - level: - root: INFO - fredboat: DEBUG - com.fredboat: DEBUG - -docs: - open: false # Open the documentation endpoints for the public, and disable certain security features. - # DO NOT OPEN these on a production system. - host: "" # Reference: https://swagger.io/docs/specification/2-0/api-host-and-base-path/ - basePath: "" # Tell swagger where to find this Quarterdeck for Try out queries diff --git a/config/templates/selfhosting.yml b/config/templates/selfhosting.yml index 69e406718..419942c13 100644 --- a/config/templates/selfhosting.yml +++ b/config/templates/selfhosting.yml @@ -6,7 +6,6 @@ # common.yml - Contains essential configuration for FredBoat and Sentinel # fredboat.yml - FredBoat config # lavalink.yml - Lavalink config -# quarterdeck.yml - Quarterdeck config # ################################################################################ @@ -24,26 +23,6 @@ version: '3' # This is not the FredBoat version, do not touch this line. services: - ################################################################################ - ## Database - ################################################################################ - db: - image: fredboat/postgres:latest - #image: fredboat/postgres:arm64v8 #for (some) arm based machines - #build: ./FredBoat/docker/database/ #useful alternative for developers - restart: always - labels: - - "com.centurylinklabs.watchtower.enable=true" - - # WINDOWS ONLY: if you are running under windows you need to comment out the following two lines: - volumes: - - ./postgres-data:/var/lib/postgresql/data - - # WINDOWS ONLY: if you are running under windows and want to take manual backups of the database - # via a docker volume, uncomment the following two lines and read the snippet at the bottom of this file - #volumes: - #- postgres-data-volume:/var/lib/postgresql/data - ################################################################################ ## RabbitMQ ################################################################################ @@ -90,38 +69,6 @@ services: # Need a bigger memory size or any other custom JVM args? Uncomment and edit the line below accordingly #entrypoint: java -Xmx512m -jar sentinel.jar - ################################################################################ - ## Quarterdeck (Database Backend) - ################################################################################ - quarterdeck: - # versions (example: v2) receive continous non-breaking (best effort) updates via watchtower. switching between versions - # (example: going from v2 to v3) usually requires manual migration. what exactly is needed is published on the - # FredBoat selfhosting website and/or the selfhosters channel (see top of this file for how to get to these places) - # Once you use the dev branch, you may not be able to go back to stable without deleting your database. - # To run on (some) arm based machines, prepend the tag with arm64v8-, for example: fredboat/quarterdeck:arm64v8-dev-v1 - #image: fredboat/quarterdeck:stable-v1 - image: fredboat/quarterdeck:dev-v1 - restart: always - labels: - - "com.centurylinklabs.watchtower.enable=true" - depends_on: - - db - ports: - - 127.0.01:4269:4269 - volumes: - - ./quarterdeck.yml:/opt/Quarterdeck/quarterdeck.yaml - - ./quarterdeck_logs:/opt/Quarterdeck/logs - - # Need a bigger memory size or any other custom JVM args? Edit the list below accordingly - entrypoint: - - java - - -Xmx128m - - -jar - - Quarterdeck.jar - - > - --spring.application.json={"security": {"admins": [{"name": "docker", "pass": "docker"}]}} - - ################################################################################ ## FredBoat ################################################################################ @@ -136,7 +83,7 @@ services: labels: - "com.centurylinklabs.watchtower.enable=true" depends_on: - - quarterdeck + - mongo - sentinel - lavalink ports: @@ -165,9 +112,17 @@ services: image: mongo:latest ports: - 127.0.0.1:27017:27017 + restart: always + + # WINDOWS ONLY: if you are running under windows you need to comment out the following two lines: volumes: - ./mongo:/data/db" + # WINDOWS ONLY: if you are running under windows and want to take manual backups of the database + # via a docker volume, uncomment the following two lines and read the snippet at the bottom of this file + #volumes: + #- mongo-data-volume:/data/db + ################################################################################ ## Automatic updates ################################################################################ @@ -189,13 +144,13 @@ services: ## Windows stuff ################################################################################ -# WINDOWS ONLY: If you are running on Windows and want to be able to backup the postgres data volume, run +# WINDOWS ONLY: If you are running on Windows and want to be able to backup the MongoDB data volume, run # -# docker volume create --name postgres-data-volume -d local +# docker volume create --name mongo-data-volume -d local # # and uncomment the following lines: # See also the WINDOWS ONLY hints in the database section #volumes: -# postgres-data-volume: +# mongo-data-volume: # external: true From ff726d3d7d2c6de5446d2e28aae30a3d9176f6d9 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 15 Feb 2019 14:12:25 +0100 Subject: [PATCH 091/172] Uh oh, @Nanabell just got BEANED https://fred.moe/HKX.png --- FredBoat/src/main/java/fredboat/config/MetricsConfiguration.java | 1 + 1 file changed, 1 insertion(+) diff --git a/FredBoat/src/main/java/fredboat/config/MetricsConfiguration.java b/FredBoat/src/main/java/fredboat/config/MetricsConfiguration.java index 5cd8cb2ca..a3695d49f 100644 --- a/FredBoat/src/main/java/fredboat/config/MetricsConfiguration.java +++ b/FredBoat/src/main/java/fredboat/config/MetricsConfiguration.java @@ -40,6 +40,7 @@ public io.prometheus.client.guava.cache.CacheMetricsCollector guavaCacheMetrics( return new io.prometheus.client.guava.cache.CacheMetricsCollector().register(); } + @Bean public io.prometheus.client.cache.caffeine.CacheMetricsCollector caffeineCacheMetrics() { return new io.prometheus.client.cache.caffeine.CacheMetricsCollector().register(); } From 001b408db6994a701f3ae0720b671fba9d49be11 Mon Sep 17 00:00:00 2001 From: OtterBoops Date: Fri, 15 Feb 2019 16:16:00 +0200 Subject: [PATCH 092/172] Fix 563 and add i18n for Requested by --- .../main/java/fredboat/command/music/info/NowplayingCommand.kt | 3 ++- FredBoat/src/main/resources/lang/af_ZA.properties | 1 + FredBoat/src/main/resources/lang/ar_SA.properties | 1 + FredBoat/src/main/resources/lang/ast_ES.properties | 1 + FredBoat/src/main/resources/lang/bg_BG.properties | 1 + FredBoat/src/main/resources/lang/bn_BD.properties | 1 + FredBoat/src/main/resources/lang/ca_ES.properties | 1 + FredBoat/src/main/resources/lang/ceb_PH.properties | 1 + FredBoat/src/main/resources/lang/cs_CZ.properties | 1 + FredBoat/src/main/resources/lang/cy_GB.properties | 1 + FredBoat/src/main/resources/lang/da_DK.properties | 1 + FredBoat/src/main/resources/lang/de_DE.properties | 1 + FredBoat/src/main/resources/lang/el_GR.properties | 1 + FredBoat/src/main/resources/lang/en_PT.properties | 1 + FredBoat/src/main/resources/lang/en_TS.properties | 1 + FredBoat/src/main/resources/lang/en_US.properties | 1 + FredBoat/src/main/resources/lang/es_ES.properties | 1 + FredBoat/src/main/resources/lang/et_EE.properties | 1 + FredBoat/src/main/resources/lang/fa_IR.properties | 1 + FredBoat/src/main/resources/lang/fi_FI.properties | 1 + FredBoat/src/main/resources/lang/fil_PH.properties | 1 + FredBoat/src/main/resources/lang/fr_FR.properties | 1 + FredBoat/src/main/resources/lang/fr_TS.properties | 1 + FredBoat/src/main/resources/lang/ga_IE.properties | 1 + FredBoat/src/main/resources/lang/he_IL.properties | 1 + FredBoat/src/main/resources/lang/hr_HR.properties | 1 + FredBoat/src/main/resources/lang/hu_HU.properties | 1 + FredBoat/src/main/resources/lang/id_ID.properties | 1 + FredBoat/src/main/resources/lang/it_IT.properties | 1 + FredBoat/src/main/resources/lang/ja_JP.properties | 1 + FredBoat/src/main/resources/lang/ko_KR.properties | 1 + FredBoat/src/main/resources/lang/ms_MY.properties | 1 + FredBoat/src/main/resources/lang/nl_NL.properties | 1 + FredBoat/src/main/resources/lang/no_NO.properties | 1 + FredBoat/src/main/resources/lang/pl_PL.properties | 1 + FredBoat/src/main/resources/lang/pt_BR.properties | 1 + FredBoat/src/main/resources/lang/pt_PT.properties | 1 + FredBoat/src/main/resources/lang/ro_RO.properties | 1 + FredBoat/src/main/resources/lang/ru_RU.properties | 1 + FredBoat/src/main/resources/lang/sk_SK.properties | 1 + FredBoat/src/main/resources/lang/sr_SP.properties | 1 + FredBoat/src/main/resources/lang/sv_SE.properties | 1 + FredBoat/src/main/resources/lang/th_TH.properties | 1 + FredBoat/src/main/resources/lang/tr_TR.properties | 1 + FredBoat/src/main/resources/lang/uk_UA.properties | 1 + FredBoat/src/main/resources/lang/vi_VN.properties | 1 + FredBoat/src/main/resources/lang/yo_NG.properties | 1 + FredBoat/src/main/resources/lang/zh_CN.properties | 1 + FredBoat/src/main/resources/lang/zh_TW.properties | 1 + 49 files changed, 50 insertions(+), 1 deletion(-) diff --git a/FredBoat/src/main/java/fredboat/command/music/info/NowplayingCommand.kt b/FredBoat/src/main/java/fredboat/command/music/info/NowplayingCommand.kt index ec5103b03..0025df1b7 100644 --- a/FredBoat/src/main/java/fredboat/command/music/info/NowplayingCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/info/NowplayingCommand.kt @@ -71,7 +71,8 @@ class NowplayingCommand(private val youtubeAPI: YoutubeAPI, name: String, vararg else -> getDefaultEmbed(atc, player, at) } if (embed.footer == null) embed.footer { - text = "Requested by ${atc.member.effectiveName}#${atc.member.discrim}" // TODO i18n + var user = atc.member.effectiveName + text = context.i18nFormat("npRequestedBy", user) iconUrl = atc.member.info.awaitSingle().iconUrl } diff --git a/FredBoat/src/main/resources/lang/af_ZA.properties b/FredBoat/src/main/resources/lang/af_ZA.properties index 13710bbf0..3612b5d3b 100644 --- a/FredBoat/src/main/resources/lang/af_ZA.properties +++ b/FredBoat/src/main/resources/lang/af_ZA.properties @@ -62,6 +62,7 @@ npDescription=Beskrywing npLoadedSoundcloud=[{0}/{1}] Gelaai van Soundcloud npLoadedBandcamp={0} gelaai van Bandcamp npLoadedTwitch=Gelaai van pyl +npRequestedBy= permissionMissingBot=Ek moet die volgende toestemming om daardie aksie uitvoer\: permissionMissingInvoker=You need the following permission to perform that action\: permissionEmbedLinks=Bed skakels diff --git a/FredBoat/src/main/resources/lang/ar_SA.properties b/FredBoat/src/main/resources/lang/ar_SA.properties index 5da695f8c..869b8ffbf 100644 --- a/FredBoat/src/main/resources/lang/ar_SA.properties +++ b/FredBoat/src/main/resources/lang/ar_SA.properties @@ -62,6 +62,7 @@ npDescription=\u0627\u0644\u0648\u0635\u0641 npLoadedSoundcloud=[{0}/{1}] \u062a\u062d\u0645\u064a\u0644 \u0645\u0646 \u0633\u0648\u0646\u062f\u0643\u0644\u0648\u062f npLoadedBandcamp={0} \u062a\u062d\u0645\u064a\u0644 \u0645\u0646 Bandcamp npLoadedTwitch=\u062a\u0645 \u062a\u062d\u0645\u064a\u0644\u0647 \u0645\u0646 \u0646\u0634\u0644 +npRequestedBy= permissionMissingBot=\u0623\u0646\u0627 \u0628\u062d\u0627\u062c\u0629 \u0625\u0644\u0649 \u0625\u0630\u0646 \u0627\u0644\u062a\u0627\u0644\u064a\u0629 \u0644\u0644\u0642\u064a\u0627\u0645 \u0628\u0647\u0630\u0627 \u0627\u0644\u0625\u062c\u0631\u0627\u0621\: permissionMissingInvoker=\u0623\u0646\u0627 \u0628\u062d\u0627\u062c\u0629 \u0625\u0644\u0649 \u0625\u0630\u0646 \u0627\u0644\u062a\u0627\u0644\u064a\u0629 \u0644\u0644\u0642\u064a\u0627\u0645 \u0628\u0647\u0630\u0627 \u0627\u0644\u0625\u062c\u0631\u0627\u0621\: i need this comment to do this action\: permissionEmbedLinks=\u062a\u0636\u0645\u064a\u0646 \u0627\u0644\u0631\u0627\u0628\u0637 diff --git a/FredBoat/src/main/resources/lang/ast_ES.properties b/FredBoat/src/main/resources/lang/ast_ES.properties index 35dccd008..30d57f8df 100644 --- a/FredBoat/src/main/resources/lang/ast_ES.properties +++ b/FredBoat/src/main/resources/lang/ast_ES.properties @@ -62,6 +62,7 @@ npDescription=Descripci\u00f3n npLoadedSoundcloud=[{0}/{1}]\n\nLoaded from Soundcloud npLoadedBandcamp={0}\n\nLoaded from Bandcamp npLoadedTwitch=Loaded from Twitch +npRequestedBy= permissionMissingBot=Preciso los permisos de darr\u00e9u pa facer esa aici\u00f3n\: permissionMissingInvoker=Precises los permisos de darr\u00e9u pa facer esa aici\u00f3n\: permissionEmbedLinks=Embed Links diff --git a/FredBoat/src/main/resources/lang/bg_BG.properties b/FredBoat/src/main/resources/lang/bg_BG.properties index 24bbabd0f..99c82f6c6 100644 --- a/FredBoat/src/main/resources/lang/bg_BG.properties +++ b/FredBoat/src/main/resources/lang/bg_BG.properties @@ -62,6 +62,7 @@ npDescription=\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 npLoadedSoundcloud=[{0}/{1}] \u0417\u0430\u0440\u0435\u0434\u0435\u043d\u043e \u043e\u0442 Soundcloud npLoadedBandcamp={0}, \u0437\u0430\u0440\u0435\u0434\u0435\u043d\u043e \u043e\u0442 Bandcamp npLoadedTwitch=\u0417\u0430\u0440\u0435\u0434\u0435\u043d\u043e \u043e\u0442 Twitch +npRequestedBy= permissionMissingBot=\u0418\u043c\u0430\u043c \u043d\u0443\u0436\u0434\u0430 \u043e\u0442 \u0441\u043b\u0435\u0434\u043d\u043e\u0442\u043e \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0437\u0430 \u0434\u0430 \u0438\u0437\u043f\u044a\u043b\u043d\u044f \u0442\u043e\u0432\u0430 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435\: permissionMissingInvoker=\u0422\u0440\u044f\u0431\u0432\u0430 \u0442\u0438 \u0441\u043b\u0435\u0434\u0432\u0430\u0448\u0442\u043e\u0442\u043e \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0434\u0430 \u043d\u0430\u043f\u0440\u0430\u0432\u0438\u0448 \u0442\u043e\u0432\u0430\: permissionEmbedLinks=Embed Links diff --git a/FredBoat/src/main/resources/lang/bn_BD.properties b/FredBoat/src/main/resources/lang/bn_BD.properties index bc515b890..b206fc8e4 100644 --- a/FredBoat/src/main/resources/lang/bn_BD.properties +++ b/FredBoat/src/main/resources/lang/bn_BD.properties @@ -62,6 +62,7 @@ npDescription=Description npLoadedSoundcloud=[{0}/{1}]\n\nLoaded from Soundcloud npLoadedBandcamp={0}\n\nLoaded from Bandcamp npLoadedTwitch=Loaded from Twitch +npRequestedBy= permissionMissingBot=I need the following permission to perform that action\: permissionMissingInvoker=You need the following permission to perform that action\: permissionEmbedLinks=Embed Links diff --git a/FredBoat/src/main/resources/lang/ca_ES.properties b/FredBoat/src/main/resources/lang/ca_ES.properties index 41376d272..3449dc5ba 100644 --- a/FredBoat/src/main/resources/lang/ca_ES.properties +++ b/FredBoat/src/main/resources/lang/ca_ES.properties @@ -62,6 +62,7 @@ npDescription=Descripci\u00f3 npLoadedSoundcloud=[{0}/{1}]\n\nCarregat de Soundcloud npLoadedBandcamp={0}\n\nCarregat de Bandcamp npLoadedTwitch=Carregat de Twitch +npRequestedBy= permissionMissingBot=Necessito el seg\u00fcent perm\u00eds per poder fer aquesta acci\u00f3\: permissionMissingInvoker=Necesitas permisos para realizar esa acci\u00f3n\: permissionEmbedLinks=Incrustar enlla\u00e7os diff --git a/FredBoat/src/main/resources/lang/ceb_PH.properties b/FredBoat/src/main/resources/lang/ceb_PH.properties index 43b5cef18..d8aae4720 100644 --- a/FredBoat/src/main/resources/lang/ceb_PH.properties +++ b/FredBoat/src/main/resources/lang/ceb_PH.properties @@ -62,6 +62,7 @@ npDescription=Deskripsyon npLoadedSoundcloud={0}{1}\nBug-at gikan Soundcloud npLoadedBandcamp={0}\nBug-at gikan Bandcamp npLoadedTwitch=Bug-at gikan Twitch +npRequestedBy= permissionMissingBot=Gusto nako eh sunod ang pag tugot na mo performa ang aksyon\: permissionMissingInvoker=You need the following permission to perform that action\: permissionEmbedLinks=Embed nga mga link diff --git a/FredBoat/src/main/resources/lang/cs_CZ.properties b/FredBoat/src/main/resources/lang/cs_CZ.properties index 485e1dd44..eec849928 100644 --- a/FredBoat/src/main/resources/lang/cs_CZ.properties +++ b/FredBoat/src/main/resources/lang/cs_CZ.properties @@ -62,6 +62,7 @@ npDescription=Popis npLoadedSoundcloud=[{0}/{1}]\n\nNa\u010dteno z Soundcloud npLoadedBandcamp={0}\n\nNa\u010dteno z Bandcamp npLoadedTwitch=Na\u010dteno z Twitch +npRequestedBy= permissionMissingBot=Pot\u0159ebuji n\u00e1sleduj\u00edc\u00ed opr\u00e1vn\u011bn\u00ed k proveden\u00ed t\u00e9to akce\: permissionMissingInvoker=Pro vykon\u00e1n\u00ed t\u00e9to akce pot\u0159ebuje\u0161 n\u00e1sleduj\u00edc\u00ed opr\u00e1vn\u011bn\u00ed\: permissionEmbedLinks=Vlo\u017een\u00ed odkaz\u016f diff --git a/FredBoat/src/main/resources/lang/cy_GB.properties b/FredBoat/src/main/resources/lang/cy_GB.properties index 25845b232..6ea7cb8de 100644 --- a/FredBoat/src/main/resources/lang/cy_GB.properties +++ b/FredBoat/src/main/resources/lang/cy_GB.properties @@ -62,6 +62,7 @@ npDescription=Disgrifiad npLoadedSoundcloud=[{0}/{1}] Lwytho o''r Soundcloud npLoadedBandcamp={0} lwytho o''r Bandcamp npLoadedTwitch=Llwytho o nerfusrwydd +npRequestedBy= permissionMissingBot=Mae angen caniat\u00e2d canlynol i wneud hynny\: permissionMissingInvoker=You need the following permission to perform that action\: permissionEmbedLinks=Sefydlu cysylltiadau diff --git a/FredBoat/src/main/resources/lang/da_DK.properties b/FredBoat/src/main/resources/lang/da_DK.properties index e794f0cdb..d3e2803e5 100644 --- a/FredBoat/src/main/resources/lang/da_DK.properties +++ b/FredBoat/src/main/resources/lang/da_DK.properties @@ -62,6 +62,7 @@ npDescription=Beskrivelse npLoadedSoundcloud=[{0}/{1}]\nIndl\u00e6st fra Soundcloud npLoadedBandcamp={0}\n\nIndl\u00e6st fra Bandcamp npLoadedTwitch=Indl\u00e6st fra Twitch +npRequestedBy= permissionMissingBot=Jeg har brug for f\u00f8lgende tilladelse til at udf\u00f8re den p\u00e5g\u00e6ldende handling\: permissionMissingInvoker=Jeg har brug for f\u00f8lgende tilladelse til at udf\u00f8re den p\u00e5g\u00e6ldende handling\: permissionEmbedLinks=Integrer links diff --git a/FredBoat/src/main/resources/lang/de_DE.properties b/FredBoat/src/main/resources/lang/de_DE.properties index a6724eb45..16b8008e7 100644 --- a/FredBoat/src/main/resources/lang/de_DE.properties +++ b/FredBoat/src/main/resources/lang/de_DE.properties @@ -62,6 +62,7 @@ npDescription=Beschreibung npLoadedSoundcloud=[{0}/{1}]\n\nGeladen von Soundcloud npLoadedBandcamp={0} aus Bandcamp geladen npLoadedTwitch=Von Twitch geladen +npRequestedBy= permissionMissingBot=Ich brauche die folgenden Berechtigungen damit ich diese Aktion ausf\u00fchren kann\: permissionMissingInvoker=Du brauchst die folgenden Berechtigung damit sie diese Aktion ausf\u00fchren k\u00f6nnen\: permissionEmbedLinks=Links einbinden diff --git a/FredBoat/src/main/resources/lang/el_GR.properties b/FredBoat/src/main/resources/lang/el_GR.properties index 3819b98e3..e0ae29229 100644 --- a/FredBoat/src/main/resources/lang/el_GR.properties +++ b/FredBoat/src/main/resources/lang/el_GR.properties @@ -62,6 +62,7 @@ npDescription=\u03a0\u03b5\u03c1\u03b9\u03b3\u03c1\u03b1\u03c6\u03ae npLoadedSoundcloud=[{0}/{1}] \n\n\u03a0\u03c1\u03bf\u03c3\u03c4\u03ad\u03b8\u03b7\u03ba\u03b5 \u03b1\u03c0\u03cc Soundcloud npLoadedBandcamp={0}\n\n\u03a0\u03c1\u03bf\u03c3\u03c4\u03ad\u03b8\u03b7\u03ba\u03b5 \u03b1\u03c0\u03cc Bandcamp npLoadedTwitch=\u03a0\u03c1\u03bf\u03c3\u03c4\u03ad\u03b8\u03b7\u03ba\u03b5 \u03b1\u03c0\u03cc \u03c4\u03bf Twitch +npRequestedBy= permissionMissingBot=\u03a7\u03c1\u03b5\u03b9\u03ac\u03b6\u03bf\u03bc\u03b1\u03b9 \u03c4\u03b1 \u03b1\u03ba\u03cc\u03bb\u03bf\u03c5\u03b8\u03b1 \u03b4\u03b9\u03ba\u03b1\u03b9\u03ce\u03bc\u03b1\u03c4\u03b1 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03ba\u03c4\u03b5\u03bb\u03ad\u03c3\u03c9 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b5\u03bd\u03ad\u03c1\u03b3\u03b5\u03b9\u03b1\: permissionMissingInvoker=\u03a7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03b1\u03ba\u03cc\u03bb\u03bf\u03c5\u03b8\u03b1 \u03b4\u03b9\u03ba\u03b1\u03b9\u03ce\u03bc\u03b1\u03c4\u03b1 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03ba\u03c4\u03b5\u03bb\u03ad\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b5\u03bd\u03ad\u03c1\u03b3\u03b5\u03b9\u03b1\: permissionEmbedLinks=\u0395\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03bc\u03ad\u03bd\u03b5\u03c2 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03b9\u03c2 diff --git a/FredBoat/src/main/resources/lang/en_PT.properties b/FredBoat/src/main/resources/lang/en_PT.properties index 66eb84f1d..5977a9e69 100644 --- a/FredBoat/src/main/resources/lang/en_PT.properties +++ b/FredBoat/src/main/resources/lang/en_PT.properties @@ -62,6 +62,7 @@ npDescription=Description npLoadedSoundcloud=[{0}/{1}]\n\nStole th'' item from Soundcloud npLoadedBandcamp={0}\n\nStole th'' item from Bandcamp npLoadedTwitch=Stole th' item from Twitch +npRequestedBy= permissionMissingBot=I be lackin' this perm for that one\: permissionMissingInvoker=I be lackin' this perm for that one\: permissionEmbedLinks=Embed Links diff --git a/FredBoat/src/main/resources/lang/en_TS.properties b/FredBoat/src/main/resources/lang/en_TS.properties index ac5f813c2..8c218b586 100644 --- a/FredBoat/src/main/resources/lang/en_TS.properties +++ b/FredBoat/src/main/resources/lang/en_TS.properties @@ -62,6 +62,7 @@ npDescription=Description npLoadedSoundcloud=[{0}/{1}]\n\nand it is from Soundcloud\! npLoadedBandcamp={0}\n\nLoaded from Bandcamp npLoadedTwitch=That was from Twitch +npRequestedBy= permissionMissingBot=How am I supposed to do that if you don't give me permission, to\!? I need this\: permissionMissingInvoker=You need the following permission to perform that action\: permissionEmbedLinks=Embed Links diff --git a/FredBoat/src/main/resources/lang/en_US.properties b/FredBoat/src/main/resources/lang/en_US.properties index 40871fa6f..f9fcad7ee 100644 --- a/FredBoat/src/main/resources/lang/en_US.properties +++ b/FredBoat/src/main/resources/lang/en_US.properties @@ -62,6 +62,7 @@ npDescription=Description npLoadedSoundcloud=[{0}/{1}]\n\nLoaded from Soundcloud npLoadedBandcamp={0}\n\nLoaded from Bandcamp npLoadedTwitch=Loaded from Twitch +npRequestedBy=Requested by {0} permissionMissingBot=I need the following permission to perform that action\: permissionMissingInvoker=You need the following permission to perform that action\: permissionEmbedLinks=Embed Links diff --git a/FredBoat/src/main/resources/lang/es_ES.properties b/FredBoat/src/main/resources/lang/es_ES.properties index 592bd658a..9b8c402b7 100644 --- a/FredBoat/src/main/resources/lang/es_ES.properties +++ b/FredBoat/src/main/resources/lang/es_ES.properties @@ -62,6 +62,7 @@ npDescription=Descripci\u00f3n npLoadedSoundcloud=[{0}/{1}]\n\nCargado desde Soundcloud npLoadedBandcamp={0}\n\nCargado desde Bandcamp npLoadedTwitch=Cargado desde Twitch +npRequestedBy= permissionMissingBot=Necesito el siguiente permiso para realizar esa acci\u00f3n\: permissionMissingInvoker=Necesita el siguiente permiso para realizar esa acci\u00f3n\: permissionEmbedLinks=Insertar enlaces diff --git a/FredBoat/src/main/resources/lang/et_EE.properties b/FredBoat/src/main/resources/lang/et_EE.properties index e79ee255f..544d0dc36 100644 --- a/FredBoat/src/main/resources/lang/et_EE.properties +++ b/FredBoat/src/main/resources/lang/et_EE.properties @@ -62,6 +62,7 @@ npDescription=Kirjeldus npLoadedSoundcloud=[{0}/{1}] Laaditud Soundcloudi npLoadedBandcamp={0} Laaditud Bandicampist npLoadedTwitch=Laetud Twitchist +npRequestedBy= permissionMissingBot=Mul on vaja selle toimingu sooritamiseks j\u00e4rgmiseid \u00f5igusi\: permissionMissingInvoker=Sul on vaja selle toimingu kasutamiseks antud \u00f5igusi\: permissionEmbedLinks=Manustatud Lingid diff --git a/FredBoat/src/main/resources/lang/fa_IR.properties b/FredBoat/src/main/resources/lang/fa_IR.properties index 2a4a59be0..233f66a31 100644 --- a/FredBoat/src/main/resources/lang/fa_IR.properties +++ b/FredBoat/src/main/resources/lang/fa_IR.properties @@ -62,6 +62,7 @@ npDescription=\u062a\u0648\u0636\u06cc\u062d\u0627\u062a npLoadedSoundcloud=[{1}/{0}]\n\n\u0644\u0648\u062f \u0634\u062f\u0647 \u0627\u0632 \u0633\u0631\u0648\u06cc\u0633 Soundcloud npLoadedBandcamp={0}\n\n\u0644\u0648\u062f \u0634\u062f\u0647 \u0627\u0632 \u0633\u0631\u0648\u06cc\u0633 Bandcamp npLoadedTwitch=\u0644\u0648\u062f \u0634\u062f\u0647 \u0627\u0632 \u0633\u0631\u0648\u06cc\u0633 Twitch +npRequestedBy= permissionMissingBot=\u0645\u0646 \u0646\u06cc\u0627\u0632 \u0628\u0647 \u0645\u062c\u0648\u0632 \u0632\u06cc\u0631 \u0628\u0631\u0627\u06cc \u0627\u0646\u062c\u0627\u0645 \u0627\u06cc\u0646 \u06a9\u0627\u0631 \u062f\u0627\u0631\u0645\: permissionMissingInvoker=\u0634\u0645\u0627 \u0628\u0647 \u0645\u062c\u0648\u0632 \u0632\u06cc\u0631 \u0628\u0631\u0627\u06cc \u0627\u0646\u062c\u0627\u0645 \u0627\u06cc\u0646 \u06a9\u0627\u0631 \u0646\u06cc\u0627\u0632 \u062f\u0627\u0631\u06cc\u062f\: permissionEmbedLinks=\u0644\u06cc\u0646\u06a9 \u0647\u0627\u06cc \u0642\u0631\u0627\u0631 \u062f\u0627\u062f\u0647 \u0634\u062f\u0647 diff --git a/FredBoat/src/main/resources/lang/fi_FI.properties b/FredBoat/src/main/resources/lang/fi_FI.properties index b9305ac83..b9c2de018 100644 --- a/FredBoat/src/main/resources/lang/fi_FI.properties +++ b/FredBoat/src/main/resources/lang/fi_FI.properties @@ -62,6 +62,7 @@ npDescription=Kuvaus npLoadedSoundcloud=[{0}/{1}]\n\nLadattu Soundcloudista npLoadedBandcamp={0}\n\nLadattu Bandcampist\u00e4 npLoadedTwitch=Ladattu Twitchist\u00e4 +npRequestedBy= permissionMissingBot=Tarvitsen tuon toimenpiteen suorittamiseen seuraavat k\u00e4ytt\u00f6oikeudet\: permissionMissingInvoker=Tarvitsen tuon toimenpiteen suorittamiseen seuraavat k\u00e4ytt\u00f6oikeudet\: permissionEmbedLinks=Upottaa linkkej\u00e4 diff --git a/FredBoat/src/main/resources/lang/fil_PH.properties b/FredBoat/src/main/resources/lang/fil_PH.properties index d83eb344f..63766bf28 100644 --- a/FredBoat/src/main/resources/lang/fil_PH.properties +++ b/FredBoat/src/main/resources/lang/fil_PH.properties @@ -62,6 +62,7 @@ npDescription=Description npLoadedSoundcloud=[{0}/{1}]\n\nNakuha mula sa Soundcloud npLoadedBandcamp={0}\n\nNakuha mula sa Bandcamp npLoadedTwitch=Nakuha mula sa Twitch +npRequestedBy= permissionMissingBot=Kaylangan ko ang mga sumununod na pahintulot para maisagawa ang pagkilos\: permissionMissingInvoker=Kailangan mo ang sumusunod na pahintulot para ma-isagawa itong pagkilos\: permissionEmbedLinks=I kabit ang Link diff --git a/FredBoat/src/main/resources/lang/fr_FR.properties b/FredBoat/src/main/resources/lang/fr_FR.properties index 07cef415c..0b24671d3 100644 --- a/FredBoat/src/main/resources/lang/fr_FR.properties +++ b/FredBoat/src/main/resources/lang/fr_FR.properties @@ -62,6 +62,7 @@ npDescription=Description npLoadedSoundcloud=[{0}/{1}] \n\nCharg\u00e9 depuis Soundcloud npLoadedBandcamp={0}\n\n charg\u00e9 depuis Bandcamp npLoadedTwitch=Charg\u00e9 depuis Twitch +npRequestedBy= permissionMissingBot=J'ai besoin de l'autorisation suivante pour ex\u00e9cuter l'action suivante \: permissionMissingInvoker=Vous avez besoin de la permission suivante pour ex\u00e9cuter l'action suivante \: permissionEmbedLinks=Incorpore les liens diff --git a/FredBoat/src/main/resources/lang/fr_TS.properties b/FredBoat/src/main/resources/lang/fr_TS.properties index 83447d1d7..608c3b003 100644 --- a/FredBoat/src/main/resources/lang/fr_TS.properties +++ b/FredBoat/src/main/resources/lang/fr_TS.properties @@ -62,6 +62,7 @@ npDescription=Description npLoadedSoundcloud=[{0}/{1}]\n\nEt \u00e7a vient de Souncloud \! npLoadedBandcamp={0}\n\nCe truc vient de heu... Bandcamp... wow, impressionant... npLoadedTwitch=Charg\u00e9 depuis Twitch, beeerk +npRequestedBy= permissionMissingBot=Donne-moi ces permissions tout de suite ou je vais m'\u00e9nerver \: permissionMissingInvoker=Donne-moi ces permissions sinon j'le fais pas \: permissionEmbedLinks=Int\u00e9grer des liens diff --git a/FredBoat/src/main/resources/lang/ga_IE.properties b/FredBoat/src/main/resources/lang/ga_IE.properties index aae160ce1..df4fe7ee9 100644 --- a/FredBoat/src/main/resources/lang/ga_IE.properties +++ b/FredBoat/src/main/resources/lang/ga_IE.properties @@ -62,6 +62,7 @@ npDescription=Cur s\u00edos npLoadedSoundcloud=[{0}/{1}] a th\u00f3g m\u00e9 \u00f3 Soundcloud npLoadedBandcamp={0} a th\u00f3g m\u00e9 \u00f3 Bandcamp npLoadedTwitch=L\u00f3d\u00e1il as Twitch +npRequestedBy= permissionMissingBot=T\u00e1 an cead seo uaim len \u00e9 sin a dh\u00e9anamh\: permissionMissingInvoker=You need the following permission to perform that action\: permissionEmbedLinks=Embed Links diff --git a/FredBoat/src/main/resources/lang/he_IL.properties b/FredBoat/src/main/resources/lang/he_IL.properties index 30c6c4ff5..12244bfb8 100644 --- a/FredBoat/src/main/resources/lang/he_IL.properties +++ b/FredBoat/src/main/resources/lang/he_IL.properties @@ -62,6 +62,7 @@ npDescription=\u05ea\u05d9\u05d0\u05d5\u05e8 npLoadedSoundcloud=[{0}/{1}]\n\n\u05e0\u05d8\u05e2\u05df \u05de\u05e1\u05d0\u05d5\u05e0\u05d3\u05e7\u05dc\u05d0\u05d5\u05d3 npLoadedBandcamp={0} \u05e0\u05d8\u05e2\u05df \u05de\u05d1\u05d0\u05e0\u05d3\u05e7\u05d0\u05de\u05e4 npLoadedTwitch=\u05e0\u05d8\u05e2\u05df \u05de\u05d8\u05d5\u05d5\u05d9\u05e5' +npRequestedBy= permissionMissingBot=\u05d0\u05e0\u05d9 \u05e6\u05e8\u05d9\u05da \u05d0\u05ea \u05d4\u05e7\u05d9\u05e9\u05d5\u05e8\u05d9\u05dd \u05d4\u05d1\u05d0\u05d9\u05dd \u05db\u05d3\u05d9 \u05dc\u05d1\u05e6\u05e2 \u05e4\u05e2\u05d5\u05dc\u05d4 \u05d6\u05d5\: permissionMissingInvoker=\u05d0\u05ea\u05d4 \u05e6\u05e8\u05d9\u05da \u05d0\u05ea \u05d4\u05d4\u05e8\u05e9\u05d0\u05d5\u05ea \u05d4\u05d1\u05d0\u05d5\u05ea \u05db\u05d3\u05d9 \u05dc\u05d1\u05e6\u05e2 \u05e4\u05e2\u05d5\u05dc\u05d4 \u05d6\u05d5\: permissionEmbedLinks=\u05dc\u05e4\u05e8\u05e1\u05dd \u05e7\u05d9\u05e9\u05d5\u05e8\u05d9\u05dd diff --git a/FredBoat/src/main/resources/lang/hr_HR.properties b/FredBoat/src/main/resources/lang/hr_HR.properties index 9367fb4cd..212ff5565 100644 --- a/FredBoat/src/main/resources/lang/hr_HR.properties +++ b/FredBoat/src/main/resources/lang/hr_HR.properties @@ -62,6 +62,7 @@ npDescription=Opis npLoadedSoundcloud=[{0}/{1}] \n\nU\u010ditano sa SoundClouda npLoadedBandcamp={0}\n\nU\u010ditano sa Bandcampa npLoadedTwitch=U\u010ditano sa Twitcha +npRequestedBy= permissionMissingBot=Trebam sljede\u0107e dozvole za izvo\u0111enje te akcije\: permissionMissingInvoker=Moras imati dozvolu da bi izveu tu radnju\: permissionEmbedLinks=Ugra\u0111ene poveznice diff --git a/FredBoat/src/main/resources/lang/hu_HU.properties b/FredBoat/src/main/resources/lang/hu_HU.properties index 4cee3fa94..691af0670 100644 --- a/FredBoat/src/main/resources/lang/hu_HU.properties +++ b/FredBoat/src/main/resources/lang/hu_HU.properties @@ -62,6 +62,7 @@ npDescription=Le\u00edr\u00e1s npLoadedSoundcloud=[{0}/{1}]\n\nSoundcloud-r\u00f3l bet\u00f6ltve npLoadedBandcamp={0}\n\nBandcamp-r\u0151l bet\u00f6ltve npLoadedTwitch=Twitch-r\u0151l bet\u00f6ltve +npRequestedBy= permissionMissingBot=A k\u00f6vetkez\u0151 jogosults\u00e1gra van sz\u00fcks\u00e9gem a m\u0171velet v\u00e9grehajt\u00e1s\u00e1hoz\: permissionMissingInvoker=A k\u00f6vetkez\u0151 jogosults\u00e1gra van sz\u00fcks\u00e9gem a m\u0171velet v\u00e9grehajt\u00e1s\u00e1hoz\: permissionEmbedLinks=Be\u00e1gyazott Linkek diff --git a/FredBoat/src/main/resources/lang/id_ID.properties b/FredBoat/src/main/resources/lang/id_ID.properties index 2586c0552..1b5a1b66e 100644 --- a/FredBoat/src/main/resources/lang/id_ID.properties +++ b/FredBoat/src/main/resources/lang/id_ID.properties @@ -62,6 +62,7 @@ npDescription=Deskripsi npLoadedSoundcloud=[{0}/{1}]\n\nmengambil dari Soundcloud npLoadedBandcamp={0}\n\nMengambil dari Bandcamp npLoadedTwitch=Mengambil dari Twitch +npRequestedBy= permissionMissingBot=Aku memerlukan izin berikut untuk melakukan tindakan\: permissionMissingInvoker=You need the following permission to perform that action\: permissionEmbedLinks=Cantumkan Tautan diff --git a/FredBoat/src/main/resources/lang/it_IT.properties b/FredBoat/src/main/resources/lang/it_IT.properties index 6952dfb6a..1ddd96d92 100644 --- a/FredBoat/src/main/resources/lang/it_IT.properties +++ b/FredBoat/src/main/resources/lang/it_IT.properties @@ -62,6 +62,7 @@ npDescription=Descrizione npLoadedSoundcloud=[{0}/{1}]\n\nCaricato da SoundCloud npLoadedBandcamp={0}\n\nCaricato da Bandcamp npLoadedTwitch=Caricato da Twitch +npRequestedBy= permissionMissingBot=Ho bisogno della seguente autorizzazione per eseguire tale azione\: permissionMissingInvoker=Hai bisogno del seguente permesso per eseguire tale azione\: permissionEmbedLinks=Incorpora i collegamenti diff --git a/FredBoat/src/main/resources/lang/ja_JP.properties b/FredBoat/src/main/resources/lang/ja_JP.properties index cd15c5721..55a0b5b26 100644 --- a/FredBoat/src/main/resources/lang/ja_JP.properties +++ b/FredBoat/src/main/resources/lang/ja_JP.properties @@ -62,6 +62,7 @@ npDescription=\u8aac\u660e npLoadedSoundcloud=[{0}/{1}] \nSoundcloud \u304b\u3089\u8aad\u307f\u8fbc\u307e\u308c\u307e\u3059 npLoadedBandcamp={0} \u306fBandcamp \u304b\u3089\u8aad\u307f\u8fbc\u307e\u308c\u307e\u3059 npLoadedTwitch=Twitch\u304b\u3089\u8aad\u307f\u8fbc\u307e\u308c\u307e\u3059 +npRequestedBy= permissionMissingBot=\u305d\u306e\u30a2\u30af\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3059\u308b\u305f\u3081\u306b\u306f\u3001\u79c1\u306b\u3053\u308c\u3089\u306e\u6a29\u9650\u3092\u4e0e\u3048\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\: permissionMissingInvoker=\u305d\u306e\u30a2\u30af\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3059\u308b\u305f\u3081\u306b\u306f\u3001\u79c1\u306b\u3053\u308c\u3089\u306e\u6a29\u9650\u3092\u4e0e\u3048\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\: permissionEmbedLinks=\u30ea\u30f3\u30af\u3092\u57cb\u3081\u8fbc\u3080 diff --git a/FredBoat/src/main/resources/lang/ko_KR.properties b/FredBoat/src/main/resources/lang/ko_KR.properties index 1cdf3e6f8..2016976ff 100644 --- a/FredBoat/src/main/resources/lang/ko_KR.properties +++ b/FredBoat/src/main/resources/lang/ko_KR.properties @@ -62,6 +62,7 @@ npDescription=\uc124\uba85 npLoadedSoundcloud=[{0}/{1}]\n\nSoundCloud\uc5d0\uc11c \ubd88\ub7ec\uc634 npLoadedBandcamp={0}\n\nBandcamp\uc5d0\uc11c \ubd88\ub7ec\uc634 npLoadedTwitch=Twitch\uc5d0\uc11c \ubd88\ub7ec\uc634 +npRequestedBy= permissionMissingBot=\uc774 \uc791\uc5c5\uc744 \uc218\ud589\ud558\ub824\uba74 \ub2e4\uc74c\uacfc \uac19\uc740 \uad8c\ud55c\uc774 \ud544\uc694\ud569\ub2c8\ub2e4\: permissionMissingInvoker=\ud574\ub2f9 \uc791\uc5c5\uc744 \uc218\ud589 \ud558\ub824\uba74 \ub2e4\uc74c \uc0ac\uc6a9 \uad8c\ud55c\uc774 \ud544\uc694 \ud569\ub2c8\ub2e4. permissionEmbedLinks=\ub0b4\uc7a5\ub41c \ub9c1\ud06c diff --git a/FredBoat/src/main/resources/lang/ms_MY.properties b/FredBoat/src/main/resources/lang/ms_MY.properties index 91c85c0e7..d1fbdf798 100644 --- a/FredBoat/src/main/resources/lang/ms_MY.properties +++ b/FredBoat/src/main/resources/lang/ms_MY.properties @@ -62,6 +62,7 @@ npDescription=Keterangan npLoadedSoundcloud=[{0}/{1}]\n\nDimuatkan dari Soundcloud npLoadedBandcamp={0}\n\nDimuatkan dari Bandcamp npLoadedTwitch=Dimuatkan dari Twitch +npRequestedBy= permissionMissingBot=Saya memerlukan kebenaran berikut untuk melaksanakan tindakan tersebut\: permissionMissingInvoker=Anda memerlukan kebenaran berikut untuk melaksanakan tindakan tersebut\: permissionEmbedLinks=Pautan Benaman diff --git a/FredBoat/src/main/resources/lang/nl_NL.properties b/FredBoat/src/main/resources/lang/nl_NL.properties index 59d778dbc..9f99e3b94 100644 --- a/FredBoat/src/main/resources/lang/nl_NL.properties +++ b/FredBoat/src/main/resources/lang/nl_NL.properties @@ -62,6 +62,7 @@ npDescription=Beschrijving npLoadedSoundcloud=[{0}/{1}] \n\nGeladen vanuit Soundcloud npLoadedBandcamp={0}\n\nGeladen vanuit Bandcamp npLoadedTwitch=Geladen vanuit Twitch +npRequestedBy= permissionMissingBot=Ik heb de volgende toestemming nodig om die opdracht uit te voeren\: permissionMissingInvoker=Je hebt de volgende toestemming nodig om die opdracht uit te voeren\: permissionEmbedLinks=Voeg links toe diff --git a/FredBoat/src/main/resources/lang/no_NO.properties b/FredBoat/src/main/resources/lang/no_NO.properties index 30d3c3f40..4f1a87d03 100644 --- a/FredBoat/src/main/resources/lang/no_NO.properties +++ b/FredBoat/src/main/resources/lang/no_NO.properties @@ -62,6 +62,7 @@ npDescription=Beskrivelse npLoadedSoundcloud=[{0}/{1}] Lastet fra Soundcloud npLoadedBandcamp={0} lastet inn fra Bandcamp npLoadedTwitch=Lastet inn fra Twitch +npRequestedBy= permissionMissingBot=Jeg trenger f\u00f8lgende tillatelse for \u00e5 utf\u00f8re handlingen\: permissionMissingInvoker=You need the following permission to perform that action\: permissionEmbedLinks=Opprette lenker diff --git a/FredBoat/src/main/resources/lang/pl_PL.properties b/FredBoat/src/main/resources/lang/pl_PL.properties index 21de13eb0..8c0a88d4c 100644 --- a/FredBoat/src/main/resources/lang/pl_PL.properties +++ b/FredBoat/src/main/resources/lang/pl_PL.properties @@ -62,6 +62,7 @@ npDescription=Opis npLoadedSoundcloud=[{0}/{1}]\n\nZa\u0142adowano z Soundcloud''a npLoadedBandcamp={0}\n\nZa\u0142adowano z Bandcamp''a npLoadedTwitch=Za\u0142adowano z Twitch'a +npRequestedBy= permissionMissingBot=Potrzebuj\u0119 nast\u0119puj\u0105cych uprawnie\u0144 do wykonania tej akcji\: permissionMissingInvoker=Musisz mie\u0107 nast\u0119puj\u0105ce uprawnienia do wykonania tej akcji\: permissionEmbedLinks=Osadzone \u0142\u0105cza diff --git a/FredBoat/src/main/resources/lang/pt_BR.properties b/FredBoat/src/main/resources/lang/pt_BR.properties index ae35cf7f9..b73da5abd 100644 --- a/FredBoat/src/main/resources/lang/pt_BR.properties +++ b/FredBoat/src/main/resources/lang/pt_BR.properties @@ -62,6 +62,7 @@ npDescription=Descri\u00e7\u00e3o npLoadedSoundcloud=[{0}/{1}] \n\nCarregado do Soundcloud npLoadedBandcamp={0} \n\nCarregado do Bandcamp npLoadedTwitch=Carregado do Twitch +npRequestedBy= permissionMissingBot=Eu preciso de permiss\u00e3o para executar essa a\u00e7\u00e3o a seguir\: permissionMissingInvoker=Eu preciso de permiss\u00e3o para executar essa a\u00e7\u00e3o a seguir\: permissionEmbedLinks=Inserir Links de Embeds diff --git a/FredBoat/src/main/resources/lang/pt_PT.properties b/FredBoat/src/main/resources/lang/pt_PT.properties index f89c5b0c7..e45ab03b4 100644 --- a/FredBoat/src/main/resources/lang/pt_PT.properties +++ b/FredBoat/src/main/resources/lang/pt_PT.properties @@ -62,6 +62,7 @@ npDescription=Descri\u00e7\u00e3o npLoadedSoundcloud=[{0}/{1}] \n\nCarregado do Soundcloud npLoadedBandcamp={0} \n\nCarregado do Bandcamp npLoadedTwitch=Carregado do Twitch +npRequestedBy= permissionMissingBot=Eu preciso da seguinte permiss\u00e3o para executar essa a\u00e7\u00e3o\: permissionMissingInvoker=Eu preciso de permiss\u00e3o para executar essa a\u00e7\u00e3o a seguir\: permissionEmbedLinks=Liga\u00e7\u00f5es Incorporadas diff --git a/FredBoat/src/main/resources/lang/ro_RO.properties b/FredBoat/src/main/resources/lang/ro_RO.properties index a1782ed75..5ceb9e310 100644 --- a/FredBoat/src/main/resources/lang/ro_RO.properties +++ b/FredBoat/src/main/resources/lang/ro_RO.properties @@ -62,6 +62,7 @@ npDescription=Descriere npLoadedSoundcloud=[{0}/{1}]\n\n \u00cenc\u0103rcat de pe Soundcloud npLoadedBandcamp={0}\n\n\u00cenc\u0103rcat de pe Bandcamp npLoadedTwitch=\u00cenc\u0103rcat de pe Twitch +npRequestedBy= permissionMissingBot=Am nevoie de urm\u0103toarea permisiune pentru a efectua aceast\u0103 ac\u0163iune\: permissionMissingInvoker=Ai nevoie de urm\u0103toarea permisiune pentru a putea efectua aceast\u0103 ac\u021biune\: permissionEmbedLinks=Embed Links diff --git a/FredBoat/src/main/resources/lang/ru_RU.properties b/FredBoat/src/main/resources/lang/ru_RU.properties index 843a2b4e4..9af9f6c00 100644 --- a/FredBoat/src/main/resources/lang/ru_RU.properties +++ b/FredBoat/src/main/resources/lang/ru_RU.properties @@ -62,6 +62,7 @@ npDescription=\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 npLoadedSoundcloud=[{0}/{1}]\n\n\u0417\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043e \u0438\u0437 Soundcloud npLoadedBandcamp={0}\n\n\u0417\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043e \u0438\u0437 Bandcamp npLoadedTwitch=\u0417\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043e \u0438\u0437 Twitch +npRequestedBy= permissionMissingBot=\u041c\u043d\u0435 \u043d\u0443\u0436\u043d\u044b \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f\: permissionMissingInvoker=\u0412\u0430\u043c \u043d\u0443\u0436\u043d\u044b \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f\: permissionEmbedLinks=\u0412\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0435 \u0441\u0441\u044b\u043b\u043a\u0438 diff --git a/FredBoat/src/main/resources/lang/sk_SK.properties b/FredBoat/src/main/resources/lang/sk_SK.properties index faeaaf192..3e0b8e070 100644 --- a/FredBoat/src/main/resources/lang/sk_SK.properties +++ b/FredBoat/src/main/resources/lang/sk_SK.properties @@ -62,6 +62,7 @@ npDescription=Popis npLoadedSoundcloud=[{0}/{1}] \n\n Na\u010d\u00edtan\u00e9 zo Soundcloud npLoadedBandcamp={0} \n\nNa\u010d\u00edtan\u00e9 z Bandcampu npLoadedTwitch=Na\u010d\u00edtanie z Twitchu +npRequestedBy= permissionMissingBot=Potrebujem nasleduj\u00face povolenie aby som mohol spravi\u0165 t\u00fato akciu\: permissionMissingInvoker=Potrebujete n\u00e1sleduj\u00face povolenie na t\u00fato akciu\: permissionEmbedLinks=Vlo\u017eien\u00e9 odkazy diff --git a/FredBoat/src/main/resources/lang/sr_SP.properties b/FredBoat/src/main/resources/lang/sr_SP.properties index ba65f2362..3bfe00c42 100644 --- a/FredBoat/src/main/resources/lang/sr_SP.properties +++ b/FredBoat/src/main/resources/lang/sr_SP.properties @@ -62,6 +62,7 @@ npDescription=\u041e\u043f\u0438\u0441 npLoadedSoundcloud=[{0}/{1}]\n\n\u0423\u0447\u0438\u0442\u0430\u043d\u043e \u0438\u0437 SoundCloud-\u0430 npLoadedBandcamp={0}\n\n\u0423\u0447\u0438\u0442\u0430\u043d\u043e \u0441\u0430 Bandcamp-\u0430 npLoadedTwitch=\u0423\u0447\u0438\u0442\u0430\u043d\u043e \u0438\u0437 Twitch-\u0430 +npRequestedBy= permissionMissingBot=\u041f\u043e\u0442\u0440\u0435\u0431\u043d\u0435 \u0441\u0443 \u043c\u0438 \u0441\u0435\u043b\u0435\u0434\u0435\u045b\u0435 \u0434\u043e\u0437\u0432\u043e\u043b\u0435 \u0437\u0430 \u0438\u0437\u0432\u043e\u0452\u0435\u043d\u0435 \u0442\u0435 \u0430\u043a\u0446\u0438\u0458\u0435\: permissionMissingInvoker=\u041c\u043e\u0440\u0430\u0442\u0435 \u0438\u043c\u0430\u0442\u0438 \u0441\u043b\u0435\u0434\u0435\u045b\u0443 \u0434\u043e\u0437\u0432\u043e\u043b\u0443 \u0434\u0430 \u0431\u0438\u0441\u0442\u0435 \u0438\u0437\u0432\u0435\u043b\u0438 \u0442\u0443 \u0440\u0430\u0434\u045a\u0443\: permissionEmbedLinks=\u0423\u0433\u0440\u0430\u0452\u0435\u043d\u0435 \u0432\u0435\u0437\u0435 diff --git a/FredBoat/src/main/resources/lang/sv_SE.properties b/FredBoat/src/main/resources/lang/sv_SE.properties index 7d7667ca8..bd15ed121 100644 --- a/FredBoat/src/main/resources/lang/sv_SE.properties +++ b/FredBoat/src/main/resources/lang/sv_SE.properties @@ -62,6 +62,7 @@ npDescription=Beskrivning npLoadedSoundcloud=[{0}/{1}]\n\nLaddas fr\u00e5n Soundcloud npLoadedBandcamp={0}\n\nLaddad fr\u00e5n Bandcamp npLoadedTwitch=Laddad fr\u00e5n Twitch +npRequestedBy= permissionMissingBot=Jag beh\u00f6ver f\u00f6ljande till\u00e5telse f\u00f6r att utf\u00f6ra den uppgiften\: permissionMissingInvoker=Du beh\u00f6ver f\u00f6ljande beh\u00f6righet f\u00f6r att utf\u00f6ra \u00e5tg\u00e4rden\: permissionEmbedLinks=B\u00e4dda in l\u00e4nkar diff --git a/FredBoat/src/main/resources/lang/th_TH.properties b/FredBoat/src/main/resources/lang/th_TH.properties index e09236dd4..e96cb4bee 100644 --- a/FredBoat/src/main/resources/lang/th_TH.properties +++ b/FredBoat/src/main/resources/lang/th_TH.properties @@ -62,6 +62,7 @@ npDescription=\u0e04\u0e33\u0e2d\u0e18\u0e34\u0e1a\u0e32\u0e22 npLoadedSoundcloud=\u0e42\u0e2b\u0e25\u0e14 [{0}/{1}] \u0e08\u0e32\u0e01 Soundcloud \u0e40\u0e40\u0e25\u0e49\u0e27 npLoadedBandcamp=\u0e42\u0e2b\u0e25\u0e14 {0} \u0e08\u0e32\u0e01 Bandcamp \u0e40\u0e40\u0e25\u0e49\u0e27 npLoadedTwitch=\u0e42\u0e2b\u0e25\u0e14\u0e08\u0e32\u0e01 Twitch \u0e40\u0e40\u0e25\u0e49\u0e27 +npRequestedBy= permissionMissingBot=\u0e15\u0e49\u0e2d\u0e07\u0e21\u0e35\u0e01\u0e32\u0e23\u0e2d\u0e19\u0e38\u0e0d\u0e32\u0e15\u0e34\u0e43\u0e2b\u0e49\u0e1c\u0e21\u0e17\u0e33\u0e21\u0e31\u0e19\u0e19\u0e30\u0e1e\u0e35\u0e48 permissionMissingInvoker=\u0e04\u0e38\u0e13\u0e44\u0e21\u0e48\u0e21\u0e35\u0e2a\u0e34\u0e17\u0e18\u0e34\u0e4c\u0e01\u0e23\u0e30\u0e17\u0e33\u0e01\u0e32\u0e23\u0e15\u0e48\u0e2d\u0e44\u0e1b\u0e19\u0e35\u0e49\: permissionEmbedLinks=\u0e1d\u0e31\u0e07\u0e40\u0e0a\u0e37\u0e48\u0e2d\u0e21\u0e42\u0e22\u0e07\u0e40\u0e02\u0e49\u0e32\u0e44\u0e1b\u0e41\u0e25\u0e49\u0e27 diff --git a/FredBoat/src/main/resources/lang/tr_TR.properties b/FredBoat/src/main/resources/lang/tr_TR.properties index af0e3851f..8e343feb9 100644 --- a/FredBoat/src/main/resources/lang/tr_TR.properties +++ b/FredBoat/src/main/resources/lang/tr_TR.properties @@ -62,6 +62,7 @@ npDescription=A\u00e7\u0131klama npLoadedSoundcloud=[{0}/{1}]\n\nSoundcloud''dan y\u00fcklendi npLoadedBandcamp={0}\n\nBandcamp''ten y\u00fcklendi npLoadedTwitch=Twitch'ten y\u00fcklendi +npRequestedBy= permissionMissingBot=Bu i\u015flemi yerine getirebilmek i\u00e7in \u015fu izne ihtiyac\u0131m var\: permissionMissingInvoker=Bu i\u015flemi ger\u00e7ekle\u015ftirmek i\u00e7in a\u015fa\u011f\u0131daki izne ihtiyac\u0131n\u0131z var\: permissionEmbedLinks=G\u00f6mme Ba\u011flant\u0131lar diff --git a/FredBoat/src/main/resources/lang/uk_UA.properties b/FredBoat/src/main/resources/lang/uk_UA.properties index 8a711752e..ef6aae1b5 100644 --- a/FredBoat/src/main/resources/lang/uk_UA.properties +++ b/FredBoat/src/main/resources/lang/uk_UA.properties @@ -62,6 +62,7 @@ npDescription=\u041e\u043f\u0438\u0441 npLoadedSoundcloud=[{0}/{1}]\n\n\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043e \u0437 Soundcloud npLoadedBandcamp={0}\n\n\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043e \u0437 Bandcamp npLoadedTwitch=\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043e \u0437 Twitch +npRequestedBy= permissionMissingBot=\u041c\u0435\u043d\u0456 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u0456 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0456 \u0434\u043e\u0437\u0432\u043e\u043b\u0438 \u0434\u043b\u044f \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u043d\u044f \u0446\u0456\u0454\u0457 \u0434\u0456\u0457\: permissionMissingInvoker=\u0412\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u0456 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0456 \u0434\u043e\u0437\u0432\u043e\u043b\u0438 \u0434\u043b\u044f \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u043d\u044f \u0446\u0456\u0454\u0457 \u0434\u0456\u0457\: permissionEmbedLinks=\u0412\u0431\u0443\u0434\u043e\u0432\u0430\u043d\u0456 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f diff --git a/FredBoat/src/main/resources/lang/vi_VN.properties b/FredBoat/src/main/resources/lang/vi_VN.properties index 59e7fd72c..2f745de93 100644 --- a/FredBoat/src/main/resources/lang/vi_VN.properties +++ b/FredBoat/src/main/resources/lang/vi_VN.properties @@ -62,6 +62,7 @@ npDescription=Mi\u00eau t\u1ea3 npLoadedSoundcloud=[{0}/{1}]\n\n\u0110\u00e3 n\u1ea1p t\u1eeb Soundcloud npLoadedBandcamp={0}\n\n\u0110\u00e3 n\u1ea1p t\u1eeb Bandcamp npLoadedTwitch=\u0110\u00e3 n\u1ea1p t\u1eeb Twitch +npRequestedBy= permissionMissingBot=T\u00f4i c\u1ea7n quy\u1ec1n sau \u0111\u1ec3 l\u00e0m h\u00e0nh \u0111\u1ed9ng \u0111\u00f3\: permissionMissingInvoker=B\u1ea1n c\u1ea7n c\u00f3 quy\u1ec1n sau \u0111\u1ec3 th\u1ef1c hi\u1ec7n h\u00e0nh \u0111\u1ed9ng \u0111\u00f3\: permissionEmbedLinks=Nh\u00fang li\u00ean k\u1ebft diff --git a/FredBoat/src/main/resources/lang/yo_NG.properties b/FredBoat/src/main/resources/lang/yo_NG.properties index d83dcf2c8..a2b711284 100644 --- a/FredBoat/src/main/resources/lang/yo_NG.properties +++ b/FredBoat/src/main/resources/lang/yo_NG.properties @@ -62,6 +62,7 @@ npDescription=Description npLoadedSoundcloud=[{0}/{1}]\n\nLoaded from Soundcloud npLoadedBandcamp={0}\n\nLoaded from Bandcamp npLoadedTwitch=Loaded from Twitch +npRequestedBy= permissionMissingBot=I need the following permission to perform that action\: permissionMissingInvoker=You need the following permission to perform that action\: permissionEmbedLinks=Embed Links diff --git a/FredBoat/src/main/resources/lang/zh_CN.properties b/FredBoat/src/main/resources/lang/zh_CN.properties index b31d079e9..3d8666812 100644 --- a/FredBoat/src/main/resources/lang/zh_CN.properties +++ b/FredBoat/src/main/resources/lang/zh_CN.properties @@ -62,6 +62,7 @@ npDescription=\u8bf4\u660e npLoadedSoundcloud=[{0}/{1}]\n\n\u5df2\u4ece Soundcloud \u52a0\u8f7d\u5b8c\u6210 npLoadedBandcamp={0}\n\n\u5df2\u4ece Bandcamp \u52a0\u8f7d\u5b8c\u6210 npLoadedTwitch=\u5df2\u4ece Twitch \u52a0\u8f7d\u5b8c\u6210 +npRequestedBy= permissionMissingBot=\u6211\u9700\u8981\u4ee5\u4e0b\u6743\u9650\u624d\u80fd\u6267\u884c\u8be5\u64cd\u4f5c\: permissionMissingInvoker=\u60a8\u9700\u8981\u4ee5\u4e0b\u6743\u9650\u624d\u80fd\u6267\u884c\u8be5\u64cd\u4f5c\uff1a permissionEmbedLinks=\u5d4c\u5165\u94fe\u63a5 diff --git a/FredBoat/src/main/resources/lang/zh_TW.properties b/FredBoat/src/main/resources/lang/zh_TW.properties index 679700f43..b0064842b 100644 --- a/FredBoat/src/main/resources/lang/zh_TW.properties +++ b/FredBoat/src/main/resources/lang/zh_TW.properties @@ -62,6 +62,7 @@ npDescription=\u8a73\u60c5 npLoadedSoundcloud=[{0}/{1}]\n\n\u5df2\u5f9e Soundcloud \u4e2d\u8f09\u5165 npLoadedBandcamp={0} \n\n\u5df2\u5f9e Bandcamp \u4e2d\u8f09\u5165 npLoadedTwitch=\u5df2\u5f9e Twitch \u4e2d\u8f09\u5165 +npRequestedBy= permissionMissingBot=\u6211\u9700\u8981\u4e0b\u5217\u6b0a\u9650\u4f86\u57f7\u884c\u6b64\u64cd\u4f5c\: permissionMissingInvoker=\u4f60\u9700\u8981\u4e0b\u5217\u6b0a\u9650\u4f86\u57f7\u884c\u6b64\u64cd\u4f5c\: permissionEmbedLinks=\u5167\u5d4c\u9023\u7d50 From a1aafd049f1308e8d06da2fb432748216ac38f79 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 15 Feb 2019 15:24:23 +0100 Subject: [PATCH 093/172] Fetch prefix async --- FredBoat/build.gradle | 3 ++- .../src/main/java/fredboat/command/config/PrefixCommand.kt | 7 ++++++- .../main/java/fredboat/commandmeta/CommandContextParser.kt | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/FredBoat/build.gradle b/FredBoat/build.gradle index fbc067f1c..e50bf070b 100644 --- a/FredBoat/build.gradle +++ b/FredBoat/build.gradle @@ -54,7 +54,8 @@ dependencies { compile group: 'it.unimi.dsi', name: 'fastutil', version: fastUtilVersion compile group: 'com.google.code.gson', name: 'gson', version: gsonVersion compile group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlinVersion - compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-reactor', version: '1.0.1' + compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-reactor', version: '1.1.1' + compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-jdk8', version: '1.1.1' compile group: 'org.springframework.amqp', name: 'spring-rabbit', version: amqpVersion compile group: 'com.fasterxml.jackson.module', name: 'jackson-module-kotlin', version: jacksonKotlinVersion diff --git a/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt b/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt index 7a5f0fc82..17e7d65a1 100644 --- a/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt @@ -37,6 +37,7 @@ import fredboat.messaging.internal.Context import fredboat.perms.PermsUtil import fredboat.sentinel.Guild import io.prometheus.client.cache.caffeine.CacheMetricsCollector +import kotlinx.coroutines.future.await import java.util.concurrent.TimeUnit /** @@ -63,11 +64,15 @@ class PrefixCommand(cacheMetrics: CacheMetricsCollector, //.concurrencyLevel(Launcher.botController.appConfig.shardCount) //each shard has a thread (main JDA thread) accessing this cache many times .buildAsync { key, _ -> getBotController().guildSettingsRepository.fetch(key).map { it.prefix }.toFuture() } + // TODO: Remove blocking versions fun giefPrefix(guildId: Long) = CUSTOM_PREFIXES.synchronous()[guildId] ?: Launcher.botController.appConfig.prefix - fun giefPrefix(guild: Guild) = giefPrefix(guild.id) + suspend fun getPrefixSuspending(guildId: Long): String = CUSTOM_PREFIXES[guildId].await() + ?: Launcher.botController.appConfig.prefix + suspend fun getPrefixSuspending(guild: Guild) = getPrefixSuspending(guild.id) + fun showPrefix(context: Context, prefix: String) { val p = if (prefix.isEmpty()) "No Prefix" else prefix context.reply(context.i18nFormat("prefixGuild", "``$p``") diff --git a/FredBoat/src/main/java/fredboat/commandmeta/CommandContextParser.kt b/FredBoat/src/main/java/fredboat/commandmeta/CommandContextParser.kt index f6dc39684..c9eedbb69 100644 --- a/FredBoat/src/main/java/fredboat/commandmeta/CommandContextParser.kt +++ b/FredBoat/src/main/java/fredboat/commandmeta/CommandContextParser.kt @@ -72,7 +72,7 @@ class CommandContextParser( input = mentionMatcher.group(3).trim { it <= ' ' } isMention = true } else { - val prefix = PrefixCommand.giefPrefix(event.guild) + val prefix = PrefixCommand.getPrefixSuspending(event.guild) val defaultPrefix = appConfig.prefix if (content.startsWith(prefix)) { input = content.substring(prefix.length) From bd0934adbe5bfccec1e586fdc958ece883c6ab34 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 15 Feb 2019 15:55:15 +0100 Subject: [PATCH 094/172] Remove unnecessary secondary prefix cache --- .../fredboat/command/config/PrefixCommand.kt | 41 ++++++------------- .../commandmeta/CommandContextParser.kt | 3 +- .../commandmeta/CommandInitializer.kt | 3 +- 3 files changed, 15 insertions(+), 32 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt b/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt index 17e7d65a1..199ae8ca2 100644 --- a/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt @@ -25,53 +25,40 @@ package fredboat.command.config -import com.github.benmanes.caffeine.cache.Caffeine import fredboat.commandmeta.abs.Command import fredboat.commandmeta.abs.CommandContext import fredboat.commandmeta.abs.IConfigCommand import fredboat.db.api.GuildSettingsRepository import fredboat.definitions.PermissionLevel import fredboat.main.Launcher -import fredboat.main.getBotController import fredboat.messaging.internal.Context import fredboat.perms.PermsUtil import fredboat.sentinel.Guild -import io.prometheus.client.cache.caffeine.CacheMetricsCollector -import kotlinx.coroutines.future.await -import java.util.concurrent.TimeUnit +import reactor.core.publisher.Mono +import java.time.Duration /** * Created by napster on 19.10.17. */ -class PrefixCommand(cacheMetrics: CacheMetricsCollector, - private val repo: GuildSettingsRepository, +class PrefixCommand(private val repo: GuildSettingsRepository, name: String, vararg aliases: String) : Command(name, *aliases), IConfigCommand { init { - cacheMetrics.addCache("customPrefixes", CUSTOM_PREFIXES) + staticRepo = repo } + // TODO: Remove blocking versions and static abuse companion object { - val CUSTOM_PREFIXES = Caffeine.newBuilder() - //it is fine to check the db for updates occasionally, as we currently dont have any use case where we change - //the value saved there through other means. in case we add such a thing (like a dashboard), consider lowering - //the refresh value to have the changes reflect faster in the bot, or consider implementing a FredBoat wide - //Listen/Notify system for changes to in memory cached values backed by the db - .recordStats() - .refreshAfterWrite(1, TimeUnit.MINUTES) //NOTE: never use refreshing without async reloading, because Guavas cache uses the thread calling it to do cleanup tasks (including refreshing) - .expireAfterAccess(1, TimeUnit.MINUTES) //evict inactive guilds - //.concurrencyLevel(Launcher.botController.appConfig.shardCount) //each shard has a thread (main JDA thread) accessing this cache many times - .buildAsync { key, _ -> getBotController().guildSettingsRepository.fetch(key).map { it.prefix }.toFuture() } + lateinit var staticRepo: GuildSettingsRepository + val defaultPrefix: String get() = Launcher.botController.appConfig.prefix - // TODO: Remove blocking versions - fun giefPrefix(guildId: Long) = CUSTOM_PREFIXES.synchronous()[guildId] - ?: Launcher.botController.appConfig.prefix - fun giefPrefix(guild: Guild) = giefPrefix(guild.id) + fun getPrefix(guildId: Long): Mono = staticRepo.fetch(guildId) + .map { it.prefix ?: defaultPrefix } + .defaultIfEmpty(defaultPrefix) + fun getPrefix(guild: Guild) = getPrefix(guild.id) - suspend fun getPrefixSuspending(guildId: Long): String = CUSTOM_PREFIXES[guildId].await() - ?: Launcher.botController.appConfig.prefix - suspend fun getPrefixSuspending(guild: Guild) = getPrefixSuspending(guild.id) + fun giefPrefix(guild: Guild) = getPrefix(guild).block(Duration.ofSeconds(10))!! fun showPrefix(context: Context, prefix: String) { val p = if (prefix.isEmpty()) "No Prefix" else prefix @@ -107,10 +94,6 @@ class PrefixCommand(cacheMetrics: CacheMetricsCollector, .let { repo.update(it) } .subscribe() - //we could do a put instead of invalidate here and probably safe one lookup, but that undermines the database - // as being the single source of truth for prefixes - CUSTOM_PREFIXES.synchronous().invalidate(context.guild.id) - showPrefix(context, giefPrefix(context.guild)) } diff --git a/FredBoat/src/main/java/fredboat/commandmeta/CommandContextParser.kt b/FredBoat/src/main/java/fredboat/commandmeta/CommandContextParser.kt index c9eedbb69..f8f42a8ef 100644 --- a/FredBoat/src/main/java/fredboat/commandmeta/CommandContextParser.kt +++ b/FredBoat/src/main/java/fredboat/commandmeta/CommandContextParser.kt @@ -34,6 +34,7 @@ import fredboat.sentinel.Message import fredboat.sentinel.RawUser import fredboat.sentinel.getGuildMono import kotlinx.coroutines.reactive.awaitFirstOrNull +import kotlinx.coroutines.reactive.awaitSingle import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Qualifier import org.springframework.stereotype.Component @@ -72,7 +73,7 @@ class CommandContextParser( input = mentionMatcher.group(3).trim { it <= ' ' } isMention = true } else { - val prefix = PrefixCommand.getPrefixSuspending(event.guild) + val prefix = PrefixCommand.getPrefix(event.guild).awaitSingle() val defaultPrefix = appConfig.prefix if (content.startsWith(prefix)) { input = content.substring(prefix.length) diff --git a/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt b/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt index 97b550b24..b9bb2a6a5 100644 --- a/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt +++ b/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt @@ -64,7 +64,6 @@ import java.util.function.Supplier @Service class CommandInitializer( cacheMetrics: CacheMetricsCollector, - caffeineCacheMetrics: io.prometheus.client.cache.caffeine.CacheMetricsCollector, weather: Weather, trackSearcher: TrackSearcher, videoSelectionCache: VideoSelectionCache, @@ -143,7 +142,7 @@ class CommandInitializer( configModule.registerCommand(ConfigCommand(CONFIG_COMM_NAME, guildSettingsRepository, "cfg")) configModule.registerCommand(LanguageCommand(LANGUAGE_COMM_NAME, "lang")) configModule.registerCommand(ModulesCommand("modules", guildSettingsRepository, "module", "mods")) - configModule.registerCommand(PrefixCommand(caffeineCacheMetrics, guildSettingsRepository, PREFIX_COMM_NAME, "pre")) + configModule.registerCommand(PrefixCommand(guildSettingsRepository, PREFIX_COMM_NAME, "pre")) configModule.registerCommand(ConfigWebInfoCommand("webinfo", repo = guildSettingsRepository, appConfig = appConfig)) /* Perms */ configModule.registerCommand(PermissionsCommand(PermissionLevel.ADMIN, guildSettingsRepository, "admin", "admins")) From 914e63a439b42049a170619afd4f0940d56d8e21 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 15 Feb 2019 16:34:54 +0100 Subject: [PATCH 095/172] Replace funky concurrency workaround --- .../fredboat/audio/player/PlayerRegistry.kt | 61 ++++++------------- 1 file changed, 20 insertions(+), 41 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index 304618942..77faf9df7 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -38,7 +38,11 @@ import fredboat.definitions.RepeatMode import fredboat.sentinel.Guild import fredboat.util.ratelimit.Ratelimiter import fredboat.util.rest.YoutubeAPI +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.reactive.awaitFirst +import kotlinx.coroutines.reactive.awaitFirstOrNull +import kotlinx.coroutines.reactive.awaitSingle +import kotlinx.coroutines.reactor.mono import lavalink.client.LavalinkUtil import lavalink.client.io.Link.State import org.slf4j.Logger @@ -46,10 +50,8 @@ import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Qualifier import org.springframework.stereotype.Component import reactor.core.publisher.Mono -import reactor.core.publisher.toMono import java.io.IOException import java.time.Duration -import java.util.* import java.util.concurrent.ConcurrentHashMap import java.util.function.BiConsumer import kotlin.concurrent.thread @@ -74,7 +76,6 @@ class PlayerRegistry( private val registry = ConcurrentHashMap() private val iteratorLock = Any() //iterators, which are also used by stream(), need to be synced, despite it being a concurrent map private val monoCache = ConcurrentHashMap>() - private val recursionSafeguard = ThreadLocal>() /** * @return a copied list of the the playing players of the registry. This may be an expensive operation depending on @@ -151,47 +152,21 @@ class PlayerRegistry( * @return a [Mono] with a fully loaded [GuildPlayer] */ @Suppress("RedundantLambdaArrow") - private fun createPlayer(guild: Guild): Mono { - // Checks for recursion, and returns the mono getting dealt with - if (recursionSafeguard.get() != null) return recursionSafeguard.get().doOnSuccess { - if (guild != it.guild) { - throw IllegalStateException("Recursion safeguard held mono for wrong guild. That shouldn't happen!") - } - } - - return monoCache.computeIfAbsent(guild.id) { _ -> - createPlayerMono(guild) - }.doOnSuccess { - registry[it.guildId] = it - it.player.link.releaseHeldEvents() - }.doFinally { - monoCache.remove(guild.id) - } + private fun createPlayer(guild: Guild): Mono = monoCache.computeIfAbsent(guild.id) { _ -> + createPlayerMono(guild) + }.doOnSuccess { + registry[it.guildId] = it + it.player.link.releaseHeldEvents() + }.doFinally { + monoCache.remove(guild.id) } private fun createPlayerMono(guild: Guild): Mono { - lateinit var player: GuildPlayer - - val mono = playerRepo.findById(guild.id) - .map { - // player repo may complete as empty. - // The zip operator will cancel the other mono, if we return empty-handed. - // We can deal with this by using Optional - Optional.of(it) - } - .switchIfEmpty(Optional.empty().toMono()) - .map { mongo -> - if (mongo.isEmpty) return@map player - loadMongoData(player, mongo.get()) - player - }.cache() // Cache avoids loading the mongo data twice - // GuildPlayer's constructor will indirectly call #createPlayer(). // We can defer the construction to a different thread, to prevent an IllegalStateException, which // would be caused by accessing monoCache recursively - try { - recursionSafeguard.set(mono) - player = GuildPlayer( + val playerDeferred: Mono = Mono.fromSupplier { + GuildPlayer( sentinelLavalink, guild, musicTextChannelProvider, @@ -200,11 +175,15 @@ class PlayerRegistry( ratelimiter, youtubeAPI ) - } finally { - recursionSafeguard.remove() } - return mono + return GlobalScope.mono { + val mongo = playerRepo.findById(guild.id).awaitFirstOrNull() + val player = playerDeferred.awaitSingle() + if (mongo == null) return@mono player + loadMongoData(player, mongo) + player + }.cache() } /** From 877a34459b8e134526cb6f024a0e947f9f48fe17 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 15 Feb 2019 16:37:47 +0100 Subject: [PATCH 096/172] Mark more i18n methods as deprecated --- FredBoat/src/main/java/fredboat/feature/I18n.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/feature/I18n.kt b/FredBoat/src/main/java/fredboat/feature/I18n.kt index 364d3405a..be2ceb91f 100644 --- a/FredBoat/src/main/java/fredboat/feature/I18n.kt +++ b/FredBoat/src/main/java/fredboat/feature/I18n.kt @@ -32,6 +32,7 @@ import org.slf4j.LoggerFactory import java.time.Duration import java.util.* +@Suppress("DeprecatedCallableAddReplaceWith") object I18n { private val log = LoggerFactory.getLogger(I18n::class.java) @@ -46,14 +47,17 @@ object I18n { log.info("Loaded " + LANGS.size + " languages: " + LANGS) } + @Deprecated("Convert this to reactive at some point!") operator fun get(guild: Guild?): ResourceBundle { return if (guild == null) DEFAULT.props else get(guild.id) } + @Deprecated("Convert this to reactive at some point!") operator fun get(guild: Long): ResourceBundle { return getLocale(guild).props } + @Deprecated("Convert this to reactive at some point!") fun getLocale(guild: Guild): FredBoatLocale { return getLocale(guild.id) } @@ -82,7 +86,7 @@ object I18n { class FredBoatLocale @Throws(MissingResourceException::class) internal constructor(private val language: Language) { - val props: ResourceBundle + val props: ResourceBundle = ResourceBundle.getBundle("lang." + language.code, language.locale) val code: String get() = language.code @@ -93,10 +97,6 @@ object I18n { val englishName: String get() = language.englishName - init { - props = ResourceBundle.getBundle("lang." + language.code, language.locale) - } - override fun toString(): String { return "[$code $nativeName]" } From 1953a474d7703c3bbd774bb1d836bc8281531525 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Fri, 15 Feb 2019 17:14:37 +0100 Subject: [PATCH 097/172] Update LLC --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6ae22496d..5b04157d4 100644 --- a/build.gradle +++ b/build.gradle @@ -65,7 +65,7 @@ subprojects { //audio deps lavaplayerVersion = '1.3.9' - lavalinkVersion = 'b53ab694' + lavalinkVersion = '44caa9d5' //utility deps jsonOrgVersion = '20180130' From 32630f75d815105b463c9c1656bfc5294f4f9904 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Sat, 16 Feb 2019 20:21:35 +0100 Subject: [PATCH 098/172] Fix WS open race condition --- FredBoat/src/main/java/fredboat/ws/UserSession.kt | 8 +------- FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt | 3 ++- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/ws/UserSession.kt b/FredBoat/src/main/java/fredboat/ws/UserSession.kt index ccd9c19eb..99b30084b 100644 --- a/FredBoat/src/main/java/fredboat/ws/UserSession.kt +++ b/FredBoat/src/main/java/fredboat/ws/UserSession.kt @@ -8,7 +8,6 @@ import org.springframework.web.reactive.socket.WebSocketMessage import org.springframework.web.reactive.socket.WebSocketSession import reactor.core.publisher.Flux import reactor.core.publisher.FluxSink -import reactor.core.publisher.Mono import java.util.regex.Pattern class UserSession( @@ -23,6 +22,7 @@ class UserSession( @Volatile private lateinit var sink: FluxSink + val sendStream: Flux = Flux.create { sink = it } var isOpen = true val guildId = expctedPath.matcher(handshakeInfo.uri.path).run { find(); group(1) }.toLong() val guild: Guild? get() = guildCache.getIfCached(guildId) @@ -36,10 +36,4 @@ class UserSession( fun send(message: WebSocketMessage) { sink.next(message) } - - /** Grants us a hook to send messages with */ - fun initSendStream(): Mono { - return session.send(Flux.create { sink = it }) - .doFinally { isOpen = false } - } } \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt b/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt index 5da6aad99..7201ffa57 100644 --- a/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt +++ b/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt @@ -48,7 +48,7 @@ class UserSessionHandler( session.sendJson(info) } - return session.initSendStream() + return rawSession.send(session.sendStream) .doOnSubscribe { interceptMono.subscribe() } .and(session.receive().doOnNext { handleMessage(session, it) }) .doFinally { @@ -63,6 +63,7 @@ class UserSessionHandler( operator fun get(guildId: Long): List = sessions[guildId] ?: emptyList() fun afterConnectionClosed(session: UserSession) { + session.isOpen = false val id = session.guildId log.info("Disconnected user for guild $id") val list = sessions[id] ?: return From 6906e27fad603263998fb3d4c161ac175f56cfec Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Sat, 16 Feb 2019 21:17:10 +0100 Subject: [PATCH 099/172] Update Spring-AMQP --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5b04157d4..63ae42961 100644 --- a/build.gradle +++ b/build.gradle @@ -80,7 +80,7 @@ subprojects { caffeineVersion = '2.6.2' bucket4jVersion = '3.1.1' gsonVersion = '2.8.2' - amqpVersion = "2.0.4.RELEASE" + amqpVersion = "2.1.4.RELEASE" jacksonKotlinVersion = "2.9.+" //logging / monitoring deps From ae625e0925415be4eee2b7a7f1bda5950168310b Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Sat, 16 Feb 2019 21:31:38 +0100 Subject: [PATCH 100/172] Fix lateinit exception --- FredBoat/src/main/java/fredboat/ws/UserSession.kt | 5 +++-- FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/ws/UserSession.kt b/FredBoat/src/main/java/fredboat/ws/UserSession.kt index 99b30084b..929b03fb2 100644 --- a/FredBoat/src/main/java/fredboat/ws/UserSession.kt +++ b/FredBoat/src/main/java/fredboat/ws/UserSession.kt @@ -13,7 +13,8 @@ import java.util.regex.Pattern class UserSession( val session: WebSocketSession, private val guildCache: GuildCache, - private val gson: Gson + private val gson: Gson, + onSubscribe: () -> Unit ) : WebSocketSession by session { companion object { @@ -22,7 +23,7 @@ class UserSession( @Volatile private lateinit var sink: FluxSink - val sendStream: Flux = Flux.create { sink = it } + val sendStream: Flux = Flux.create { sink = it; onSubscribe() } var isOpen = true val guildId = expctedPath.matcher(handshakeInfo.uri.path).run { find(); group(1) }.toLong() val guild: Guild? get() = guildCache.getIfCached(guildId) diff --git a/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt b/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt index 7201ffa57..f12a7c978 100644 --- a/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt +++ b/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt @@ -27,10 +27,11 @@ class UserSessionHandler( private val sessions = ConcurrentHashMap>() override fun handle(rawSession: WebSocketSession): Mono { - val session = UserSession(rawSession, guildCache, gson) + lateinit var interceptMono: Mono + val session = UserSession(rawSession, guildCache, gson) { interceptMono.subscribe() } log.info("Established user connection for guild ${session.guildId}") - val interceptMono = repository.fetch(session.guildId) + interceptMono = repository.fetch(session.guildId) .defaultIfEmpty(GuildSettings(session.guildId)) .doOnError { e -> log.error("Exception while validating privacy setting", e) @@ -49,7 +50,6 @@ class UserSessionHandler( } return rawSession.send(session.sendStream) - .doOnSubscribe { interceptMono.subscribe() } .and(session.receive().doOnNext { handleMessage(session, it) }) .doFinally { afterConnectionClosed(session) From cfe81fa4dcad465015fa55aec2dea694451161e2 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Sat, 16 Feb 2019 21:34:22 +0100 Subject: [PATCH 101/172] Fix bad import --- FredBoat/src/main/java/fredboat/config/RabbitConfiguration.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FredBoat/src/main/java/fredboat/config/RabbitConfiguration.kt b/FredBoat/src/main/java/fredboat/config/RabbitConfiguration.kt index 6e224ef1c..dea4d1a7e 100644 --- a/FredBoat/src/main/java/fredboat/config/RabbitConfiguration.kt +++ b/FredBoat/src/main/java/fredboat/config/RabbitConfiguration.kt @@ -13,7 +13,7 @@ import org.springframework.amqp.core.DirectExchange import org.springframework.amqp.core.Queue import org.springframework.amqp.rabbit.AsyncRabbitTemplate import org.springframework.amqp.rabbit.core.RabbitTemplate -import org.springframework.amqp.rabbit.listener.RabbitListenerErrorHandler +import org.springframework.amqp.rabbit.listener.api.RabbitListenerErrorHandler import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter import org.springframework.amqp.support.converter.MessageConverter import org.springframework.beans.factory.annotation.Qualifier From d823795e9fec00de15f63c6ffd021d257c9ce117 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 17 Feb 2019 12:49:45 +0100 Subject: [PATCH 102/172] Add everyone Role to User & DJ list by default --- FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt b/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt index 140594714..a103364ad 100644 --- a/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt +++ b/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt @@ -15,7 +15,7 @@ data class GuildSettings( var lang: String = "en_US", var prefix: String? = null, var modules: List = Module.values().toList().map { ModuleEntity(it, true) }, - var permissions: PermissionEntity = PermissionEntity() + var permissions: PermissionEntity = PermissionEntity(userList = listOf(id), djList = listOf(id)) ) : MongoEntity { fun get(module: Module): ModuleEntity { From 5fb021af90b19bd5d6ea4b763f61c958c6e36b88 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 17 Feb 2019 20:25:18 +0100 Subject: [PATCH 103/172] Remove old MusicPersistenceHandler --- .../AudioPlayerManagerConfiguration.java | 3 - .../fredboat/event/MusicPersistenceHandler.kt | 326 ------------------ .../java/fredboat/sentinel/RabbitConsumer.kt | 2 - 3 files changed, 331 deletions(-) delete mode 100644 FredBoat/src/main/java/fredboat/event/MusicPersistenceHandler.kt diff --git a/FredBoat/src/main/java/fredboat/config/AudioPlayerManagerConfiguration.java b/FredBoat/src/main/java/fredboat/config/AudioPlayerManagerConfiguration.java index 022accef2..281be05c8 100644 --- a/FredBoat/src/main/java/fredboat/config/AudioPlayerManagerConfiguration.java +++ b/FredBoat/src/main/java/fredboat/config/AudioPlayerManagerConfiguration.java @@ -63,9 +63,6 @@ * request (either returns with success or throws a failure) * - the paste service AudioPlayerManager should not contain the paste playlist importer to avoid recursion / users * abusing fredboat into paste file chains - * - * - * We manage the lifecycles of these Beans ourselves. See {@link fredboat.event.MusicPersistenceHandler} */ @Configuration public class AudioPlayerManagerConfiguration { diff --git a/FredBoat/src/main/java/fredboat/event/MusicPersistenceHandler.kt b/FredBoat/src/main/java/fredboat/event/MusicPersistenceHandler.kt deleted file mode 100644 index 442425e94..000000000 --- a/FredBoat/src/main/java/fredboat/event/MusicPersistenceHandler.kt +++ /dev/null @@ -1,326 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package fredboat.event - -import com.fredboat.sentinel.entities.LifecycleEventEnum -import com.fredboat.sentinel.entities.SendMessageResponse -import com.fredboat.sentinel.entities.Shard -import com.fredboat.sentinel.entities.ShardLifecycleEvent -import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager -import com.sedmelluq.discord.lavaplayer.tools.io.MessageInput -import com.sedmelluq.discord.lavaplayer.tools.io.MessageOutput -import com.sedmelluq.discord.lavaplayer.track.AudioTrack -import fredboat.audio.player.MusicTextChannelProvider -import fredboat.audio.player.PlayerRegistry -import fredboat.audio.player.joinChannel -import fredboat.audio.player.voiceChannel -import fredboat.audio.queue.AudioTrackContext -import fredboat.audio.queue.SplitAudioTrackContext -import fredboat.config.property.AppConfig -import fredboat.config.property.Credentials -import fredboat.definitions.RepeatMode -import fredboat.feature.I18n -import fredboat.sentinel.getGuild -import fredboat.shared.constant.DistributionEnum -import fredboat.shared.constant.ExitCodes -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import org.apache.commons.codec.binary.Base64 -import org.apache.commons.io.FileUtils -import org.json.JSONObject -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.stereotype.Component -import reactor.core.publisher.Mono -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.io.File -import java.io.IOException -import java.nio.charset.Charset -import java.text.MessageFormat -import java.time.Duration -import java.util.* -import java.util.function.BiConsumer - -@Component -class MusicPersistenceHandler(private val playerRegistry: PlayerRegistry, private val credentials: Credentials, - private val musicTextChannelProvider: MusicTextChannelProvider, - @param:Qualifier("loadAudioPlayerManager") private val audioPlayerManager: AudioPlayerManager, - private val appConfig: AppConfig, private val allPlayerManagers: Set -) : SentinelEventHandler() { - - companion object { - private val log = LoggerFactory.getLogger(MusicPersistenceHandler::class.java) - } - - //TODO this needs to happen before the shard manager is shut down, inside of a shutdown hook (so shutdown signals are properly processed) - fun handlePreShutdown(code: Int) { - if (!appConfig.isMusicDistribution) { - val announcements = announceAndPersist(code) - - // this makes sure that the announcements actually reach the users. if we go into full shut down before - // that, JDA's requester may not deliver all announcements - for (announcement in announcements) { - try { - announcement.block(Duration.ofSeconds(30)) //30 seconds is enough on patron boat, we don't announce on public boat (music distribution) - } catch (ignored: Exception) { - } - - } - } - - //will also shutdown all AudioSourceManagers registered with the AudioPlayerManagers - for (playerManager in allPlayerManagers) { - playerManager.shutdown() - } - } - - /** - * @return a list of futures that will completed as soon as we sent out all announcements to users about the shutdown - */ - private fun announceAndPersist(code: Int): MutableList> { - val dir = File("music_persistence") - if (!dir.exists()) { - val created = dir.mkdir() - if (!created) { - log.error("Failed to create music persistence directory") - return emptyList>().toMutableList() - } - } - - val isUpdate = code == ExitCodes.EXIT_CODE_UPDATE - val isRestart = code == ExitCodes.EXIT_CODE_RESTART - - val announcements = mutableListOf>() - playerRegistry.forEach(BiConsumer { guildId, player -> - try { - - val msg: String = when { - isUpdate -> I18n.get(player.guild).getString("shutdownUpdating") - isRestart -> I18n.get(player.guild).getString("shutdownRestarting") - else -> I18n.get(player.guild).getString("shutdownIndef") - } - - val activeTextChannel = player.activeTextChannel - if (activeTextChannel != null && player.isPlaying) { - announcements.add(activeTextChannel.send(msg)) - } - - val data = JSONObject() - val vc = player.voiceChannel - data.put("vc", vc?.id ?: 0) - data.put("tc", activeTextChannel?.id ?: 0) - data.put("isPaused", player.isPaused) - data.put("volume", player.volume.toString()) - data.put("repeatMode", player.repeatMode) - data.put("shuffle", player.isShuffle) - - if (player.playingTrack != null) { - data.put("position", player.position) - } - - val identifiers = ArrayList() - - for (atc in player.remainingTracks) { - val baos = ByteArrayOutputStream() - audioPlayerManager.encodeTrack(MessageOutput(baos), atc.track) - - val ident = JSONObject() - .put("message", Base64.encodeBase64String(baos.toByteArray())) - .put("user", atc.userId) - - if (atc is SplitAudioTrackContext) { - val split = JSONObject() - split.put("title", atc.effectiveTitle) - .put("startPos", atc.startPosition) - .put("endPos", atc.startPosition + atc.effectiveDuration) - - ident.put("split", split) - } - - identifiers.add(ident) - } - - data.put("sources", identifiers) - - try { - FileUtils.writeStringToFile( - File(dir, guildId.toString()), - data.toString(), - Charset.forName("UTF-8") - ) - } catch (ex: IOException) { - activeTextChannel?.send(MessageFormat.format( - I18n.get(player.guild).getString("shutdownPersistenceFail"), - ex.message - ))?.subscribe() - } - - } catch (ex: Exception) { - log.error("Error when saving persistence file", ex) - } - }) - - return announcements - } - - override fun onShardLifecycle(event: ShardLifecycleEvent) { - if (event.change != LifecycleEventEnum.READIED) return - - //the current implementation of music persistence is not a good idea on big bots - if (appConfig.shardCount <= 10 && appConfig.distribution != DistributionEnum.MUSIC) { - GlobalScope.launch { - try { - reloadPlaylists(event.shard) - } catch (e: Exception) { - log.error("Uncaught exception when dispatching ready event to music persistence handler", e) - } - } - } - } - - private suspend fun reloadPlaylists(shard: Shard) { - val dir = File("music_persistence") - - if (appConfig.isMusicDistribution) { - log.warn("Music persistence loading is disabled on the MUSIC distribution! Use PATRON or DEVELOPMENT instead" + "How did this call end up in here anyways?") - return - } - - log.info("Began reloading playlists for shard {}", shard) - if (!dir.exists()) { - log.info("No music persistence directory found.") - return - } - val files = dir.listFiles() - if (files == null || files.isEmpty()) { - log.info("No files present in music persistence directory") - return - } - - for (file in files) { - try { - val guild = getGuild(file.name.toLong()) ?: continue - - if (guild.shardId != shard.id || !guild.selfPresent) continue - - val data = JSONObject(FileUtils.readFileToString(file, Charset.forName("UTF-8"))) - - val isPaused = data.getBoolean("isPaused") - val sources = data.getJSONArray("sources") - val vc = guild.getVoiceChannel(data.getLong("vc")) - val tc = guild.getTextChannel(data.getLong("tc")) - val volume = data.getString("volume").toFloat() - val repeatMode = data.getEnum(RepeatMode::class.java, "repeatMode") - val shuffle = data.getBoolean("shuffle") - - val player = playerRegistry.awaitPlayer(guild) - - if (tc != null) { - musicTextChannelProvider.setMusicChannel(tc) - } - if (appConfig.distribution.volumeSupported()) { - player.volume = volume - } - player.repeatMode = repeatMode - player.isShuffle = shuffle - - val isFirst = booleanArrayOf(true) - - val tracks = ArrayList() - sources.forEach { t: Any -> - val json = t as JSONObject - val message = Base64.decodeBase64(json.getString("message")) - val member = guild.getMember(json.getLong("user")) ?: guild.selfMember - //The member may have left the guild meanwhile, so we may set ourselves as the one who added the song - - val at: AudioTrack? - try { - val bais = ByteArrayInputStream(message) - at = audioPlayerManager.decodeTrack(MessageInput(bais)).decodedTrack - } catch (e: IOException) { - throw RuntimeException(e) - } - - if (at == null) { - log.error("Loaded track that was null! Skipping...") - return@forEach - } - - // Handle split tracks - val atc: AudioTrackContext - val split = json.optJSONObject("split") - if (split != null) { - atc = SplitAudioTrackContext(at, member, - split.getLong("startPos"), - split.getLong("endPos"), - split.getString("title") - ) - at.position = split.getLong("startPos") - - if (isFirst[0]) { - isFirst[0] = false - if (data.has("position")) { - at.position = split.getLong("startPos") + data.getLong("position") - } - } - } else { - atc = AudioTrackContext(at, member) - - if (isFirst[0]) { - isFirst[0] = false - if (data.has("position")) { - at.position = data.getLong("position") - } - } - } - - tracks.add(atc) - } - - player.loadAll(tracks) - if (!isPaused) { - if (vc != null) { - GlobalScope.launch { - try { - player.joinChannel(vc) - player.play() - } catch (ignored: Exception) { - } - } - } - tc?.send(MessageFormat.format(I18n.get(guild).getString("reloadSuccess"), sources.length())) - ?.subscribe() - } - } catch (ex: Exception) { - log.error("Error when loading persistence file", ex) - } - - val deleted = file.delete() - log.info(if (deleted) "Deleted persistence file: $file" else "Failed to delete persistence file: $file") - } - } - -} diff --git a/FredBoat/src/main/java/fredboat/sentinel/RabbitConsumer.kt b/FredBoat/src/main/java/fredboat/sentinel/RabbitConsumer.kt index f7f030436..56594c9bf 100644 --- a/FredBoat/src/main/java/fredboat/sentinel/RabbitConsumer.kt +++ b/FredBoat/src/main/java/fredboat/sentinel/RabbitConsumer.kt @@ -23,7 +23,6 @@ class RabbitConsumer( guildHandler: GuildEventHandler, audioHandler: AudioEventHandler, messageHandler: MessageEventHandler, - musicPersistenceHandler: MusicPersistenceHandler, shardReviveHandler: ShardLifecycleHandler ) { @@ -36,7 +35,6 @@ class RabbitConsumer( guildHandler, audioHandler, messageHandler, - musicPersistenceHandler, shardReviveHandler ) From 68f01b29e6b2789005c8a70d1dc7bbe2d8d6c8e1 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 17 Feb 2019 20:34:39 +0100 Subject: [PATCH 104/172] Auto set Rand to Integer MIN when ATC is priority --- .../src/main/java/fredboat/audio/queue/AudioTrackContext.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt b/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt index 3ece71593..b6ad3baa8 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt @@ -37,7 +37,7 @@ import java.util.concurrent.ThreadLocalRandom open class AudioTrackContext(val track: AudioTrack, val member: Member, priority: Boolean = false) : Comparable { val added: Long = System.currentTimeMillis() - var rand: Int = 0 + var rand: Int = if (!priority) 0 else Integer.MIN_VALUE var isPriority: Boolean = priority val trackId: ObjectId // used to identify this track even when the track gets cloned and the rand reranded From 5e2e2ad104559f31f5dc51c6397fd936f8fcadef Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 17 Feb 2019 20:37:36 +0100 Subject: [PATCH 105/172] Remove TrackProvider queueFirst, instead use atc.isPriority in add --- .../java/fredboat/audio/player/GuildPlayer.kt | 8 +++---- .../fredboat/audio/player/PlayerRegistry.kt | 2 +- .../fredboat/audio/queue/ITrackProvider.kt | 16 +------------ .../audio/queue/SimpleTrackProvider.kt | 24 +++++++------------ .../command/music/control/SelectCommand.kt | 2 +- 5 files changed, 16 insertions(+), 36 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index 7a75fa4d9..0c812f089 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -206,7 +206,7 @@ class GuildPlayer( audioLoader.loadAsync(ic) } - fun queue(atc: AudioTrackContext, isPriority: Boolean = false) { + fun queue(atc: AudioTrackContext) { if (!guild.selfPresent) throw IllegalStateException("Attempt to queue track in a guild we are not present in") val member = guild.getMember(atc.userId) @@ -214,14 +214,14 @@ class GuildPlayer( joinChannel(member) } - if (isPriority) audioTrackProvider.addFirst(atc) else audioTrackProvider.add(atc) + audioTrackProvider.add(atc) if (isPlaying) updateClients() play() } /** Add a bunch of tracks to the track provider */ - fun loadAll(tracks: Collection) { - audioTrackProvider.addAll(tracks) + fun loadAll(tracks: Collection, isPriority: Boolean) { + audioTrackProvider.addAll(tracks, isPriority) } override fun toString(): String { diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index 77faf9df7..a693b15e8 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -228,7 +228,7 @@ class PlayerRegistry( val channel = mongo.textChannel?.let { guild.getTextChannel(it) } if (channel != null) musicTextChannelProvider.setMusicChannel(channel) - player.loadAll(queue) + player.loadAll(queue, false) // no Priority on reload } private fun beforeShutdown() { diff --git a/FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt b/FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt index 1b0a2976f..796e6d438 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt @@ -77,21 +77,7 @@ interface ITrackProvider { /** * @param tracks add several tracks to the queue */ - fun addAll(tracks: Collection) - - /** - * Add track to the front of the queue - * - * @param track track to be added - */ - fun addFirst(track: AudioTrackContext) - - /** - * Add a collection of tracks to the front of the queue - * - * @param tracks collection of tracks to be added - */ - fun addAllFirst(tracks: Collection) + fun addAll(tracks: Collection, isPriority: Boolean) /** * empty the queue diff --git a/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt b/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt index 91af3d2f9..fd08d147e 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt @@ -189,26 +189,20 @@ class SimpleTrackProvider : AbstractTrackProvider() { override fun add(track: AudioTrackContext) { shouldUpdateShuffledQueue = true - queue.add(track) - } - override fun addAll(tracks: Collection) { - shouldUpdateShuffledQueue = true - queue.addAll(tracks) + if (!track.isPriority) + queue.add(track) + else + queue.addFirst(track) } - override fun addFirst(track: AudioTrackContext) { + override fun addAll(tracks: Collection, isPriority: Boolean) { shouldUpdateShuffledQueue = true - track.rand = Integer.MIN_VALUE - queue.addFirst(track) - } - override fun addAllFirst(tracks: Collection) { - shouldUpdateShuffledQueue = true - tracks.reversed().forEach { - it.rand = Integer.MIN_VALUE - queue.addFirst(it) - } + if (!isPriority) + queue.addAll(tracks) + else + tracks.reversed().forEach { queue.addFirst(it) } } override fun clear() { diff --git a/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.kt index bab36be5f..2e42cbeb0 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.kt @@ -115,7 +115,7 @@ class SelectCommand(private val videoSelectionCache: VideoSelectionCache, name: } outputMsgBuilder.append(msg) - player.queue(AudioTrackContext(selectedTracks[i]!!, invoker, selection.isPriority), selection.isPriority) + player.queue(AudioTrackContext(selectedTracks[i]!!, invoker, selection.isPriority)) } videoSelectionCache.remove(invoker) From 7952101e118010aef5bb626054bb1007b40c4c21 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 17 Feb 2019 20:38:17 +0100 Subject: [PATCH 106/172] Force audioLoading to use GuildPlayer to add to queue --- .../java/fredboat/audio/player/GuildPlayer.kt | 2 +- .../java/fredboat/audio/queue/audioLoading.kt | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index 0c812f089..68012027d 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -68,7 +68,7 @@ class GuildPlayer( ) : PlayerEventListenerAdapter() { val audioTrackProvider: ITrackProvider = SimpleTrackProvider() - private val audioLoader = AudioLoader(ratelimiter, audioTrackProvider, audioPlayerManager, this, youtubeAPI) + private val audioLoader = AudioLoader(ratelimiter, audioPlayerManager, this, youtubeAPI) val guildId = guild.id val player: LavalinkPlayer = lavalink.getLink(guild.id.toString()).player var internalContext: AudioTrackContext? = null diff --git a/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt b/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt index 7911b4a1b..8872cfd24 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt @@ -50,9 +50,8 @@ import java.util.* import java.util.concurrent.ConcurrentLinkedQueue import java.util.regex.Pattern -class AudioLoader(private val ratelimiter: Ratelimiter, internal val trackProvider: ITrackProvider, - private val playerManager: AudioPlayerManager, internal val gplayer: GuildPlayer, - internal val youtubeAPI: YoutubeAPI) { +class AudioLoader(private val ratelimiter: Ratelimiter, private val playerManager: AudioPlayerManager, + internal val player: GuildPlayer, internal val youtubeAPI: YoutubeAPI) { private val identifierQueue = ConcurrentLinkedQueue() @Volatile private var isLoading = false @@ -81,7 +80,7 @@ class AudioLoader(private val ratelimiter: Ratelimiter, internal val trackProvid if (context != null) { isLoading = true - if (gplayer.trackCount >= QUEUE_TRACK_LIMIT) { + if (player.trackCount >= QUEUE_TRACK_LIMIT) { context.replyWithName(context.i18nFormat("loadQueueTrackLimit", QUEUE_TRACK_LIMIT)) isLoading = false return @@ -195,7 +194,7 @@ private class ResultHandler(val loader: AudioLoader, val context: IdentifierCont } else { if (!context.isQuiet) { - context.reply(if (loader.gplayer.isPlaying) + context.reply(if (loader.player.isPlaying) context.i18nFormat(if (context.isPriority) "loadSingleTrackFirst" else "loadSingleTrack", TextUtils.escapeAndDefuse(at.info.title)) else @@ -208,10 +207,10 @@ private class ResultHandler(val loader: AudioLoader, val context: IdentifierCont at.position = context.position val atc = AudioTrackContext(at, context.member, context.isPriority) - if (context.isPriority) loader.trackProvider.addFirst(atc) else loader.trackProvider.add(atc) + loader.player.queue(atc) - if (!loader.gplayer.isPaused) { - loader.gplayer.play() + if (!loader.player.isPaused) { + loader.player.play() } } } catch (th: Throwable) { @@ -234,10 +233,11 @@ private class ResultHandler(val loader: AudioLoader, val context: IdentifierCont for (at in ap.tracks) { toAdd.add(AudioTrackContext(at, context.member, context.isPriority)) } - if (context.isPriority) loader.trackProvider.addAllFirst(toAdd) else loader.trackProvider.addAll(toAdd) + + loader.player.loadAll(toAdd, context.isPriority) context.reply(context.i18nFormat("loadListSuccess", ap.tracks.size, ap.name)) - if (!loader.gplayer.isPaused) { - loader.gplayer.play() + if (!loader.player.isPaused) { + loader.player.play() } } catch (th: Throwable) { loader.handleThrowable(context, th) @@ -313,7 +313,7 @@ private class ResultHandler(val loader: AudioLoader, val context: IdentifierCont val atc = SplitAudioTrackContext(newAt, context.member, startPos, endPos, pair.right) list.add(atc) - loader.gplayer.queue(atc) + loader.player.queue(atc) } var mb = localMessageBuilder() From cbb38544c04e70651190078efa8c2f3d51b92332 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 17 Feb 2019 20:41:14 +0100 Subject: [PATCH 107/172] Rename GuildPlayer addAll to queueAll --- FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt | 2 +- FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt | 2 +- FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index 68012027d..88cf5b54c 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -220,7 +220,7 @@ class GuildPlayer( } /** Add a bunch of tracks to the track provider */ - fun loadAll(tracks: Collection, isPriority: Boolean) { + fun queueAll(tracks: Collection, isPriority: Boolean) { audioTrackProvider.addAll(tracks, isPriority) } diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index a693b15e8..1c44fa2fb 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -228,7 +228,7 @@ class PlayerRegistry( val channel = mongo.textChannel?.let { guild.getTextChannel(it) } if (channel != null) musicTextChannelProvider.setMusicChannel(channel) - player.loadAll(queue, false) // no Priority on reload + player.queueAll(queue, false) // no Priority on reload } private fun beforeShutdown() { diff --git a/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt b/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt index 8872cfd24..60fe527d9 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt @@ -234,7 +234,7 @@ private class ResultHandler(val loader: AudioLoader, val context: IdentifierCont toAdd.add(AudioTrackContext(at, context.member, context.isPriority)) } - loader.player.loadAll(toAdd, context.isPriority) + loader.player.queueAll(toAdd, context.isPriority) context.reply(context.i18nFormat("loadListSuccess", ap.tracks.size, ap.name)) if (!loader.player.isPaused) { loader.player.play() From 482a488fed0f4321575470029d32cfd5699905dd Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Mon, 18 Feb 2019 14:54:57 +0100 Subject: [PATCH 108/172] Fix sometimes refusing to join VC --- FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt index 02f4e8f9d..ee4ea007d 100644 --- a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt +++ b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt @@ -47,7 +47,8 @@ class SentinelLink(val lavalink: SentinelLavalink, guildId: String) : Link(laval perms.assertHas(VOICE_SPEAK, "We do not have permission to speak in $channel") // Do nothing if we are already connected to that channel - if (skipIfSameChannel && super.getChannel() == channel.id.toString()) return + val alreadyInChannel = channel.members.any { it.isUs } && super.getChannel() == channel.id.toString() + if (skipIfSameChannel && alreadyInChannel) return if (channel.userLimit > 1 // Is there a user limit? && channel.userLimit <= channel.members.size // Is that limit reached? From e173513f01c8b7fcf37cf5198152cf265014a8cc Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Mon, 18 Feb 2019 14:55:33 +0100 Subject: [PATCH 109/172] Fix rejoining channel when leaving intentionally --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 63ae42961..ee8ca02fc 100644 --- a/build.gradle +++ b/build.gradle @@ -65,7 +65,7 @@ subprojects { //audio deps lavaplayerVersion = '1.3.9' - lavalinkVersion = '44caa9d5' + lavalinkVersion = '9f6c9c50' //utility deps jsonOrgVersion = '20180130' From a13aba9ef4adb072d232707d9629ba409bdda5a2 Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Mon, 18 Feb 2019 21:51:11 +0100 Subject: [PATCH 110/172] Fix sending to multiple WS clients ... maybe? --- .../src/main/java/fredboat/ws/UserSessionHandler.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt b/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt index f12a7c978..8e665923a 100644 --- a/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt +++ b/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt @@ -11,6 +11,7 @@ import org.springframework.web.reactive.socket.WebSocketHandler import org.springframework.web.reactive.socket.WebSocketMessage import org.springframework.web.reactive.socket.WebSocketSession import reactor.core.publisher.Mono +import java.lang.Exception import java.util.concurrent.ConcurrentHashMap @Controller @@ -71,13 +72,19 @@ class UserSessionHandler( else list.remove(session) } - final inline fun sendLazy(guildId: Long, producer: () -> Any) { + final fun sendLazy(guildId: Long, producer: () -> Any) { val sessions = this[guildId] if (sessions.isEmpty()) return - val msg = sessions.first().textMessage(gson.toJson(producer())) + val msg = gson.toJson(producer()) sessions.forEach { - if (it.isOpen) it.send(msg) + log.info("Sending message to ${it.session.id}. Open: ${it.isOpen}") + //if (it.isOpen) it.send(it.textMessage(msg)) + try { + it.send(it.textMessage(msg)) + } catch(e: Exception) { + log.error("Error sending WS message") + } } } From 76c91d0ba09de95246d0a8fa63527b5d04068caa Mon Sep 17 00:00:00 2001 From: Marlon Haenen Date: Fri, 22 Feb 2019 10:39:16 +0100 Subject: [PATCH 111/172] Exempt FBH mods from FBH command restriction --- FredBoat/src/main/java/fredboat/commandmeta/CommandManager.kt | 3 ++- .../src/main/java/fredboat/shared/constant/BotConstants.java | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/FredBoat/src/main/java/fredboat/commandmeta/CommandManager.kt b/FredBoat/src/main/java/fredboat/commandmeta/CommandManager.kt index 8cb2cfadf..99186f386 100644 --- a/FredBoat/src/main/java/fredboat/commandmeta/CommandManager.kt +++ b/FredBoat/src/main/java/fredboat/commandmeta/CommandManager.kt @@ -40,8 +40,8 @@ import fredboat.sentinel.RawUser import fredboat.shared.constant.BotConstants import fredboat.util.DiscordUtil import fredboat.util.TextUtils -import kotlinx.coroutines.reactive.awaitSingle import kotlinx.coroutines.delay +import kotlinx.coroutines.reactive.awaitSingle import org.springframework.beans.factory.annotation.Qualifier import org.springframework.stereotype.Component import java.util.* @@ -83,6 +83,7 @@ class CommandManager(private val patronageChecker: PatronageChecker, private val if (guild.id == BotConstants.FREDBOAT_HANGOUT_ID && DiscordUtil.isOfficialBot(selfUser.id)) { if (channel.id != 174821093633294338L // #spam_and_music && channel.id != 217526705298866177L // #staff + && invoker.roles.any { it.id == BotConstants.FBH_MODERATOR_ROLE_ID } && !PermsUtil.checkPerms(PermissionLevel.ADMIN, invoker)) { context.deleteMessage() val response = context.replyWithNameMono( diff --git a/Shared/src/main/java/fredboat/shared/constant/BotConstants.java b/Shared/src/main/java/fredboat/shared/constant/BotConstants.java index a0c0d3698..732f9e69a 100644 --- a/Shared/src/main/java/fredboat/shared/constant/BotConstants.java +++ b/Shared/src/main/java/fredboat/shared/constant/BotConstants.java @@ -36,6 +36,7 @@ public class BotConstants { public static final long CUTTING_EDGE_BOT_ID = 341924447139135488L; public static final long FREDBOAT_HANGOUT_ID = 174820236481134592L; + public static final long FBH_MODERATOR_ROLE_ID = 242377373947920384L; public static final Color FREDBOAT_COLOR = new Color(28, 191, 226); //#1CBFE2 public static final String FREDBOAT_URL = "https://fredboat.com"; From fabc3c82d63daaf427c13ea0a17c9d8b88d1fb07 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sat, 23 Feb 2019 13:10:35 +0100 Subject: [PATCH 112/172] Allow I18n getLocale(Guild) to be null --- FredBoat/src/main/java/fredboat/feature/I18n.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/feature/I18n.kt b/FredBoat/src/main/java/fredboat/feature/I18n.kt index be2ceb91f..70e7e398a 100644 --- a/FredBoat/src/main/java/fredboat/feature/I18n.kt +++ b/FredBoat/src/main/java/fredboat/feature/I18n.kt @@ -58,13 +58,15 @@ object I18n { } @Deprecated("Convert this to reactive at some point!") - fun getLocale(guild: Guild): FredBoatLocale { - return getLocale(guild.id) + fun getLocale(guild: Guild?): FredBoatLocale { + return getLocale(guild?.id) } @Deprecated("Convert this to reactive at some point!") - fun getLocale(guild: Long): FredBoatLocale { + fun getLocale(guild: Long?): FredBoatLocale { return try { + if (guild == null) return DEFAULT + LANGS.getOrDefault(getBotController().guildSettingsRepository.fetch(guild).block(Duration.ofSeconds(5))?.lang, DEFAULT) } catch (e: Exception) { log.error("Error when reading entity", e) From 8cb5fba353b01c222d0cf5a4791d2600fbc60f2b Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sat, 23 Feb 2019 13:11:23 +0100 Subject: [PATCH 113/172] Add NullableContext as base Context --- .../messaging/internal/NullableContext.kt | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 FredBoat/src/main/java/fredboat/messaging/internal/NullableContext.kt diff --git a/FredBoat/src/main/java/fredboat/messaging/internal/NullableContext.kt b/FredBoat/src/main/java/fredboat/messaging/internal/NullableContext.kt new file mode 100644 index 000000000..d21cfa2ea --- /dev/null +++ b/FredBoat/src/main/java/fredboat/messaging/internal/NullableContext.kt @@ -0,0 +1,95 @@ +package fredboat.messaging.internal + +import com.fredboat.sentinel.entities.Embed +import com.fredboat.sentinel.entities.SendMessageResponse +import com.fredboat.sentinel.entities.embed +import com.fredboat.sentinel.entities.passed +import fredboat.command.config.PrefixCommand +import fredboat.commandmeta.MessagingException +import fredboat.feature.I18n +import fredboat.perms.IPermissionSet +import fredboat.perms.PermissionSet +import fredboat.perms.PermsUtil +import fredboat.sentinel.* +import fredboat.shared.constant.BotConstants +import fredboat.util.TextUtils +import kotlinx.coroutines.reactive.awaitSingle +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import reactor.core.publisher.Mono +import java.text.MessageFormat +import java.util.* +import javax.annotation.CheckReturnValue + +abstract class NullableContext { + + companion object { + private val log: Logger = LoggerFactory.getLogger(Context::class.java) + } + + /* Convenience properties */ + abstract val textChannel: TextChannel? + abstract val guild: Guild? + abstract val member: Member? + abstract val user: User? + + // ******************************************************************************** + // Internal context stuff + // ******************************************************************************** + + private var i18n: ResourceBundle? = null + + /** + * Return a single translated string. + * + * @param key Key of the i18n string. + * @return Formatted i18n string, or a default language string if i18n is not found. + */ + @CheckReturnValue + fun i18n(key: String): String { + return if (getI18n().containsKey(key)) { + getI18n().getString(key) + } else { + log.warn("Missing language entry for key {} in language {}", key, I18n.getLocale(guild).code) + I18n.DEFAULT.props.getString(key) + } + } + + /** + * Return a translated string with applied formatting. + * + * @param key Key of the i18n string. + * @param params Parameter(s) to be apply into the i18n string. + * @return Formatted i18n string. + */ + @CheckReturnValue + fun i18nFormat(key: String, vararg params: Any): String { + if (params.isEmpty()) { + log.warn("Context#i18nFormat() called with empty or null params, this is likely a bug.", + MessagingException("a stack trace to help find the source")) + } + return try { + MessageFormat.format(this.i18n(key), *params) + } catch (e: IllegalArgumentException) { + log.warn("Failed to format key '{}' for language '{}' with following parameters: {}", + key, getI18n().baseBundleName, params, e) + //fall back to default props + MessageFormat.format(I18n.DEFAULT.props.getString(key), *params) + } + + } + + private fun getI18n(): ResourceBundle { + var result = i18n + if (result == null) { + result = I18n.get(guild) + i18n = result + } + return result + } + + protected fun embedImage(url: String): Embed = embed { + color = BotConstants.FREDBOAT_COLOR.rgb + image = url + } +} \ No newline at end of file From c0dd1e80c83b915c82f2c9cf452e1653f4f283b2 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sat, 23 Feb 2019 13:11:43 +0100 Subject: [PATCH 114/172] Make Context inherit from NullableContext --- .../fredboat/messaging/internal/Context.kt | 82 ++----------------- 1 file changed, 5 insertions(+), 77 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/messaging/internal/Context.kt b/FredBoat/src/main/java/fredboat/messaging/internal/Context.kt index 94cbfad7a..487dd06a2 100644 --- a/FredBoat/src/main/java/fredboat/messaging/internal/Context.kt +++ b/FredBoat/src/main/java/fredboat/messaging/internal/Context.kt @@ -27,39 +27,27 @@ package fredboat.messaging.internal import com.fredboat.sentinel.entities.Embed import com.fredboat.sentinel.entities.SendMessageResponse -import com.fredboat.sentinel.entities.embed import com.fredboat.sentinel.entities.passed import fredboat.command.config.PrefixCommand -import fredboat.commandmeta.MessagingException -import fredboat.feature.I18n import fredboat.perms.IPermissionSet import fredboat.perms.PermissionSet import fredboat.perms.PermsUtil import fredboat.sentinel.* -import fredboat.shared.constant.BotConstants import fredboat.util.TextUtils import kotlinx.coroutines.reactive.awaitSingle -import org.slf4j.Logger -import org.slf4j.LoggerFactory import reactor.core.publisher.Mono -import java.text.MessageFormat -import java.util.* import javax.annotation.CheckReturnValue /** * Provides a context to whats going on. Where is it happening, who caused it? * Also home to a bunch of convenience methods */ -abstract class Context { +abstract class Context : NullableContext() { - companion object { - private val log: Logger = LoggerFactory.getLogger(Context::class.java) - } - - abstract val textChannel: TextChannel - abstract val guild: Guild - abstract val member: Member - abstract val user: User + abstract override val textChannel: TextChannel + abstract override val guild: Guild + abstract override val member: Member + abstract override val user: User /* Convenience properties */ val prefix: String get() = PrefixCommand.giefPrefix(guild) @@ -67,12 +55,6 @@ abstract class Context { val sentinel: Sentinel get() = guild.sentinel val routingKey: String get() = guild.routingKey - // ******************************************************************************** - // Internal context stuff - // ******************************************************************************** - - private var i18n: ResourceBundle? = null - // ******************************************************************************** // Convenience reply methods // ******************************************************************************** @@ -190,59 +172,5 @@ abstract class Context { return false } - /** - * Return a single translated string. - * - * @param key Key of the i18n string. - * @return Formatted i18n string, or a default language string if i18n is not found. - */ - @CheckReturnValue - fun i18n(key: String): String { - return if (getI18n().containsKey(key)) { - getI18n().getString(key) - } else { - log.warn("Missing language entry for key {} in language {}", key, I18n.getLocale(guild).code) - I18n.DEFAULT.props.getString(key) - } - } - - /** - * Return a translated string with applied formatting. - * - * @param key Key of the i18n string. - * @param params Parameter(s) to be apply into the i18n string. - * @return Formatted i18n string. - */ - @CheckReturnValue - fun i18nFormat(key: String, vararg params: Any): String { - if (params.isEmpty()) { - log.warn("Context#i18nFormat() called with empty or null params, this is likely a bug.", - MessagingException("a stack trace to help find the source")) - } - return try { - MessageFormat.format(this.i18n(key), *params) - } catch (e: IllegalArgumentException) { - log.warn("Failed to format key '{}' for language '{}' with following parameters: {}", - key, getI18n().baseBundleName, params, e) - //fall back to default props - MessageFormat.format(I18n.DEFAULT.props.getString(key), *params) - } - - } - - fun getI18n(): ResourceBundle { - var result = i18n - if (result == null) { - result = I18n.get(guild) - i18n = result - } - return result - } - - private fun embedImage(url: String): Embed = embed { - color = BotConstants.FREDBOAT_COLOR.rgb - image = url - } - suspend fun memberLevel() = PermsUtil.getPerms(member) } From a839dcd4c0e53134e7518fc85701768abf462d6a Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sat, 23 Feb 2019 13:11:59 +0100 Subject: [PATCH 115/172] Make AudioTrackContext inherit from NullableContext --- .../fredboat/audio/queue/AudioTrackContext.kt | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt b/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt index b6ad3baa8..746563fd3 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt @@ -28,18 +28,31 @@ package fredboat.audio.queue import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioTrack import com.sedmelluq.discord.lavaplayer.track.AudioTrack import fredboat.audio.player.GuildPlayer -import fredboat.feature.I18n import fredboat.main.Launcher +import fredboat.messaging.internal.NullableContext +import fredboat.sentinel.Guild import fredboat.sentinel.Member import fredboat.sentinel.TextChannel +import fredboat.sentinel.User import org.bson.types.ObjectId import java.util.concurrent.ThreadLocalRandom -open class AudioTrackContext(val track: AudioTrack, val member: Member, priority: Boolean = false) : Comparable { - val added: Long = System.currentTimeMillis() +open class AudioTrackContext( + val track: AudioTrack, + override val member: Member, + priority: Boolean = false +) : NullableContext(), Comparable { + + val trackId: ObjectId // used to identify this track even when the track gets cloned and the rand reranded var rand: Int = if (!priority) 0 else Integer.MIN_VALUE var isPriority: Boolean = priority - val trackId: ObjectId // used to identify this track even when the track gets cloned and the rand reranded + val added: Long = System.currentTimeMillis() + + override val guild: Guild + get() = member.guild + + override val user: User + get() = member.user val userId: Long get() = member.id @@ -57,7 +70,7 @@ open class AudioTrackContext(val track: AudioTrack, val member: Member, priority get() = 0 //return the currently active text channel of the associated guildplayer - val textChannel: TextChannel? + override val textChannel: TextChannel? get() { val guildPlayer = Launcher.botController.playerRegistry.getExisting(guildId) return guildPlayer?.activeTextChannel @@ -110,13 +123,4 @@ open class AudioTrackContext(val track: AudioTrack, val member: Member, priority result = 31 * result + trackId.hashCode() return result } - - fun i18n(key: String) = I18n.get(guildId).getString(key)!! - fun i18nFormat(key: String, vararg values: Any): String { - var str = i18n(key) - values.forEachIndexed { i, v -> str = str.replace("{$i}", v.toString()) } - return str - } - - } From 62d0c949c201959035ff08e20f2d3ea9c5cffac0 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sat, 23 Feb 2019 21:47:42 +0100 Subject: [PATCH 116/172] At .floo files --- .floo | 3 +++ .flooignore | 11 +++++++++++ 2 files changed, 14 insertions(+) create mode 100644 .floo create mode 100644 .flooignore diff --git a/.floo b/.floo new file mode 100644 index 000000000..46a97aac8 --- /dev/null +++ b/.floo @@ -0,0 +1,3 @@ +{ + "url": "https://floobits.com/Frederikam/FredBoat" +} \ No newline at end of file diff --git a/.flooignore b/.flooignore new file mode 100644 index 000000000..a78842c63 --- /dev/null +++ b/.flooignore @@ -0,0 +1,11 @@ +extern +node_modules +tmp +vendor +.idea/workspace.xml +.idea/misc.xml +.idea + +fredboat.yml +fredboat.yaml +commons.yml From 9e7ef1829dc5d4596fc0d5b5fb410910fbef6e64 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sat, 23 Feb 2019 21:50:20 +0100 Subject: [PATCH 117/172] #I need to commit more. Implement QueueLimiter with 4 QueueLimits for now --- .../java/fredboat/audio/player/GuildPlayer.kt | 28 +++++- .../fredboat/audio/player/guildPlayerUtils.kt | 6 ++ .../audio/queue/AudioPlaylistContext.kt | 6 ++ .../fredboat/audio/queue/AudioTrackContext.kt | 6 +- .../fredboat/audio/queue/ITrackProvider.kt | 5 + .../audio/queue/SimpleTrackProvider.kt | 4 + .../audio/queue/SplitAudioTrackContext.kt | 2 +- .../java/fredboat/audio/queue/audioLoading.kt | 96 +++++++++++-------- .../audio/queue/limiter/QueueLimit.kt | 27 ++++++ .../audio/queue/limiter/QueueLimitStatus.kt | 30 ++++++ .../audio/queue/limiter/QueueLimiter.kt | 63 ++++++++++++ .../command/music/control/PlayCommand.kt | 4 +- .../command/music/control/PlaySplitCommand.kt | 2 +- .../command/music/control/SelectCommand.kt | 2 +- .../fredboat/db/transfer/GuildSettings.kt | 4 + .../fredboat/feature/metrics/Metrics.java | 6 ++ .../fredboat/messaging/internal/Context.kt | 62 ++---------- .../messaging/internal/NullableContext.kt | 59 ++++++++++-- .../src/main/resources/lang/en_US.properties | 4 + 19 files changed, 301 insertions(+), 115 deletions(-) create mode 100644 FredBoat/src/main/java/fredboat/audio/queue/AudioPlaylistContext.kt create mode 100644 FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimit.kt create mode 100644 FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimitStatus.kt create mode 100644 FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimiter.kt diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index 88cf5b54c..7c8d68ced 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -32,6 +32,8 @@ import com.sedmelluq.discord.lavaplayer.track.TrackMarker import fredboat.audio.lavalink.SentinelLavalink import fredboat.audio.lavalink.SentinelLink import fredboat.audio.queue.* +import fredboat.audio.queue.limiter.QueueLimitStatus +import fredboat.audio.queue.limiter.QueueLimiter import fredboat.command.music.control.VoteSkipCommand import fredboat.commandmeta.MessagingException import fredboat.commandmeta.abs.CommandContext @@ -56,6 +58,7 @@ import reactor.core.publisher.Mono import java.util.* import java.util.concurrent.ConcurrentLinkedQueue import java.util.function.Consumer +import javax.annotation.CheckReturnValue class GuildPlayer( val lavalink: SentinelLavalink, @@ -69,6 +72,7 @@ class GuildPlayer( val audioTrackProvider: ITrackProvider = SimpleTrackProvider() private val audioLoader = AudioLoader(ratelimiter, audioPlayerManager, this, youtubeAPI) + private val queueLimiter = QueueLimiter(guildSettingsRepository) val guildId = guild.id val player: LavalinkPlayer = lavalink.getLink(guild.id.toString()).player var internalContext: AudioTrackContext? = null @@ -191,7 +195,7 @@ class GuildPlayer( activeTextChannel?.send("Something went wrong!\n${t.message}")?.subscribe() } - fun queue(identifier: String, context: CommandContext, isPriority: Boolean = false) { + fun queueAsync(identifier: String, context: CommandContext, isPriority: Boolean = false) { val ic = IdentifierContext(identifier, context.textChannel, context.member) ic.isPriority = isPriority @@ -200,7 +204,7 @@ class GuildPlayer( audioLoader.loadAsync(ic) } - fun queue(ic: IdentifierContext) { + fun queueAsync(ic: IdentifierContext) { joinChannel(ic.member) audioLoader.loadAsync(ic) @@ -224,6 +228,26 @@ class GuildPlayer( audioTrackProvider.addAll(tracks, isPriority) } + @CheckReturnValue + suspend fun queueLimited(atc: AudioTrackContext): QueueLimitStatus { + val status = queueLimiter.isQueueLimited(atc, this) + + // only queue if track was not limited + if (status.canQueue) { + queue(atc) + } + + return status + } + + suspend fun queueLimited(tracks: List, isPriority: Boolean): List { + val states = queueLimiter.isQueueLimited(tracks, this) + + audioTrackProvider.addAll(states.filter { it.canQueue }.map { it.atc }, isPriority) + return states + } + + override fun toString(): String { return "[GP:$guildId]" } diff --git a/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt b/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt index ef3a0768b..b2c3f9d67 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt @@ -69,6 +69,12 @@ val GuildPlayer.trackCountInHistory: Int val GuildPlayer.isHistoryQueueEmpty: Boolean get() = historyQueue.isEmpty() +fun GuildPlayer.userTackCount(userId: Long): Int { + var trackCount = audioTrackProvider.size(userId) + if (player.playingTrack != null && internalContext?.userId == userId) trackCount++ + return trackCount +} + fun GuildPlayer.joinChannel(usr: Member) { joinChannel(usr.voiceChannel) } diff --git a/FredBoat/src/main/java/fredboat/audio/queue/AudioPlaylistContext.kt b/FredBoat/src/main/java/fredboat/audio/queue/AudioPlaylistContext.kt new file mode 100644 index 000000000..3581fb829 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/queue/AudioPlaylistContext.kt @@ -0,0 +1,6 @@ +package fredboat.audio.queue + +import com.sedmelluq.discord.lavaplayer.track.AudioTrack +import fredboat.sentinel.Member + +open class AudioPlaylistContext(track: AudioTrack, member: Member, priority: Boolean = false) : AudioTrackContext(track, member, priority) \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt b/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt index 746563fd3..e7021093d 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt @@ -54,12 +54,12 @@ open class AudioTrackContext( override val user: User get() = member.user - val userId: Long - get() = member.id - val guildId: Long get() = member.guild.id + val userId: Long + get() = member.id + open val effectiveDuration: Long get() = track.duration diff --git a/FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt b/FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt index 796e6d438..1bfd69443 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt @@ -69,6 +69,11 @@ interface ITrackProvider { */ fun size(): Int + /** + * @return amount of tracks in the queue filtered by userId + */ + fun size(userId: Long): Int + /** * @param track add a track to the queue */ diff --git a/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt b/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt index fd08d147e..abe422794 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt @@ -187,6 +187,10 @@ class SimpleTrackProvider : AbstractTrackProvider() { return queue.size } + override fun size(userId: Long): Int { + return queue.filter { it.userId == userId }.size + } + override fun add(track: AudioTrackContext) { shouldUpdateShuffledQueue = true diff --git a/FredBoat/src/main/java/fredboat/audio/queue/SplitAudioTrackContext.kt b/FredBoat/src/main/java/fredboat/audio/queue/SplitAudioTrackContext.kt index cf1f0ec23..916d547db 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/SplitAudioTrackContext.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/SplitAudioTrackContext.kt @@ -36,7 +36,7 @@ class SplitAudioTrackContext( override val startPosition: Long, val endPosition: Long, override val effectiveTitle: String -) : AudioTrackContext(at, member) { +) : AudioPlaylistContext(at, member) { override val effectiveDuration: Long get() = endPosition - startPosition diff --git a/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt b/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt index 60fe527d9..854b519ed 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt @@ -33,6 +33,7 @@ import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist import com.sedmelluq.discord.lavaplayer.track.AudioTrack import fredboat.audio.player.GuildPlayer import fredboat.audio.player.trackCount +import fredboat.audio.queue.limiter.* import fredboat.audio.source.PlaylistImportSourceManager import fredboat.audio.source.PlaylistImporter import fredboat.audio.source.SpotifyPlaylistSourceManager @@ -42,9 +43,10 @@ import fredboat.util.extension.escapeAndDefuse import fredboat.util.localMessageBuilder import fredboat.util.ratelimit.Ratelimiter import fredboat.util.rest.YoutubeAPI +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.reactor.mono import org.apache.commons.lang3.tuple.ImmutablePair import org.apache.commons.lang3.tuple.Pair -import org.slf4j.Logger import org.slf4j.LoggerFactory import java.util.* import java.util.concurrent.ConcurrentLinkedQueue @@ -175,10 +177,6 @@ class AudioLoader(private val ratelimiter: Ratelimiter, private val playerManage private class ResultHandler(val loader: AudioLoader, val context: IdentifierContext) : AudioLoadResultHandler { - companion object { - private val log: Logger = LoggerFactory.getLogger(ResultHandler::class.java) - } - override fun loadFailed(fe: FriendlyException) { Metrics.trackLoadsFailed.inc() loader.handleThrowable(context, fe) @@ -192,22 +190,20 @@ private class ResultHandler(val loader: AudioLoader, val context: IdentifierCont if (context.isSplit) { loadSplit(at, context) } else { - - if (!context.isQuiet) { - context.reply(if (loader.player.isPlaying) - context.i18nFormat(if (context.isPriority) "loadSingleTrackFirst" else "loadSingleTrack", - TextUtils.escapeAndDefuse(at.info.title)) - else - context.i18nFormat("loadSingleTrackAndPlay", TextUtils.escapeAndDefuse(at.info.title)) - ) - } else { - log.info("Quietly loaded " + at.identifier) - } - at.position = context.position - val atc = AudioTrackContext(at, context.member, context.isPriority) - loader.player.queue(atc) + GlobalScope.mono { loader.player.queueLimited(atc) }.subscribe { + if (it.canQueue) { + context.reply(if (loader.player.isPlaying) + context.i18nFormat(if (context.isPriority) "loadSingleTrackFirst" else "loadSingleTrack", + TextUtils.escapeAndDefuse(at.info.title)) + else + context.i18nFormat("loadSingleTrackAndPlay", TextUtils.escapeAndDefuse(at.info.title)) + ) + } else { + context.replyWithMention(it.errorMessage) + } + } if (!loader.player.isPaused) { loader.player.play() @@ -229,16 +225,33 @@ private class ResultHandler(val loader: AudioLoader, val context: IdentifierCont return } - val toAdd = ArrayList() + val toAdd = ArrayList() for (at in ap.tracks) { - toAdd.add(AudioTrackContext(at, context.member, context.isPriority)) + toAdd.add(AudioPlaylistContext(at, context.member, context.isPriority)) } - loader.player.queueAll(toAdd, context.isPriority) - context.reply(context.i18nFormat("loadListSuccess", ap.tracks.size, ap.name)) - if (!loader.player.isPaused) { - loader.player.play() + GlobalScope.mono { loader.player.queueLimited(toAdd, context.isPriority) }.subscribe { + if (it.isPlaylistDisabledError) { + context.replyWithMention(context.i18n(it.playlistDisabledError)) + return@subscribe + } + + val mb = localMessageBuilder().append(context.i18nFormat( + "loadListSuccess", + it.successful.map { s -> s.atc }.size, ap.name)) + + // TODO: Better way to display these messages since its a failure of a system if we have to add every limiter case aswell + if (it.isTrackLimitExceededError) { + mb.append(context.i18nFormat(it.trackLimitExceededError, it.trackLimitExceededErrorCount)) + } + + if (it.successful.isNotEmpty()) { + if (!loader.player.isPaused) { + loader.player.play() + } + } } + } catch (th: Throwable) { loader.handleThrowable(context, th) } @@ -313,25 +326,26 @@ private class ResultHandler(val loader: AudioLoader, val context: IdentifierCont val atc = SplitAudioTrackContext(newAt, context.member, startPos, endPos, pair.right) list.add(atc) - loader.player.queue(atc) } - var mb = localMessageBuilder() - .append(ic.i18n("loadFollowingTracksAdded")).append("\n") - for (atc in list) { - mb.append("`[") - .append(TextUtils.formatTime(atc.effectiveDuration)) - .append("]` ") - .append(atc.effectiveTitle.escapeAndDefuse()) - .append("\n") - } + GlobalScope.mono { loader.player.queueLimited(list, context.isPriority) }.subscribe { + var mb = localMessageBuilder().append(ic.i18n("loadFollowingTracksAdded")).append("\n") - //This is pretty spammy .. let's use a shorter one - if (mb.length > 800) { - mb = localMessageBuilder() - .append(ic.i18nFormat("loadPlaylistTooMany", list.size)) - } + for (atc in it.filter { status -> status.canQueue }.map { status -> status.atc }) { + mb.append("`[") + .append(TextUtils.formatTime(atc.effectiveDuration)) + .append("]` ") + .append(atc.effectiveTitle.escapeAndDefuse()) + .append("\n") + } - context.reply(mb.build()) + //This is pretty spammy .. let's use a shorter one + if (mb.length > 800) { + mb = localMessageBuilder() + .append(ic.i18nFormat("loadPlaylistTooMany", list.size)) + } + + context.reply(mb.build()) + } } } diff --git a/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimit.kt b/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimit.kt new file mode 100644 index 000000000..4734b27ca --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimit.kt @@ -0,0 +1,27 @@ +package fredboat.audio.queue.limiter + +import fredboat.audio.player.GuildPlayer +import fredboat.audio.queue.AudioTrackContext +import fredboat.db.api.GuildSettingsRepository +import fredboat.messaging.internal.Context +import kotlinx.coroutines.reactive.awaitSingle +import reactor.core.publisher.Mono + +class QueueLimit( + val name: String, + private val limit: (AudioTrackContext, GuildPlayer, Int) -> Mono, + + val errorCode: QueueLimiterEnum, + val errorMessage: (AudioTrackContext, QueueLimiterEnum) -> Mono +) { + suspend fun isAllowed(atc: AudioTrackContext, player: GuildPlayer, preemptive: Int): QueueLimitStatus { + + // DJ and above are not affected by Limits + // TODO: Uncomment +/* if (PermsUtil.checkPerms(PermissionLevel.DJ, atc.member)) + return QueueLimitStatus(true, atc)*/ + + // Dynamic execution of the limit + return limit(atc, player, preemptive).awaitSingle() + } +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimitStatus.kt b/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimitStatus.kt new file mode 100644 index 000000000..bbf7a6980 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimitStatus.kt @@ -0,0 +1,30 @@ +package fredboat.audio.queue.limiter + +import fredboat.audio.queue.AudioTrackContext + +class QueueLimitStatus( + val canQueue: Boolean, + val atc: AudioTrackContext, + var errorCode: QueueLimiterEnum = QueueLimiterEnum.SUCCESS, + var errorMessage: String = "Successful" +) + +val List.successful get() = filter { it.canQueue } +val List.errored get() = filter { !it.canQueue } + +val List.isPlaylistDisabledError get() = any { it.errorCode == QueueLimiterEnum.PLAYLIST_DISABLED } +val List.playlistDisabledError get() = first { it.errorCode == QueueLimiterEnum.PLAYLIST_DISABLED }.errorCode.i18n + +val List.isTrackLimitExceededError get() = any { it.errorCode == QueueLimiterEnum.TRACK_LIMIT_EXCEEDED } +val List.trackLimitExceededError get() = first { it.errorCode == QueueLimiterEnum.TRACK_LIMIT_EXCEEDED }.errorCode.i18n +val List.trackLimitExceededErrorCount get() = count { it.errorCode == QueueLimiterEnum.TRACK_LIMIT_EXCEEDED } + +// TODO: Naming +enum class QueueLimiterEnum(val i18n: String) { + PLAYLIST_DISABLED("loadPlaylistDisabled"), + USER_TRACK_LIMIT_EXCEEDED("loadMaxUserTracksExceeded"), + TRACK_LIMIT_EXCEEDED("loadMaxTracksExceeded"), + TRACK_LENGTH_EXCEEDED("loadMaxTrackLengthExceeded"), + UNKNOWN("loadLimitedUnknown"), + SUCCESS("") +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimiter.kt b/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimiter.kt new file mode 100644 index 000000000..790a1581d --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimiter.kt @@ -0,0 +1,63 @@ +package fredboat.audio.queue.limiter + +import fredboat.audio.player.GuildPlayer +import fredboat.audio.player.trackCount +import fredboat.audio.player.userTackCount +import fredboat.audio.queue.AudioPlaylistContext +import fredboat.audio.queue.AudioTrackContext +import fredboat.db.api.GuildSettingsRepository +import fredboat.feature.metrics.Metrics +import kotlinx.coroutines.reactive.awaitSingle +import reactor.core.publisher.Mono + +class QueueLimiter(repository: GuildSettingsRepository) { + private val queueLimits: ArrayList = arrayListOf() + + init { + queueLimits.add(QueueLimit("NoPlaylist", { atc, _, _ -> + repository.fetch(atc.guildId).map { QueueLimitStatus(atc !is AudioPlaylistContext || it.allowPlaylist, atc) } + }, QueueLimiterEnum.PLAYLIST_DISABLED, { context, status -> + Mono.just(context.i18n(status.i18n)) + })) + + queueLimits.add(QueueLimit("UserTrackLimit", { atc, player, preemptive -> + repository.fetch(atc.guildId).map { QueueLimitStatus(it.userMaxTrackCount == null || it.userMaxTrackCount!! > player.userTackCount(atc.userId) + preemptive, atc) } + }, QueueLimiterEnum.USER_TRACK_LIMIT_EXCEEDED, { context, status -> + repository.fetch(context.guild.id).map { context.i18nFormat(status.i18n, it.userMaxTrackCount ?: "UNLIMITED") } + })) + + queueLimits.add(QueueLimit("TrackLimit", { atc, player, preemptive -> + repository.fetch(atc.guildId).map { QueueLimitStatus(it.maxTrackCount == null || it.maxTrackCount!! > player.trackCount + preemptive, atc) } + }, QueueLimiterEnum.TRACK_LIMIT_EXCEEDED, { context, status -> + repository.fetch(context.guild.id).map { context.i18nFormat(status.i18n, it.maxTrackCount ?: "UNLIMITED") } + })) + + queueLimits.add(QueueLimit("TrackLength", { atc, _, _ -> + repository.fetch(atc.guildId).map { QueueLimitStatus(it.maxTrackLength == null || it.maxTrackLength!! > atc.effectiveDuration, atc) } + }, QueueLimiterEnum.TRACK_LENGTH_EXCEEDED, { context, status -> + repository.fetch(context.guild.id).map { context.i18nFormat(status.i18n, it.maxTrackLength ?: "UNLIMITED") } + })) + } + + suspend fun isQueueLimited(atc: AudioTrackContext, player: GuildPlayer, preemptive: Int = 0): QueueLimitStatus { + var status = QueueLimitStatus(true, atc) + + for (limit in queueLimits) { + status = limit.isAllowed(atc, player, preemptive) + + if (!status.canQueue) { + Metrics.queuePrevented.labels(limit.name).inc() + + status.errorCode = limit.errorCode + status.errorMessage = limit.errorMessage(atc, limit.errorCode).awaitSingle() + break + } + } + + return status + } + + suspend fun isQueueLimited(tracks: List, player: GuildPlayer): List { + return tracks.mapIndexed { i, atc -> isQueueLimited(atc, player, i) } + } +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt index b65d89de9..5d67afa4e 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt @@ -65,7 +65,7 @@ class PlayCommand(private val playerLimiter: PlayerLimiter, private val trackSea val player = Launcher.botController.playerRegistry.awaitPlayer(context.guild) for (atc in context.msg.attachments) { - player.queue(atc, context, isPriority) + player.queueAsync(atc, context, isPriority) } player.setPause(false) @@ -95,7 +95,7 @@ class PlayCommand(private val playerLimiter: PlayerLimiter, private val trackSea } val player = Launcher.botController.playerRegistry.awaitPlayer(context.guild) - player.queue(url, context, isPriority) + player.queueAsync(url, context, isPriority) player.setPause(false) context.deleteMessage() diff --git a/FredBoat/src/main/java/fredboat/command/music/control/PlaySplitCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/PlaySplitCommand.kt index efed26fa9..922550158 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/PlaySplitCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/PlaySplitCommand.kt @@ -56,7 +56,7 @@ class PlaySplitCommand(private val playerLimiter: PlayerLimiter, name: String, v ic.isSplit = true val player = playerRegistry.awaitPlayer(context.guild) - player.queue(ic) + player.queueAsync(ic) player.setPause(false) context.deleteMessage() diff --git a/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.kt index 2e42cbeb0..4abac0ec7 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.kt @@ -115,7 +115,7 @@ class SelectCommand(private val videoSelectionCache: VideoSelectionCache, name: } outputMsgBuilder.append(msg) - player.queue(AudioTrackContext(selectedTracks[i]!!, invoker, selection.isPriority)) + player.queueLimited(AudioTrackContext(selectedTracks[i]!!, invoker, selection.isPriority)) } videoSelectionCache.remove(invoker) diff --git a/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt b/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt index a103364ad..40c0fd05c 100644 --- a/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt +++ b/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt @@ -12,6 +12,10 @@ data class GuildSettings( var trackAnnounce: Boolean = false, var autoResume: Boolean = false, var allowPublicPlayerInfo: Boolean = false, + var allowPlaylist: Boolean = true, + var maxTrackLength: Long? = null, + var maxTrackCount: Int? = null, + var userMaxTrackCount: Int? = null, var lang: String = "en_US", var prefix: String? = null, var modules: List = Module.values().toList().map { ModuleEntity(it, true) }, diff --git a/FredBoat/src/main/java/fredboat/feature/metrics/Metrics.java b/FredBoat/src/main/java/fredboat/feature/metrics/Metrics.java index bde10b678..8abe0f996 100644 --- a/FredBoat/src/main/java/fredboat/feature/metrics/Metrics.java +++ b/FredBoat/src/main/java/fredboat/feature/metrics/Metrics.java @@ -110,6 +110,12 @@ public Metrics(CacheMetricsCollector cacheMetrics, InstrumentedAppender promethe .labelNames("class") // use the simple name of the command class .register(); + // QueueLimiter + public static final Counter queuePrevented = Counter.build() + .name("fredboat_queue_attempts_prevented") + .help("Total queue attempts that have been prevented") + .labelNames("limit") + .register(); //music stuff diff --git a/FredBoat/src/main/java/fredboat/messaging/internal/Context.kt b/FredBoat/src/main/java/fredboat/messaging/internal/Context.kt index 487dd06a2..4a4fc85c5 100644 --- a/FredBoat/src/main/java/fredboat/messaging/internal/Context.kt +++ b/FredBoat/src/main/java/fredboat/messaging/internal/Context.kt @@ -33,7 +33,6 @@ import fredboat.perms.IPermissionSet import fredboat.perms.PermissionSet import fredboat.perms.PermsUtil import fredboat.sentinel.* -import fredboat.util.TextUtils import kotlinx.coroutines.reactive.awaitSingle import reactor.core.publisher.Mono import javax.annotation.CheckReturnValue @@ -59,60 +58,13 @@ abstract class Context : NullableContext() { // Convenience reply methods // ******************************************************************************** - - fun replyMono(message: String): Mono = textChannel.send(message) - - fun reply(message: String) { - textChannel.send(message).subscribe() - } - - fun replyMono(message: Embed): Mono = textChannel.send(message) - - fun reply(message: Embed) { - textChannel.send(message).subscribe() - } - - fun replyWithNameMono(message: String): Mono { - return replyMono(TextUtils.prefaceWithName(member, message)) - } - - fun replyWithName(message: String) { - reply(TextUtils.prefaceWithName(member, message)) - } - - fun replyWithMentionMono(message: String): Mono { - return replyMono(TextUtils.prefaceWithMention(member, message)) - } - - fun replyWithMention(message: String) { - reply(TextUtils.prefaceWithMention(member, message)) - } - - fun replyImageMono(url: String, message: String = ""): Mono { - val embed = embedImage(url) - embed.description = message - return textChannel.send(embed) - } - - fun replyImage(url: String, message: String = "") { - replyImageMono(url, message).subscribe() - } - - fun sendTyping() { - textChannel.sendTyping() - } - - /* Private messages */ - /** - * Privately DM the invoker - */ - fun replyPrivateMono(message: String) = user.sendPrivate(message) - /** - * Privately DM the invoker - */ - fun replyPrivate(message: String) { - user.sendPrivate(message).subscribe() - } + // We can be sure that nothing can be null in this context override the Mono methods for null safe access + override fun replyMono(message: String): Mono = super.replyMono(message)!! + override fun replyMono(embed: Embed): Mono = super.replyMono(embed)!! + override fun replyWithNameMono(message: String): Mono = super.replyWithNameMono(message)!! + override fun replyWithMentionMono(message: String): Mono = super.replyWithMentionMono(message)!! + override fun replyImageMono(url: String, message: String): Mono = super.replyImageMono(url, message)!! + override fun replyPrivateMono(message: String) = super.replyPrivateMono(message)!! /** * Checks whether we have the provided permissions for the channel of this context diff --git a/FredBoat/src/main/java/fredboat/messaging/internal/NullableContext.kt b/FredBoat/src/main/java/fredboat/messaging/internal/NullableContext.kt index d21cfa2ea..110ea8fe2 100644 --- a/FredBoat/src/main/java/fredboat/messaging/internal/NullableContext.kt +++ b/FredBoat/src/main/java/fredboat/messaging/internal/NullableContext.kt @@ -3,17 +3,14 @@ package fredboat.messaging.internal import com.fredboat.sentinel.entities.Embed import com.fredboat.sentinel.entities.SendMessageResponse import com.fredboat.sentinel.entities.embed -import com.fredboat.sentinel.entities.passed -import fredboat.command.config.PrefixCommand import fredboat.commandmeta.MessagingException import fredboat.feature.I18n -import fredboat.perms.IPermissionSet -import fredboat.perms.PermissionSet -import fredboat.perms.PermsUtil -import fredboat.sentinel.* +import fredboat.sentinel.Guild +import fredboat.sentinel.Member +import fredboat.sentinel.TextChannel +import fredboat.sentinel.User import fredboat.shared.constant.BotConstants import fredboat.util.TextUtils -import kotlinx.coroutines.reactive.awaitSingle import org.slf4j.Logger import org.slf4j.LoggerFactory import reactor.core.publisher.Mono @@ -39,6 +36,50 @@ abstract class NullableContext { private var i18n: ResourceBundle? = null + + // ******************************************************************************** + // Convenience reply methods + // ******************************************************************************** + + open fun replyMono(message: String): Mono? = textChannel?.send(message) + fun reply(message: String) { + replyMono(message)?.subscribe() + } + + open fun replyMono(embed: Embed): Mono? = textChannel?.send(embed) + fun reply(message: Embed) { + replyMono(message)?.subscribe() + } + + open fun replyWithNameMono(message: String): Mono? = replyMono(TextUtils.prefaceWithName(member, message)) + fun replyWithName(message: String) { + replyWithNameMono(message)?.subscribe() + } + + open fun replyWithMentionMono(message: String): Mono? = replyMono(TextUtils.prefaceWithMention(member, message)) + fun replyWithMention(message: String) { + replyWithMentionMono(message)?.subscribe() + } + + open fun replyImageMono(url: String, message: String = ""): Mono? { + val embed = embedImage(url) + embed.description = message + return textChannel?.send(embed) + } + + fun replyImage(url: String, message: String = "") { + replyImageMono(url, message)?.subscribe() + } + + fun sendTyping() { + textChannel?.sendTyping() + } + + open fun replyPrivateMono(message: String) = user?.sendPrivate(message) + fun replyPrivate(message: String) { + replyPrivateMono(message)?.subscribe() + } + /** * Return a single translated string. * @@ -79,7 +120,7 @@ abstract class NullableContext { } - private fun getI18n(): ResourceBundle { + fun getI18n(): ResourceBundle { var result = i18n if (result == null) { result = I18n.get(guild) @@ -88,7 +129,7 @@ abstract class NullableContext { return result } - protected fun embedImage(url: String): Embed = embed { + private fun embedImage(url: String): Embed = embed { color = BotConstants.FREDBOAT_COLOR.rgb image = url } diff --git a/FredBoat/src/main/resources/lang/en_US.properties b/FredBoat/src/main/resources/lang/en_US.properties index e57d882c4..6205318fe 100644 --- a/FredBoat/src/main/resources/lang/en_US.properties +++ b/FredBoat/src/main/resources/lang/en_US.properties @@ -91,6 +91,10 @@ loadPlaylistTooMany=Added {0} tracks. Found too many tracks to display. loadErrorCommon=Error occurred when loading info for `{0}`\:\n{1} loadErrorSusp=Suspicious error when loading info for `{0}`. loadQueueTrackLimit=You can't add tracks to a queue with more than {0} tracks! This is to prevent abuse. +loadPlaylistDisabled=TODO loadPlaylistDisabled +loadMaxTracksExceeded=TODO loadMaxTracksExceeded +loadMaxUserTracksExceeded=TODO loadMaxUserTracksExceeded +loadMaxTrackLengthExceeded=TODO loadMaxTrackLengthExcceded loadAnnouncePlaylist=About to load playlist **{0}** with up to `{1}` tracks. This may take a while, please be patient. playerUserNotInChannel=You must join a voice channel first. playerJoinConnectDenied=I am not permitted to connect to that voice channel. From a38809c00d383553914dee446de690dcf3827ffc Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sat, 23 Feb 2019 21:58:58 +0100 Subject: [PATCH 118/172] Whitelist DJ and above for QueueLimits --- .../main/java/fredboat/audio/queue/limiter/QueueLimit.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimit.kt b/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimit.kt index 4734b27ca..288e8f54f 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimit.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimit.kt @@ -2,8 +2,8 @@ package fredboat.audio.queue.limiter import fredboat.audio.player.GuildPlayer import fredboat.audio.queue.AudioTrackContext -import fredboat.db.api.GuildSettingsRepository -import fredboat.messaging.internal.Context +import fredboat.definitions.PermissionLevel +import fredboat.perms.PermsUtil import kotlinx.coroutines.reactive.awaitSingle import reactor.core.publisher.Mono @@ -17,9 +17,8 @@ class QueueLimit( suspend fun isAllowed(atc: AudioTrackContext, player: GuildPlayer, preemptive: Int): QueueLimitStatus { // DJ and above are not affected by Limits - // TODO: Uncomment -/* if (PermsUtil.checkPerms(PermissionLevel.DJ, atc.member)) - return QueueLimitStatus(true, atc)*/ + if (PermsUtil.checkPerms(PermissionLevel.DJ, atc.member)) + return QueueLimitStatus(true, atc) // Dynamic execution of the limit return limit(atc, player, preemptive).awaitSingle() From bdd34026fb35831abe73c39691ba884e0970c54d Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sat, 23 Feb 2019 22:00:32 +0100 Subject: [PATCH 119/172] Remove old databaseException Metric entry --- FredBoat/src/main/java/fredboat/feature/metrics/Metrics.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/feature/metrics/Metrics.java b/FredBoat/src/main/java/fredboat/feature/metrics/Metrics.java index 8abe0f996..32891eddf 100644 --- a/FredBoat/src/main/java/fredboat/feature/metrics/Metrics.java +++ b/FredBoat/src/main/java/fredboat/feature/metrics/Metrics.java @@ -235,11 +235,6 @@ public Metrics(CacheMetricsCollector cacheMetrics, InstrumentedAppender promethe // ## Various // ################################################################################ - public static final Counter databaseExceptionsCreated = Counter.build() - .name("fredboat_db_exceptions_created_total") - .help("Total database exceptions created") - .register(); - public static final Histogram guildLifespan = Histogram.build() .name("fredboat_guild_lifespan_seconds") .help("How long were we part of a guild when leaving it") From 236a0accdf17a4b769c1039212e55e89f4ffc0c9 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sat, 23 Feb 2019 22:10:46 +0100 Subject: [PATCH 120/172] Simplify audioLoading PlaylistLoaded QueueLimit UX handling --- .../src/main/java/fredboat/audio/queue/audioLoading.kt | 9 ++++----- .../fredboat/audio/queue/limiter/QueueLimitStatus.kt | 4 ---- FredBoat/src/main/resources/lang/en_US.properties | 1 + 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt b/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt index 854b519ed..e379cce99 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt @@ -237,12 +237,11 @@ private class ResultHandler(val loader: AudioLoader, val context: IdentifierCont } val mb = localMessageBuilder().append(context.i18nFormat( - "loadListSuccess", - it.successful.map { s -> s.atc }.size, ap.name)) + "loadListSuccess", it.successful.map { s -> s.atc }.size, ap.name)) - // TODO: Better way to display these messages since its a failure of a system if we have to add every limiter case aswell - if (it.isTrackLimitExceededError) { - mb.append(context.i18nFormat(it.trackLimitExceededError, it.trackLimitExceededErrorCount)) + // TODO: maybe better way, this is to general + if (it.errored.isNotEmpty()) { + mb.append("\n").append(context.i18nFormat("loadPlaylistGeneralError", "`${it.errored.size}`")) } if (it.successful.isNotEmpty()) { diff --git a/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimitStatus.kt b/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimitStatus.kt index bbf7a6980..58895335f 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimitStatus.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimitStatus.kt @@ -15,10 +15,6 @@ val List.errored get() = filter { !it.canQueue } val List.isPlaylistDisabledError get() = any { it.errorCode == QueueLimiterEnum.PLAYLIST_DISABLED } val List.playlistDisabledError get() = first { it.errorCode == QueueLimiterEnum.PLAYLIST_DISABLED }.errorCode.i18n -val List.isTrackLimitExceededError get() = any { it.errorCode == QueueLimiterEnum.TRACK_LIMIT_EXCEEDED } -val List.trackLimitExceededError get() = first { it.errorCode == QueueLimiterEnum.TRACK_LIMIT_EXCEEDED }.errorCode.i18n -val List.trackLimitExceededErrorCount get() = count { it.errorCode == QueueLimiterEnum.TRACK_LIMIT_EXCEEDED } - // TODO: Naming enum class QueueLimiterEnum(val i18n: String) { PLAYLIST_DISABLED("loadPlaylistDisabled"), diff --git a/FredBoat/src/main/resources/lang/en_US.properties b/FredBoat/src/main/resources/lang/en_US.properties index 6205318fe..72ab66328 100644 --- a/FredBoat/src/main/resources/lang/en_US.properties +++ b/FredBoat/src/main/resources/lang/en_US.properties @@ -95,6 +95,7 @@ loadPlaylistDisabled=TODO loadPlaylistDisabled loadMaxTracksExceeded=TODO loadMaxTracksExceeded loadMaxUserTracksExceeded=TODO loadMaxUserTracksExceeded loadMaxTrackLengthExceeded=TODO loadMaxTrackLengthExcceded +loadPlaylistGeneralError={0} tracks have not been added due to queue limitations! loadAnnouncePlaylist=About to load playlist **{0}** with up to `{1}` tracks. This may take a while, please be patient. playerUserNotInChannel=You must join a voice channel first. playerJoinConnectDenied=I am not permitted to connect to that voice channel. From bfb51e0f967ace1a63be22e8a4a998f4b9455907 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sat, 23 Feb 2019 22:23:54 +0100 Subject: [PATCH 121/172] Move atc priority rand set to correct place --- .../src/main/java/fredboat/audio/queue/AudioTrackContext.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt b/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt index e7021093d..000351ecb 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt @@ -44,7 +44,7 @@ open class AudioTrackContext( ) : NullableContext(), Comparable { val trackId: ObjectId // used to identify this track even when the track gets cloned and the rand reranded - var rand: Int = if (!priority) 0 else Integer.MIN_VALUE + var rand: Int var isPriority: Boolean = priority val added: Long = System.currentTimeMillis() @@ -83,7 +83,7 @@ open class AudioTrackContext( } init { - this.rand = ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE) + this.rand = if (!priority) ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE) else Integer.MIN_VALUE this.trackId = ObjectId() }//It's ok to set a non-existing channelId, since inside the AudioTrackContext, the channel needs to be looked up // every time. See the getTextChannel() below for doing that. From 9ddb76b00cffa02c2d00fdebe0cc53beffb8c73e Mon Sep 17 00:00:00 2001 From: Nanabell Date: Tue, 26 Feb 2019 19:37:12 +0100 Subject: [PATCH 122/172] Update Guildplayer#loadAll (again) to take isPriority parameter --- FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt | 4 ++-- .../src/main/java/fredboat/audio/player/PlayerRegistry.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index 7a75fa4d9..77ac0ef33 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -220,8 +220,8 @@ class GuildPlayer( } /** Add a bunch of tracks to the track provider */ - fun loadAll(tracks: Collection) { - audioTrackProvider.addAll(tracks) + fun loadAll(tracks: Collection, isPriority: Boolean) { + if (isPriority) audioTrackProvider.addAllFirst(tracks) else audioTrackProvider.addAll(tracks) } override fun toString(): String { diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index 77faf9df7..2f20698d0 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -228,7 +228,7 @@ class PlayerRegistry( val channel = mongo.textChannel?.let { guild.getTextChannel(it) } if (channel != null) musicTextChannelProvider.setMusicChannel(channel) - player.loadAll(queue) + player.loadAll(queue, false) } private fun beforeShutdown() { From ce521915610f2e6c4a5baf82d91f482d465eab4d Mon Sep 17 00:00:00 2001 From: Nanabell Date: Tue, 26 Feb 2019 19:38:52 +0100 Subject: [PATCH 123/172] Add ReplayCommand --- .../command/music/control/ReplayCommand.kt | 46 +++++++++++++++++++ .../commandmeta/CommandInitializer.kt | 3 +- .../src/main/resources/lang/en_US.properties | 3 ++ 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 FredBoat/src/main/java/fredboat/command/music/control/ReplayCommand.kt diff --git a/FredBoat/src/main/java/fredboat/command/music/control/ReplayCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/ReplayCommand.kt new file mode 100644 index 000000000..4a50da214 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/command/music/control/ReplayCommand.kt @@ -0,0 +1,46 @@ +package fredboat.command.music.control + +import fredboat.commandmeta.abs.Command +import fredboat.commandmeta.abs.CommandContext +import fredboat.commandmeta.abs.ICommandRestricted +import fredboat.commandmeta.abs.IMusicCommand +import fredboat.definitions.PermissionLevel +import fredboat.main.Launcher +import fredboat.messaging.internal.Context +import fredboat.util.extension.escapeAndDefuse + +class ReplayCommand(name: String, vararg aliases: String) : Command(name, *aliases), IMusicCommand, ICommandRestricted { + + override val minimumPerms: PermissionLevel + get() = PermissionLevel.DJ + + override suspend fun invoke(context: CommandContext) { + val player = Launcher.botController.playerRegistry.awaitPlayer(context.guild) + + val last = player.historyQueue.lastOrNull() + if (last == null) { + context.reply(context.i18n("replayHistoryEmpty")) + return + } + + // requeue newest history track as priority track + last.isPriority = true + val toQueue = arrayListOf(last) // as list or else we get issues queueing 2 tracks fast one after another + + // if the player is playing requeue the currently playing track as priority track and skip it + val old = player.internalContext?.makeClone() + if (old != null) { + old.isPriority = true + toQueue.add(old) + + player.skip() + } + + player.loadAll(toQueue, true) + context.reply(context.i18nFormat("replayWillNowReplay", "**${last.effectiveTitle.escapeAndDefuse()}**")) + } + + override fun help(context: Context): String { + return "{0}{1}\n# " + context.i18n("helpReplayCommand") + } +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt b/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt index b9bb2a6a5..6213d9631 100644 --- a/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt +++ b/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt @@ -249,6 +249,7 @@ class CommandInitializer( musicModule.registerCommand(PlayCommand(playerLimiter, trackSearcher, videoSelectionCache, listOf(SearchProvider.SOUNDCLOUD), SOUNDCLOUD_COMM_NAME, "sc")) + musicModule.registerCommand(ReplayCommand("replay", "rp")) musicModule.registerCommand(PlayCommand(playerLimiter, trackSearcher, videoSelectionCache, listOf(SearchProvider.YOUTUBE, SearchProvider.SOUNDCLOUD), "playnext", "playtop", "pn", isPriority = true)) @@ -273,7 +274,7 @@ class CommandInitializer( /* Seeking */ musicModule.registerCommand(ForwardCommand("forward", "fwd")) - musicModule.registerCommand(RestartCommand("restart", "replay")) + musicModule.registerCommand(RestartCommand("restart")) musicModule.registerCommand(RewindCommand("rewind", "rew")) musicModule.registerCommand(SeekCommand("seek")) initialized = true diff --git a/FredBoat/src/main/resources/lang/en_US.properties b/FredBoat/src/main/resources/lang/en_US.properties index e57d882c4..8b91c5e65 100644 --- a/FredBoat/src/main/resources/lang/en_US.properties +++ b/FredBoat/src/main/resources/lang/en_US.properties @@ -7,6 +7,8 @@ playSearching=Searching YouTube for `{q}`... playYoutubeSearchError=An error occurred when searching YouTube. Consider linking directly to audio sources instead.\n```\n;;play ``` playSearchNoResults=No results for `{q}` playSelectVideo=**Please select a track with the `{0}play 1-5` command\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=No Track in History to replay! joinJoining=Joining {0} joinErrorAlreadyJoining=An error occurred. Couldn''t join {0} because I am already trying to connect to that channel. Please try again. pauseAlreadyPaused=The player is already paused. @@ -240,6 +242,7 @@ helpJoinCommand=Make the bot join your current voice channel. helpLeaveCommand=Make the bot leave the current voice channel. helpPauseCommand=Pause the player. helpPlayCommand=Play music from the given URL or search for a track. For a full list of sources please visit {0} +helpReplayCommand=Replay the most recent track in the history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Split a YouTube video into a tracklist provided in it\'s description. helpRepeatCommand=Toggle between repeat modes. From d9b80569e171309198be92c9525c58e08f847f0a Mon Sep 17 00:00:00 2001 From: Nanabell Date: Tue, 26 Feb 2019 19:51:37 +0100 Subject: [PATCH 124/172] PlayCommand only search if user is in videoSelectionCache --- .../src/main/java/fredboat/command/music/control/PlayCommand.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt index b65d89de9..987f3d051 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/PlayCommand.kt @@ -79,7 +79,7 @@ class PlayCommand(private val playerLimiter: PlayerLimiter, private val trackSea return } - if (TextUtils.isSplitSelect(context.rawArgs)) { + if (TextUtils.isSplitSelect(context.rawArgs) && videoSelectionCache[context.member] != null) { SelectCommand.select(context, videoSelectionCache) return } From 3b1346f42e41020818e844bdfcaccad360ccb0ee Mon Sep 17 00:00:00 2001 From: Nanabell Date: Tue, 26 Feb 2019 19:52:19 +0100 Subject: [PATCH 125/172] Limit SPLIT_SELECT_ALLOWED CharMatcher to only account for nrs 1-5 --- FredBoat/src/main/java/fredboat/util/TextUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FredBoat/src/main/java/fredboat/util/TextUtils.java b/FredBoat/src/main/java/fredboat/util/TextUtils.java index 5fb095711..4d1194702 100644 --- a/FredBoat/src/main/java/fredboat/util/TextUtils.java +++ b/FredBoat/src/main/java/fredboat/util/TextUtils.java @@ -68,7 +68,7 @@ public class TextUtils { .precomputed(); public static final CharMatcher SPLIT_SELECT_ALLOWED = - SPLIT_SELECT_SEPARATOR.or(CharMatcher.inRange('0', '9')) + SPLIT_SELECT_SEPARATOR.or(CharMatcher.inRange('1', '5')) .precomputed(); public static final Splitter COMMA_OR_WHITESPACE = Splitter.on(SPLIT_SELECT_SEPARATOR) From 07adaabc285c00a5949ce526d0dc4fd50d9a01e6 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 3 Mar 2019 13:53:17 +0100 Subject: [PATCH 126/172] Add IQueueHandler to replace TrackProvider --- .../fredboat/audio/queue/tbd/IQueueHandler.kt | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 FredBoat/src/main/java/fredboat/audio/queue/tbd/IQueueHandler.kt diff --git a/FredBoat/src/main/java/fredboat/audio/queue/tbd/IQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/tbd/IQueueHandler.kt new file mode 100644 index 000000000..e205d0275 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/queue/tbd/IQueueHandler.kt @@ -0,0 +1,63 @@ +package fredboat.audio.queue.tbd + +import fredboat.audio.queue.AudioTrackContext +import org.bson.types.ObjectId +import java.util.concurrent.ConcurrentLinkedDeque + +interface IQueueHandler { + + val queue: ConcurrentLinkedDeque + + val isEmpty: Boolean get() = queue.isEmpty() + + val size: Int get() = queue.size + + val totalDuration: Long get() = queue.fold(0L) { acc, v -> acc + v.effectiveDuration } + + val streamCount: Int get() = queue.count { it.track.info.isStream } + + fun peek(): AudioTrackContext? = queue.peek() + + fun take(silent: Boolean = false): AudioTrackContext? = queue.poll()?.also { onTaken(it, silent) } + + fun onTaken(track: AudioTrackContext, silent: Boolean) + + fun getInRange(start: Int, end: Int): Collection = queue.filterIndexed { i, _ -> i in start..end } + + fun add(track: AudioTrackContext, silent: Boolean = false) { + if (track.isPriority) queue.addFirst(track) else queue.add(track) + + onAdded(track, silent) + } + + fun onAdded(track: AudioTrackContext, silent: Boolean) + + fun addAll(tracks: Collection, silent: Boolean = false) { + if (tracks.all { it.isPriority }) queue.addAllFirst(tracks) else queue.addAll(tracks) + + onListAdded(tracks, silent) + } + + fun onListAdded(tracks: Collection, silent: Boolean) + + fun remove(track: AudioTrackContext, silent: Boolean = false) = queue.remove(track).also { onRemove(track, silent) } + + fun onRemove(track: AudioTrackContext, silent: Boolean) + + fun removeAll(tracks: Collection, silent: Boolean = false) = queue.removeAll(tracks).also { onListRemoved(tracks, silent) } + + fun removeById(trackIds: Collection, silent: Boolean = false) = queue.removeAll(queue.filter { trackIds.contains(it.trackId) }.also { onListRemoved(it, silent) }) + + fun onListRemoved(tracks: Collection, silent: Boolean) + + fun clear() = queue.clear() + + fun onSkipped() + + fun isUserTrackOwner(userId: Long, trackIds: Collection): Boolean = queue.filter { it.userId == userId }.all { trackIds.contains(it.trackId) } +} + +fun ConcurrentLinkedDeque.addAllFirst(tracks: Collection) { + tracks.reversed().forEach { addFirst(it) } +} + From 45c54b95ee4f33f02e40298484e3c0243f8294f7 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 3 Mar 2019 13:53:54 +0100 Subject: [PATCH 127/172] Add SimpleQueueHandler to do basic logging of QueueHandler events --- .../audio/queue/tbd/SimpleQueueHandler.kt | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 FredBoat/src/main/java/fredboat/audio/queue/tbd/SimpleQueueHandler.kt diff --git a/FredBoat/src/main/java/fredboat/audio/queue/tbd/SimpleQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/tbd/SimpleQueueHandler.kt new file mode 100644 index 000000000..68f34c645 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/queue/tbd/SimpleQueueHandler.kt @@ -0,0 +1,39 @@ +package fredboat.audio.queue.tbd + +import fredboat.audio.queue.AudioTrackContext +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.util.concurrent.ConcurrentLinkedDeque + +open class SimpleQueueHandler : IQueueHandler { + + companion object { + val log: Logger = LoggerFactory.getLogger(SimpleQueueHandler::class.java) + } + + final override val queue: ConcurrentLinkedDeque = ConcurrentLinkedDeque() + + override fun onTaken(track: AudioTrackContext, silent: Boolean) { + log.debug("Track {} was taken from the queue", track.effectiveTitle) + } + + override fun onAdded(track: AudioTrackContext, silent: Boolean) { + log.debug("Track {} was added to the queue", track.effectiveTitle) + } + + override fun onListAdded(tracks: Collection, silent: Boolean) { + log.debug("{} tracks were added to the queue", tracks.size) + } + + override fun onRemove(track: AudioTrackContext, silent: Boolean) { + log.debug("Track {} was removed from the queue", track.effectiveTitle) + } + + override fun onListRemoved(tracks: Collection, silent: Boolean) { + log.debug("{} tracks were removed from the queue", tracks.size) + } + + override fun onSkipped() { + log.debug("The currently playing track was skipped") + } +} \ No newline at end of file From 08700969e9af5d10d330f4a4f94eae0e16600f3b Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 3 Mar 2019 13:54:42 +0100 Subject: [PATCH 128/172] Add ShufflableQueueHandler to add shuffle capabilities to queue --- .../queue/tbd/IShufflableQueueHandler.kt | 13 +++ .../audio/queue/tbd/ShufflableQueueHandler.kt | 95 +++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 FredBoat/src/main/java/fredboat/audio/queue/tbd/IShufflableQueueHandler.kt create mode 100644 FredBoat/src/main/java/fredboat/audio/queue/tbd/ShufflableQueueHandler.kt diff --git a/FredBoat/src/main/java/fredboat/audio/queue/tbd/IShufflableQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/tbd/IShufflableQueueHandler.kt new file mode 100644 index 000000000..196db5b7c --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/queue/tbd/IShufflableQueueHandler.kt @@ -0,0 +1,13 @@ +package fredboat.audio.queue.tbd + +import fredboat.audio.queue.AudioTrackContext +import java.util.concurrent.ConcurrentLinkedDeque + +interface IShufflableQueueHandler : IQueueHandler { + + val shuffleQueue: ConcurrentLinkedDeque? + + var shuffle: Boolean + + fun reshuffle() +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/audio/queue/tbd/ShufflableQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/tbd/ShufflableQueueHandler.kt new file mode 100644 index 000000000..b00f3f9da --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/queue/tbd/ShufflableQueueHandler.kt @@ -0,0 +1,95 @@ +package fredboat.audio.queue.tbd + +import fredboat.audio.queue.AudioTrackContext +import java.util.* +import java.util.concurrent.ConcurrentLinkedDeque +import java.util.concurrent.ThreadLocalRandom + +open class ShufflableQueueHandler : SimpleQueueHandler(), IShufflableQueueHandler { + + private var _shuffleQueue: ConcurrentLinkedDeque = ConcurrentLinkedDeque() + override var shuffleQueue: ConcurrentLinkedDeque + get() { + if (size != _shuffleQueue.size) reshuffle() + return _shuffleQueue + } + set(value) { + _shuffleQueue = value + } + + private var _shuffle = false + override var shuffle: Boolean + get() = _shuffle + set(value) { + _shuffle = value + reshuffle() + } + + /** + * reshuffle all the rand values of the [queue] and pass them to [shuffleQueue] in sorted order + */ + override fun reshuffle() { + // if shuffle is disabled do nothing + if (!shuffle) { + shuffleQueue = queue + return + } + + // Get a list of the original queue + val cached = ArrayList() + cached.addAll(queue.toList()) + + // for each entry randomize 'rand' (This will update the track directly thus updating both lists) + for (track in cached) { + if (!track.isPriority) track.rand = ThreadLocalRandom.current().nextInt() + } + + // sort our locally cached list by the shuffled rand values and update our cachedQueue + cached.sort() + shuffleQueue = ConcurrentLinkedDeque(cached) + } + + override fun take(silent: Boolean): AudioTrackContext? { + if (!shuffle) + return super.take(silent) + + return shuffleQueue.poll()?.also { super.queue.remove(it); onTaken(it, silent) } + } + + override fun getInRange(start: Int, end: Int): Collection { + if (!shuffle) + return super.getInRange(start, end) + + return shuffleQueue.filterIndexed { i, _ -> i in start..end } + } + + override fun onAdded(track: AudioTrackContext, silent: Boolean) { + super.onAdded(track, silent) + + if (!silent) reshuffle() + } + + override fun onListAdded(tracks: Collection, silent: Boolean) { + super.onListAdded(tracks, silent) + + if (!silent) reshuffle() + } + + override fun onRemove(track: AudioTrackContext, silent: Boolean) { + super.onRemove(track, silent) + + if (!silent) reshuffle() + } + + override fun onListRemoved(tracks: Collection, silent: Boolean) { + super.onListRemoved(tracks, silent) + + if (!silent) reshuffle() + } + + override fun clear() { + super.clear() + + shuffleQueue.clear() + } +} \ No newline at end of file From 640f3fe64236c76f936547f668f130cc8cdcc8a3 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 3 Mar 2019 13:55:23 +0100 Subject: [PATCH 129/172] Add RoundRobinQueueHandler to add roundRobin queue capabilities to queue --- .../queue/tbd/IRoundRobinQueueHandler.kt | 11 ++ .../audio/queue/tbd/RoundRobinQueueHandler.kt | 129 ++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 FredBoat/src/main/java/fredboat/audio/queue/tbd/IRoundRobinQueueHandler.kt create mode 100644 FredBoat/src/main/java/fredboat/audio/queue/tbd/RoundRobinQueueHandler.kt diff --git a/FredBoat/src/main/java/fredboat/audio/queue/tbd/IRoundRobinQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/tbd/IRoundRobinQueueHandler.kt new file mode 100644 index 000000000..6023ea793 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/queue/tbd/IRoundRobinQueueHandler.kt @@ -0,0 +1,11 @@ +package fredboat.audio.queue.tbd + +import fredboat.audio.queue.AudioTrackContext +import java.util.concurrent.ConcurrentLinkedDeque + +interface IRoundRobinQueueHandler : IShufflableQueueHandler { + + var roundRobinQueue: ConcurrentLinkedDeque + + var roundRobin: Boolean +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/audio/queue/tbd/RoundRobinQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/tbd/RoundRobinQueueHandler.kt new file mode 100644 index 000000000..a35c0b0d4 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/queue/tbd/RoundRobinQueueHandler.kt @@ -0,0 +1,129 @@ +package fredboat.audio.queue.tbd + +import com.google.common.collect.Iterators +import fredboat.audio.player.GuildPlayer +import fredboat.audio.queue.AudioTrackContext +import java.util.concurrent.ConcurrentLinkedDeque + +open class RoundRobinQueueHandler(private val player: GuildPlayer) : ShufflableQueueHandler(), IRoundRobinQueueHandler { + + private var _roundRobinQueue: ConcurrentLinkedDeque = ConcurrentLinkedDeque() + override var roundRobinQueue: ConcurrentLinkedDeque + get() { + if (size != _roundRobinQueue.size) buildRoundRobin() // means we got out of sync. Redo + return _roundRobinQueue + } + set(value) { + _roundRobinQueue = value + } + + private var _roundRobin: Boolean = true + override var roundRobin: Boolean + get() = _roundRobin + set(value) { + _roundRobin = value + buildRoundRobin() + } + + private fun buildRoundRobin() { + // if Round Robin is disabled nothing to do here + if (!roundRobin) { + roundRobinQueue = shuffleQueue + return + } + + val userQueues = shuffleQueue.groupByTo(mutableMapOf()) { it.userId } + val cache = ArrayList() + + var userIterator = Iterators.cycle(userQueues.keys) + var index: Long? = null + + while (true) { + val next = index ?: nextUser(userIterator, player.playingTrack?.userId ?: 0, userQueues.size) + val userList = userQueues[next] + if (userList != null && !userList.isEmpty()) { + userList.firstOrNull()?.also { userList.remove(it); cache.add(it) } + + if (userList.isEmpty()) { + userQueues.remove(next) + userIterator = Iterators.cycle(userQueues.keys) + } + } + + if (userQueues.isEmpty()) break + + index = nextUser(userIterator, next, userQueues.size) + } + + roundRobinQueue = ConcurrentLinkedDeque(cache) + } + + private fun nextUser(iterator: Iterator, current: Long, maxRuns: Int): Long { + var runs = 0 + + var next: Long = 0 + while (true) { + if (!iterator.hasNext()) + break + + next = iterator.next() + + if (next == current || ++runs >= maxRuns) { + next = iterator.next() + break + } + } + + return next + } + + override fun take(silent: Boolean): AudioTrackContext? { + if (!roundRobin) + return super.take(silent) + + return roundRobinQueue.poll()?.also { queue.remove(it); onTaken(it, silent) } + } + + override fun getInRange(start: Int, end: Int): Collection { + if (!roundRobin) + return super.getInRange(start, end) + + return roundRobinQueue.filterIndexed { i, _ -> i in start..end } + } + + override fun onAdded(track: AudioTrackContext, silent: Boolean) { + super.onAdded(track, silent) + + if (!silent) buildRoundRobin() + } + + override fun onListAdded(tracks: Collection, silent: Boolean) { + super.onListAdded(tracks, silent) + + if (!silent) buildRoundRobin() + } + + override fun onRemove(track: AudioTrackContext, silent: Boolean) { + super.onRemove(track, silent) + + if (!silent) buildRoundRobin() + } + + override fun onListRemoved(tracks: Collection, silent: Boolean) { + super.onListRemoved(tracks, silent) + + if (!silent) buildRoundRobin() + } + + override fun clear() { + super.clear() + + roundRobinQueue.clear() + } + + override fun reshuffle() { + super.reshuffle() + + buildRoundRobin() + } +} \ No newline at end of file From 9816cd7f8834a78b2e0ee652d149949f49fff508 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 3 Mar 2019 13:55:49 +0100 Subject: [PATCH 130/172] Add RepeatableQueueHandler to add repeat capabilities to queue --- .../queue/tbd/IRepeatableQueueHandler.kt | 12 ++++++ .../audio/queue/tbd/RepeatableQueueHandler.kt | 38 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 FredBoat/src/main/java/fredboat/audio/queue/tbd/IRepeatableQueueHandler.kt create mode 100644 FredBoat/src/main/java/fredboat/audio/queue/tbd/RepeatableQueueHandler.kt diff --git a/FredBoat/src/main/java/fredboat/audio/queue/tbd/IRepeatableQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/tbd/IRepeatableQueueHandler.kt new file mode 100644 index 000000000..0c3d7fb19 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/queue/tbd/IRepeatableQueueHandler.kt @@ -0,0 +1,12 @@ +package fredboat.audio.queue.tbd + +import fredboat.audio.queue.AudioTrackContext +import fredboat.definitions.RepeatMode + +interface IRepeatableQueueHandler : IRoundRobinQueueHandler { + + val lastTrack: AudioTrackContext? + + var repeat: RepeatMode + +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/audio/queue/tbd/RepeatableQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/tbd/RepeatableQueueHandler.kt new file mode 100644 index 000000000..8e2fa545b --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/queue/tbd/RepeatableQueueHandler.kt @@ -0,0 +1,38 @@ +package fredboat.audio.queue.tbd + +import fredboat.audio.player.GuildPlayer +import fredboat.audio.queue.AudioTrackContext +import fredboat.definitions.RepeatMode + +open class RepeatableQueueHandler(player: GuildPlayer) : RoundRobinQueueHandler(player), IRepeatableQueueHandler { + + override var lastTrack: AudioTrackContext? = null + + override var repeat: RepeatMode = RepeatMode.OFF + + override fun take(silent: Boolean): AudioTrackContext? { + if (repeat == RepeatMode.SINGLE && lastTrack != null) + return lastTrack + + val track = super.take(silent) + if (repeat == RepeatMode.ALL && track != null) { + track.isPriority = false + add(track, true) + } + lastTrack = track + + return track + } + + override fun clear() { + super.clear() + + lastTrack = null + } + + override fun onSkipped() { + super.onSkipped() + + lastTrack = null + } +} \ No newline at end of file From 9ea8f43d12b8c631a9ae87caa63ccbe6b59abc84 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 3 Mar 2019 13:57:15 +0100 Subject: [PATCH 131/172] Use new IQueueHandler in GuildPlayer and depending --- .../java/fredboat/audio/player/GuildPlayer.kt | 52 ++++++++++--------- .../fredboat/audio/player/guildPlayerUtils.kt | 13 ++--- .../java/fredboat/audio/queue/audioLoading.kt | 7 +-- .../command/music/control/SelectCommand.kt | 2 +- 4 files changed, 40 insertions(+), 34 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index 7a75fa4d9..1ec57d433 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -32,6 +32,10 @@ import com.sedmelluq.discord.lavaplayer.track.TrackMarker import fredboat.audio.lavalink.SentinelLavalink import fredboat.audio.lavalink.SentinelLink import fredboat.audio.queue.* +import fredboat.audio.queue.tbd.IQueueHandler +import fredboat.audio.queue.tbd.IRepeatableQueueHandler +import fredboat.audio.queue.tbd.IShufflableQueueHandler +import fredboat.audio.queue.tbd.RepeatableQueueHandler import fredboat.command.music.control.VoteSkipCommand import fredboat.commandmeta.MessagingException import fredboat.commandmeta.abs.CommandContext @@ -67,8 +71,8 @@ class GuildPlayer( youtubeAPI: YoutubeAPI ) : PlayerEventListenerAdapter() { - val audioTrackProvider: ITrackProvider = SimpleTrackProvider() - private val audioLoader = AudioLoader(ratelimiter, audioTrackProvider, audioPlayerManager, this, youtubeAPI) + val queueHandler: IQueueHandler = RepeatableQueueHandler(this) + private val audioLoader = AudioLoader(ratelimiter, queueHandler, audioPlayerManager, this, youtubeAPI) val guildId = guild.id val player: LavalinkPlayer = lavalink.getLink(guild.id.toString()).player var internalContext: AudioTrackContext? = null @@ -96,23 +100,23 @@ class GuildPlayer( } var repeatMode: RepeatMode - get() = if (audioTrackProvider is AbstractTrackProvider) - audioTrackProvider.repeatMode + get() = if (queueHandler is IRepeatableQueueHandler) + queueHandler.repeat else RepeatMode.OFF - set(repeatMode) = if (audioTrackProvider is AbstractTrackProvider) { - audioTrackProvider.repeatMode = repeatMode + set(repeatMode) = if (queueHandler is IRepeatableQueueHandler) { + queueHandler.repeat = repeatMode } else { - throw UnsupportedOperationException("Can't repeat " + audioTrackProvider.javaClass) + throw UnsupportedOperationException("Can't repeat " + queueHandler.javaClass) } var isShuffle: Boolean - get() = audioTrackProvider is AbstractTrackProvider && audioTrackProvider.isShuffle - set(shuffle) = if (audioTrackProvider is AbstractTrackProvider) { - audioTrackProvider.isShuffle = shuffle + get() = queueHandler is IShufflableQueueHandler && queueHandler.shuffle + set(shuffle) = if (queueHandler is IShufflableQueueHandler) { + queueHandler.shuffle = shuffle internalContext?.isPriority = false } else { - throw UnsupportedOperationException("Can't shuffle " + audioTrackProvider.javaClass) + throw UnsupportedOperationException("Can't shuffle " + queueHandler.javaClass) } private fun getTrackAnnounceStatus(): Mono { @@ -126,7 +130,7 @@ class GuildPlayer( val playingTrack: AudioTrackContext? get() { return if (player.playingTrack == null && internalContext == null) { - audioTrackProvider.peek() + queueHandler.peek() } else internalContext } @@ -141,7 +145,7 @@ class GuildPlayer( list.add(atc) } - list.addAll(audioTrackProvider.asList) + list.addAll(queueHandler.queue.toList()) return list } @@ -206,7 +210,7 @@ class GuildPlayer( audioLoader.loadAsync(ic) } - fun queue(atc: AudioTrackContext, isPriority: Boolean = false) { + fun queue(atc: AudioTrackContext) { if (!guild.selfPresent) throw IllegalStateException("Attempt to queue track in a guild we are not present in") val member = guild.getMember(atc.userId) @@ -214,14 +218,14 @@ class GuildPlayer( joinChannel(member) } - if (isPriority) audioTrackProvider.addFirst(atc) else audioTrackProvider.add(atc) + queueHandler.add(atc) if (isPlaying) updateClients() play() } /** Add a bunch of tracks to the track provider */ fun loadAll(tracks: Collection) { - audioTrackProvider.addAll(tracks) + queueHandler.addAll(tracks) } override fun toString(): String { @@ -229,12 +233,12 @@ class GuildPlayer( } fun reshuffle() { - if (audioTrackProvider is AbstractTrackProvider) { - audioTrackProvider.reshuffle() + if (queueHandler is IShufflableQueueHandler) { + queueHandler.reshuffle() internalContext?.isPriority = false updateClients() } else { - throw UnsupportedOperationException("Can't reshuffle " + audioTrackProvider.javaClass) + throw UnsupportedOperationException("Can't reshuffle " + queueHandler.javaClass) } } @@ -263,7 +267,7 @@ class GuildPlayer( } fun destroy() { - audioTrackProvider.clear() + queueHandler.clear() stop() player.removeListener(this) player.link.destroy() @@ -332,7 +336,7 @@ class GuildPlayer( fun stop() { log.trace("stop()") - audioTrackProvider.clear() + queueHandler.clear() stopTrack() } @@ -342,7 +346,7 @@ class GuildPlayer( fun skip() { log.trace("skip()") - audioTrackProvider.skipped() + queueHandler.onSkipped() stopTrack() } @@ -367,7 +371,7 @@ class GuildPlayer( } else if (endReason == AudioTrackEndReason.LOAD_FAILED) { if (onErrorHook != null) onErrorHook!!.accept(MessagingException("Track `" + TextUtils.escapeAndDefuse(track.info.title) + "` failed to load. Skipping...")) - audioTrackProvider.skipped() + queueHandler.onSkipped() loadAndPlay() } else { log.warn("Track " + track.identifier + " ended with unexpected reason: " + endReason) @@ -378,7 +382,7 @@ class GuildPlayer( private fun loadAndPlay() { log.trace("loadAndPlay()") - val atc = audioTrackProvider.provideAudioTrack() + val atc = queueHandler.take() lastLoadedTrack = atc atc?.let { playTrack(it) } updateClients() diff --git a/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt b/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt index ef3a0768b..7294c8b0c 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt @@ -2,6 +2,7 @@ package fredboat.audio.player import com.google.common.collect.Lists import fredboat.audio.queue.AudioTrackContext +import fredboat.audio.queue.tbd.IQueueHandler import fredboat.commandmeta.MessagingException import fredboat.commandmeta.abs.CommandContext import fredboat.definitions.PermissionLevel @@ -22,7 +23,7 @@ private val log: Logger = LoggerFactory.getLogger(GuildPlayer::class.java) val GuildPlayer.trackCount: Int get() { - var trackCount = audioTrackProvider.size() + var trackCount = queueHandler.size if (player.playingTrack != null) trackCount++ return trackCount } @@ -30,7 +31,7 @@ val GuildPlayer.trackCount: Int /** Live streams are considered to have a length of 0 */ val GuildPlayer.totalRemainingMusicTimeMillis: Long get() { - var millis = audioTrackProvider.durationMillis + var millis = queueHandler.totalDuration val currentTrack = if (player.playingTrack != null) internalContext else null if (currentTrack != null && !currentTrack.track.info.isStream) { @@ -41,7 +42,7 @@ val GuildPlayer.totalRemainingMusicTimeMillis: Long val GuildPlayer.streamsCount: Long get() { - var streams = audioTrackProvider.streamsCount().toLong() + var streams = queueHandler.streamCount.toLong() val atc = if (player.playingTrack != null) internalContext else null if (atc != null && atc.track.info.isStream) streams++ return streams @@ -60,7 +61,7 @@ val GuildPlayer.isQueueEmpty: Boolean get() { log.trace("isQueueEmpty()") - return player.playingTrack == null && audioTrackProvider.isEmpty + return player.playingTrack == null && queueHandler.isEmpty } val GuildPlayer.trackCountInHistory: Int @@ -149,7 +150,7 @@ fun GuildPlayer.getTracksInRange(start: Int, end: Int): List //nothing to do here, args are fine to pass on } - result.addAll(audioTrackProvider.getTracksInRange(start_, end_)) + result.addAll((queueHandler as IQueueHandler).getInRange(start_, end_)) return result } @@ -184,7 +185,7 @@ private suspend fun GuildPlayer.canMemberSkipTracks(member: Member, trackIds: Co currentTrackSkippable = false } - return if (currentTrackSkippable && audioTrackProvider.isUserTrackOwner(userId, trackIds)) { //check ownership of the queued tracks + return if (currentTrackSkippable && queueHandler.isUserTrackOwner(userId, trackIds)) { //check ownership of the queued tracks ImmutablePair(true, null) } else { ImmutablePair(false, I18n.get(guild).getString("skipDeniedTooManyTracks")) diff --git a/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt b/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt index 7911b4a1b..acef197c6 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt @@ -33,6 +33,7 @@ import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist import com.sedmelluq.discord.lavaplayer.track.AudioTrack import fredboat.audio.player.GuildPlayer import fredboat.audio.player.trackCount +import fredboat.audio.queue.tbd.IQueueHandler import fredboat.audio.source.PlaylistImportSourceManager import fredboat.audio.source.PlaylistImporter import fredboat.audio.source.SpotifyPlaylistSourceManager @@ -50,7 +51,7 @@ import java.util.* import java.util.concurrent.ConcurrentLinkedQueue import java.util.regex.Pattern -class AudioLoader(private val ratelimiter: Ratelimiter, internal val trackProvider: ITrackProvider, +class AudioLoader(private val ratelimiter: Ratelimiter, internal val queueHandler: IQueueHandler, private val playerManager: AudioPlayerManager, internal val gplayer: GuildPlayer, internal val youtubeAPI: YoutubeAPI) { private val identifierQueue = ConcurrentLinkedQueue() @@ -208,7 +209,7 @@ private class ResultHandler(val loader: AudioLoader, val context: IdentifierCont at.position = context.position val atc = AudioTrackContext(at, context.member, context.isPriority) - if (context.isPriority) loader.trackProvider.addFirst(atc) else loader.trackProvider.add(atc) + loader.queueHandler.add(atc) if (!loader.gplayer.isPaused) { loader.gplayer.play() @@ -234,7 +235,7 @@ private class ResultHandler(val loader: AudioLoader, val context: IdentifierCont for (at in ap.tracks) { toAdd.add(AudioTrackContext(at, context.member, context.isPriority)) } - if (context.isPriority) loader.trackProvider.addAllFirst(toAdd) else loader.trackProvider.addAll(toAdd) + loader.queueHandler.addAll(toAdd) context.reply(context.i18nFormat("loadListSuccess", ap.tracks.size, ap.name)) if (!loader.gplayer.isPaused) { loader.gplayer.play() diff --git a/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.kt index bab36be5f..2e42cbeb0 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.kt @@ -115,7 +115,7 @@ class SelectCommand(private val videoSelectionCache: VideoSelectionCache, name: } outputMsgBuilder.append(msg) - player.queue(AudioTrackContext(selectedTracks[i]!!, invoker, selection.isPriority), selection.isPriority) + player.queue(AudioTrackContext(selectedTracks[i]!!, invoker, selection.isPriority)) } videoSelectionCache.remove(invoker) From 0dae434b2d888df625a8aab0bc937ea8ff4c14af Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 3 Mar 2019 13:57:48 +0100 Subject: [PATCH 132/172] Fix raising onListRemove event when no tracks need to be removed --- FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index 1ec57d433..f344b7d21 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -256,8 +256,7 @@ class GuildPlayer( } } - audioTrackProvider.removeAllById(toRemove) - + if (toRemove.size > 0) queueHandler.removeById(toRemove) if (skipCurrentTrack) skip() } From 9fe362782fb9c2f074efab0979ee83ac4037366e Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 3 Mar 2019 14:04:01 +0100 Subject: [PATCH 133/172] Rename guildPlayerUtils#userTackCount to getUserTrackCount --- .../src/main/java/fredboat/audio/player/guildPlayerUtils.kt | 2 +- .../main/java/fredboat/audio/queue/limiter/QueueLimiter.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt b/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt index b2c3f9d67..a76e9449d 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt @@ -69,7 +69,7 @@ val GuildPlayer.trackCountInHistory: Int val GuildPlayer.isHistoryQueueEmpty: Boolean get() = historyQueue.isEmpty() -fun GuildPlayer.userTackCount(userId: Long): Int { +fun GuildPlayer.getUserTrackCount(userId: Long): Int { var trackCount = audioTrackProvider.size(userId) if (player.playingTrack != null && internalContext?.userId == userId) trackCount++ return trackCount diff --git a/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimiter.kt b/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimiter.kt index 790a1581d..16c182161 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimiter.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimiter.kt @@ -1,8 +1,8 @@ package fredboat.audio.queue.limiter import fredboat.audio.player.GuildPlayer +import fredboat.audio.player.getUserTrackCount import fredboat.audio.player.trackCount -import fredboat.audio.player.userTackCount import fredboat.audio.queue.AudioPlaylistContext import fredboat.audio.queue.AudioTrackContext import fredboat.db.api.GuildSettingsRepository @@ -21,7 +21,7 @@ class QueueLimiter(repository: GuildSettingsRepository) { })) queueLimits.add(QueueLimit("UserTrackLimit", { atc, player, preemptive -> - repository.fetch(atc.guildId).map { QueueLimitStatus(it.userMaxTrackCount == null || it.userMaxTrackCount!! > player.userTackCount(atc.userId) + preemptive, atc) } + repository.fetch(atc.guildId).map { QueueLimitStatus(it.userMaxTrackCount == null || it.userMaxTrackCount!! > player.getUserTrackCount(atc.userId) + preemptive, atc) } }, QueueLimiterEnum.USER_TRACK_LIMIT_EXCEEDED, { context, status -> repository.fetch(context.guild.id).map { context.i18nFormat(status.i18n, it.userMaxTrackCount ?: "UNLIMITED") } })) From 34eeabcd436715fb72d89acaba541e86432d32e7 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 3 Mar 2019 14:04:35 +0100 Subject: [PATCH 134/172] Rename ITrackProvider#size(Long) to getCountByUser(Long) --- .../src/main/java/fredboat/audio/player/guildPlayerUtils.kt | 2 +- FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt | 2 +- .../src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt b/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt index a76e9449d..c76e79428 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt @@ -70,7 +70,7 @@ val GuildPlayer.isHistoryQueueEmpty: Boolean get() = historyQueue.isEmpty() fun GuildPlayer.getUserTrackCount(userId: Long): Int { - var trackCount = audioTrackProvider.size(userId) + var trackCount = audioTrackProvider.getCountByUser(userId) if (player.playingTrack != null && internalContext?.userId == userId) trackCount++ return trackCount } diff --git a/FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt b/FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt index 1bfd69443..fd212ea0c 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt @@ -72,7 +72,7 @@ interface ITrackProvider { /** * @return amount of tracks in the queue filtered by userId */ - fun size(userId: Long): Int + fun getCountByUser(userId: Long): Int /** * @param track add a track to the queue diff --git a/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt b/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt index abe422794..774fb0750 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt @@ -187,7 +187,7 @@ class SimpleTrackProvider : AbstractTrackProvider() { return queue.size } - override fun size(userId: Long): Int { + override fun getCountByUser(userId: Long): Int { return queue.filter { it.userId == userId }.size } From d07d16cde8e725527d75dec6c1c4c2a25fdbdb72 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 3 Mar 2019 14:44:05 +0100 Subject: [PATCH 135/172] Update translations with the help of @OtterBoops --- FredBoat/src/main/resources/lang/en_US.properties | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/FredBoat/src/main/resources/lang/en_US.properties b/FredBoat/src/main/resources/lang/en_US.properties index 72ab66328..a473c5ac4 100644 --- a/FredBoat/src/main/resources/lang/en_US.properties +++ b/FredBoat/src/main/resources/lang/en_US.properties @@ -91,10 +91,10 @@ loadPlaylistTooMany=Added {0} tracks. Found too many tracks to display. loadErrorCommon=Error occurred when loading info for `{0}`\:\n{1} loadErrorSusp=Suspicious error when loading info for `{0}`. loadQueueTrackLimit=You can't add tracks to a queue with more than {0} tracks! This is to prevent abuse. -loadPlaylistDisabled=TODO loadPlaylistDisabled -loadMaxTracksExceeded=TODO loadMaxTracksExceeded -loadMaxUserTracksExceeded=TODO loadMaxUserTracksExceeded -loadMaxTrackLengthExceeded=TODO loadMaxTrackLengthExcceded +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=The maximum number of {0} tracks has been reached for this server. +loadMaxUserTracksExceeded=You have been limited to a maximum of {0} tracks! Please wait before trying to add any more. +loadMaxTrackLengthExceeded=This track exceeds the maximum length of {0}. Please try something shorter. loadPlaylistGeneralError={0} tracks have not been added due to queue limitations! loadAnnouncePlaylist=About to load playlist **{0}** with up to `{1}` tracks. This may take a while, please be patient. playerUserNotInChannel=You must join a voice channel first. From 9aed2c5f09dd5e8d4c2b8277597b896199677602 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 3 Mar 2019 15:03:29 +0100 Subject: [PATCH 136/172] sugar syntax --- .../src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt b/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt index 774fb0750..df7a6b417 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt @@ -188,7 +188,7 @@ class SimpleTrackProvider : AbstractTrackProvider() { } override fun getCountByUser(userId: Long): Int { - return queue.filter { it.userId == userId }.size + return queue.count { it.userId == userId } } override fun add(track: AudioTrackContext) { From 296c744c60b0592c96f2a53e43f0bc745e4e0ba0 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 3 Mar 2019 19:10:12 +0100 Subject: [PATCH 137/172] make config command modular and add player limit options --- .../fredboat/command/config/ConfigCommand.kt | 91 ++++++++++++++----- 1 file changed, 68 insertions(+), 23 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt b/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt index 3705bc2ed..33d237e4f 100644 --- a/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt @@ -31,17 +31,52 @@ import fredboat.commandmeta.abs.CommandContext import fredboat.commandmeta.abs.ICommandRestricted import fredboat.commandmeta.abs.IConfigCommand import fredboat.db.api.GuildSettingsRepository +import fredboat.db.transfer.GuildSettings import fredboat.definitions.PermissionLevel import fredboat.messaging.internal.Context import fredboat.perms.PermsUtil -import fredboat.util.extension.escapeAndDefuse +import fredboat.util.TextUtils import fredboat.util.localMessageBuilder +import org.apache.commons.lang3.StringUtils +import java.util.function.Predicate class ConfigCommand(name: String, private val repo: GuildSettingsRepository, vararg aliases: String) : Command(name, *aliases), IConfigCommand, ICommandRestricted { override val minimumPerms: PermissionLevel get() = PermissionLevel.BASE + private val configOptions: MutableList = mutableListOf() + + init { + val booleanPredicate = Predicate { it.equals("true", true) || it.equals("false", true) } + val nullableIntPredicate = Predicate { (it.toIntOrNull() != null && it.toInt() > -1) || StringUtils.equalsAnyIgnoreCase(it, "none", "unlimited") } + val nullableLongPredicate = Predicate { StringUtils.equalsAnyIgnoreCase(it, "none", "unlimited") || TextUtils.parseTimeString(it) != 0L } + + configOptions.add(ConfigOption("track_announce", booleanPredicate, + { it.trackAnnounce.toString() }, { gs, value -> gs.trackAnnounce = value.toBoolean() })) + + configOptions.add(ConfigOption("auto_resume", booleanPredicate, + { it.trackAnnounce.toString() }, { gs, value -> gs.trackAnnounce = value.toBoolean() })) + + configOptions.add(ConfigOption("allow_playlist", booleanPredicate, + { it.allowPlaylist.toString() }, { gs, v -> gs.allowPlaylist = v.toBoolean() })) + + configOptions.add(ConfigOption("max_tracks", nullableIntPredicate, + { it.maxTrackCount?.toString() ?: "UNLIMITED" }, { gs, v -> + gs.maxTrackCount = if (v.equals("unlimited", true)) null else v.toInt() + })) + + configOptions.add(ConfigOption("max_user_tracks", nullableIntPredicate, + { it.userMaxTrackCount?.toString() ?: "UNLIMITED" }, { gs, v -> + gs.userMaxTrackCount = if (StringUtils.equalsAnyIgnoreCase(v, "none", "unlimited")) null else v.toInt() + })) + + configOptions.add(ConfigOption("max_track_length", nullableLongPredicate, + { if (it.maxTrackLength != null) TextUtils.formatTime(it.maxTrackLength!!) else "UNLIMITED" }, { gs, v -> + gs.maxTrackLength = if (StringUtils.equalsAnyIgnoreCase(v, "none", "unlimited")) null else TextUtils.parseTimeString(v) + })) + } + override suspend fun invoke(context: CommandContext) { if (!context.hasArguments()) { printConfig(context) @@ -52,16 +87,17 @@ class ConfigCommand(name: String, private val repo: GuildSettingsRepository, var private fun printConfig(context: CommandContext) { repo.fetch(context.guild.id).subscribe { - context.reply(localMessageBuilder() - .append(context.i18nFormat("configNoArgs", context.guild.name)).append("\n") - .append("track_announce = ${it.trackAnnounce}\n") - .append("auto_resume = ${it.autoResume}\n") - .append("```").build()) //opening ``` is part of the configNoArgs language string + val mb = localMessageBuilder().append(context.i18nFormat("configNoArgs", context.guild.name)).append("\n") + + for (config in configOptions) { + mb.append("${config.name} = ").append(config.getter(it)).append("\n") + } + + context.reply(mb.append("```").build()) } } private suspend fun setConfig(context: CommandContext) { - val invoker = context.member if (!PermsUtil.checkPermsWithFeedback(PermissionLevel.ADMIN, context)) { return } @@ -74,26 +110,17 @@ class ConfigCommand(name: String, private val repo: GuildSettingsRepository, var val key = context.args[0] val value = context.args[1] - if (!(value.equals("true", ignoreCase = true) or value.equals("false", ignoreCase = true))) { - context.reply(context.i18nFormat("configMustBeBoolean", invoker.effectiveName.escapeAndDefuse())) + val config = configOptions.firstOrNull { it.name == key } + if (config == null) { + context.replyWithName(context.i18n("configUnknownKey")) return } - when (key) { - "track_announce" -> { - repo.fetch(context.guild.id) - .doOnSuccess { it.trackAnnounce = value.toBoolean() } - .let { repo.update(it) } - .subscribe { context.replyWithName("`track_announce` " + context.i18nFormat("configSetTo", value)) } - } - "auto_resume" -> { - repo.fetch(context.guild.id) - .doOnSuccess { it.autoResume = value.toBoolean() } - .let { repo.update(it) } - .subscribe { context.replyWithName("`auto_resume` " + context.i18nFormat("configSetTo", value)) } - } - else -> context.reply(context.i18nFormat("configUnknownKey", invoker.effectiveName.escapeAndDefuse())) + if (!config.valueFormat.test(value)) { + context.replyWithName(context.i18n("configValueTypeInvalid")) } + + config.update(repo, context, value) } override fun help(context: Context): String { @@ -101,3 +128,21 @@ class ConfigCommand(name: String, private val repo: GuildSettingsRepository, var return usage + context.i18n("helpConfigCommand") } } + +private data class ConfigOption( + val name: String, + val valueFormat: Predicate, + val getter: (GuildSettings) -> String, + val setter: (GuildSettings, String) -> Unit +) { + fun update(repo: GuildSettingsRepository, context: CommandContext, value: String) { + repo.fetch(context.guild.id) + .doOnSuccess { setter(it, value) } + .let { repo.update(it) } + .subscribe { context.replyWithName("`$name` ${context.i18nFormat("configSetTo", value)}") } + } + + override fun toString(): String { + return name + } +} From bf66a10642514f076475d2167ccaa658489ee7c6 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 3 Mar 2019 19:20:14 +0100 Subject: [PATCH 138/172] Add roundRobin field to GuildPlayer --- .../main/java/fredboat/audio/player/GuildPlayer.kt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index f344b7d21..eef0a227d 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -32,10 +32,7 @@ import com.sedmelluq.discord.lavaplayer.track.TrackMarker import fredboat.audio.lavalink.SentinelLavalink import fredboat.audio.lavalink.SentinelLink import fredboat.audio.queue.* -import fredboat.audio.queue.tbd.IQueueHandler -import fredboat.audio.queue.tbd.IRepeatableQueueHandler -import fredboat.audio.queue.tbd.IShufflableQueueHandler -import fredboat.audio.queue.tbd.RepeatableQueueHandler +import fredboat.audio.queue.tbd.* import fredboat.command.music.control.VoteSkipCommand import fredboat.commandmeta.MessagingException import fredboat.commandmeta.abs.CommandContext @@ -110,6 +107,14 @@ class GuildPlayer( throw UnsupportedOperationException("Can't repeat " + queueHandler.javaClass) } + var roundRobin : Boolean + get() = queueHandler is IRoundRobinQueueHandler && queueHandler.roundRobin + set(value) = if (queueHandler is IRoundRobinQueueHandler) { + queueHandler.roundRobin = value + } else { + throw UnsupportedOperationException("Can't round robin " + queueHandler.javaClass) + } + var isShuffle: Boolean get() = queueHandler is IShufflableQueueHandler && queueHandler.shuffle set(shuffle) = if (queueHandler is IShufflableQueueHandler) { From 0384c2f5bd7be13123176ab87232bd4568669f0d Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 3 Mar 2019 19:20:34 +0100 Subject: [PATCH 139/172] Add persistence for roundRobin --- .../src/main/java/fredboat/audio/player/PlayerRegistry.kt | 1 + FredBoat/src/main/java/fredboat/db/mongo/player.kt | 1 + FredBoat/src/main/java/fredboat/db/mongo/repositories.kt | 6 +++++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index 77faf9df7..aedc84b81 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -193,6 +193,7 @@ class PlayerRegistry( val guild = player.guild player.setPause(mongo.paused) player.isShuffle = mongo.shuffled + player.roundRobin = mongo.roundRobin player.repeatMode = RepeatMode.values()[mongo.repeat.toInt()] if (appConfig.distribution.volumeSupported()) { diff --git a/FredBoat/src/main/java/fredboat/db/mongo/player.kt b/FredBoat/src/main/java/fredboat/db/mongo/player.kt index ffc1f99fa..f0363587f 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/player.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/player.kt @@ -14,6 +14,7 @@ class MongoPlayer( val playing: Boolean, val paused: Boolean, val shuffled: Boolean, + val roundRobin: Boolean, /** [fredboat.definitions.RepeatMode] ordinal */ val repeat: Byte, /** 0-1 */ diff --git a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt index 57309c385..704c3a76e 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt @@ -3,7 +3,10 @@ package fredboat.db.mongo import fredboat.audio.player.GuildPlayer import fredboat.audio.player.voiceChannel import fredboat.audio.queue.SplitAudioTrackContext -import fredboat.db.transfer.* +import fredboat.db.transfer.BlacklistEntity +import fredboat.db.transfer.GuildSettings +import fredboat.db.transfer.SearchResult +import fredboat.db.transfer.SearchResultId import lavalink.client.LavalinkUtil import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -42,6 +45,7 @@ private fun GuildPlayer.toMongo() = MongoPlayer( isPlaying, isPaused, isShuffle, + roundRobin, repeatMode.ordinal.toByte(), volume, playingTrack?.track?.position, From 330c7e900751669bb8aaaa16db024286dedb6447 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 3 Mar 2019 19:41:37 +0100 Subject: [PATCH 140/172] add is prefix to roundRobin field --- FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt | 2 +- FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt | 2 +- FredBoat/src/main/java/fredboat/db/mongo/repositories.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index eef0a227d..8e2af97dc 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -107,7 +107,7 @@ class GuildPlayer( throw UnsupportedOperationException("Can't repeat " + queueHandler.javaClass) } - var roundRobin : Boolean + var isRoundRobin : Boolean get() = queueHandler is IRoundRobinQueueHandler && queueHandler.roundRobin set(value) = if (queueHandler is IRoundRobinQueueHandler) { queueHandler.roundRobin = value diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index aedc84b81..40b45943d 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -193,7 +193,7 @@ class PlayerRegistry( val guild = player.guild player.setPause(mongo.paused) player.isShuffle = mongo.shuffled - player.roundRobin = mongo.roundRobin + player.isRoundRobin = mongo.roundRobin player.repeatMode = RepeatMode.values()[mongo.repeat.toInt()] if (appConfig.distribution.volumeSupported()) { diff --git a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt index 704c3a76e..98a6f5a55 100644 --- a/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt +++ b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt @@ -45,7 +45,7 @@ private fun GuildPlayer.toMongo() = MongoPlayer( isPlaying, isPaused, isShuffle, - roundRobin, + isRoundRobin, repeatMode.ordinal.toByte(), volume, playingTrack?.track?.position, From 267b2f3f7bbb419143e878cbc8b8d3bdd21203cf Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 3 Mar 2019 19:42:19 +0100 Subject: [PATCH 141/172] Add RoundRobinCommand --- .../music/control/RoundRobbinCommand.kt | 25 +++++++++++++++++++ .../commandmeta/CommandInitializer.kt | 1 + .../src/main/resources/lang/en_US.properties | 3 +++ 3 files changed, 29 insertions(+) create mode 100644 FredBoat/src/main/java/fredboat/command/music/control/RoundRobbinCommand.kt diff --git a/FredBoat/src/main/java/fredboat/command/music/control/RoundRobbinCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/RoundRobbinCommand.kt new file mode 100644 index 000000000..0bcf9bb16 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/command/music/control/RoundRobbinCommand.kt @@ -0,0 +1,25 @@ +package fredboat.command.music.control + +import fredboat.commandmeta.abs.Command +import fredboat.commandmeta.abs.CommandContext +import fredboat.commandmeta.abs.ICommandRestricted +import fredboat.commandmeta.abs.IMusicCommand +import fredboat.definitions.PermissionLevel +import fredboat.main.getBotController +import fredboat.messaging.internal.Context + +class RoundRobbinCommand(name: String, vararg aliases: String) : Command(name, *aliases), IMusicCommand, ICommandRestricted { + + override val minimumPerms: PermissionLevel = PermissionLevel.DJ + + override suspend fun invoke(context: CommandContext) { + val player = getBotController().playerRegistry.awaitPlayer(context.guild) + player.isRoundRobin = !player.isRoundRobin + + context.reply(if (player.isRoundRobin) "roundRobinOn" else "roundRobinOff") + } + + override fun help(context: Context): String { + return "{0}{1}\n# " + context.i18n("helpRoundRobinCommand") + } +} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt b/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt index b9bb2a6a5..205fc9400 100644 --- a/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt +++ b/FredBoat/src/main/java/fredboat/commandmeta/CommandInitializer.kt @@ -258,6 +258,7 @@ class CommandInitializer( musicModule.registerCommand(SelectCommand(videoSelectionCache, "select", *buildNumericalSelectAliases("sel"))) musicModule.registerCommand(ShuffleCommand("shuffle", "sh", "random")) + musicModule.registerCommand(RoundRobbinCommand("roundrobin", "rr")) musicModule.registerCommand(SkipCommand(SKIP_COMM_NAME, "sk", "s")) musicModule.registerCommand(StopCommand("stop", "st")) musicModule.registerCommand(UnpauseCommand("unpause", "unp", "resume")) diff --git a/FredBoat/src/main/resources/lang/en_US.properties b/FredBoat/src/main/resources/lang/en_US.properties index e57d882c4..fba5c9650 100644 --- a/FredBoat/src/main/resources/lang/en_US.properties +++ b/FredBoat/src/main/resources/lang/en_US.properties @@ -21,6 +21,9 @@ shuffleOn=The player is now shuffled. shuffleOff=The player is no longer shuffled. reshufflePlaylist=Queue reshuffled. reshufflePlayerNotShuffling=You must first turn on shuffle mode. +roundRobinOn=The player is now in Round-Robin mode. +roundRobinOff=The player is no longer in Round-Robin mode. +listShowRoundRobin=Showing round robin playlist. skipEmpty=The queue is empty\! skipOutOfBounds=Can''t remove track number {0} when there are only {1} tracks. skipNumberTooLow=Given number must be greater than 0. From 91614bff4b3513b198e6e0d76b72b7a4724d3281 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 3 Mar 2019 19:42:35 +0100 Subject: [PATCH 142/172] Update ListCommand to show roundRobin State --- .../src/main/java/fredboat/command/music/info/ListCommand.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FredBoat/src/main/java/fredboat/command/music/info/ListCommand.kt b/FredBoat/src/main/java/fredboat/command/music/info/ListCommand.kt index 2c0fa1e92..863ed9ba6 100644 --- a/FredBoat/src/main/java/fredboat/command/music/info/ListCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/info/ListCommand.kt @@ -82,6 +82,10 @@ class ListCommand(name: String, vararg aliases: String) : JCommand(name, *aliase if (player.repeatMode == RepeatMode.OFF) mb.append("\n") } + + if (player.isRoundRobin) + mb.append(context.i18n("listShowRoundRobin")).append("\n") + if (player.repeatMode == RepeatMode.SINGLE) { mb.append(context.i18n("listShowRepeatSingle")) mb.append("\n") From 4e948fbfe02529f5ede2a498442a0f0564962579 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 3 Mar 2019 19:43:56 +0100 Subject: [PATCH 143/172] Set internal default RoundRobin setting to false --- .../java/fredboat/audio/queue/tbd/RoundRobinQueueHandler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FredBoat/src/main/java/fredboat/audio/queue/tbd/RoundRobinQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/tbd/RoundRobinQueueHandler.kt index a35c0b0d4..adf06891c 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/tbd/RoundRobinQueueHandler.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/tbd/RoundRobinQueueHandler.kt @@ -17,7 +17,7 @@ open class RoundRobinQueueHandler(private val player: GuildPlayer) : ShufflableQ _roundRobinQueue = value } - private var _roundRobin: Boolean = true + private var _roundRobin: Boolean = false override var roundRobin: Boolean get() = _roundRobin set(value) { From de608b2bfdcc4a890a0bccf3583345d5e0efd243 Mon Sep 17 00:00:00 2001 From: "Frederik Ar. Mikkelsen" Date: Fri, 8 Mar 2019 18:18:52 +0100 Subject: [PATCH 144/172] Simplify code a bit --- .../main/java/fredboat/command/music/info/NowplayingCommand.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/command/music/info/NowplayingCommand.kt b/FredBoat/src/main/java/fredboat/command/music/info/NowplayingCommand.kt index 0025df1b7..4484de7ad 100644 --- a/FredBoat/src/main/java/fredboat/command/music/info/NowplayingCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/info/NowplayingCommand.kt @@ -71,8 +71,7 @@ class NowplayingCommand(private val youtubeAPI: YoutubeAPI, name: String, vararg else -> getDefaultEmbed(atc, player, at) } if (embed.footer == null) embed.footer { - var user = atc.member.effectiveName - text = context.i18nFormat("npRequestedBy", user) + text = context.i18nFormat("npRequestedBy", atc.member.effectiveName) iconUrl = atc.member.info.awaitSingle().iconUrl } From a1e7195fb32c2d55b588656b961a3e46a32f4c0b Mon Sep 17 00:00:00 2001 From: Nanabell Date: Fri, 8 Mar 2019 23:23:05 +0100 Subject: [PATCH 145/172] Fredboat-566 Revert "Make AudioTrackContext inherit from NullableContext" --- .../fredboat/audio/queue/AudioTrackContext.kt | 83 ++++++++-- .../fredboat/messaging/internal/Context.kt | 144 ++++++++++++++++-- .../messaging/internal/NullableContext.kt | 136 ----------------- 3 files changed, 204 insertions(+), 159 deletions(-) delete mode 100644 FredBoat/src/main/java/fredboat/messaging/internal/NullableContext.kt diff --git a/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt b/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt index 000351ecb..5a51be130 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt @@ -28,30 +28,37 @@ package fredboat.audio.queue import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioTrack import com.sedmelluq.discord.lavaplayer.track.AudioTrack import fredboat.audio.player.GuildPlayer +import fredboat.commandmeta.MessagingException +import fredboat.feature.I18n import fredboat.main.Launcher -import fredboat.messaging.internal.NullableContext import fredboat.sentinel.Guild import fredboat.sentinel.Member import fredboat.sentinel.TextChannel import fredboat.sentinel.User import org.bson.types.ObjectId +import org.slf4j.LoggerFactory +import java.text.MessageFormat +import java.util.* import java.util.concurrent.ThreadLocalRandom +import javax.annotation.CheckReturnValue open class AudioTrackContext( val track: AudioTrack, - override val member: Member, + val member: Member, priority: Boolean = false -) : NullableContext(), Comparable { +) : Comparable { + + private var i18n: ResourceBundle? = null val trackId: ObjectId // used to identify this track even when the track gets cloned and the rand reranded var rand: Int var isPriority: Boolean = priority val added: Long = System.currentTimeMillis() - override val guild: Guild + val guild: Guild get() = member.guild - override val user: User + val user: User get() = member.user val guildId: Long @@ -70,17 +77,18 @@ open class AudioTrackContext( get() = 0 //return the currently active text channel of the associated guildplayer - override val textChannel: TextChannel? + val textChannel: TextChannel? get() { val guildPlayer = Launcher.botController.playerRegistry.getExisting(guildId) return guildPlayer?.activeTextChannel } - val thumbnailUrl: String? get() { - return if (track is YoutubeAudioTrack) { - "https://img.youtube.com/vi/${track.info.identifier}/mqdefault.jpg" - } else null - } + val thumbnailUrl: String? + get() { + return if (track is YoutubeAudioTrack) { + "https://img.youtube.com/vi/${track.info.identifier}/mqdefault.jpg" + } else null + } init { this.rand = if (!priority) ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE) else Integer.MIN_VALUE @@ -102,6 +110,55 @@ open class AudioTrackContext( return guildPlayer.position } + /** + * Return a single translated string. + * + * @param key Key of the i18n string. + * @return Formatted i18n string, or a default language string if i18n is not found. + */ + @CheckReturnValue + fun i18n(key: String): String { + return if (getI18n().containsKey(key)) { + getI18n().getString(key) + } else { + log.warn("Missing language entry for key {} in language {}", key, I18n.getLocale(guild).code) + I18n.DEFAULT.props.getString(key) + } + } + + /** + * Return a translated string with applied formatting. + * + * @param key Key of the i18n string. + * @param params Parameter(s) to be apply into the i18n string. + * @return Formatted i18n string. + */ + @CheckReturnValue + fun i18nFormat(key: String, vararg params: Any): String { + if (params.isEmpty()) { + log.warn("Context#i18nFormat() called with empty or null params, this is likely a bug.", + MessagingException("a stack trace to help find the source")) + } + return try { + MessageFormat.format(this.i18n(key), *params) + } catch (e: IllegalArgumentException) { + log.warn("Failed to format key '{}' for language '{}' with following parameters: {}", + key, getI18n().baseBundleName, params, e) + //fall back to default props + MessageFormat.format(I18n.DEFAULT.props.getString(key), *params) + } + + } + + private fun getI18n(): ResourceBundle { + var result = i18n + if (result == null) { + result = I18n.get(guild) + i18n = result + } + return result + } + override fun compareTo(other: AudioTrackContext): Int { return Integer.compare(rand, other.rand) } @@ -123,4 +180,8 @@ open class AudioTrackContext( result = 31 * result + trackId.hashCode() return result } + + companion object { + private val log = LoggerFactory.getLogger(AudioTrackContext::class.java) + } } diff --git a/FredBoat/src/main/java/fredboat/messaging/internal/Context.kt b/FredBoat/src/main/java/fredboat/messaging/internal/Context.kt index 4a4fc85c5..94cbfad7a 100644 --- a/FredBoat/src/main/java/fredboat/messaging/internal/Context.kt +++ b/FredBoat/src/main/java/fredboat/messaging/internal/Context.kt @@ -27,26 +27,39 @@ package fredboat.messaging.internal import com.fredboat.sentinel.entities.Embed import com.fredboat.sentinel.entities.SendMessageResponse +import com.fredboat.sentinel.entities.embed import com.fredboat.sentinel.entities.passed import fredboat.command.config.PrefixCommand +import fredboat.commandmeta.MessagingException +import fredboat.feature.I18n import fredboat.perms.IPermissionSet import fredboat.perms.PermissionSet import fredboat.perms.PermsUtil import fredboat.sentinel.* +import fredboat.shared.constant.BotConstants +import fredboat.util.TextUtils import kotlinx.coroutines.reactive.awaitSingle +import org.slf4j.Logger +import org.slf4j.LoggerFactory import reactor.core.publisher.Mono +import java.text.MessageFormat +import java.util.* import javax.annotation.CheckReturnValue /** * Provides a context to whats going on. Where is it happening, who caused it? * Also home to a bunch of convenience methods */ -abstract class Context : NullableContext() { +abstract class Context { - abstract override val textChannel: TextChannel - abstract override val guild: Guild - abstract override val member: Member - abstract override val user: User + companion object { + private val log: Logger = LoggerFactory.getLogger(Context::class.java) + } + + abstract val textChannel: TextChannel + abstract val guild: Guild + abstract val member: Member + abstract val user: User /* Convenience properties */ val prefix: String get() = PrefixCommand.giefPrefix(guild) @@ -54,17 +67,70 @@ abstract class Context : NullableContext() { val sentinel: Sentinel get() = guild.sentinel val routingKey: String get() = guild.routingKey + // ******************************************************************************** + // Internal context stuff + // ******************************************************************************** + + private var i18n: ResourceBundle? = null + // ******************************************************************************** // Convenience reply methods // ******************************************************************************** - // We can be sure that nothing can be null in this context override the Mono methods for null safe access - override fun replyMono(message: String): Mono = super.replyMono(message)!! - override fun replyMono(embed: Embed): Mono = super.replyMono(embed)!! - override fun replyWithNameMono(message: String): Mono = super.replyWithNameMono(message)!! - override fun replyWithMentionMono(message: String): Mono = super.replyWithMentionMono(message)!! - override fun replyImageMono(url: String, message: String): Mono = super.replyImageMono(url, message)!! - override fun replyPrivateMono(message: String) = super.replyPrivateMono(message)!! + + fun replyMono(message: String): Mono = textChannel.send(message) + + fun reply(message: String) { + textChannel.send(message).subscribe() + } + + fun replyMono(message: Embed): Mono = textChannel.send(message) + + fun reply(message: Embed) { + textChannel.send(message).subscribe() + } + + fun replyWithNameMono(message: String): Mono { + return replyMono(TextUtils.prefaceWithName(member, message)) + } + + fun replyWithName(message: String) { + reply(TextUtils.prefaceWithName(member, message)) + } + + fun replyWithMentionMono(message: String): Mono { + return replyMono(TextUtils.prefaceWithMention(member, message)) + } + + fun replyWithMention(message: String) { + reply(TextUtils.prefaceWithMention(member, message)) + } + + fun replyImageMono(url: String, message: String = ""): Mono { + val embed = embedImage(url) + embed.description = message + return textChannel.send(embed) + } + + fun replyImage(url: String, message: String = "") { + replyImageMono(url, message).subscribe() + } + + fun sendTyping() { + textChannel.sendTyping() + } + + /* Private messages */ + /** + * Privately DM the invoker + */ + fun replyPrivateMono(message: String) = user.sendPrivate(message) + /** + * Privately DM the invoker + */ + fun replyPrivate(message: String) { + user.sendPrivate(message).subscribe() + } /** * Checks whether we have the provided permissions for the channel of this context @@ -124,5 +190,59 @@ abstract class Context : NullableContext() { return false } + /** + * Return a single translated string. + * + * @param key Key of the i18n string. + * @return Formatted i18n string, or a default language string if i18n is not found. + */ + @CheckReturnValue + fun i18n(key: String): String { + return if (getI18n().containsKey(key)) { + getI18n().getString(key) + } else { + log.warn("Missing language entry for key {} in language {}", key, I18n.getLocale(guild).code) + I18n.DEFAULT.props.getString(key) + } + } + + /** + * Return a translated string with applied formatting. + * + * @param key Key of the i18n string. + * @param params Parameter(s) to be apply into the i18n string. + * @return Formatted i18n string. + */ + @CheckReturnValue + fun i18nFormat(key: String, vararg params: Any): String { + if (params.isEmpty()) { + log.warn("Context#i18nFormat() called with empty or null params, this is likely a bug.", + MessagingException("a stack trace to help find the source")) + } + return try { + MessageFormat.format(this.i18n(key), *params) + } catch (e: IllegalArgumentException) { + log.warn("Failed to format key '{}' for language '{}' with following parameters: {}", + key, getI18n().baseBundleName, params, e) + //fall back to default props + MessageFormat.format(I18n.DEFAULT.props.getString(key), *params) + } + + } + + fun getI18n(): ResourceBundle { + var result = i18n + if (result == null) { + result = I18n.get(guild) + i18n = result + } + return result + } + + private fun embedImage(url: String): Embed = embed { + color = BotConstants.FREDBOAT_COLOR.rgb + image = url + } + suspend fun memberLevel() = PermsUtil.getPerms(member) } diff --git a/FredBoat/src/main/java/fredboat/messaging/internal/NullableContext.kt b/FredBoat/src/main/java/fredboat/messaging/internal/NullableContext.kt deleted file mode 100644 index 110ea8fe2..000000000 --- a/FredBoat/src/main/java/fredboat/messaging/internal/NullableContext.kt +++ /dev/null @@ -1,136 +0,0 @@ -package fredboat.messaging.internal - -import com.fredboat.sentinel.entities.Embed -import com.fredboat.sentinel.entities.SendMessageResponse -import com.fredboat.sentinel.entities.embed -import fredboat.commandmeta.MessagingException -import fredboat.feature.I18n -import fredboat.sentinel.Guild -import fredboat.sentinel.Member -import fredboat.sentinel.TextChannel -import fredboat.sentinel.User -import fredboat.shared.constant.BotConstants -import fredboat.util.TextUtils -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import reactor.core.publisher.Mono -import java.text.MessageFormat -import java.util.* -import javax.annotation.CheckReturnValue - -abstract class NullableContext { - - companion object { - private val log: Logger = LoggerFactory.getLogger(Context::class.java) - } - - /* Convenience properties */ - abstract val textChannel: TextChannel? - abstract val guild: Guild? - abstract val member: Member? - abstract val user: User? - - // ******************************************************************************** - // Internal context stuff - // ******************************************************************************** - - private var i18n: ResourceBundle? = null - - - // ******************************************************************************** - // Convenience reply methods - // ******************************************************************************** - - open fun replyMono(message: String): Mono? = textChannel?.send(message) - fun reply(message: String) { - replyMono(message)?.subscribe() - } - - open fun replyMono(embed: Embed): Mono? = textChannel?.send(embed) - fun reply(message: Embed) { - replyMono(message)?.subscribe() - } - - open fun replyWithNameMono(message: String): Mono? = replyMono(TextUtils.prefaceWithName(member, message)) - fun replyWithName(message: String) { - replyWithNameMono(message)?.subscribe() - } - - open fun replyWithMentionMono(message: String): Mono? = replyMono(TextUtils.prefaceWithMention(member, message)) - fun replyWithMention(message: String) { - replyWithMentionMono(message)?.subscribe() - } - - open fun replyImageMono(url: String, message: String = ""): Mono? { - val embed = embedImage(url) - embed.description = message - return textChannel?.send(embed) - } - - fun replyImage(url: String, message: String = "") { - replyImageMono(url, message)?.subscribe() - } - - fun sendTyping() { - textChannel?.sendTyping() - } - - open fun replyPrivateMono(message: String) = user?.sendPrivate(message) - fun replyPrivate(message: String) { - replyPrivateMono(message)?.subscribe() - } - - /** - * Return a single translated string. - * - * @param key Key of the i18n string. - * @return Formatted i18n string, or a default language string if i18n is not found. - */ - @CheckReturnValue - fun i18n(key: String): String { - return if (getI18n().containsKey(key)) { - getI18n().getString(key) - } else { - log.warn("Missing language entry for key {} in language {}", key, I18n.getLocale(guild).code) - I18n.DEFAULT.props.getString(key) - } - } - - /** - * Return a translated string with applied formatting. - * - * @param key Key of the i18n string. - * @param params Parameter(s) to be apply into the i18n string. - * @return Formatted i18n string. - */ - @CheckReturnValue - fun i18nFormat(key: String, vararg params: Any): String { - if (params.isEmpty()) { - log.warn("Context#i18nFormat() called with empty or null params, this is likely a bug.", - MessagingException("a stack trace to help find the source")) - } - return try { - MessageFormat.format(this.i18n(key), *params) - } catch (e: IllegalArgumentException) { - log.warn("Failed to format key '{}' for language '{}' with following parameters: {}", - key, getI18n().baseBundleName, params, e) - //fall back to default props - MessageFormat.format(I18n.DEFAULT.props.getString(key), *params) - } - - } - - fun getI18n(): ResourceBundle { - var result = i18n - if (result == null) { - result = I18n.get(guild) - i18n = result - } - return result - } - - private fun embedImage(url: String): Embed = embed { - color = BotConstants.FREDBOAT_COLOR.rgb - image = url - } -} \ No newline at end of file From 4ca312244d4204c3ff273a05f2c99122f8d4e8fc Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 10 Mar 2019 20:30:14 +0100 Subject: [PATCH 146/172] Only Limit non BOT_ADMINS with the new Unban & Softban --- .../fredboat/command/moderation/DiscordModerationCommand.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FredBoat/src/main/java/fredboat/command/moderation/DiscordModerationCommand.kt b/FredBoat/src/main/java/fredboat/command/moderation/DiscordModerationCommand.kt index fde5d490a..8cb3db09e 100644 --- a/FredBoat/src/main/java/fredboat/command/moderation/DiscordModerationCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/moderation/DiscordModerationCommand.kt @@ -272,7 +272,7 @@ abstract class DiscordModerationCommand protected constructor(name: String, vara } override suspend fun invoke(context: CommandContext) { - if (context.memberLevel() >= PermissionLevel.BOT_ADMIN && + if (context.memberLevel() < PermissionLevel.BOT_ADMIN && getBotController().appConfig.distribution != DistributionEnum.DEVELOPMENT) { if (this is UnbanCommand || this is SoftbanCommand) { context.reply("The unban and softban commands have been temporarily disabled. We have made significant" + From 661b6d3dd7da71f73f2cbdcc17af2aa5ba01ac21 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sat, 23 Mar 2019 10:08:30 +0100 Subject: [PATCH 147/172] Ensure that lastTrack cannot turn null after we do null check --- FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index 7a75fa4d9..4f01ccc5e 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -385,14 +385,15 @@ class GuildPlayer( } private fun updateHistoryQueue() { - if (lastLoadedTrack == null) { + val lastTrack = lastLoadedTrack + if (lastTrack == null) { log.warn("No lastLoadedTrack in $this after track end") return } if (historyQueue.size == MAX_HISTORY_SIZE) { historyQueue.poll() } - historyQueue.add(lastLoadedTrack) + historyQueue.add(lastTrack) } /** From 119f0f8b76e289d518ef6a6466934dfd88ee63fd Mon Sep 17 00:00:00 2001 From: Napster Date: Sat, 23 Mar 2019 23:12:38 +0100 Subject: [PATCH 148/172] Add basic auth to wastebin POSTs --- .../fredboat/config/property/Credentials.java | 4 ++++ .../config/property/CredentialsProperties.kt | 12 ++++++++++++ .../src/main/java/fredboat/util/TextUtils.java | 18 ++++++++++++++---- .../fredboat/testutil/config/MockConfig.kt | 4 ++++ 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/config/property/Credentials.java b/FredBoat/src/main/java/fredboat/config/property/Credentials.java index 34927e06c..18053bd8f 100644 --- a/FredBoat/src/main/java/fredboat/config/property/Credentials.java +++ b/FredBoat/src/main/java/fredboat/config/property/Credentials.java @@ -96,4 +96,8 @@ default String getRandomGoogleKey() { // ******************************************************************************** String getCarbonKey(); + + String getWastebinUser(); + + String getWastebinPass(); } diff --git a/FredBoat/src/main/java/fredboat/config/property/CredentialsProperties.kt b/FredBoat/src/main/java/fredboat/config/property/CredentialsProperties.kt index cce94c45f..963add19d 100644 --- a/FredBoat/src/main/java/fredboat/config/property/CredentialsProperties.kt +++ b/FredBoat/src/main/java/fredboat/config/property/CredentialsProperties.kt @@ -53,6 +53,8 @@ class CredentialsProperties : Credentials { private var openWeatherKey = "" private var sentryDsn = "" private var carbonKey = "" + private var wastebinUser = "" + private var wastebinPass = "" private var dikeUrl = "" override fun getBotToken(): String { @@ -89,6 +91,8 @@ class CredentialsProperties : Credentials { override fun getOpenWeatherKey() = openWeatherKey override fun getSentryDsn() = sentryDsn override fun getCarbonKey() = carbonKey + override fun getWastebinUser() = wastebinUser + override fun getWastebinPass() = wastebinPass fun setDiscordBotToken(discordBotToken: String) { this.discordBotToken = discordBotToken @@ -133,6 +137,14 @@ class CredentialsProperties : Credentials { this.carbonKey = carbonKey } + fun setWastebinUser(wastebinUser: String) { + this.wastebinUser = wastebinUser + } + + fun setWastebinPass(wastebinPass: String) { + this.wastebinPass = wastebinPass + } + fun setDikeUrl(dikeUrl: String) { this.dikeUrl = dikeUrl } diff --git a/FredBoat/src/main/java/fredboat/util/TextUtils.java b/FredBoat/src/main/java/fredboat/util/TextUtils.java index 4d1194702..e4b731207 100644 --- a/FredBoat/src/main/java/fredboat/util/TextUtils.java +++ b/FredBoat/src/main/java/fredboat/util/TextUtils.java @@ -31,6 +31,7 @@ import fredboat.commandmeta.MessagingException; import fredboat.feature.metrics.Metrics; import fredboat.main.BotController; +import fredboat.main.Launcher; import fredboat.messaging.internal.Context; import fredboat.sentinel.Member; import fredboat.sentinel.User; @@ -142,15 +143,24 @@ public static void handleException(String logMessage, Throwable e, Context conte context.replyWithMention(SORRY + "\n" + BotConstants.hangoutInvite); } - private static CompletionStage postToHasteBasedService(String baseUrl, String body) { - return BotController.Companion.getHTTP().post(baseUrl, body, "text/plain") - .enqueue() + private static CompletionStage postToHasteBasedService(String baseUrl, String body, + Optional user, Optional pass) { + + var request = BotController.Companion.getHTTP().post(baseUrl, body, "text/plain"); + + if (user.isPresent() && pass.isPresent()) { + request = request.basicAuth(user.get(), pass.get()); + } + + return request.enqueue() .asJson() .thenApply(json -> json.getString("key")); } private static CompletionStage postToWastebin(String body) { - return postToHasteBasedService("https://wastebin.party/documents", body); + var creds = Launcher.Companion.getBotController().getCredentials(); + return postToHasteBasedService("https://wastebin.party/documents", body, + Optional.of(creds.getWastebinUser()), Optional.of(creds.getWastebinPass())); } /** diff --git a/FredBoat/src/test/java/fredboat/testutil/config/MockConfig.kt b/FredBoat/src/test/java/fredboat/testutil/config/MockConfig.kt index c128f6557..5073d4f82 100644 --- a/FredBoat/src/test/java/fredboat/testutil/config/MockConfig.kt +++ b/FredBoat/src/test/java/fredboat/testutil/config/MockConfig.kt @@ -108,6 +108,10 @@ class MockConfig : AppConfig, AudioSourcesConfig, Credentials, EventLoggerConfig override fun getCarbonKey() = "" + override fun getWastebinUser() = "" + + override fun getWastebinPass() = "" + override fun getWebInfoBaseUrl() = "" override fun getQuarterdeck() = object : BackendConfig.Quarterdeck { From d247b0508b475e75c628be1d44739e657740ff98 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 31 Mar 2019 17:08:52 +0200 Subject: [PATCH 149/172] Update translations according to fred's suggestions --- FredBoat/src/main/resources/lang/en_US.properties | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/FredBoat/src/main/resources/lang/en_US.properties b/FredBoat/src/main/resources/lang/en_US.properties index a473c5ac4..f89aaffdf 100644 --- a/FredBoat/src/main/resources/lang/en_US.properties +++ b/FredBoat/src/main/resources/lang/en_US.properties @@ -92,10 +92,10 @@ loadErrorCommon=Error occurred when loading info for `{0}`\:\n{1} loadErrorSusp=Suspicious error when loading info for `{0}`. loadQueueTrackLimit=You can't add tracks to a queue with more than {0} tracks! This is to prevent abuse. loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. -loadMaxTracksExceeded=The maximum number of {0} tracks has been reached for this server. -loadMaxUserTracksExceeded=You have been limited to a maximum of {0} tracks! Please wait before trying to add any more. -loadMaxTrackLengthExceeded=This track exceeds the maximum length of {0}. Please try something shorter. -loadPlaylistGeneralError={0} tracks have not been added due to queue limitations! +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions! loadAnnouncePlaylist=About to load playlist **{0}** with up to `{1}` tracks. This may take a while, please be patient. playerUserNotInChannel=You must join a voice channel first. playerJoinConnectDenied=I am not permitted to connect to that voice channel. From fa79226a7a0b98ba7356d5e957bbae1772c4d89a Mon Sep 17 00:00:00 2001 From: Nanabell Date: Sun, 31 Mar 2019 17:14:09 +0200 Subject: [PATCH 150/172] Unify list priority queueing across all PR's --- .../main/java/fredboat/audio/player/GuildPlayer.kt | 6 +++--- .../java/fredboat/audio/player/PlayerRegistry.kt | 2 +- .../main/java/fredboat/audio/queue/ITrackProvider.kt | 2 +- .../java/fredboat/audio/queue/SimpleTrackProvider.kt | 12 +++++++----- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index 7c8d68ced..d1d5f05c9 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -224,8 +224,8 @@ class GuildPlayer( } /** Add a bunch of tracks to the track provider */ - fun queueAll(tracks: Collection, isPriority: Boolean) { - audioTrackProvider.addAll(tracks, isPriority) + fun queueAll(tracks: Collection) { + audioTrackProvider.addAll(tracks) } @CheckReturnValue @@ -243,7 +243,7 @@ class GuildPlayer( suspend fun queueLimited(tracks: List, isPriority: Boolean): List { val states = queueLimiter.isQueueLimited(tracks, this) - audioTrackProvider.addAll(states.filter { it.canQueue }.map { it.atc }, isPriority) + audioTrackProvider.addAll(states.filter { it.canQueue }.map { it.atc }) return states } diff --git a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index 1c44fa2fb..7588554e6 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -228,7 +228,7 @@ class PlayerRegistry( val channel = mongo.textChannel?.let { guild.getTextChannel(it) } if (channel != null) musicTextChannelProvider.setMusicChannel(channel) - player.queueAll(queue, false) // no Priority on reload + player.queueAll(queue) // no Priority on reload } private fun beforeShutdown() { diff --git a/FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt b/FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt index fd212ea0c..fd2ca6585 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt @@ -82,7 +82,7 @@ interface ITrackProvider { /** * @param tracks add several tracks to the queue */ - fun addAll(tracks: Collection, isPriority: Boolean) + fun addAll(tracks: Collection) /** * empty the queue diff --git a/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt b/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt index df7a6b417..0129fd42f 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/SimpleTrackProvider.kt @@ -200,13 +200,10 @@ class SimpleTrackProvider : AbstractTrackProvider() { queue.addFirst(track) } - override fun addAll(tracks: Collection, isPriority: Boolean) { + override fun addAll(tracks: Collection) { shouldUpdateShuffledQueue = true - if (!isPriority) - queue.addAll(tracks) - else - tracks.reversed().forEach { queue.addFirst(it) } + if (tracks.all { it.isPriority }) queue.addAllFirst(tracks) else queue.addAll(tracks) } override fun clear() { @@ -253,3 +250,8 @@ class SimpleTrackProvider : AbstractTrackProvider() { return true } } + +fun ConcurrentLinkedDeque.addAllFirst(tracks: Collection) { + tracks.reversed().forEach { addFirst(it) } +} + From eb91416dd2a29cdc0cf7cfd9952b0fedad0230f2 Mon Sep 17 00:00:00 2001 From: "Frederik Ar. Mikkelsen" Date: Mon, 1 Apr 2019 01:23:24 +0200 Subject: [PATCH 151/172] Make failed assertions easier to debug --- .../main/java/fredboat/audio/lavalink/SentinelLink.kt | 2 +- .../test/java/fredboat/testutil/sentinel/testDsl.kt | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt index 87902425e..b4daf24a2 100644 --- a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt +++ b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt @@ -33,7 +33,7 @@ class SentinelLink(val lavalink: SentinelLavalink, guildId: String) : Link(laval public override fun queueAudioDisconnect() = lavalink.sentinel.sendAndForget(routingKey, AudioQueueRequest(QUEUE_DISCONNECT, guildId.toLong())) - + fun connect(channel: VoiceChannel, skipIfSameChannel: Boolean = true) { if (channel.guild.id != guild) throw IllegalArgumentException("The provided VoiceChannel is not a part of the Guild that this AudioManager " + diff --git a/FredBoat/src/test/java/fredboat/testutil/sentinel/testDsl.kt b/FredBoat/src/test/java/fredboat/testutil/sentinel/testDsl.kt index d8ea8c64e..bef6fd9dc 100644 --- a/FredBoat/src/test/java/fredboat/testutil/sentinel/testDsl.kt +++ b/FredBoat/src/test/java/fredboat/testutil/sentinel/testDsl.kt @@ -70,22 +70,22 @@ class CommandTester(private val commandContextParser: CommandContextParser, temp } } -fun assertReply(testMsg: String = "Assert outgoing message {}", assertion: (String) -> Boolean) { +fun assertReply(testMsg: String = "Assert outgoing message to equal: »{}«", assertion: (String) -> Boolean) { val message = SentinelState.poll(SendMessageRequest::class.java) ?: throw TimeoutException("Command failed to send message") Assert.assertTrue(testMsg.replace("{}", message.toString()), assertion(message.message)) } -fun assertReply(expected: String, testMsg: String = "Assert outgoing message {}") { +fun assertReply(expected: String, testMsg: String = "Assert outgoing message to equal »{}«") { val message = (SentinelState.poll(SendMessageRequest::class.java) ?: throw TimeoutException("Command failed to send message")) - Assert.assertEquals(testMsg.replace("{}", message.toString()), expected, message.message) + Assert.assertEquals(testMsg.replace("{}", expected), expected, message.message) } -fun assertReplyContains(expected: String, testMsg: String = "Assert outgoing message contains {}") { +fun assertReplyContains(expected: String, testMsg: String = "Assert outgoing message contains »{}«") { val message = (SentinelState.poll(SendMessageRequest::class.java) ?: throw TimeoutException("Command failed to send message")) - Assert.assertTrue(testMsg.replace("{}", message.toString()), message.message.contains(expected)) + Assert.assertTrue(testMsg.replace("{}", expected), message.message.contains(expected)) } fun assertRequest(testMsg: String = "Failed to assert outgoing request {}", assertion: (T) -> Boolean) { From d33a012eb8c1d05fc3435b1e7a3be287866e6974 Mon Sep 17 00:00:00 2001 From: "Frederik Ar. Mikkelsen" Date: Mon, 1 Apr 2019 01:36:00 +0200 Subject: [PATCH 152/172] Fix assert wording --- FredBoat/src/test/java/fredboat/testutil/sentinel/testDsl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FredBoat/src/test/java/fredboat/testutil/sentinel/testDsl.kt b/FredBoat/src/test/java/fredboat/testutil/sentinel/testDsl.kt index bef6fd9dc..e456342db 100644 --- a/FredBoat/src/test/java/fredboat/testutil/sentinel/testDsl.kt +++ b/FredBoat/src/test/java/fredboat/testutil/sentinel/testDsl.kt @@ -70,7 +70,7 @@ class CommandTester(private val commandContextParser: CommandContextParser, temp } } -fun assertReply(testMsg: String = "Assert outgoing message to equal: »{}«", assertion: (String) -> Boolean) { +fun assertReply(testMsg: String = "Assert outgoing message to equal. Message: »{}«", assertion: (String) -> Boolean) { val message = SentinelState.poll(SendMessageRequest::class.java) ?: throw TimeoutException("Command failed to send message") Assert.assertTrue(testMsg.replace("{}", message.toString()), assertion(message.message)) From 1a2d83da818d0115ce7190ef4df5e757131306cc Mon Sep 17 00:00:00 2001 From: Nanabell Date: Mon, 1 Apr 2019 06:09:23 +0200 Subject: [PATCH 153/172] Rename valueFormat to validator and turn it into a typeAlias Validator --- .../src/main/java/fredboat/command/config/ConfigCommand.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt b/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt index 33d237e4f..08b8181e6 100644 --- a/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt @@ -40,6 +40,8 @@ import fredboat.util.localMessageBuilder import org.apache.commons.lang3.StringUtils import java.util.function.Predicate +private typealias Validator = Predicate + class ConfigCommand(name: String, private val repo: GuildSettingsRepository, vararg aliases: String) : Command(name, *aliases), IConfigCommand, ICommandRestricted { override val minimumPerms: PermissionLevel @@ -116,7 +118,7 @@ class ConfigCommand(name: String, private val repo: GuildSettingsRepository, var return } - if (!config.valueFormat.test(value)) { + if (!config.validator.test(value)) { context.replyWithName(context.i18n("configValueTypeInvalid")) } @@ -131,7 +133,7 @@ class ConfigCommand(name: String, private val repo: GuildSettingsRepository, var private data class ConfigOption( val name: String, - val valueFormat: Predicate, + val validator: Validator, val getter: (GuildSettings) -> String, val setter: (GuildSettings, String) -> Unit ) { From 381610e679e44e87daa35391ddff19656c61b9e5 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Mon, 1 Apr 2019 06:18:50 +0200 Subject: [PATCH 154/172] Move update() Subscribe to calling method --- .../main/java/fredboat/command/config/ConfigCommand.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt b/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt index 08b8181e6..470656f2b 100644 --- a/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt @@ -38,6 +38,7 @@ import fredboat.perms.PermsUtil import fredboat.util.TextUtils import fredboat.util.localMessageBuilder import org.apache.commons.lang3.StringUtils +import reactor.core.publisher.Mono import java.util.function.Predicate private typealias Validator = Predicate @@ -122,7 +123,9 @@ class ConfigCommand(name: String, private val repo: GuildSettingsRepository, var context.replyWithName(context.i18n("configValueTypeInvalid")) } - config.update(repo, context, value) + config.update(repo, context, value).subscribe { + context.replyWithName("`$name` ${context.i18nFormat("configSetTo", value)}") + } } override fun help(context: Context): String { @@ -137,11 +140,10 @@ private data class ConfigOption( val getter: (GuildSettings) -> String, val setter: (GuildSettings, String) -> Unit ) { - fun update(repo: GuildSettingsRepository, context: CommandContext, value: String) { - repo.fetch(context.guild.id) + fun update(repo: GuildSettingsRepository, context: CommandContext, value: String): Mono { + return repo.fetch(context.guild.id) .doOnSuccess { setter(it, value) } .let { repo.update(it) } - .subscribe { context.replyWithName("`$name` ${context.i18nFormat("configSetTo", value)}") } } override fun toString(): String { From d89e3659c41827bba91ee4b9e78ccdea8135fa82 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Mon, 1 Apr 2019 06:20:01 +0200 Subject: [PATCH 155/172] Make Config Options more readable? --- .../fredboat/command/config/ConfigCommand.kt | 68 ++++++++++++------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt b/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt index 470656f2b..7a129185b 100644 --- a/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt @@ -55,29 +55,51 @@ class ConfigCommand(name: String, private val repo: GuildSettingsRepository, var val nullableIntPredicate = Predicate { (it.toIntOrNull() != null && it.toInt() > -1) || StringUtils.equalsAnyIgnoreCase(it, "none", "unlimited") } val nullableLongPredicate = Predicate { StringUtils.equalsAnyIgnoreCase(it, "none", "unlimited") || TextUtils.parseTimeString(it) != 0L } - configOptions.add(ConfigOption("track_announce", booleanPredicate, - { it.trackAnnounce.toString() }, { gs, value -> gs.trackAnnounce = value.toBoolean() })) - - configOptions.add(ConfigOption("auto_resume", booleanPredicate, - { it.trackAnnounce.toString() }, { gs, value -> gs.trackAnnounce = value.toBoolean() })) - - configOptions.add(ConfigOption("allow_playlist", booleanPredicate, - { it.allowPlaylist.toString() }, { gs, v -> gs.allowPlaylist = v.toBoolean() })) - - configOptions.add(ConfigOption("max_tracks", nullableIntPredicate, - { it.maxTrackCount?.toString() ?: "UNLIMITED" }, { gs, v -> - gs.maxTrackCount = if (v.equals("unlimited", true)) null else v.toInt() - })) - - configOptions.add(ConfigOption("max_user_tracks", nullableIntPredicate, - { it.userMaxTrackCount?.toString() ?: "UNLIMITED" }, { gs, v -> - gs.userMaxTrackCount = if (StringUtils.equalsAnyIgnoreCase(v, "none", "unlimited")) null else v.toInt() - })) - - configOptions.add(ConfigOption("max_track_length", nullableLongPredicate, - { if (it.maxTrackLength != null) TextUtils.formatTime(it.maxTrackLength!!) else "UNLIMITED" }, { gs, v -> - gs.maxTrackLength = if (StringUtils.equalsAnyIgnoreCase(v, "none", "unlimited")) null else TextUtils.parseTimeString(v) - })) + configOptions.add(ConfigOption( + "track_announce", + booleanPredicate, + { it.trackAnnounce.toString() }, + { gs, value -> gs.trackAnnounce = value.toBoolean() })) + + configOptions.add(ConfigOption( + "auto_resume", + booleanPredicate, + { it.trackAnnounce.toString() }, + { gs, value -> gs.trackAnnounce = value.toBoolean() })) + + configOptions.add(ConfigOption( + "allow_playlist", + booleanPredicate, + { it.allowPlaylist.toString() }, + { gs, v -> gs.allowPlaylist = v.toBoolean() })) + + configOptions.add(ConfigOption( + "max_tracks", + nullableIntPredicate, + { it.maxTrackCount?.toString() ?: "UNLIMITED" }, + { gs, v -> gs.maxTrackCount = if (v.equals("unlimited", true)) null else v.toInt() })) + + configOptions.add(ConfigOption( + "max_user_tracks", + nullableIntPredicate, + { it.userMaxTrackCount?.toString() ?: "UNLIMITED" }, + { gs, v -> + gs.userMaxTrackCount = if (StringUtils.equalsAnyIgnoreCase(v, "none", "unlimited")) + null + else + v.toInt() + })) + + configOptions.add(ConfigOption( + "max_track_length", + nullableLongPredicate, + { if (it.maxTrackLength != null) TextUtils.formatTime(it.maxTrackLength!!) else "UNLIMITED" }, + { gs, v -> + gs.maxTrackLength = if (StringUtils.equalsAnyIgnoreCase(v, "none", "unlimited")) + null + else + TextUtils.parseTimeString(v) + })) } override suspend fun invoke(context: CommandContext) { From 64059ed90a7c0ea40e9df8ad6001f9fcea3e7c51 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Mon, 1 Apr 2019 15:06:57 +0200 Subject: [PATCH 156/172] Negate FBH Moderation Role check for blocking execution --- FredBoat/src/main/java/fredboat/commandmeta/CommandManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FredBoat/src/main/java/fredboat/commandmeta/CommandManager.kt b/FredBoat/src/main/java/fredboat/commandmeta/CommandManager.kt index 99186f386..baa9b17a4 100644 --- a/FredBoat/src/main/java/fredboat/commandmeta/CommandManager.kt +++ b/FredBoat/src/main/java/fredboat/commandmeta/CommandManager.kt @@ -83,7 +83,7 @@ class CommandManager(private val patronageChecker: PatronageChecker, private val if (guild.id == BotConstants.FREDBOAT_HANGOUT_ID && DiscordUtil.isOfficialBot(selfUser.id)) { if (channel.id != 174821093633294338L // #spam_and_music && channel.id != 217526705298866177L // #staff - && invoker.roles.any { it.id == BotConstants.FBH_MODERATOR_ROLE_ID } + && invoker.roles.none { it.id == BotConstants.FBH_MODERATOR_ROLE_ID } && !PermsUtil.checkPerms(PermissionLevel.ADMIN, invoker)) { context.deleteMessage() val response = context.replyWithNameMono( From d81ac124933d8fc62b1e1812f134ed2468a1ab0f Mon Sep 17 00:00:00 2001 From: Nanabell Date: Mon, 1 Apr 2019 18:02:21 +0200 Subject: [PATCH 157/172] Convert update() to expression --- .../main/java/fredboat/command/config/ConfigCommand.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt b/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt index 7a129185b..911748e50 100644 --- a/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt @@ -38,7 +38,6 @@ import fredboat.perms.PermsUtil import fredboat.util.TextUtils import fredboat.util.localMessageBuilder import org.apache.commons.lang3.StringUtils -import reactor.core.publisher.Mono import java.util.function.Predicate private typealias Validator = Predicate @@ -162,11 +161,9 @@ private data class ConfigOption( val getter: (GuildSettings) -> String, val setter: (GuildSettings, String) -> Unit ) { - fun update(repo: GuildSettingsRepository, context: CommandContext, value: String): Mono { - return repo.fetch(context.guild.id) - .doOnSuccess { setter(it, value) } - .let { repo.update(it) } - } + fun update(repo: GuildSettingsRepository, context: CommandContext, value: String) = repo.fetch(context.guild.id) + .doOnSuccess { setter(it, value) } + .let { repo.update(it) } override fun toString(): String { return name From 8ea1f9e4180e7502629445cb1d0c6b3ca2459c30 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Tue, 2 Apr 2019 16:14:58 +0200 Subject: [PATCH 158/172] Fix if for dynamic play response always being true at this stage --- .../main/java/fredboat/audio/queue/audioLoading.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt b/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt index e379cce99..7fe6cc54e 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt @@ -33,7 +33,10 @@ import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist import com.sedmelluq.discord.lavaplayer.track.AudioTrack import fredboat.audio.player.GuildPlayer import fredboat.audio.player.trackCount -import fredboat.audio.queue.limiter.* +import fredboat.audio.queue.limiter.errored +import fredboat.audio.queue.limiter.isPlaylistDisabledError +import fredboat.audio.queue.limiter.playlistDisabledError +import fredboat.audio.queue.limiter.successful import fredboat.audio.source.PlaylistImportSourceManager import fredboat.audio.source.PlaylistImporter import fredboat.audio.source.SpotifyPlaylistSourceManager @@ -194,11 +197,11 @@ private class ResultHandler(val loader: AudioLoader, val context: IdentifierCont val atc = AudioTrackContext(at, context.member, context.isPriority) GlobalScope.mono { loader.player.queueLimited(atc) }.subscribe { if (it.canQueue) { - context.reply(if (loader.player.isPlaying) + context.reply(if (loader.player.trackCount == 1) + context.i18nFormat("loadSingleTrackAndPlay", TextUtils.escapeAndDefuse(at.info.title)) + else context.i18nFormat(if (context.isPriority) "loadSingleTrackFirst" else "loadSingleTrack", TextUtils.escapeAndDefuse(at.info.title)) - else - context.i18nFormat("loadSingleTrackAndPlay", TextUtils.escapeAndDefuse(at.info.title)) ) } else { context.replyWithMention(it.errorMessage) From f127a96e64c5eea6ca160d136a05449be55ec57f Mon Sep 17 00:00:00 2001 From: Frederik Mikkelsen Date: Tue, 2 Apr 2019 16:38:11 +0200 Subject: [PATCH 159/172] Update translations --- .../src/main/resources/lang/af_ZA.properties | 7 +- .../src/main/resources/lang/ar_SA.properties | 7 +- .../src/main/resources/lang/ast_ES.properties | 7 +- .../src/main/resources/lang/bg_BG.properties | 7 +- .../src/main/resources/lang/bn_BD.properties | 7 +- .../src/main/resources/lang/ca_ES.properties | 7 +- .../src/main/resources/lang/ceb_PH.properties | 7 +- .../src/main/resources/lang/cs_CZ.properties | 7 +- .../src/main/resources/lang/cy_GB.properties | 7 +- .../src/main/resources/lang/da_DK.properties | 7 +- .../src/main/resources/lang/de_DE.properties | 17 ++- .../src/main/resources/lang/el_GR.properties | 21 +-- .../src/main/resources/lang/en_PT.properties | 23 ++-- .../src/main/resources/lang/en_TS.properties | 7 +- .../src/main/resources/lang/es_ES.properties | 13 +- .../src/main/resources/lang/et_EE.properties | 9 +- .../src/main/resources/lang/fa_IR.properties | 7 +- .../src/main/resources/lang/fi_FI.properties | 7 +- .../src/main/resources/lang/fil_PH.properties | 53 ++++---- .../src/main/resources/lang/fr_FR.properties | 17 ++- .../src/main/resources/lang/fr_TS.properties | 125 +++++++++--------- .../src/main/resources/lang/ga_IE.properties | 7 +- .../src/main/resources/lang/he_IL.properties | 7 +- .../src/main/resources/lang/hr_HR.properties | 7 +- .../src/main/resources/lang/hu_HU.properties | 7 +- .../src/main/resources/lang/id_ID.properties | 37 +++--- .../src/main/resources/lang/it_IT.properties | 25 ++-- .../src/main/resources/lang/ja_JP.properties | 21 +-- .../src/main/resources/lang/ko_KR.properties | 7 +- .../src/main/resources/lang/ms_MY.properties | 7 +- .../src/main/resources/lang/nl_NL.properties | 17 ++- .../src/main/resources/lang/no_NO.properties | 7 +- .../src/main/resources/lang/pl_PL.properties | 9 +- .../src/main/resources/lang/pt_BR.properties | 19 ++- .../src/main/resources/lang/pt_PT.properties | 57 ++++---- .../src/main/resources/lang/ro_RO.properties | 13 +- .../src/main/resources/lang/ru_RU.properties | 13 +- .../src/main/resources/lang/sk_SK.properties | 7 +- .../src/main/resources/lang/sr_SP.properties | 13 +- .../src/main/resources/lang/sv_SE.properties | 21 +-- .../src/main/resources/lang/th_TH.properties | 23 ++-- .../src/main/resources/lang/tr_TR.properties | 15 ++- .../src/main/resources/lang/uk_UA.properties | 7 +- .../src/main/resources/lang/vi_VN.properties | 9 +- .../src/main/resources/lang/yo_NG.properties | 7 +- .../src/main/resources/lang/zh_CN.properties | 7 +- .../src/main/resources/lang/zh_TW.properties | 63 +++++---- 47 files changed, 518 insertions(+), 283 deletions(-) diff --git a/FredBoat/src/main/resources/lang/af_ZA.properties b/FredBoat/src/main/resources/lang/af_ZA.properties index 3612b5d3b..a69f30971 100644 --- a/FredBoat/src/main/resources/lang/af_ZA.properties +++ b/FredBoat/src/main/resources/lang/af_ZA.properties @@ -62,7 +62,7 @@ npDescription=Beskrywing npLoadedSoundcloud=[{0}/{1}] Gelaai van Soundcloud npLoadedBandcamp={0} gelaai van Bandcamp npLoadedTwitch=Gelaai van pyl -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Ek moet die volgende toestemming om daardie aksie uitvoer\: permissionMissingInvoker=You need the following permission to perform that action\: permissionEmbedLinks=Bed skakels @@ -92,6 +92,11 @@ loadPlaylistTooMany=Bykomende {0} snitte. Gevind te veel spore wys. loadErrorCommon=Fout het voorgekom met die laai inligting vir ''{0}''\:{1} loadErrorSusp=Verdagte fout wanneer laai inligting vir ''{0}''. loadQueueTrackLimit=Jy kan snitte voeg by ''n tou met meer as {0} snitte\! Dit is om te verhoed dat mishandeling. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Oor te laai speellys * *{0} * * met tot ''{1}'' snitte. Dit kan ''n rukkie neem, wees asseblief geduldig. playerUserNotInChannel=Jy moet eers 'n stem kanaal aansluit. playerJoinConnectDenied=Ek is nie toegelaat om te koppel aan daardie stem kanaal. diff --git a/FredBoat/src/main/resources/lang/ar_SA.properties b/FredBoat/src/main/resources/lang/ar_SA.properties index 869b8ffbf..63f7cff59 100644 --- a/FredBoat/src/main/resources/lang/ar_SA.properties +++ b/FredBoat/src/main/resources/lang/ar_SA.properties @@ -62,7 +62,7 @@ npDescription=\u0627\u0644\u0648\u0635\u0641 npLoadedSoundcloud=[{0}/{1}] \u062a\u062d\u0645\u064a\u0644 \u0645\u0646 \u0633\u0648\u0646\u062f\u0643\u0644\u0648\u062f npLoadedBandcamp={0} \u062a\u062d\u0645\u064a\u0644 \u0645\u0646 Bandcamp npLoadedTwitch=\u062a\u0645 \u062a\u062d\u0645\u064a\u0644\u0647 \u0645\u0646 \u0646\u0634\u0644 -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=\u0623\u0646\u0627 \u0628\u062d\u0627\u062c\u0629 \u0625\u0644\u0649 \u0625\u0630\u0646 \u0627\u0644\u062a\u0627\u0644\u064a\u0629 \u0644\u0644\u0642\u064a\u0627\u0645 \u0628\u0647\u0630\u0627 \u0627\u0644\u0625\u062c\u0631\u0627\u0621\: permissionMissingInvoker=\u0623\u0646\u0627 \u0628\u062d\u0627\u062c\u0629 \u0625\u0644\u0649 \u0625\u0630\u0646 \u0627\u0644\u062a\u0627\u0644\u064a\u0629 \u0644\u0644\u0642\u064a\u0627\u0645 \u0628\u0647\u0630\u0627 \u0627\u0644\u0625\u062c\u0631\u0627\u0621\: i need this comment to do this action\: permissionEmbedLinks=\u062a\u0636\u0645\u064a\u0646 \u0627\u0644\u0631\u0627\u0628\u0637 @@ -92,6 +92,11 @@ loadPlaylistTooMany=\u0648\u0623\u0636\u0627\u0641 {0} \u0627\u0644\u0645\u0633\ loadErrorCommon=\u062d\u062f\u062b \u062e\u0637\u0623 \u0623\u062b\u0646\u0627\u0621 \u062a\u062d\u0645\u064a\u0644 \u0645\u0639\u0644\u0648\u0645\u0627\u062a \u0639\u0646 ''{0}''\:{1} loadErrorSusp=\u0627\u0644\u0645\u0634\u0628\u0648\u0647\u0629 \u0645\u0646 \u0627\u0644\u062e\u0637\u0623 \u0639\u0646\u062f \u062a\u062d\u0645\u064a\u0644 \u0645\u0639\u0644\u0648\u0645\u0627\u062a \u0639\u0646 ''{0}''. loadQueueTrackLimit=\u0644\u0627 \u064a\u0645\u0643\u0646\u0643 \u0625\u0636\u0627\u0641\u0629 \u0645\u0633\u0627\u0631\u0627\u062a \u0625\u0644\u0649 \u0642\u0627\u0626\u0645\u0629 \u0627\u0646\u062a\u0638\u0627\u0631 \u0645\u0639 \u0623\u0643\u062b\u0631 \u0645\u0646 \u0627\u0644\u0645\u0633\u0627\u0631\u0627\u062a {0}\! \u0648\u0647\u0630\u0627 \u0644\u0645\u0646\u0639 \u0627\u0644\u0627\u0639\u062a\u062f\u0627\u0621. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=\u0644\u062a\u062d\u0645\u064a\u0644 \u0642\u0627\u0626\u0645\u0629 \u0627\u0644\u062a\u0634\u063a\u064a\u0644 * *{0} * * \u0645\u0639 \u0645\u0627 \u064a\u0635\u0644 \u0625\u0644\u0649 \u0627\u0644\u0645\u0633\u0627\u0631\u0627\u062a ''{1}''. \u0648\u0647\u0630\u0627 \u0642\u062f \u064a\u0633\u062a\u063a\u0631\u0642 \u0628\u0639\u0636 \u0627\u0644\u0648\u0642\u062a\u060c \u064a\u0631\u062c\u0649 \u0623\u0646 \u064a\u0643\u0648\u0646 \u0627\u0644\u0645\u0631\u064a\u0636. playerUserNotInChannel=\u064a\u062c\u0628 \u0623\u0646 \u062a\u0643\u0648\u0646 \u0641\u064a \u0642\u0646\u0627\u0629 \u0635\u0648\u062a\u064a\u0629. playerJoinConnectDenied=\u0644\u064a\u0633 \u0645\u0633\u0645\u0648\u062d \u0644\u064a \u0627\u0646 \u0627\u062a\u0635\u0644 \u0625\u0644\u0627 \u062a\u0644\u0643 \u0627\u0644\u0642\u0646\u0627\u0629 \u0627\u0644\u0635\u0648\u062a\u064a\u0629. diff --git a/FredBoat/src/main/resources/lang/ast_ES.properties b/FredBoat/src/main/resources/lang/ast_ES.properties index 30d57f8df..fa3b08e8a 100644 --- a/FredBoat/src/main/resources/lang/ast_ES.properties +++ b/FredBoat/src/main/resources/lang/ast_ES.properties @@ -62,7 +62,7 @@ npDescription=Descripci\u00f3n npLoadedSoundcloud=[{0}/{1}]\n\nLoaded from Soundcloud npLoadedBandcamp={0}\n\nLoaded from Bandcamp npLoadedTwitch=Loaded from Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Preciso los permisos de darr\u00e9u pa facer esa aici\u00f3n\: permissionMissingInvoker=Precises los permisos de darr\u00e9u pa facer esa aici\u00f3n\: permissionEmbedLinks=Embed Links @@ -92,6 +92,11 @@ loadPlaylistTooMany=Added {0} tracks. Found too many tracks to display. loadErrorCommon=Asocedi\u00f3 un fallu mentanto se cargaba la informaci\u00f3n de \u00ab{0}\u00bb\:\n{1} loadErrorSusp=Asocedi\u00f3 un fallu sospechosu al cargar la informaci\u00f3n de \u00ab{0}\u00bb. loadQueueTrackLimit=You can''t add tracks to a queue with more than {0} tracks\! This is to prevent abuse. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=About to load playlist **{0}** with up to `{1}` tracks. This may take a while, please be patient. playerUserNotInChannel=Has xunite primero a una canal de voz. playerJoinConnectDenied=Nun teo permisu pa coneutame a esa canal de voz. diff --git a/FredBoat/src/main/resources/lang/bg_BG.properties b/FredBoat/src/main/resources/lang/bg_BG.properties index 99c82f6c6..fb03cfcb4 100644 --- a/FredBoat/src/main/resources/lang/bg_BG.properties +++ b/FredBoat/src/main/resources/lang/bg_BG.properties @@ -62,7 +62,7 @@ npDescription=\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 npLoadedSoundcloud=[{0}/{1}] \u0417\u0430\u0440\u0435\u0434\u0435\u043d\u043e \u043e\u0442 Soundcloud npLoadedBandcamp={0}, \u0437\u0430\u0440\u0435\u0434\u0435\u043d\u043e \u043e\u0442 Bandcamp npLoadedTwitch=\u0417\u0430\u0440\u0435\u0434\u0435\u043d\u043e \u043e\u0442 Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=\u0418\u043c\u0430\u043c \u043d\u0443\u0436\u0434\u0430 \u043e\u0442 \u0441\u043b\u0435\u0434\u043d\u043e\u0442\u043e \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0437\u0430 \u0434\u0430 \u0438\u0437\u043f\u044a\u043b\u043d\u044f \u0442\u043e\u0432\u0430 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435\: permissionMissingInvoker=\u0422\u0440\u044f\u0431\u0432\u0430 \u0442\u0438 \u0441\u043b\u0435\u0434\u0432\u0430\u0448\u0442\u043e\u0442\u043e \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0434\u0430 \u043d\u0430\u043f\u0440\u0430\u0432\u0438\u0448 \u0442\u043e\u0432\u0430\: permissionEmbedLinks=Embed Links @@ -92,6 +92,11 @@ loadPlaylistTooMany=\u0414\u043e\u0431\u0430\u0432\u0435\u043d\u0438 {0} \u043f\ loadErrorCommon=\u0412\u044a\u0437\u043d\u0438\u043a\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430 \u043f\u0440\u0438 \u0437\u0430\u0440\u0435\u0436\u0434\u0430\u043d\u0435 \u043d\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0437\u0430 "{0}"\:{1} loadErrorSusp=\u041f\u043e\u0434\u043e\u0437\u0440\u0438\u0442\u0435\u043b\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430 \u043f\u0440\u0438 \u0437\u0430\u0440\u0435\u0436\u0434\u0430\u043d\u0435 \u043d\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0437\u0430 "{0}". loadQueueTrackLimit=\u041d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u0430 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u0435 \u043f\u0435\u0441\u043d\u0438 \u0432 \u043e\u043f\u0430\u0448\u043a\u0430\u0442\u0430 \u0441 \u043f\u043e\u0432\u0435\u0447\u0435 \u043e\u0442 {0} \u043f\u0435\u0441\u043d\u0438\! \u0422\u043e\u0432\u0430 \u0435 \u0437\u0430 \u0434\u0430 \u0441\u0435 \u0438\u0437\u0431\u0435\u0433\u043d\u0435 \u0437\u043b\u043e\u0443\u043f\u043e\u0442\u0440\u0435\u0431\u0430. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=\u0417\u0430\u0440\u0435\u0436\u0434\u0430\u043c playlist**{0}** \u0441 `{1}` \u043f\u0435\u0441\u043d\u0438. \u0422\u043e\u0432\u0430 \u043c\u043e\u0436\u0435 \u0434\u0430 \u043e\u0442\u043d\u0435\u043c\u0435 \u043c\u0430\u043b\u043a\u043e \u0432\u0440\u0435\u043c\u0435 \u043c\u043e\u043b\u044f \u0431\u044a\u0434\u0435\u0442\u0435 \u0442\u044a\u0440\u043f\u0435\u043b\u0438\u0432\u0438. playerUserNotInChannel=\u0422\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u0432\u043b\u0435\u0437\u0435\u0442\u0435 \u0432 \u0433\u043b\u0430\u0441\u043e\u0432\u0438\u044f \u043a\u0430\u043d\u0430\u043b \u043f\u044a\u0440\u0432\u043e. playerJoinConnectDenied=\u041d\u0435 \u043c\u0438 \u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u0435\u043d\u043e \u0434\u0430 \u0432\u043b\u0438\u0437\u0430\u043c \u0432 \u0442\u043e\u0437\u0438 \u0433\u043b\u0430\u0441\u043e\u0432 \u043a\u0430\u043d\u0430\u043b. diff --git a/FredBoat/src/main/resources/lang/bn_BD.properties b/FredBoat/src/main/resources/lang/bn_BD.properties index b206fc8e4..3674c2282 100644 --- a/FredBoat/src/main/resources/lang/bn_BD.properties +++ b/FredBoat/src/main/resources/lang/bn_BD.properties @@ -62,7 +62,7 @@ npDescription=Description npLoadedSoundcloud=[{0}/{1}]\n\nLoaded from Soundcloud npLoadedBandcamp={0}\n\nLoaded from Bandcamp npLoadedTwitch=Loaded from Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=I need the following permission to perform that action\: permissionMissingInvoker=You need the following permission to perform that action\: permissionEmbedLinks=Embed Links @@ -92,6 +92,11 @@ loadPlaylistTooMany=Added {0} tracks. Found too many tracks to display. loadErrorCommon=Error occurred when loading info for `{0}`\:\n{1} loadErrorSusp=Suspicious error when loading info for `{0}`. loadQueueTrackLimit=You can''t add tracks to a queue with more than {0} tracks\! This is to prevent abuse. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=About to load playlist **{0}** with up to `{1}` tracks. This may take a while, please be patient. playerUserNotInChannel=You must join a voice channel first. playerJoinConnectDenied=I am not permitted to connect to that voice channel. diff --git a/FredBoat/src/main/resources/lang/ca_ES.properties b/FredBoat/src/main/resources/lang/ca_ES.properties index 3449dc5ba..ba1b8aa2c 100644 --- a/FredBoat/src/main/resources/lang/ca_ES.properties +++ b/FredBoat/src/main/resources/lang/ca_ES.properties @@ -62,7 +62,7 @@ npDescription=Descripci\u00f3 npLoadedSoundcloud=[{0}/{1}]\n\nCarregat de Soundcloud npLoadedBandcamp={0}\n\nCarregat de Bandcamp npLoadedTwitch=Carregat de Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Necessito el seg\u00fcent perm\u00eds per poder fer aquesta acci\u00f3\: permissionMissingInvoker=Necesitas permisos para realizar esa acci\u00f3n\: permissionEmbedLinks=Incrustar enlla\u00e7os @@ -92,6 +92,11 @@ loadPlaylistTooMany=Afegides {0} pistes. Hi han massa pistes per mostrar. loadErrorCommon=S''ha produ\u00eft un error en carregar la informaci\u00f3 per a `{0}`\:\n{1} loadErrorSusp=Sospit\u00f3s error en carregar la informaci\u00f3 per a `{0}`. loadQueueTrackLimit=No pots afegir pistes a una cua amb m\u00e9s de {0} pistes\! Aix\u00f2 es per evitar abusos. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=A punt de carregar la llista de reproducci\u00f3 **{0}** amb `{1}` pistes. Aix\u00f2 pot tardar una estona, si us plau, sigui pacient. playerUserNotInChannel=Primer has de unirte a un canal de veu. playerJoinConnectDenied=No tinc perm\u00eds per connectar-me a aquest canal de veu. diff --git a/FredBoat/src/main/resources/lang/ceb_PH.properties b/FredBoat/src/main/resources/lang/ceb_PH.properties index d8aae4720..d5f65fdcb 100644 --- a/FredBoat/src/main/resources/lang/ceb_PH.properties +++ b/FredBoat/src/main/resources/lang/ceb_PH.properties @@ -62,7 +62,7 @@ npDescription=Deskripsyon npLoadedSoundcloud={0}{1}\nBug-at gikan Soundcloud npLoadedBandcamp={0}\nBug-at gikan Bandcamp npLoadedTwitch=Bug-at gikan Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Gusto nako eh sunod ang pag tugot na mo performa ang aksyon\: permissionMissingInvoker=You need the following permission to perform that action\: permissionEmbedLinks=Embed nga mga link @@ -92,6 +92,11 @@ loadPlaylistTooMany=Nagdugang og {0} tracks. Daghan ra kaayong tracks ang nakapl loadErrorCommon=Naay error nga nahitabo sa dihang nag-load og info alang sa `{0}`\: {1} loadErrorSusp=Kadudahang error sa dihang nag-load og info alang sa `{0}`. loadQueueTrackLimit=Dili ka makadugang og mga track sa usa ka laray nga adunay labaw pa kay sa {0} ka mga track\! Kini aron malikayan ang pag-abuso. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Mag-load og playlist **{0}** nga adunay `{1}` kadaghanon sa tracks. Tingali madugay kini, palihug pagpailub. playerUserNotInChannel=Kinahanglan una ka moapil sa usa ka voice channel. playerJoinConnectDenied=Wala ko gitugutan nga mokonek sa maong voice channel. diff --git a/FredBoat/src/main/resources/lang/cs_CZ.properties b/FredBoat/src/main/resources/lang/cs_CZ.properties index eec849928..a366a9cab 100644 --- a/FredBoat/src/main/resources/lang/cs_CZ.properties +++ b/FredBoat/src/main/resources/lang/cs_CZ.properties @@ -62,7 +62,7 @@ npDescription=Popis npLoadedSoundcloud=[{0}/{1}]\n\nNa\u010dteno z Soundcloud npLoadedBandcamp={0}\n\nNa\u010dteno z Bandcamp npLoadedTwitch=Na\u010dteno z Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Pot\u0159ebuji n\u00e1sleduj\u00edc\u00ed opr\u00e1vn\u011bn\u00ed k proveden\u00ed t\u00e9to akce\: permissionMissingInvoker=Pro vykon\u00e1n\u00ed t\u00e9to akce pot\u0159ebuje\u0161 n\u00e1sleduj\u00edc\u00ed opr\u00e1vn\u011bn\u00ed\: permissionEmbedLinks=Vlo\u017een\u00ed odkaz\u016f @@ -92,6 +92,11 @@ loadPlaylistTooMany=P\u0159id\u00e1no {0} stop. Nalezeno p\u0159\u00edli\u0161 m loadErrorCommon=Do\u0161lo k chyb\u011b p\u0159i na\u010d\u00edt\u00e1n\u00ed informac\u00ed o `{0}`\:\n{1} loadErrorSusp=Do\u0161lo k podez\u0159el\u00e9 chyb\u011b p\u0159i na\u010d\u00edt\u00e1n\u00ed informac\u00ed o `{0}`. loadQueueTrackLimit=Nelze p\u0159idat skladby do fronty, kde je jich v\u00edce ne\u017e {0}\! Je to prevence proti zneu\u017eit\u00ed. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Na\u010d\u00edt\u00e1m playlist **{0}**, kter\u00fd m\u00e1 "{1}" skladeb. M\u016f\u017ee to chvilku trvat, bu\u010fte trp\u011bliv\u00ed. playerUserNotInChannel=Mus\u00edte se nejprve p\u0159ipojit k hlasov\u00e9mu kan\u00e1lu. playerJoinConnectDenied=Nem\u00e1m povoleno se p\u0159ipojit k tomuto hlasov\u00e9mu kan\u00e1lu. diff --git a/FredBoat/src/main/resources/lang/cy_GB.properties b/FredBoat/src/main/resources/lang/cy_GB.properties index 6ea7cb8de..11353d508 100644 --- a/FredBoat/src/main/resources/lang/cy_GB.properties +++ b/FredBoat/src/main/resources/lang/cy_GB.properties @@ -62,7 +62,7 @@ npDescription=Disgrifiad npLoadedSoundcloud=[{0}/{1}] Lwytho o''r Soundcloud npLoadedBandcamp={0} lwytho o''r Bandcamp npLoadedTwitch=Llwytho o nerfusrwydd -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Mae angen caniat\u00e2d canlynol i wneud hynny\: permissionMissingInvoker=You need the following permission to perform that action\: permissionEmbedLinks=Sefydlu cysylltiadau @@ -92,6 +92,11 @@ loadPlaylistTooMany=Traciau {0} ychwanegol. Canfod gormod o draciau i arddangos. loadErrorCommon=Digwyddodd gwall wrth lwytho gwybodaeth am ''{0}''\:{1} loadErrorSusp=Amheus gwall wrth lwytho gwybodaeth am ''{0}''. loadQueueTrackLimit=Chewch chi ddim ychwanegu traciau i ciw \u00e2 mwy na llwybrau {0}\! Mae hyn i atal cam-drin. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=I lwytho''r rhestr chwarae **{0} ** gyda hyd at traciau ''{1}''. Gall hyn gymryd amser, byddwch yn amyneddgar. playerUserNotInChannel=Rhaid i chi ymuno \u00e2 llais sianel gyntaf. playerJoinConnectDenied=Nid caniateir i mi gysylltu \u00e2 sianel y llais. diff --git a/FredBoat/src/main/resources/lang/da_DK.properties b/FredBoat/src/main/resources/lang/da_DK.properties index d3e2803e5..1ad51d259 100644 --- a/FredBoat/src/main/resources/lang/da_DK.properties +++ b/FredBoat/src/main/resources/lang/da_DK.properties @@ -62,7 +62,7 @@ npDescription=Beskrivelse npLoadedSoundcloud=[{0}/{1}]\nIndl\u00e6st fra Soundcloud npLoadedBandcamp={0}\n\nIndl\u00e6st fra Bandcamp npLoadedTwitch=Indl\u00e6st fra Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Jeg har brug for f\u00f8lgende tilladelse til at udf\u00f8re den p\u00e5g\u00e6ldende handling\: permissionMissingInvoker=Jeg har brug for f\u00f8lgende tilladelse til at udf\u00f8re den p\u00e5g\u00e6ldende handling\: permissionEmbedLinks=Integrer links @@ -92,6 +92,11 @@ loadPlaylistTooMany=Tilf\u00f8jede {0} numre. Fandte alt for mange numre til at loadErrorCommon=Der opstod en fejl under indl\u00e6sning af info for ''{0}''\:{1} loadErrorSusp=Mist\u00e6nkelig fejl under indl\u00e6sning af info for ''{0}''. loadQueueTrackLimit=Du kan ikke tilf\u00f8je numre til en k\u00f8 med mere end {0} sange\! Dette er for at forhindre misbrug. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Klar til at indl\u00e6se afspilningsliste **{0} ** med op til ''{1}'' sang. Dette kan tage et stykke tid, v\u00e6r venligst t\u00e5lmodig. playerUserNotInChannel=Du skal f\u00f8rst forbindes til en voice chat. playerJoinConnectDenied=Jeg har ikke tilladelse til at forbinde til den voice chat. diff --git a/FredBoat/src/main/resources/lang/de_DE.properties b/FredBoat/src/main/resources/lang/de_DE.properties index 16b8008e7..5803b9411 100644 --- a/FredBoat/src/main/resources/lang/de_DE.properties +++ b/FredBoat/src/main/resources/lang/de_DE.properties @@ -62,7 +62,7 @@ npDescription=Beschreibung npLoadedSoundcloud=[{0}/{1}]\n\nGeladen von Soundcloud npLoadedBandcamp={0} aus Bandcamp geladen npLoadedTwitch=Von Twitch geladen -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Ich brauche die folgenden Berechtigungen damit ich diese Aktion ausf\u00fchren kann\: permissionMissingInvoker=Du brauchst die folgenden Berechtigung damit sie diese Aktion ausf\u00fchren k\u00f6nnen\: permissionEmbedLinks=Links einbinden @@ -92,6 +92,11 @@ loadPlaylistTooMany={0} Tracks hinzugef\u00fcgt. Zu viele Tracks gefunden, um si loadErrorCommon=Fehler aufgetreten beim Laden der Informationen f\u00fcr `{0}`\:\n{1} loadErrorSusp=Verd\u00e4chtiger Fehler beim Laden der Informationen f\u00fcr `{0}`. loadQueueTrackLimit=Du kannst keine Lieder zu einer Warteschlange die {0} Lieder beinhaltet hinzuf\u00fcgen, dies ist dazu da um Missbrauch zu verhindern. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Die Playlist **{0}** mit bis zu `{1}` Liedern wird geladen. Dies kann eine Weile dauern, bitte habe etwas Geduld. playerUserNotInChannel=Du musst zuerst einem Sprachkanal beitreten. playerJoinConnectDenied=Mir ist es nicht gestattet, mich mit diesem Sprachkanal zu verbinden. @@ -217,7 +222,7 @@ commandsModeration=Moderation commandsMaintenance=Wartung commandsBotOwner=Boteigent\u00fcmer commandsMoreHelp=Sag {0}, um mehr Informationen zu einem bestimmten Befehl zu erhalten. -commandsModulesHint=Du k\u00f6nnen zus\u00e4tzliche Module mit {0} aktivieren und deaktivieren +commandsModulesHint=Du kannst zus\u00e4tzliche Module mit {0} aktivieren und deaktivieren helpUnknownCommand=Unbekannter Befehl. helpDocsLocation=Die Dokumentation befindet sich hier\: helpBotInvite=Willst du FredBoat zu deinem Server hinzuf\u00fcgen? Wenn du die "Server verwalten"-Berechtigung f\u00fcr deinen Server hast, kannst du FredBoat hiermit einladen\: @@ -239,7 +244,7 @@ helpJoinCommand=Lass den Bot sich mit deinem aktuellen Sprachkanal verbinden. helpLeaveCommand=Lass den Bot deinem aktuellen Sprachkanal verlassen. helpPauseCommand=Halte den Spieler an. helpPlayCommand=Gebe Musik von der angegebenen URL wieder oder suche nach einem Lied. Eine vollst\u00e4ndige Liste der Quellen findest Du auf {0} -helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} +helpPlayNextCommand=Spiele musik durch eine URL ab oder suche direkt nach dem Lied. Lieder werden an den Anfang der Warteliste hinzugef\u00fcgt. Um eine liste der gesamten Quellen zu sehen, besuchen Sie bitte {0} helpPlaySplitCommand=Teile ein YouTube-Video in eine Tracklist, welche in dessen Beschreibung angegeben ist. helpRepeatCommand=Umschalten der Wiederholungsmodi. helpReshuffleCommand=Mische die Lieder f\u00fcr die aktuelle Warteschlange neu. @@ -273,7 +278,7 @@ helpUserInfoCommand=Zeigen Sie Informationen \u00fcber sich selbst oder einen Be helpPerms=Erm\u00f6glicht das whitelisten von Mitgliedern und Rollen f\u00fcr den {0} Rang. helpPrefixCommand=Legt das Pr\u00e4fix f\u00fcr diese Gilde fest. helpVoteSkip=Voten um den Song zu \u00fcberspringen. Es m\u00fcssen mindestens 50% von allen Usern im Voice-Channel daf\u00fcr voten, um den Song zu \u00fcberspringen. -helpUnvoteSkip=Remove your vote to skip the current song. +helpUnvoteSkip=Entfernen Sie Ihre Abstimmung um den aktuellen Titel zu \u00fcberspringen. helpMathOperationAdd=Ergibt die Summe von Num1 und Num2. helpMathOperationSub=Gibt die Differenz von num1-num2 aus. helpMathOperationMult=Gibt das Produkt von num1*num2 aus. @@ -302,8 +307,8 @@ skipUserMultiple={0} Lieder, welche von {1} hinzugef\u00fcgt wurden, \u00fcbersp skipUsersMultiple={0} Lieder, welche von {1} Nutzern hinzugef\u00fcgt wurden, \u00fcbersprungen. skipUserNoTracks=Keiner der genannten Nutzer hat Tracks in der Warteschlange. voteSkipAdded=Deine Stimme wurde hinzugef\u00fcgt\! -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! +voteSkipRemoved=Ihre Stimme zum \u00fcberspringen wurde entfernt\! +voteSkipNotFound=Du hast nicht daf\u00fcr abgestimmt diesen Titel zu \u00fcberspringen\! voteSkipAlreadyVoted=Du hast bereits f\u00fcr das \u00fcberspringen dieses Titels gestimmt\! voteSkipSkipping={0} haben abgestimmt, das Lied zu \u00fcberspringen. Das Lied {1} wird \u00fcbersprungen. voteSkipNotEnough={0} haben f\u00fcr das \u00fcberspringen des Liedes gestimmt. Doch es werden mindestens {1} Stimmen ben\u00f6tigt. diff --git a/FredBoat/src/main/resources/lang/el_GR.properties b/FredBoat/src/main/resources/lang/el_GR.properties index e0ae29229..caa1d4111 100644 --- a/FredBoat/src/main/resources/lang/el_GR.properties +++ b/FredBoat/src/main/resources/lang/el_GR.properties @@ -62,7 +62,7 @@ npDescription=\u03a0\u03b5\u03c1\u03b9\u03b3\u03c1\u03b1\u03c6\u03ae npLoadedSoundcloud=[{0}/{1}] \n\n\u03a0\u03c1\u03bf\u03c3\u03c4\u03ad\u03b8\u03b7\u03ba\u03b5 \u03b1\u03c0\u03cc Soundcloud npLoadedBandcamp={0}\n\n\u03a0\u03c1\u03bf\u03c3\u03c4\u03ad\u03b8\u03b7\u03ba\u03b5 \u03b1\u03c0\u03cc Bandcamp npLoadedTwitch=\u03a0\u03c1\u03bf\u03c3\u03c4\u03ad\u03b8\u03b7\u03ba\u03b5 \u03b1\u03c0\u03cc \u03c4\u03bf Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=\u03a7\u03c1\u03b5\u03b9\u03ac\u03b6\u03bf\u03bc\u03b1\u03b9 \u03c4\u03b1 \u03b1\u03ba\u03cc\u03bb\u03bf\u03c5\u03b8\u03b1 \u03b4\u03b9\u03ba\u03b1\u03b9\u03ce\u03bc\u03b1\u03c4\u03b1 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03ba\u03c4\u03b5\u03bb\u03ad\u03c3\u03c9 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b5\u03bd\u03ad\u03c1\u03b3\u03b5\u03b9\u03b1\: permissionMissingInvoker=\u03a7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c3\u03c4\u03b5 \u03c4\u03b1 \u03b1\u03ba\u03cc\u03bb\u03bf\u03c5\u03b8\u03b1 \u03b4\u03b9\u03ba\u03b1\u03b9\u03ce\u03bc\u03b1\u03c4\u03b1 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b5\u03ba\u03c4\u03b5\u03bb\u03ad\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b5\u03bd\u03ad\u03c1\u03b3\u03b5\u03b9\u03b1\: permissionEmbedLinks=\u0395\u03bd\u03c3\u03c9\u03bc\u03b1\u03c4\u03c9\u03bc\u03ad\u03bd\u03b5\u03c2 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03b9\u03c2 @@ -92,6 +92,11 @@ loadPlaylistTooMany=\u03a0\u03c1\u03bf\u03c3\u03c4\u03ad\u03b8\u03b7\u03ba\u03b5 loadErrorCommon=\u03a0\u03b1\u03c1\u03bf\u03c5\u03c3\u03b9\u03ac\u03c3\u03c4\u03b7\u03ba\u03b5 \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7 \u03c6\u03cc\u03c1\u03c4\u03c9\u03c3\u03b7 \u03c4\u03c9\u03bd \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03c9\u03bd \u03b3\u03b9\u03b1 \u00ab{0}\u00bb\:{1} loadErrorSusp=\u038e\u03c0\u03bf\u03c0\u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7 \u03c6\u03cc\u03c1\u03c4\u03c9\u03c3\u03b7 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03b9\u03ce\u03bd \u03b3\u03b9\u03b1 \u00ab{0}\u00bb. loadQueueTrackLimit=\u0394\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03ba\u03bf\u03bc\u03bc\u03ac\u03c4\u03b9\u03b1 \u03c3\u03b5 \u03bc\u03b9\u03b1 \u03bf\u03c5\u03c1\u03ac \u03bc\u03b5 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b1 \u03b1\u03c0\u03cc {0} \u03ba\u03bf\u03bc\u03bc\u03ac\u03c4\u03b9\u03b1\! \u0391\u03c5\u03c4\u03cc \u03b5\u03af\u03bd\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b1\u03c0\u03bf\u03c4\u03c1\u03b1\u03c0\u03b5\u03af \u03b7 \u03ba\u03b1\u03c4\u03ac\u03c7\u03c1\u03b7\u03c3\u03b7. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=\u03a0\u03c1\u03cc\u03ba\u03b5\u03b9\u03c4\u03b1\u03b9 \u03c6\u03bf\u03c1\u03c4\u03ce\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf playlist **{0} ** \u03bc\u03b5 *{1}* \u03ba\u03bf\u03bc\u03bc\u03ac\u03c4\u03b9\u03b1. \u0391\u03c5\u03c4\u03cc \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b4\u03b9\u03b1\u03c1\u03ba\u03ad\u03c3\u03b5\u03b9 \u03bb\u03af\u03b3\u03bf, \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03bd\u03b1 \u03b5\u03af\u03c3\u03c4\u03b5 \u03c5\u03c0\u03bf\u03bc\u03bf\u03bd\u03b5\u03c4\u03b9\u03ba\u03bf\u03af. playerUserNotInChannel=\u03a0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03bc\u03c0\u03b5\u03af\u03c4\u03b5 \u03c3\u03b5 \u03ad\u03bd\u03b1 Voice Channle \u03c0\u03c1\u03ce\u03c4\u03b1. playerJoinConnectDenied=\u0394\u03b5\u03bd \u03bc\u03bf\u03c5 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03b5\u03c4\u03b1\u03b9 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03ce \u03c3\u03c4\u03bf \u03c0\u03b1\u03c1\u03ce\u03bd Voice Channel. @@ -103,7 +108,7 @@ shutdownRestarting=\u03a4\u03bf FredBoat\u266a\u266a \u03ba\u03ac\u03bd\u03b5\u0 shutdownIndef=\u03a4\u03bf FredBoat\u266a\u266a \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b5. \u038c\u03c4\u03b1\u03bd \u03c4\u03bf bot \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03c0\u03ac\u03bb\u03b9 \u03b7 \u03c4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03bb\u03af\u03c3\u03c4\u03b1 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2 \u03b8\u03b1 \u03be\u03b1\u03bd\u03b1\u03c0\u03b1\u03af\u03be\u03b5\u03b9. shutdownPersistenceFail=\u03a0\u03b1\u03c1\u03bf\u03c5\u03c3\u03b9\u03ac\u03c3\u03c4\u03b7\u03ba\u03b5 \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b1\u03c0\u03bf\u03b8\u03ae\u03ba\u03b5\u03c5\u03c3\u03b7 \u03b1\u03c1\u03c7\u03b5\u03af\u03bf\u03c5\: {0} reloadSuccess=\u0395\u03c0\u03b1\u03bd\u03b1\u03c6\u03cc\u03c1\u03c4\u03c9\u03c3\u03b7 playlist. ''{0}'' \u03c4\u03c1\u03b1\u03b3\u03bf\u03cd\u03b4\u03b9\u03b1 \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd. -trackAnnounce=Now playing **{0}**. Requested by\: **{1}**. +trackAnnounce=\u03a4\u03ce\u03c1\u03b1 \u03c0\u03b1\u03af\u03b6\u03b5\u03b9 **{0}**. \u0396\u03b7\u03c4\u03bf\u03cd\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc\: **{1} **. cmdAccessDenied=\u0394\u03b5\u03bd \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03b5\u03c4\u03b1\u03b9 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7\u03bd \u03b5\u03bd\u03c4\u03bf\u03bb\u03ae\! malRevealAnime={0}\: \u03b7 \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7 \u03b1\u03c0\u03bf\u03ba\u03ac\u03bb\u03c5\u03c8\u03b5 \u03ad\u03bd\u03b1 anime.\n malTitle={0} ** \u03a4\u03af\u03c4\u03bb\u03bf\u03c2\: **{1}\n @@ -181,7 +186,7 @@ langSuccess=\u0391\u03bb\u03bb\u03b1\u03b3\u03ae \u03b3\u03bb\u03ce\u03c3\u03c3\ langInfo=FredBoat \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03b9 \u03c0\u03bf\u03bb\u03bb\u03ad\u03c2 \u03b3\u03bb\u03ce\u03c3\u03c3\u03b5\u03c2 \u03c0\u03bf\u03c5 \u03c3\u03c5\u03bd\u03ad\u03b2\u03b1\u03bb\u03b1\u03bd \u03c7\u03c1\u03ae\u03c3\u03c4\u03b5\u03c2 \u03c0\u03bf\u03c5 \u03bc\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03b5\u03c4\u03b5 \u03bc\u03b5 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7\u03bd \u03b5\u03bd\u03c4\u03bf\u03bb\u03ae. \u039f \u03b4\u03b9\u03b1\u03c7\u03b5\u03b9\u03c1\u03b9\u03c3\u03c4\u03ae\u03c2 \u03c3\u03b5 \u03b1\u03c5\u03c4\u03cc\u03bd \u03c4\u03bf \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03b3\u03bb\u03ce\u03c3\u03c3\u03b1 \u03bc\u03b5 *;;lang .\u0395\u03b4\u03ce \u03b5\u03af\u03bd\u03b1\u03b9 \u03bf \u03c0\u03bb\u03ae\u03c1\u03b7\u03c2 \u03ba\u03b1\u03c4\u03ac\u03bb\u03bf\u03b3\u03bf\u03c2 \u03c4\u03c9\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03c9\u03bd \u03b3\u03bb\u03c9\u03c3\u03c3\u03ce\u03bd\: langDisclaimer=\u039f\u03b9 \u03bc\u03b5\u03c4\u03b1\u03c6\u03c1\u03ac\u03c3\u03b5\u03b9\u03c2 \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 100% \u03b1\u03ba\u03c1\u03b9\u03b2\u03b5\u03af\u03c2 \u03ae \u03c0\u03bb\u03ae\u03c1\u03b5\u03b9\u03c2. \u03a5\u03c0\u03bf\u03bb\u03b5\u03b9\u03c0\u03cc\u03bc\u03b5\u03bd\u03b5\u03c2 \u03bc\u03b5\u03c4\u03b1\u03c6\u03c1\u03ac\u03c3\u03b5\u03b9\u03c2 \u03bc\u03c0\u03bf\u03c1\u03bf\u03cd\u03bd \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b5\u03b9\u03c3\u03c6\u03ad\u03c1\u03bf\u03c5\u03bd \u03c3\u03c4\u03bf < https\://crowdin.com/project/fredboat >. loadSingleTrack=**{0} ** \u03ad\u03c7\u03b5\u03b9 \u03c0\u03c1\u03bf\u03c3\u03c4\u03b5\u03b8\u03b5\u03af \u03c3\u03c4\u03b7\u03bd \u03bf\u03c5\u03c1\u03ac. -loadSingleTrackFirst=**{0}** has been added to the top of the queue. +loadSingleTrackFirst=**{0} ** \u03ad\u03c7\u03b5\u03b9 \u03c0\u03c1\u03bf\u03c3\u03c4\u03b5\u03b8\u03b5\u03af \u03c3\u03c4\u03b7\u03bd \u03bf\u03c5\u03c1\u03ac. loadSingleTrackAndPlay=**{0} ** \u03c4\u03ce\u03c1\u03b1 \u03b8\u03b1 \u03c0\u03b1\u03b9\u03c7\u03c4\u03b5\u03af. invite=\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03bc\u03bf\u03c2 \u03c0\u03c1\u03cc\u03c3\u03ba\u03bb\u03b7\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 **{0}**\: ratelimitedCommandsUser=\u03a3\u03c4\u03ad\u03bb\u03bd\u03b5\u03b9\u03c2 \u03b5\u03bd\u03c4\u03bf\u03bb\u03ad\u03c2 \u03c0\u03ac\u03c1\u03b1 \u03c0\u03bf\u03bb\u03cd \u03b3\u03c1\u03ae\u03b3\u03bf\u03c1\u03b1\! \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03c0\u03b9\u03b2\u03c1\u03b1\u03b4\u03cd\u03bd\u03b5. @@ -239,7 +244,7 @@ helpJoinCommand=\u039a\u03ac\u03bd\u03c4\u03b5 \u03c4\u03bf bot \u03bd\u03b1 \u0 helpLeaveCommand=\u039a\u03ac\u03bd\u03c4\u03b5 \u03c4\u03bf bot \u03bd\u03b1 \u03c6\u03cd\u03b3\u03b5\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf voice channel \u03c3\u03b1\u03c2. helpPauseCommand=\u03a0\u03b1\u03cd\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2. helpPlayCommand=\u0391\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae \u03bc\u03bf\u03c5\u03c3\u03b9\u03ba\u03ae\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03b4\u03b9\u03b4\u03cc\u03bc\u03b5\u03bd\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03ae \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03ad\u03bd\u03b1 \u03ba\u03bf\u03bc\u03bc\u03ac\u03c4\u03b9. \u0393\u03b9\u03b1 \u03bc\u03b9\u03b1 \u03c0\u03bb\u03ae\u03c1\u03b7 \u03bb\u03af\u03c3\u03c4\u03b1 \u03c0\u03b7\u03b3\u03ce\u03bd \u03b5\u03c0\u03b9\u03c3\u03ba\u03b5\u03c6\u03b8\u03b5\u03af\u03c4\u03b5 {0} -helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} +helpPlayNextCommand=\u0391\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae \u03bc\u03bf\u03c5\u03c3\u03b9\u03ba\u03ae\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03ae \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03bc\u03b9\u03b1 \u03c0\u03af\u03c3\u03c4\u03b1. \u039a\u03bf\u03bc\u03bc\u03ac\u03c4\u03b9\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c4\u03af\u03b8\u03b5\u03bd\u03c4\u03b1\u03b9 \u03c3\u03c4\u03b7\u03bd \u03ba\u03bf\u03c1\u03c5\u03c6\u03ae \u03c4\u03b7\u03c2 \u03bf\u03c5\u03c1\u03ac\u03c2. \u0393\u03b9\u03b1 \u03bc\u03b9\u03b1 \u03c0\u03bb\u03ae\u03c1\u03b7 \u03bb\u03af\u03c3\u03c4\u03b1 \u03c0\u03b7\u03b3\u03ce\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03c0\u03b9\u03c3\u03ba\u03b5\u03c6\u03b8\u03b5\u03af\u03c4\u03b5 {0} helpPlaySplitCommand=\u0394\u03b9\u03b1\u03b9\u03c1\u03ad\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03b2\u03af\u03bd\u03c4\u03b5\u03bf YouTube \u03c3\u03b5 \u03bc\u03b9\u03b1 \u03bb\u03af\u03c3\u03c4\u03b1 \u03c4\u03c1\u03b1\u03b3\u03bf\u03c5\u03b4\u03b9\u03ce\u03bd \u03c0\u03bf\u03c5 \u03c0\u03b1\u03c1\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03b7\u03bd \u03c0\u03b5\u03c1\u03b9\u03b3\u03c1\u03b1\u03c6\u03ae \u03c4\u03bf\u03c5. helpRepeatCommand=\u0395\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03b9\u03ce\u03bd \u03b5\u03c0\u03b1\u03bd\u03ac\u03bb\u03b7\u03c8\u03b7\u03c2. helpReshuffleCommand=\u0395\u03c0\u03b1\u03bd\u03ac\u03bb\u03b7\u03c8\u03b7 \u03c4\u03c5\u03c7\u03b1\u03af\u03b1\u03c2 \u03c3\u03b5\u03b9\u03c1\u03ac\u03c2 \u03c3\u03c4\u03b7\u03bd \u03c0\u03b1\u03c1\u03bf\u03cd\u03c3\u03b1 \u03bb\u03af\u03c3\u03c4\u03b1 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2. @@ -249,7 +254,7 @@ helpSkipCommand=\u03a0\u03b1\u03c1\u03b1\u03bb\u03b5\u03af\u03c8\u03b5\u03c4\u03 helpStopCommand=\u03a3\u03c4\u03b1\u03bc\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd player \u03ba\u03b1\u03b9 \u03b1\u03c0\u03b1\u03bb\u03bf\u03b9\u03c6\u03ae \u03c4\u03b7\u03c2 \u03bb\u03af\u03c3\u03c4\u03b1\u03c2 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2. \u03a0\u03c1\u03bf\u03bf\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c3\u03c5\u03bd\u03c4\u03bf\u03bd\u03b9\u03c3\u03c4\u03ad\u03c2 \u03bc\u03b5 \u03ac\u03b4\u03b5\u03b9\u03b1 \u0394\u03b9\u03b1\u03c7\u03b5\u03af\u03c1\u03b9\u03c3\u03b7 \u03bc\u03b7\u03bd\u03c5\u03bc\u03ac\u03c4\u03c9\u03bd. helpUnpauseCommand=\u03a3\u03c5\u03bd\u03ad\u03c7\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2. helpVolumeCommand=\u0391\u03bb\u03bb\u03ac\u03b6\u03b5\u03b9 \u03c4\u03b7\u03bd \u03ad\u03bd\u03c4\u03b1\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03ae\u03c7\u03bf\u03c5. \u039f\u03b9 \u03c4\u03b9\u03bc\u03ad\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 0-150 \u03ba\u03b1\u03b9 100 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b7 \u03c0\u03c1\u03bf\u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae. \u0397 \u03b5\u03bd\u03c4\u03bf\u03bb\u03ae \u03c4\u03bf\u03c5 \u03c4\u03cc\u03bc\u03bf\u03c5 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c4\u03bf \u03b4\u03b7\u03bc\u03cc\u03c3\u03b9\u03bf bot. -helpExportCommand=Export the current queue to a wastebin link, can be later used as a playlist. +helpExportCommand=\u0395\u03be\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03bf\u03c5\u03c1\u03ac \u03c3\u03b5 \u03bc\u03b9\u03b1 hastebin \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7, \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1 \u03c9\u03c2 \u03bb\u03af\u03c3\u03c4\u03b1 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2. helpGensokyoRadioCommand=\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03c4\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03bf\u03c2 \u03c4\u03c1\u03b1\u03b3\u03bf\u03c5\u03b4\u03b9\u03bf\u03cd \u03c0\u03bf\u03c5 \u03c0\u03b1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03bf gensokyoradio.net helpListCommand=\u0395\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03bb\u03af\u03c3\u03c4\u03b1 \u03bc\u03b5 \u03c4\u03b1 \u03c4\u03c1\u03ad\u03c7\u03bf\u03bd\u03c4\u03b1 \u03c4\u03c1\u03b1\u03b3\u03bf\u03cd\u03b4\u03b9\u03b1 \u03c3\u03c4\u03bf playlist. helpHistoryCommand=\u0395\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03b9 \u03bc\u03b9\u03b1 \u03bb\u03af\u03c3\u03c4\u03b1 \u03bc\u03b5 \u03c4\u03b1 \u03c4\u03c1\u03b1\u03b3\u03bf\u03cd\u03b4\u03b9\u03b1 \u03c3\u03c4\u03bf \u03b9\u03c3\u03c4\u03bf\u03c1\u03b9\u03ba\u03cc \u03c4\u03b7\u03c2 \u03bb\u03af\u03c3\u03c4\u03b1\u03c2 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2. @@ -273,7 +278,7 @@ helpUserInfoCommand=\u0395\u03bc\u03c6\u03b1\u03bd\u03af\u03b6\u03b5\u03b9 \u03c helpPerms=\u0395\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03b5\u03b9 \u03c4\u03bf whitelisting \u03bc\u03b5\u03bb\u03ce\u03bd \u03ba\u03b1\u03b9 \u03c1\u03cc\u03bb\u03c9\u03bd \u03b3\u03b9\u03b1 \u03c4\u03bf \u03b2\u03b1\u03b8\u03bc\u03cc \u03c4\u03bf\u03c5 {0}. helpPrefixCommand=\u039f\u03c1\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7 prefix \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf guild. helpVoteSkip=\u03a8\u03b7\u03c6\u03af\u03b6\u03b5\u03b9 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03bc\u03c8\u03b5\u03b9 \u03c4\u03bf \u03c4\u03c1\u03ad\u03c7\u03bf\u03bd \u03c4\u03c1\u03b1\u03b3\u03bf\u03cd\u03b4\u03b9. \u03a7\u03c1\u03b5\u03b9\u03ac\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c4\u03bf 50% \u03cc\u03bb\u03c9\u03bd \u03c4\u03c9\u03bd \u03c7\u03c1\u03b7\u03c3\u03c4\u03ce\u03bd \u03bc\u03b5 \u03c4\u03b7 \u03b4\u03c5\u03bd\u03b1\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1 \u03c6\u03c9\u03bd\u03b7\u03c4\u03b9\u03ba\u03ae\u03c2 \u03c3\u03c5\u03bd\u03bf\u03bc\u03b9\u03bb\u03af\u03b1\u03c2 \u03bd\u03b1 \u03c8\u03b7\u03c6\u03af\u03c3\u03bf\u03c5\u03bd. -helpUnvoteSkip=Remove your vote to skip the current song. +helpUnvoteSkip=\u0393\u03b9\u03b1 \u03bd\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03c8\u03ae\u03c6\u03bf \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03bc\u03c8\u03b5\u03c4\u03b5 \u03c4\u03bf \u03c4\u03c1\u03ad\u03c7\u03bf\u03bd \u03c4\u03c1\u03b1\u03b3\u03bf\u03cd\u03b4\u03b9. helpMathOperationAdd=\u0395\u03ba\u03c4\u03c5\u03c0\u03ce\u03bd\u03b5\u03b9 \u03c4\u03bf \u03ac\u03b8\u03c1\u03bf\u03b9\u03c3\u03bc\u03b1 \u03c4\u03c9\u03bd num1 \u03ba\u03b1\u03b9 num2. helpMathOperationSub=\u0395\u03ba\u03c4\u03c5\u03c0\u03ce\u03bd\u03b5\u03b9 \u03c4\u03b7 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03ac \u03c4\u03b7\u03c2 \u03b1\u03c6\u03b1\u03af\u03c1\u03b5\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 num2 \u03b1\u03c0\u03cc \u03c4\u03bf num1. helpMathOperationMult=\u0395\u03ba\u03c4\u03c5\u03c0\u03ce\u03bd\u03b5\u03b9 \u03c4\u03bf \u03b3\u03b9\u03bd\u03cc\u03bc\u03b5\u03bd\u03bf \u03c4\u03b7\u03c2 \u03c0\u03c1\u03ac\u03be\u03b7\u03c2 num1 * num2. @@ -302,8 +307,8 @@ skipUserMultiple=\u03a0\u03b1\u03c1\u03b1\u03bb\u03b5\u03af\u03c0\u03b5\u03c4\u0 skipUsersMultiple=\u03a0\u03b1\u03c1\u03b1\u03bb\u03b5\u03af\u03c0\u03b5\u03c4\u03b1\u03b9 {0} \u03ba\u03bf\u03bc\u03bc\u03ac\u03c4\u03b9\u03b1 \u03c0\u03bf\u03c5 \u03c0\u03c1\u03bf\u03c3\u03c4\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03b1\u03c0\u03cc {1} \u03c7\u03c1\u03ae\u03c3\u03c4\u03b5\u03c2. skipUserNoTracks=\u039a\u03b1\u03bd\u03ad\u03bd\u03b1\u03c2 \u03b1\u03c0\u03cc \u03c4\u03bf\u03c5\u03c2 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b5\u03c2 \u03c0\u03bf\u03c5 \u03b1\u03bd\u03b1\u03c6\u03ad\u03c1\u03b8\u03b7\u03ba\u03b1\u03bd \u03b4\u03b5\u03bd \u03ad\u03c7\u03bf\u03c5\u03bd \u03bf\u03c0\u03bf\u03b9\u03b1\u03b4\u03ae\u03c0\u03bf\u03c4\u03b5 \u03ba\u03bf\u03bc\u03bc\u03ac\u03c4\u03b9\u03b1 \u03c3\u03c4\u03b7\u03bd \u03bf\u03c5\u03c1\u03ac. voteSkipAdded=\u0397 \u03c8\u03ae\u03c6\u03bf\u03c2 \u03c3\u03b1\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03c0\u03c1\u03bf\u03c3\u03c4\u03b5\u03b8\u03b5\u03af\! -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! +voteSkipRemoved=\u0397 \u03c8\u03ae\u03c6\u03bf\u03c2 \u03c3\u03b1\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03b1\u03c6\u03b1\u03b9\u03c1\u03b5\u03b8\u03b5\u03af\! +voteSkipNotFound=\u0388\u03c7\u03b5\u03c4\u03b5 \u03ae\u03b4\u03b7 \u03c8\u03b7\u03c6\u03af\u03c3\u03b5\u03b9 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03bc\u03c8\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03ba\u03bf\u03bc\u03bc\u03ac\u03c4\u03b9\! voteSkipAlreadyVoted=\u0388\u03c7\u03b5\u03c4\u03b5 \u03ae\u03b4\u03b7 \u03c8\u03b7\u03c6\u03af\u03c3\u03b5\u03b9 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03bc\u03c8\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03ba\u03bf\u03bc\u03bc\u03ac\u03c4\u03b9\! voteSkipSkipping={0} \u03c8\u03ae\u03c6\u03b9\u03c3\u03b1\u03bd \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bb\u03b5\u03af\u03c8\u03b5\u03c4\u03b5. \u03a0\u03b1\u03c1\u03b1\u03ba\u03ac\u03bc\u03c0\u03c4\u03bf\u03bd\u03c4\u03b1\u03c2 \u03c4\u03bf \u03ba\u03bf\u03bc\u03bc\u03ac\u03c4\u03b9 {1}. voteSkipNotEnough={0} \u03c8\u03ae\u03c6\u03b9\u03c3\u03b1\u03bd \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03bb\u03b5\u03af\u03c8\u03b5\u03c4\u03b5. \u03a4\u03bf\u03c5\u03bb\u03ac\u03c7\u03b9\u03c3\u03c4\u03bf\u03bd {1} \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9. diff --git a/FredBoat/src/main/resources/lang/en_PT.properties b/FredBoat/src/main/resources/lang/en_PT.properties index 5977a9e69..947046447 100644 --- a/FredBoat/src/main/resources/lang/en_PT.properties +++ b/FredBoat/src/main/resources/lang/en_PT.properties @@ -62,7 +62,7 @@ npDescription=Description npLoadedSoundcloud=[{0}/{1}]\n\nStole th'' item from Soundcloud npLoadedBandcamp={0}\n\nStole th'' item from Bandcamp npLoadedTwitch=Stole th' item from Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=I be lackin' this perm for that one\: permissionMissingInvoker=I be lackin' this perm for that one\: permissionEmbedLinks=Embed Links @@ -92,6 +92,11 @@ loadPlaylistTooMany=Added {0} tracks. You added so much songs that i just can''t loadErrorCommon=Error occurred when loading info for `{0}`\:\n{1} loadErrorSusp=Suspicious error when loading info for `{0}`. loadQueueTrackLimit=Cap says no more than {0} tracks at a time\!\nThis be to stop us sinkin''\! +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=The playlist **{0}** has `{1}` tracks\! Be patient while we navigate these waters. playerUserNotInChannel=You join, I join\! I'm not feeling very safe when I'm lonely in the cabin. playerJoinConnectDenied=You told me i'm not alowed to be there\! then it's stupid to aks me if i wanna join \:expressionless\: @@ -140,14 +145,14 @@ configSetTo=now be set t'' `{0}`. configUnknownKey={0}\: Th'' skull key be unknown\! configMustBeBoolean={0} Valur must be true er'' false. modReason=Yer reason -modAuditLogMessage=Action issued by {0} against {1} -modAudioLogNoReason=No reason provided. -modFailUserHierarchy=You do not have a higher role than {0}. -modFailBotHierarchy=I need to have a higher role than {0}. -modBanlistFail=Failed to fetch the ban list. -modBanFail=Failed to ban {0} -modUnbanFail=Failed to unban {0} -modUnbanFailNotBanned=User {0} is not banned in this guild. +modAuditLogMessage=Shiver me timbers\! Action issued by {0} against {1} +modAudioLogNoReason=Blimey\! No reason provided. +modFailUserHierarchy=Blimey\! Ye do not have a higher role than {0}. +modFailBotHierarchy=Shiver me timbers\! I need to have a higher role than {0}. +modBanlistFail=Ahoy\! Failed to fetch thee ban list. +modBanFail=Well shave me belly with a rusty razor\! Failed to ban {0} +modUnbanFail=Yo-ho-ho\! Failed to unban {0} +modUnbanFailNotBanned=Shiver me timbers\! User {0} is not banned in this crew. modKeepMessages=Add {0} to not delete any messages. modActionTargetDmKicked=Ahoy\! You have been kicked from the guild {0} by user {1}. modActionTargetDmBanned=Ahoy\! You have been banned from the guild {0} by user {1}. diff --git a/FredBoat/src/main/resources/lang/en_TS.properties b/FredBoat/src/main/resources/lang/en_TS.properties index 8c218b586..ded724105 100644 --- a/FredBoat/src/main/resources/lang/en_TS.properties +++ b/FredBoat/src/main/resources/lang/en_TS.properties @@ -62,7 +62,7 @@ npDescription=Description npLoadedSoundcloud=[{0}/{1}]\n\nand it is from Soundcloud\! npLoadedBandcamp={0}\n\nLoaded from Bandcamp npLoadedTwitch=That was from Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=How am I supposed to do that if you don't give me permission, to\!? I need this\: permissionMissingInvoker=You need the following permission to perform that action\: permissionEmbedLinks=Embed Links @@ -92,6 +92,11 @@ loadPlaylistTooMany=I added {0} tracks... But, there''s too many. No way am I ty loadErrorCommon=Something''s not right\! I couldn''t load the info for `{0}`\: \n{1}\nMust be your fault.. m-mostly. loadErrorSusp=That''s.. weird. It''s strange and I don''t like it\! I couldn''t load the info for '' {0} ''. loadQueueTrackLimit=You can''t add tracks to a queue with more than {0} tracks\! This is to prevent abuse. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=About to load playlist **{0}** with up to `{1}` tracks. This may take a while, please be patient. playerUserNotInChannel=You want me to play a song and are not even in a voice channel to listen to it?\! Go join a voice channel first... playerJoinConnectDenied=I won't go into that voice channel because you want me to\! diff --git a/FredBoat/src/main/resources/lang/es_ES.properties b/FredBoat/src/main/resources/lang/es_ES.properties index 9b8c402b7..ca0de6a0a 100644 --- a/FredBoat/src/main/resources/lang/es_ES.properties +++ b/FredBoat/src/main/resources/lang/es_ES.properties @@ -62,7 +62,7 @@ npDescription=Descripci\u00f3n npLoadedSoundcloud=[{0}/{1}]\n\nCargado desde Soundcloud npLoadedBandcamp={0}\n\nCargado desde Bandcamp npLoadedTwitch=Cargado desde Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Necesito el siguiente permiso para realizar esa acci\u00f3n\: permissionMissingInvoker=Necesita el siguiente permiso para realizar esa acci\u00f3n\: permissionEmbedLinks=Insertar enlaces @@ -92,6 +92,11 @@ loadPlaylistTooMany=Se han a\u00f1adido {0} pistas. Se han encontrado demasiadas loadErrorCommon=Hubo un error al cargar informaci\u00f3n desde `{0}`\:\n{1} loadErrorSusp=Sospechoso error al cargar informaci\u00f3n para `{0}`. loadQueueTrackLimit=\u00a1No se puede agregar pistas a una cola con m\u00e1s de {0} pistas\! Esto es para prevenir el abuso. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Para cargar listas de reproducci\u00f3n **{0} ** con hasta pistas de ''{1}''. Esto puede tardar un poco, por favor se paciente. playerUserNotInChannel=Usted debe unirse a un canal de voz primero. playerJoinConnectDenied=No tengo permitido conectarme a ese canal de voz. @@ -273,7 +278,7 @@ helpUserInfoCommand=Mostrar informaci\u00f3n acerca de usted o un usuario conoci helpPerms=Permite la inclusi\u00f3n de miembros y roles para el rango {0}. helpPrefixCommand=Configura el prefijo para este servidor. helpVoteSkip=Vote para saltar la canci\u00f3n actual. Se necesita 50% de todos los usuarios en el chat de voz para que voten. -helpUnvoteSkip=Remove your vote to skip the current song. +helpUnvoteSkip=Quitar su voto para saltar la canci\u00f3n actual. helpMathOperationAdd=Imprimir la suma de num1 y num2. helpMathOperationSub=Imprimir la diferencia de restar num2 de num1. helpMathOperationMult=Imprimir el producto de num1*num2. @@ -302,8 +307,8 @@ skipUserMultiple=Saltadas {0} pistas a\u00f1adidas por {1}. skipUsersMultiple=Saltadas {0} pistas a\u00f1adidas por {1} usuarios. skipUserNoTracks=Ninguno de los usuarios mencionados tiene alguna pista en cola. voteSkipAdded=\u00a1Tu voto ha sido a\u00f1adido\! -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! +voteSkipRemoved=\u00a1Tu voto ha sido eliminado\! +voteSkipNotFound=\u00a1No has votado para saltar esta pista\! voteSkipAlreadyVoted=\u00a1Ya votaste para saltar esta canci\u00f3n\! voteSkipSkipping={0} han votado para saltar. Saltando canci\u00f3n {1}. voteSkipNotEnough={0} han votado para saltar. Se nececitan {1} m\u00ednimo. diff --git a/FredBoat/src/main/resources/lang/et_EE.properties b/FredBoat/src/main/resources/lang/et_EE.properties index 544d0dc36..683a92ef0 100644 --- a/FredBoat/src/main/resources/lang/et_EE.properties +++ b/FredBoat/src/main/resources/lang/et_EE.properties @@ -62,7 +62,7 @@ npDescription=Kirjeldus npLoadedSoundcloud=[{0}/{1}] Laaditud Soundcloudi npLoadedBandcamp={0} Laaditud Bandicampist npLoadedTwitch=Laetud Twitchist -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Mul on vaja selle toimingu sooritamiseks j\u00e4rgmiseid \u00f5igusi\: permissionMissingInvoker=Sul on vaja selle toimingu kasutamiseks antud \u00f5igusi\: permissionEmbedLinks=Manustatud Lingid @@ -92,6 +92,11 @@ loadPlaylistTooMany=Lisatud {0} lugu. Leitud liiga palju lugusid mida n\u00e4ida loadErrorCommon=Tekkis viga selle info laadimisel\: `{0}`\:\n{1} loadErrorSusp=Kahtlane viga kui laadida infot selle jaoks\: `{0}`. loadQueueTrackLimit=Sa ei saa lisada rohkem kui {0} lugu\! See on, et v\u00e4ltida v\u00e4\u00e4rkasutust. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Laen playlisti **{0}** kus on kuni `{1}` lugu. See v\u00f5ib veidi aega v\u00f5tta, palun ole kannatlik. playerUserNotInChannel=Sa pead enne h\u00e4\u00e4lkanalis viibima. playerJoinConnectDenied=Mul ei ole lubatud selle h\u00e4\u00e4lkanaliga liituda. @@ -181,7 +186,7 @@ langSuccess=Switched to speaking {0}. langInfo=FredBoat toetab mitu kasutajate poolt lisatud teksti, mida saate valida selle k\u00e4suga. Administraatorid saavad valida keelt k\u00e4suga "; lang " Siin on t\u00e4ielik nimekiri toetatud keeletest\: langDisclaimer=T\u00f5lked ei pruugi olla 100% t\u00e4psed ja t\u00e4ielikud. Puuduvad t\u00f5lkeid saab lisada lehek\u00fcljel . loadSingleTrack=**{0}** on lisatud j\u00e4rjekorda. -loadSingleTrackFirst=**{0}** has been added to the top of the queue. +loadSingleTrackFirst=**{0}** on lisatud j\u00e4rjekorda. loadSingleTrackAndPlay=**{0}** hakkab n\u00fc\u00fcd m\u00e4ngima. invite=Kutse link **{0}**-ile\: ratelimitedCommandsUser=Te saadate k\u00e4ske liiga kiirelt\! Palun v\u00f5tke aeglasemalt. diff --git a/FredBoat/src/main/resources/lang/fa_IR.properties b/FredBoat/src/main/resources/lang/fa_IR.properties index 233f66a31..0a7277b59 100644 --- a/FredBoat/src/main/resources/lang/fa_IR.properties +++ b/FredBoat/src/main/resources/lang/fa_IR.properties @@ -62,7 +62,7 @@ npDescription=\u062a\u0648\u0636\u06cc\u062d\u0627\u062a npLoadedSoundcloud=[{1}/{0}]\n\n\u0644\u0648\u062f \u0634\u062f\u0647 \u0627\u0632 \u0633\u0631\u0648\u06cc\u0633 Soundcloud npLoadedBandcamp={0}\n\n\u0644\u0648\u062f \u0634\u062f\u0647 \u0627\u0632 \u0633\u0631\u0648\u06cc\u0633 Bandcamp npLoadedTwitch=\u0644\u0648\u062f \u0634\u062f\u0647 \u0627\u0632 \u0633\u0631\u0648\u06cc\u0633 Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=\u0645\u0646 \u0646\u06cc\u0627\u0632 \u0628\u0647 \u0645\u062c\u0648\u0632 \u0632\u06cc\u0631 \u0628\u0631\u0627\u06cc \u0627\u0646\u062c\u0627\u0645 \u0627\u06cc\u0646 \u06a9\u0627\u0631 \u062f\u0627\u0631\u0645\: permissionMissingInvoker=\u0634\u0645\u0627 \u0628\u0647 \u0645\u062c\u0648\u0632 \u0632\u06cc\u0631 \u0628\u0631\u0627\u06cc \u0627\u0646\u062c\u0627\u0645 \u0627\u06cc\u0646 \u06a9\u0627\u0631 \u0646\u06cc\u0627\u0632 \u062f\u0627\u0631\u06cc\u062f\: permissionEmbedLinks=\u0644\u06cc\u0646\u06a9 \u0647\u0627\u06cc \u0642\u0631\u0627\u0631 \u062f\u0627\u062f\u0647 \u0634\u062f\u0647 @@ -92,6 +92,11 @@ loadPlaylistTooMany={0} \u0622\u0647\u0646\u06af \u0627\u0636\u0627\u0641\u0647 loadErrorCommon=\u062e\u0637\u0627\u06cc\u06cc \u0647\u0646\u06af\u0627\u0645 \u0644\u0648\u062f \u0627\u0637\u0644\u0627\u0639\u0627\u062a \u0628\u0631\u0627\u06cc `{0}` \u0631\u062e \u062f\u0627\u062f\:\n{1} loadErrorSusp=\u062e\u0637\u0627\u06cc\u06cc \u0645\u0634\u06a9\u0648\u06a9 \u0647\u0646\u06af\u0627\u0645 \u0644\u0648\u062f \u0627\u0637\u0644\u0627\u0639\u0627\u062a \u0628\u0631\u0627\u06cc `{0}`. loadQueueTrackLimit=\u0634\u0645\u0627 \u0642\u0627\u062f\u0631 \u0628\u0647 \u0627\u0636\u0627\u0641\u0647 \u06a9\u0631\u062f\u0646 \u0622\u0647\u0646\u06af \u0628\u0647 \u0635\u0641 \u0628\u0627 \u0628\u06cc\u0634 \u0627\u0632 {0} \u0622\u0647\u0646\u06af \u0646\u0645\u06cc \u0628\u0627\u0634\u06cc\u062f\! \u0627\u06cc\u0646 \u0628\u0631\u0627\u06cc \u062c\u0644\u0648\u06af\u06cc\u0631\u06cc \u0627\u0632 \u0633\u0648\u0621 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0627\u0633\u062a. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=About to load playlist **{0}** with up to `{1}` tracks. This may take a while, please be patient. playerUserNotInChannel=\u0634\u0645\u0627 \u0628\u0627\u06cc\u062f \u0627\u0628\u062a\u062f\u0627 \u0648\u0627\u0631\u062f \u06cc\u06a9 \u0686\u0646\u0644 \u0635\u0648\u062a\u06cc \u0634\u0648\u06cc\u062f. playerJoinConnectDenied=\u0645\u0646 \u0645\u062c\u0648\u0632 \u06a9\u0627\u0641\u06cc \u0628\u0631\u0627\u06cc \u0627\u062a\u0635\u0627\u0644 \u0628\u0647 \u0622\u0646 \u0686\u0646\u0644 \u0635\u0648\u062a\u06cc \u0631\u0627 \u0646\u062f\u0627\u0631\u0645. diff --git a/FredBoat/src/main/resources/lang/fi_FI.properties b/FredBoat/src/main/resources/lang/fi_FI.properties index b9c2de018..d4ac21e83 100644 --- a/FredBoat/src/main/resources/lang/fi_FI.properties +++ b/FredBoat/src/main/resources/lang/fi_FI.properties @@ -62,7 +62,7 @@ npDescription=Kuvaus npLoadedSoundcloud=[{0}/{1}]\n\nLadattu Soundcloudista npLoadedBandcamp={0}\n\nLadattu Bandcampist\u00e4 npLoadedTwitch=Ladattu Twitchist\u00e4 -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Tarvitsen tuon toimenpiteen suorittamiseen seuraavat k\u00e4ytt\u00f6oikeudet\: permissionMissingInvoker=Tarvitsen tuon toimenpiteen suorittamiseen seuraavat k\u00e4ytt\u00f6oikeudet\: permissionEmbedLinks=Upottaa linkkej\u00e4 @@ -92,6 +92,11 @@ loadPlaylistTooMany=Lis\u00e4tty {0} kappaletta. L\u00f6ytyi liian monta kappale loadErrorCommon=Virhe ladattaessa tietoja `{0}`\:\n{1} loadErrorSusp=Ep\u00e4ilytt\u00e4v\u00e4 virhe ladattaessa tietoja `{0}`. loadQueueTrackLimit=Et voi lis\u00e4t\u00e4 kappaleita soittolistaan jossa on jo {0} kappaletta\! T\u00e4m\u00e4 on v\u00e4\u00e4rink\u00e4yt\u00f6n est\u00e4miseksi. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Soittolistan **{0}** jossa on `{1}` kappaletta, lataus on aloitettu. T\u00e4ss\u00e4 voi kest\u00e4\u00e4 jonkin aikaa, olkaa k\u00e4rsiv\u00e4llisi\u00e4, kiitos. playerUserNotInChannel=Sinun pit\u00e4\u00e4 liitty\u00e4 ensin \u00e4\u00e4nikanavalle. playerJoinConnectDenied=Minulla ei ole oikeuksia liitty\u00e4 kyseiselle \u00e4\u00e4nikanavalle. diff --git a/FredBoat/src/main/resources/lang/fil_PH.properties b/FredBoat/src/main/resources/lang/fil_PH.properties index 63766bf28..2a6ef8874 100644 --- a/FredBoat/src/main/resources/lang/fil_PH.properties +++ b/FredBoat/src/main/resources/lang/fil_PH.properties @@ -62,9 +62,9 @@ npDescription=Description npLoadedSoundcloud=[{0}/{1}]\n\nNakuha mula sa Soundcloud npLoadedBandcamp={0}\n\nNakuha mula sa Bandcamp npLoadedTwitch=Nakuha mula sa Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Kaylangan ko ang mga sumununod na pahintulot para maisagawa ang pagkilos\: -permissionMissingInvoker=Kailangan mo ang sumusunod na pahintulot para ma-isagawa itong pagkilos\: +permissionMissingInvoker=Kailangan mo ng pahintulot para ma-isagawa itong pagkilos\: permissionEmbedLinks=I kabit ang Link rating=Marka listeners=Mga nakikinig @@ -92,6 +92,11 @@ loadPlaylistTooMany=Nagdagdag ng {0} tracks. Masyado madaming tracks ang nahanap loadErrorCommon=May nangyaring error nung nag-load ng info para sa `{0}`\: {1} loadErrorSusp=Kahina-hinalang error kapag nag-loload ng info para sa ''{0}''. loadQueueTrackLimit=Hindi ka maari magdagdag ng tracks na may {0} karaming tracks\! Ito''y para maiwasan ang pang-aabuso. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Mag-loload ng playlist na **{0}** na may `{1}` kadaming tracks. Ito''y maaaring tumagal ng ilang sandali, maaari pong pagpasensyahan. playerUserNotInChannel=Kailangan mo muna sumali sa isang voice channel. playerJoinConnectDenied=Wala akong pahintulot na kumonekta sa voice channel na iyan. @@ -103,7 +108,7 @@ shutdownRestarting=Mag-rerestart lamang ang FredBoat\u266a\u266a. Ito ay magtata shutdownIndef=Isasara muna ang FredBoat\u266a\u266a. Kapag bumalik ang bot, ang kasalukuyang playlist ay ibabalik. shutdownPersistenceFail=Nagkaroon ng error sa pag-save ng persistence file\: {0} reloadSuccess=Ni-rereload ang playlist. `{0}` tracks ang nahanap. -trackAnnounce=Now playing **{0}**. Requested by\: **{1}**. +trackAnnounce=Tumutugtog **{0}** Gusto ni\: **{1}**. cmdAccessDenied=Hindi mo pwedeng gamitin ang command na iyan\! malRevealAnime={0}\: Ang pagsusuri ay naglahad ng isang anime.\n malTitle={0}**Pamagat\: **{1}\n @@ -140,31 +145,31 @@ configSetTo=ito ay nakatakda na ngayon `{0}`. configUnknownKey={0}\: Hindi kilala na susi. configMustBeBoolean={0}\: Ang halaga ay kailangan tama or mali. modReason=Dahilan -modAuditLogMessage=Aksyon na inisyu ng {0} laban sa {1} +modAuditLogMessage=Aksyon na inisyu ni {0} laban kay {1} modAudioLogNoReason=Walang rason na ibinigay. modFailUserHierarchy=Wala ka ng mataas na tungkulin kasya sa {0}. modFailBotHierarchy=Kailangan kong magkaroon ng mas mataas na tungkulin kaysa sa {0}. modBanlistFail=Bigong makuha ang listahan ng ban. modBanFail=Nabigo sa pagbabawal sa {0} -modUnbanFail=Bigo para i-unban {0} -modUnbanFailNotBanned=Gumagamit na {0} ay hindi bawal sa guild na ito. -modKeepMessages=Idagdag ang {0} para hindi mabura ang anumang mga mensahe. -modActionTargetDmKicked=Ahoy\! Ikaw ay inalis mula sa guild {0} ng gumagamit {1}. -modActionTargetDmBanned=Ahoy\! Ikaw ay pinagbawalan na sa guild {0} ng gumagamit {1}. -kickSuccess=Gumagamit {0} ay inalis na. +modUnbanFail=Nabigo para i-unban {0} +modUnbanFailNotBanned=Si {0} ay hindi bawal sa guild na ito. +modKeepMessages=Idagdag si {0} para hindi mabura ang anumang mga mensahe. +modActionTargetDmKicked=Ahoy\! Ikaw ay inalis mula sa guild {0} dahil ni {1}. +modActionTargetDmBanned=Ahoy\! Ikaw ay pinagbawalan na sa guild {0} dahil ni {1}. +kickSuccess=Si {0} ay inalis na. kickFail=Nabigo sa pagsipa {0} kickFailSelf=Hindi mo kayang sipain ang iyong sarili. kickFailOwner=Hindi mo kayang sipain ang may ari ng serber. kickFailMyself=Hindi ko kayang sipain ang aking sarili. -softbanSuccess=Gumagamit {0} ay naging softbanned. +softbanSuccess=Si {0} ay na softbanned. softbanFailSelf=Hindi mo kayang pansamantalang pagbawalan ang iyong sarili. softbanFailOwner=Hindi mo kayang pansamantalang pagbawalan ang may ari ng serber. softbanFailMyself=Hindi ko kayang pagbawalan ng pansamantala ang aking sarili. -hardbanSuccess=Gumagamit {0} ay ipinagbawal na. +hardbanSuccess=Si {0} ay na-ban na. hardbanFailSelf=Hindi mo kayang pagbawalan ang iyong sarili. hardbanFailOwner=Hindi mo kayang pagbawalan ang may ari ng serber. hardbanFailMyself=Hindi ko kayang pagbawalan ang aking sarili. -unbanSuccess=Gumagamit {0} ay hindi na binabawalan. +unbanSuccess=Si {0} ay hindi na ban. getidSuccess=Ang id ng mga samahan ay {0}. Ang id ng tsanel ng tekstong ito ay {1}. statsParagraph=\ Ang bot na ito ay tumatakbo para sa {0} araw, {1} oras, {2} minuto at {3} segundo. Ang bot na ito ay magsasagawa ng {4} utos sa sesyong ito. statsRate={0} Yan ang halaga ng {1} ng utos kada oras @@ -181,11 +186,11 @@ langSuccess=Lumipat para sa pagsasalit {0}. langInfo=Ang FredBoat ay sinusuportahan ang mga gumagamit, nag-ambag ng wikang iyong mapipili sa komandong ito. Ang taga pangasiwa sa serber na ito ay may kakayahang pumili ng wika ng `;;lang ` Ito ang kabuoang listahan ng mga suportadong wika\: langDisclaimer=Ang pagpapalit ay maaaring hindi 100% na tumpak o kumpleto. Ang pagpapalit na nawawala ay maaaring naiambag dito sa . loadSingleTrack=**{0}** ay naidagdag na sa pagpipilian. -loadSingleTrackFirst=**{0}** has been added to the top of the queue. +loadSingleTrackFirst=**{0}** ay naidagdag sa taas ng pila. loadSingleTrackAndPlay=**{0}** ay tumutugtog ngayon. invite=Mag-imbita ng mga link para sa **{0}**\: -ratelimitedCommandsUser=Mabilis po masiyado ang pagpapadala mo ng mga utos\! Mangyaring dahanin mo po. -ratelimitedCommandsGuild=Ang guild ay nagpapadala ng mga iniuutos ng masyadong mabilis\! Mangyaring mabagal ang pagtype ninyo. +ratelimitedCommandsUser=Mabilis masiyado ang pagpapadala mo ng mga utos\! dahan-dahanin mo. +ratelimitedCommandsGuild=Itong guild ay nagpapada ng mga iniuttos ng masyadong mabilis\! Dahan-dahan magtype. ratelimitedSkipCommand=Maaaring mong laktawan ng higit sa isa ang mga awitin sa pamamagitan ng pindutang ito\: {0} ratelimitedGuildSlowLoadingPlaylist=Ang serber na ito ay pansamantalang hindi pwedeng mag dagdag ng maraming palatugtogan. Pakiusap huwag iispam ang mahabang palatugtogan. unblacklisted=Tinanggal {0} mula sa pagkakablaklist. @@ -239,7 +244,7 @@ helpJoinCommand=Gumawa ang bot para sumali sa inyong kasalukuyang tsanel ng tini helpLeaveCommand=Nagawa na bot na iwanan ang kasalukuyang tsanel ng tinig. helpPauseCommand=Ihinto ang musiko. helpPlayCommand=Magpatugtog ng musika mula sa ibinigay na URL o kaya ay humanap sa trak. Para sa kabuong listahan ng mapagkukunan mangyaring bisitahin ang {0} -helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} +helpPlayNextCommand=Itutog ang musika na galing sa URL o humanap ng track. Idinadagdag ang mga track sa taas ng pila. Para sa buong listahan ng pinagmulaan ng musika bisitahin ang {0} helpPlaySplitCommand=Nahati ang bidyo sa YouTube sa traklis na inilalalaan sa paglalarawan. helpRepeatCommand=Magpaulit-ulit sa pagitan ng inulit na modes. helpReshuffleCommand=Pag-aayos sa kasalukuyang pila. @@ -249,7 +254,7 @@ helpSkipCommand=Laktawan ang kasalukuyang awitin, ang n'th na awitin sa mga pagp helpStopCommand=Ihinto ang pagpapatugtutog at linisin ang listahan. Nakareserba para sa moderators sa pahintulot ng pangangasiwa ng mga mensahe. helpUnpauseCommand=Alisin sa pagkakahinto ang tugtog. helpVolumeCommand=Hinaan ang tunog. Ang halaga ay 0-150 at 100 ang pangunahin. Ang tunog ay naririnig sa publiko ng bot. -helpExportCommand=Export the current queue to a wastebin link, can be later used as a playlist. +helpExportCommand=I-eksport ang kalusukuyang queue sa wastebin link, ito ay pwedeng gamitin uli para sa queue. helpGensokyoRadioCommand=Ipakita ang kasalukuyang awiting tumutugtog sa gensokyoradio.net helpListCommand=Ihayag ang kasalukuyang listahan ng mga kana sa palagtugtugan. helpHistoryCommand=Ihayag ang listahan ng mga awitin sa kasaysayan ng palatugtugan. @@ -273,7 +278,7 @@ helpUserInfoCommand=Ihayag ang imporsyon tungkol sa iyong sarili o kaya ay mga g helpPerms=Payagan ang mga miyembro ng putinglistahan at gampanan para sa {0} ranggo. helpPrefixCommand=Itakda ang unlapit para sa guild na ito. helpVoteSkip=Bumoto para malaktawan ang kasalukuyang awitin. Kailangan ang 50% ng lahat ng gumagamit ng tinig sa usapan upang bumoto. -helpUnvoteSkip=Remove your vote to skip the current song. +helpUnvoteSkip=Tangalin ang iyong boto para alisin ang kasalukuyang musika. helpMathOperationAdd=I-print ang kabuuan ng num1 at num2. helpMathOperationSub=I-print ang pagkakaiba ng pagbawas ng num2 mula sa num1. helpMathOperationMult=I-print ang produkto ng num1 * num2. @@ -302,8 +307,8 @@ skipUserMultiple=Nilaktawan {0} ang mga track na idinagdag ng {1}. skipUsersMultiple=Nilaktaw {0} ang mga track na idinagdag ng {1} mga gumagamit. skipUserNoTracks=Wala sa mga nabangit na mga gumagamit ay may anumang mga track naka-pila. voteSkipAdded=Ang iyong boto ay idinagdag na\! -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! +voteSkipRemoved=Ang iyong boto ay inalis na\! +voteSkipNotFound=Hindi ka nagboto para tangalin ang track na ito\! voteSkipAlreadyVoted=Ikaw ay naka boto na laktawan ang track na ito\! voteSkipSkipping={0} Binoto na upang laktawan. laktaw na track {1}. voteSkipNotEnough={0} Binoto na upang laktawan. kahit {1} kailangan. @@ -331,7 +336,7 @@ moduleShowCommands=Sabihin {0} upang makita ang mga command ng modyul na ito. modulesCommands=Sabihin {0} Magpakita ng mga command para sa isang modyul na ito, o {1} ipakita ang lahat ng mga command. modulesEnabledInGuild=Paganahin ang modyul para sa guild na ito\: modulesHowTo=Sabihin {0} para paganahin/huwag paganahin ang mga modyul. -parseNotAUser=Iyong input na {0} hindi nagbunga ng mga user ng pagtatalo. -parseNotAMember=Gumagamit na {0} ay hindi isang miyembro ng guild na ito. -parseSnowflakeIdHelp=Nagkakaproblema ka ba sa pagkuha ng id ng isang user/message/channel/may iba? Suriin ang mga docunento ng pagtatalo sa {0} +parseNotAUser=Iyong input na {0} ay hindi tunay na Discord user. +parseNotAMember=Gumagamit na si {0} ay hindi isang miyembro ng guild na ito. +parseSnowflakeIdHelp=Nagkakaproblema ka ba sa pagkuha ng id ng isang user/message/channel/may iba? Suriin ang mga documento para sa Discord sa {0} diff --git a/FredBoat/src/main/resources/lang/fr_FR.properties b/FredBoat/src/main/resources/lang/fr_FR.properties index 0b24671d3..60043deb0 100644 --- a/FredBoat/src/main/resources/lang/fr_FR.properties +++ b/FredBoat/src/main/resources/lang/fr_FR.properties @@ -62,7 +62,7 @@ npDescription=Description npLoadedSoundcloud=[{0}/{1}] \n\nCharg\u00e9 depuis Soundcloud npLoadedBandcamp={0}\n\n charg\u00e9 depuis Bandcamp npLoadedTwitch=Charg\u00e9 depuis Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=J'ai besoin de l'autorisation suivante pour ex\u00e9cuter l'action suivante \: permissionMissingInvoker=Vous avez besoin de la permission suivante pour ex\u00e9cuter l'action suivante \: permissionEmbedLinks=Incorpore les liens @@ -92,6 +92,11 @@ loadPlaylistTooMany=Ajout de {0} pistes. Il y a trop de pistes ajout\u00e9es pou loadErrorCommon=Une erreur est survenue lors du chargement de `{0}`\:\n{1} loadErrorSusp=Une erreur bizarre est survenue lors du chargement de `{0}`. loadQueueTrackLimit=Vous ne pouvez ajouter des pistes \u00e0 une file de plus de {0}\u00a0titres \! Ceci permet d\u2019\u00e9viter les abus. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Liste de lecture en cours de chargement **{0} ** avec jusqu''\u00e0\u00a0`{1}` titres. Cela peut prendre un certain temps, merci de patienter. playerUserNotInChannel=Vous devez en premier lieu rejoindre un canal vocal. playerJoinConnectDenied=Je n'ai pas la permission de rejoindre ce canal vocal. @@ -181,7 +186,7 @@ langSuccess=La langue choisie est le {0}. langInfo=FredBoat prend en charge plusieurs langues parl\u00e9es par les utilisateurs que vous pouvez s\u00e9lectionner avec cette commande. Les Administrateurs de ce serveur peuvent s\u00e9lectionner une langue avec ';;lang ' Voici la liste des langues prisent en charge\: langDisclaimer=Les traductions ne peuvent pas \u00eatre exactes ou compl\u00e8tes \u00e0 100%. Les traductions manquantes peuvent \u00eatre \u00e9tablies en se rendant \u00e0 cette adresse . loadSingleTrack=**{0}** a \u00e9t\u00e9 ajout\u00e9 \u00e0 la file d''attente. -loadSingleTrackFirst=**{0}** a \u00e9t\u00e9 ajout\u00e9 tout au d\u00e9but de la file d''attente. +loadSingleTrackFirst=**{0}** a \u00e9t\u00e9 ajout\u00e9 au d\u00e9but de la file d''attente. loadSingleTrackAndPlay=**{0}** va \u00eatre lue. invite=Lien d''invitation pour **{0}** \: ratelimitedCommandsUser=Vous envoyez beaucoup trop de commandes \u00e0 la suite. Veuillez ralentir s'il vous pla\u00eet. @@ -239,7 +244,7 @@ helpJoinCommand=Fait venir le bot dans votre canal vocal actuel. helpLeaveCommand=Fait partir le bot de votre canal vocal actuel. helpPauseCommand=Met le lecteur en pause. helpPlayCommand=Joue de la musique \u00e0 partir d''un lien ou recherche une piste. Pour une liste compl\u00e8te des sources, veuillez visiter {0} -helpPlayNextCommand=Aide\u00e0jouer\u00e0lacommandesuivante +helpPlayNextCommand=Jouer la musique \u00e0 partir d''un URL ou rechercher une musique. Les musiques sont ajout\u00e9es au d\u00e9but de la liste. Pour une liste compl\u00e8te des sources, visitez {0} helpPlaySplitCommand=Divise une vid\u00e9o YouTube en une liste de lecture fournie depuis sa description. helpRepeatCommand=Bascule entre les modes de r\u00e9p\u00e9tition. helpReshuffleCommand=Remet en mode al\u00e9atoire la file d'attente actuelle. @@ -273,7 +278,7 @@ helpUserInfoCommand=Affiche les informations sur vous ou un autre utilisateur co helpPerms=Permet d''ajouter les membres et les r\u00f4les \u00e0 la liste blanche pour le grade {0}. helpPrefixCommand=Configure le pr\u00e9fixe pour le serveur. helpVoteSkip=Vote pour passer la musique en cours. A besoin de 50 % de tous les d'utilisateurs dans le salon vocal pour voter. -helpUnvoteSkip=Remove your vote to skip the current song. +helpUnvoteSkip=Retirez votre vote pour passer la chanson actuelle. helpMathOperationAdd=\u00c9crit la somme de num1 et num2. helpMathOperationSub=\u00c9crit le r\u00e9sultat de la soustraction de num1 moins num2. helpMathOperationMult=\u00c9crit le produit de num1 x num2. @@ -302,8 +307,8 @@ skipUserMultiple=Pass\u00e9 {0} piste(s) ajout\u00e9e(s) par {1}. skipUsersMultiple=Pass\u00e9 {0} piste(s) ajout\u00e9e(s) par {1} utilisateur(s). skipUserNoTracks=Aucun des utilisateurs mentionn\u00e9s n'a de piste dans la file d'attente. voteSkipAdded=Votre vote a \u00e9t\u00e9 ajout\u00e9 \! -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! +voteSkipRemoved=Votre vote a \u00e9t\u00e9 retir\u00e9 \! +voteSkipNotFound=Vous n'avez pas vot\u00e9 pour passer cette musique \! voteSkipAlreadyVoted=Vous avez d\u00e9j\u00e0 vot\u00e9 pour passer cette musique \! voteSkipSkipping={0} a vot\u00e9 pour passer. Passage de la piste {1}. voteSkipNotEnough={0} a vot\u00e9 pour passer. Au moins {1} vote(s) n\u00e9cessaire(s). diff --git a/FredBoat/src/main/resources/lang/fr_TS.properties b/FredBoat/src/main/resources/lang/fr_TS.properties index 608c3b003..c59815d4e 100644 --- a/FredBoat/src/main/resources/lang/fr_TS.properties +++ b/FredBoat/src/main/resources/lang/fr_TS.properties @@ -16,7 +16,7 @@ repeatOnAll=J'vais tout r\u00e9p\u00e9ter. J'vais tout r\u00e9p\u00e9ter. J'vais repeatOff=...r\u00e9p\u00e9tition r\u00e9p\u00e9tition r\u00e9p\u00e9ti... Ah j'peux enfin arr\u00eater ? Pas trop t\u00f4t, je fatigue. selectSuccess=Num\u00e9rooo **\#{0}** \: **{1}** ({2}) selectInterval=\u00c7a doit \u00eatre un nombre entre 1 et {0}, esp\u00e8ce d''idiot. -selectSelectionNotGiven=Il vous faut d'abord une s\u00e9lection de propositions parmi lesquelles faire un choix. +selectSelectionNotGiven=Tu dois d'abord s\u00e9lectionner ton choix . shuffleOn=(\u256f\u00b0\u25a1\u00b0\uff09\u256f\ufe35 \u253b\u2501\u253b Voil\u00e0, c'est m\u00e9lang\u00e9 \! T'es content ? shuffleOff=D'abord faut m\u00e9langer, ensuite trier, tu l'fais expr\u00e8s ou quoi ? reshufflePlaylist=(\u30ce \u309c\u0414\u309c)\u30ce \ufe35 \u253b\u2501\u253b BAM \! Re-m\u00e9lang\u00e9e \! @@ -36,18 +36,18 @@ unpauseQueueEmpty=Ya rien dans ta file l\u00e0, t'es aveugle ? De toutes fa\u00e unpausePlayerNotPaused=C'est pas en pause, t'es sourd ?\! unpauseNoUsers=Mais ya personne dans ce salon vocal, j'vais pas jouer dans le vide. Pfff, t'es vraiment stupide... unpauseSuccess=Et on est reparti... on peut jamais faire de pause avec toi... -volumeApology=D\u00e9sol\u00e9\u00a0\! La commande de volume n\u2019est pas disponible sur le Fredboat public. Le r\u00e9glage du volume audio augmente fortement la charge du processeur qui est trop cher pour une fonctionnalit\u00e9 sur un bot musical tr\u00e8s populaire. Vous pouvez baisser le volume par un clic droit sur le bot (ou en tapant sur celui-ci pour les utilisateurs mobiles) dans le salon vocal et l\u2019aide du curseur. Si vous souhaitez avoir une commande de volume et quelques fonctionnalit\u00e9s suppl\u00e9mentaires impressionnantes, d\u00e9couvrez comment devenir un Patron et acc\u00e9der \u00e0 un bot exclusif ici\u00a0\: +volumeApology=D\u00e9sol\u00e9, mais la commande de volume n\u2019est pas disponible sur le Fredboat public. Dommage \! Le r\u00e9glage du volume audio augmente fortement la charge du processeur qui est trop cher pour une fonctionnalit\u00e9 sur un bot musical tr\u00e8s populaire. Vous pouvez baisser le volume par un clic droit sur le bot (ou en tapant sur celui-ci pour les utilisateurs mobiles) dans le salon vocal et l\u2019aide du curseur. Si vous souhaitez avoir une commande de volume et quelques fonctionnalit\u00e9s suppl\u00e9mentaires impressionnantes, d\u00e9couvrez comment devenir un Patron et acc\u00e9der \u00e0 un bot exclusif ici\u00a0\: volumeSyntax=*En train de hurler*\nUTILISEZ `;;volume <0-150>`. {0}% EST LA VALEUR PAR D\u00c9FAUT. LE LECTEUR EST ACTUELLEMENT \u00c0 **{1} %** \! volumeSuccess=Volume chang\u00e9 **%{0}** \u00e0 **{1}%**. exportEmpty=0 musiques \u00e0 exporter, comme ton 0 de Q.I. exportPlaylistResulted=Liste de lecture export\u00e9e \: {0} \nVous pouvez fournir cette URL pour jouer cette liste plus tard. -exportPlaylistFail=Echec de la mise en ligne de la liste de lecture. +exportPlaylistFail=J'ai \u00e9chou\u00e9, mais c'est pas grave, on a qu'\u00e0 jouer aux \u00e9checs. listShowShuffled=Playlist que l\u00e0 j'te regardes signale une m\u00e9lang\u00e9e tu. listShowRepeatSingle=R\u00e9p\u00e9tition de la piste en cours. R\u00e9p\u00e9tition de la piste en cours. R\u00e9p\u00e9tition de la piste en cours. R\u00e9p\u00e9tition de la piste en cours. R\u00e9p\u00e9tition de la piste en cours... *continue* listShowRepeatAll=R\u00e9p\u00e9tition de la liste de lecture en cours. R\u00e9p\u00e9tition de la liste de lecture en cours. R\u00e9p\u00e9tition de la liste de lecture en cours. R\u00e9p\u00e9tition de la liste de lecture en cours... *continue* listShowHistory=V'l\u00e0 l'historique des pistes, vu que t'as pas de m\u00e9moire. -listAddedBy=**{0} ** ajout\u00e9 par **{1} ** \u00ab\u00a0[{2}]\u00a0\u00bb -listStreamsOnlySingle=Il y a **{0} ** diffusion {1} dans la file d\u2019attente. +listAddedBy=**{0}** ajout\u00e9 par **{1}** `[{2}]` +listStreamsOnlySingle=Il y a **{0} ** diffusion {1} dans la file d\u2019attente. listStreamsOnlyMultiple=Il y a **{0} ** diffusions {1} dans la file d\u2019attente. listStreamsOrTracksSingle=Il y a **{0}**{1} avec une longueur restante de **{2}**{3} dans la file d''attente. listStreamsOrTracksMultiple=Il y a **{0}**{1} avec une longueur restante de **{2}**{3} dans la file d''attente. @@ -62,7 +62,7 @@ npDescription=Description npLoadedSoundcloud=[{0}/{1}]\n\nEt \u00e7a vient de Souncloud \! npLoadedBandcamp={0}\n\nCe truc vient de heu... Bandcamp... wow, impressionant... npLoadedTwitch=Charg\u00e9 depuis Twitch, beeerk -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Donne-moi ces permissions tout de suite ou je vais m'\u00e9nerver \: permissionMissingInvoker=Donne-moi ces permissions sinon j'le fais pas \: permissionEmbedLinks=Int\u00e9grer des liens @@ -77,7 +77,7 @@ npLoadedDefault={0}\n\n~~Vol\u00e9~~ charg\u00e9 depuis {1} noneYet=Aucun pour le moment npRatingRange={0}/5 (j''aurais donn\u00e9 0 perso) fwdSuccess=Avance **{0}** de {1}. -restartSuccess=**{0}** a \u00e9t\u00e9 red\u00e9marr\u00e9. +restartSuccess=**{0}** a \u00e9t\u00e9 red\u00e9marr\u00e9, c''est reparti pour un tour. queueEmpty=Que. Dalle. Comme dans ton cerveau. rewSuccess=Rebobinage **{0}** par {1}. seekSuccess=Recherchant **{0}** \u00e0 {1}. @@ -92,20 +92,25 @@ loadPlaylistTooMany=Ajout de {0} pistes. Il y a trop de pistes ajout\u00e9es pou loadErrorCommon=Une erreur est survenue lors du chargement de `{0}`\:\n{1} loadErrorSusp=Une erreur bizarre est survenue lors du chargement de `{0}`. loadQueueTrackLimit=Vous ne pouvez ajouter des pistes \u00e0 une file de plus de {0}\u00a0titres \! Ceci permet d\u2019\u00e9viter les abus. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Liste de lecture en cours de chargement **{0} ** avec jusqu''\u00e0\u00a0`{1}` titres. Cela peut prendre un certain temps, merci de patienter. playerUserNotInChannel=Non. Hors de question que je vienne alors que toi t'y es m\u00eame pas. Et puis tu m'\u00e9nerves de toutes fa\u00e7ons. playerJoinConnectDenied=M-mais... je ne peux pas rejoindre ce salon... ;_; playerJoinSpeakDenied=*Ne peut pas parler dans ce salon vocal*\nHmmm, hmmmm, hmm hmm \! \:anger\: -playerNotInChannel=Vous n'\u00eates pas actuellement dans un canal vocal. +playerNotInChannel=Tu n'es pas dans un canal vocal, idiot. playerLeftChannel=Ouais c'est \u00e7a, je quitte ton salon pourri et ses gens pourris. shutdownUpdating=STOP \! J'me mets \u00e0 jour, sinon je pourrai pas te supporter plus longtemps. shutdownRestarting=A-attends, juste une pause \! Une petite m-minute, s'il te pla\u00eet... je me relance... shutdownIndef=J'en ai marre, je pars dormir. Je reviendrai plus tard... si tu m'\u00e9nerves pas. shutdownPersistenceFail=Une erreur est survenue lors de l''enregistrement du fichier persistant \: `{0}` -reloadSuccess=Rechargement de la liste. `{0}` pistes trouv\u00e9es. +reloadSuccess=Je... Je vais recharger la playlist. J''ai trouv\u00e9 `{0}` musiques \u00e0 \u00e9couter. trackAnnounce=Lecture en cours **{0}**. Demand\u00e9 par\u00a0\: **{1}**. cmdAccessDenied=Touche pas \u00e0 \u00e7a sinon j-je... je... vais m'\u00e9nerver, voil\u00e0 \! >.< -malRevealAnime={0}\u00a0\: La recherche a trouv\u00e9 un anime.\n +malRevealAnime={0} \: La recherche a trouv\u00e9 un de tes animes d''otaku.\n malTitle={0}**Titre\u00a0\: **{1}\n malEnglishTitle={0} **Anglais\u00a0\: **{1}\n malSynonyms={0}**Synonymes\u00a0\: **{1}\n @@ -123,9 +128,9 @@ malUrl={0}**URL\u00a0\: **{1}\n luaError=\ U-une erreur Lua... \:c\n```{0}``` luaErrorOutputTooBig=\ L-la r\u00e9ponse est trop grande... J''peux envoyer que 2 000 caract\u00e8res au maximum, et elle en fait {0}... \:c luaTimeout=\ Fonction timout \:anger\: Le temps autoris\u00e9 est de {0} secondes. -helpSuccess=La documentation a \u00e9t\u00e9 envoy\u00e9e dans votre messagerie priv\u00e9e\! +helpSuccess=Je t'ai envoy\u00e9 la doc en Mp, mais c'est pas comme si je voulais te parler ou quelque chose comme \u00e7a \! helpDmFailed=On dirait que tu ne veux pas mon m-essage... T-tu les as peut-\u00eatre d-d\u00e9sactiv\u00e9s... >.< -helpCommandsPromotion=Dites {0} pour savoir ce que ce bot peut faire \! +helpCommandsPromotion=dis moi {0} pour savoir ce dont je suis capable \! musicCommandsPromotion=Dites {0} pour voir une liste compl\u00e8te des commandes de musique. fuzzyNoResults=J'trouve personne. Sans doute parce que tu sais pas \u00e9crire \:p brainfuckCycleLimit=Bravo, ton code a d\u00e9pass\u00e9 le maximum de cycles, {0}. T''es trop fier hein ? Ton code marche pas, bravo. @@ -143,53 +148,53 @@ modReason=Raison modAuditLogMessage=Action \u00e9mise par {0} contre {1} modAudioLogNoReason=Aucune raison indiqu\u00e9e. Sans doute un mod\u00e9rateur fain\u00e9ant. modFailUserHierarchy=Nan mais ho, tu te crois au-dessus de {0} ? \:anger\: -modFailBotHierarchy=J\u2019ai besoin d\u2019avoir un r\u00f4le plus \u00e9lev\u00e9 que celui de {0}. +modFailBotHierarchy=Je ne le ferais que si j''ai le r\u00f4le de {0}, sinon ne me le demande pas \! modBanlistFail=Impossible de r\u00e9cup\u00e9rer la liste des bannis. -modBanFail=Erreur lors du bannissement de {0} -modUnbanFail=Je n''ai pas pu d\u00e9-bannir {0} +modBanFail=Je n'ai pas r\u00e9ussi \u00e0 le bannir, je... Je suis d\u00e9sol\u00e9. +modUnbanFail=Je n'ai pas r\u00e9ussi \u00e0 le d\u00e9bannir, pard... pard... Pardonne moi. modUnbanFailNotBanned=L''utilisateur {0} n''est pas banni de cette guilde. modKeepMessages=Ajoutez {0} pour ne supprimer aucun message. modActionTargetDmKicked=Hey toi \! Tu viens de te faire exclure par {1} du serveur {0}. J''suis contente de plus te voir tu sais \:3 modActionTargetDmBanned=Hey toi \! Tu viens de te faire bannir par {1} du serveur {0}. Au fait, je t''ai jamais aim\u00e9 \:3 -kickSuccess=L''utilisateur {0} a bien \u00e9t\u00e9 exclu. +kickSuccess={0} a bien \u00e9t\u00e9 exclu, la vie est trop triste. kickFail=J-je n''arrive pas \u00e0 exclure {0}, d\u00e9sol\u00e9e... il me manque des permissions ou je n''ai pas de r\u00f4le assez \u00e9lev\u00e9... \:sob\: kickFailSelf=T'sais si tu veux partir ya un joli bouton \u00ab\u202fQuitter le serveur\u202f\u00bb. Tu le vois ? Clique dessus et je serai enfin d\u00e9barrass\u00e9e de toi. -kickFailOwner=Vous ne pouvez pas expulser le propri\u00e9taire du serveur. C'est con... -kickFailMyself=Je ne peux pas m'exclure moi-m\u00eame. C'est con... +kickFailOwner=Baka \! Tu ne peux pas exclure le propri\u00e9taire du serveur . +kickFailMyself=Mais... Je ne peu pas m'exclure moi m\u00eame, r\u00e9fl\u00e9chis un peu. softbanSuccess=L''utilisateur {0} a bien \u00e9t\u00e9 softban. -softbanFailSelf=Vous ne pouvez pas vous exclure temporairement. C'est con... +softbanFailSelf=Tu ne peux pas t'exclure temporairement toi-m\u00eame, idiot -_- . softbanFailOwner=Vous ne pouvez exclure temporairement l'administrateur du serveur. C'est con... -softbanFailMyself=Je ne peux pas m'exclure temporairement. C'est con... +softbanFailMyself=Je ne peux pas m'exclure temporairement, idiot \! hardbanSuccess=L''utilisateur {0} a bien \u00e9t\u00e9 banni. -hardbanFailSelf=Vous ne pouvez pas vous bannir vous-m\u00eame. C'est con... +hardbanFailSelf=Tu ne peux pas te bannir toi m\u00eame. Au moins tu peux rester ici \! hardbanFailOwner=Vous ne pouvez pas bannir le propri\u00e9taire du serveur. C'est con... -hardbanFailMyself=Je ne peux pas me bannir moi-m\u00eame. C'est con... -unbanSuccess=L''utilisateur {0} a bien \u00e9t\u00e9 d\u00e9-banni. +hardbanFailMyself=Je ne vais pas m'exclure moi m\u00eame \! Idiot \! +unbanSuccess={0} a bien \u00e9t\u00e9 d\u00e9-banni, f\u00e9licitation, je suppose ? getidSuccess=L''ID de cette guilde est {0}. L''ID de ce channel est {1}. -statsParagraph=\ Ce bot fonctionne depuis {0} jours, {1} heures, {2} minutes et {3} secondes.\nCe bot a ex\u00e9cut\u00e9 {4} commandes cette session. -statsRate={0}C''est un taux de {1} commandes par heure -catgirlFail=Erreur lors de l''extraction de l''image depuis {0} -catgirlFailConn=Erreur de connexion \u00e0 {0} +statsParagraph=\ Je fonctionne depuis {0} jours, {1} heures, {2} minutes et {3} secondes. J''ai ex\u00e9cut\u00e9 {4} commandes cette session. Tu me pousses vraiment \u00e0 bout... +statsRate={0}C''est un taux de {1} commande par heure. Je suis rapide hein? HEIN? +catgirlFail=Je n''ai pas r\u00e9ussi \u00e0 extraire l''image depuis {0}. Elle \u00e9tait moche de toute fa\u00e7on. +catgirlFailConn=Impossible de se connecter \u00e0 {0} hugBot=Un c\u00e2lin...? J-je... m-merci... >////< -hugSuccess=C\u00e2line {0}. -patBot=Merci pour les caresses \:blush\: -patSuccess=Caresse {0}. -rollSuccess={0} roule sur le sol. -facedeskSuccess={0} tape sa t\u00eate. -langInvalidCode=Ce langage {0} n''existe pas ou n''est pas support\u00e9. -langSuccess=La langue choisie est le {0}. -langInfo=FredBoat prend en charge plusieurs langues parl\u00e9es par les utilisateurs que vous pouvez s\u00e9lectionner avec cette commande. Les Administrateurs de ce serveur peuvent s\u00e9lectionner une langue avec ';;lang ' Voici la liste des langues prisent en charge\: -langDisclaimer=Les traductions ne peuvent pas \u00eatre exactes ou compl\u00e8tes \u00e0 100%. Les traductions manquantes peuvent \u00eatre \u00e9tablies en se rendant \u00e0 cette adresse . +hugSuccess=C\u00e2line {0}. Mais juste pour cette fois... +patBot=N-Ne touche pas ma t\u00eate si soudainement, imb\u00e9cile... +patSuccess=Caresse {0}. Mais juste pour cette fois... +rollSuccess=Cet idiot de {0} roule sur le sol... Mais je n''ai pas envie de le rejoindre hein\! +facedeskSuccess={0} Tape sa t\u00eate sur le bureau. +langInvalidCode=C''est quoi \u00e7a? "{0}" ne veut rien dire, abruti\! +langSuccess=Bien\! Je parlerais en {0} maintenant. +langInfo=Je prend en charge plusieurs langues parl\u00e9es par les utilisateurs que vous pouvez s\u00e9lectionner avec cette commande. Les Administrateurs de ce serveur peuvent s\u00e9lectionner une langue avec ';;lang ' Voici la liste des langues prisent en charge\: +langDisclaimer=Ma traduction n'est pas pas totalement exacte ou compl\u00e8te... Je suis d\u00e9sol\u00e9, vraiment. Tu peux toujours les compl\u00e9ter ici, je suis pas ton larbin \: . loadSingleTrack=**{0}** a \u00e9t\u00e9 ajout\u00e9 \u00e0 la file d''attente. -loadSingleTrackFirst=**{0}** a \u00e9t\u00e9 ajout\u00e9 tout au d\u00e9but de la file d''attente. -loadSingleTrackAndPlay=**{0}** va \u00eatre lue. -invite=Lien d''invitation pour **{0}** \: +loadSingleTrackFirst=**{0}** a \u00e9t\u00e9 ajout\u00e9 au tout d\u00e9but de la file d''attente, hihi. +loadSingleTrackAndPlay=Je vais te jouer **{0}** rien que pour toi. +invite=Lien d''invitation divin pour **{0}** \: ratelimitedCommandsUser=Houl\u00e0 ON SE CALME, tu vas trop vite, ralentis >< -ratelimitedCommandsGuild=Votre guilde envoie beaucoup trop de commandes \u00e0 la suite. Veuillez ralentir s'il vous pla\u00eet. -ratelimitedSkipCommand=Vous pouvez passer plus d''une chanson en utilisant la commande suivante \: {0} -ratelimitedGuildSlowLoadingPlaylist=Ce serveur ne peut pas charger plus de listes de lecture pour le moment. S'il vous pla\u00eet, ne spammez pas de longues listes de lecture. +ratelimitedCommandsGuild=On se calme, tu vas trop vite, ralentis ><. +ratelimitedSkipCommand=Tu ne peux pas passer plusieurs musique en m\u00eame temps avec cette commande, dommage +ratelimitedGuildSlowLoadingPlaylist=Le serveur va exploser si tu rajoute des musiques \u00e0 lire, donc on va attendre un moment avant. unblacklisted={0} a \u00e9t\u00e9 retir\u00e9 de la liste noire. -serverinfoTitle=Infos \u00e0 propos de {0} \: +serverinfoTitle=Tout ce que je sais \u00e0 propos de {0} \: serverinfoOnlineUsers=Utilisateurs en ligne \: serverinfoTotalUsers=Nombre total d'utilisateurs \: serverinfoRoles=R\u00f4les \: @@ -206,17 +211,17 @@ userinfoNick=Pseudo \: userinfoKnownServer=Serveurs connus \: userinfoJoinDate=Date d'inscription\: userinfoCreationTime=Date de cr\u00e9ation \: -userinfoBlacklisted="En liste noire \: +userinfoBlacklisted=La liste noire du mal absolue \: skipDeniedTooManyTracks=Vous ne pouvez pas passer le morceau de quelqu'un d'autre si vous n'\u00eates pas DJ. Envisagez d'utiliser la commande de Voteskip. eventUsersLeftVC=Tous les utilisateurs ont quitt\u00e9 le canal vocal. Le lecteur a \u00e9t\u00e9 mis en pause. eventAutoResumed=Pr\u00e9sence d'utilisateur d\u00e9tect\u00e9e, reprise automatique du lecteur. -commandsFun=Amusement +commandsFun=Fun commandsMemes=M\u00e8mes commandsUtility=Utilitaire commandsModeration=Mod\u00e9ration commandsMaintenance=Maintenance commandsBotOwner=Propri\u00e9taire du bot -commandsMoreHelp=Dites {0} pour obtenir plus d\u2019informations sur une commande sp\u00e9cifique. +commandsMoreHelp=Dis moi {0} si tu veux plus d''info sur une certaine commande. Je peux \u00eatre gentille parfois. commandsModulesHint=Vous pouvez activer et d\u00e9sactiver des modules suppl\u00e9mentaires avec {0} helpUnknownCommand=J'connais pas cette commande. helpDocsLocation=La documentation peut \u00eatre trouv\u00e9e ici \: @@ -238,10 +243,10 @@ helpMusicCommandsHeader=Commandes musicales de Fredboat helpJoinCommand=Fait venir le bot dans votre canal vocal actuel. helpLeaveCommand=Fait partir le bot de votre canal vocal actuel. helpPauseCommand=Met le lecteur en pause. -helpPlayCommand=Joue de la musique \u00e0 partir d''un lien ou recherche une piste. Pour une liste compl\u00e8te des sources, veuillez visiter {0} -helpPlayNextCommand=Aide\u00e0jouer\u00e0lacommandesuivante +helpPlayCommand=Joue de la musique \u00e0 partir d''un l''URL donn\u00e9e ou d''une recherche par noms. Pour une liste compl\u00e8te des sources, veuillez visiter {0} +helpPlayNextCommand=Joue de la musique \u00e0 partir d''un l''URL donn\u00e9e ou d''une recherche par noms, et l''ajoute au d\u00e9but de la file d''attente. Pour une liste compl\u00e8te des sources, veuillez visiter {0} helpPlaySplitCommand=**D\u00e9coupe** une vid\u00e9o Youtube pour la manger en plusieurs fois, selon la ~~recette~~ description. >\:3c -helpRepeatCommand=Bascule entre les modes de r\u00e9p\u00e9tition. +helpRepeatCommand=Propose diff\u00e9rents modes de r\u00e9p\u00e9titions. helpReshuffleCommand=Remet en mode al\u00e9atoire la file d'attente actuelle. helpSelectCommand=S\u00e9lectionne et lance l'une des pistes propos\u00e9es apr\u00e8s une recherche. helpShuffleCommand=Bascule en mode al\u00e9atoire pour la file d\u2019attente actuelle. @@ -258,7 +263,7 @@ helpForwardCommand=Avance la piste d'un temps donn\u00e9 par la commande. Exempl helpRestartCommand=Rejoue depuis le d\u00e9but la piste en cours de lecture. helpRewindCommand=Rembobine la piste d'un temps donn\u00e9 par la commande. Exemple \: helpSeekCommand=R\u00e8gle la position dans la piste par un temps donn\u00e9 par la commande. Exemple \: -helpAvatarCommand=Affiche l\u2019avatar d\u2019un utilisateur. +helpAvatarCommand=Affiche l\u2019avatar d\u2019un utilisateur dans le chat. helpBrainfuckCommand=Ex\u00e9cute du code Brainfuck. Nan, n'essaie pas, t'as pas assez de cervelle pour \u00e7a. Regarde, tu vas rien comprendre \: helpWeatherCommand=Affiche la m\u00e9t\u00e9o actuelle par emplacement. helpClearCommand=Supprime tous les messages de ce bot dans les 50 derniers messages de ce salon. @@ -273,7 +278,7 @@ helpUserInfoCommand=Affiche les informations sur vous ou un autre utilisateur co helpPerms=Permet d''ajouter les membres et les r\u00f4les \u00e0 la liste blanche pour le grade {0}. helpPrefixCommand=Configure le pr\u00e9fixe pour le serveur. helpVoteSkip=Vote pour passer la musique en cours. A besoin de 50 % de tous les d'utilisateurs dans le salon vocal pour voter. -helpUnvoteSkip=Remove your vote to skip the current song. +helpUnvoteSkip=Retirez votre vote pour passer la chanson actuelle. helpMathOperationAdd=\u00c9crit la somme de num1 et num2. helpMathOperationSub=\u00c9crit le r\u00e9sultat de la soustraction de num1 moins num2. helpMathOperationMult=\u00c9crit le produit de num1 x num2. @@ -284,33 +289,33 @@ helpMathOperationSqrt=\u00c9crit la racine carr\u00e9e de n. helpMathOperationPow=\u00c9crit le r\u00e9sultat de num1 puissance num2. destroyDenied=Vous devez avoir la permission de g\u00e9rer les messages pour r\u00e9initialiser le lecteur. destroyHelp=R\u00e9initialise le lecteur et efface la liste de lecture. Ceci est r\u00e9serv\u00e9 aux mod\u00e9rateurs ayant la permission de g\u00e9rer les messages. -destroySucc=Le lecteur a \u00e9t\u00e9 r\u00e9initialis\u00e9 et la file d'attente vid\u00e9e. +destroySucc=J'ai \u00e9t\u00e9 r\u00e9initialis\u00e9, et ma file d'attente aussi \! Je suis toute propre maintenant. listPageNum=Page **{0}** sur **{1}**. permsListTitle=Utilisateurs et r\u00f4les avec les permissions suivantes {0} permsAdded=J''ai ajout\u00e9 de `{0}` \u00e0 `{1}`. permsRemoved=Suppression de `{0}` dans `{1}`. permsFailSelfDemotion=Tu ne peux pas retirer \u00e7a car cela te retirerait une permission d'admin \! permsAlreadyAdded={0} d\u00e9j\u00e0 ajout\u00e9 \u00e0 {1} -permsNotAdded={0} n''est pas dans {1} -fuzzyMultiple=Plusieurs \u00e9l\u00e9ments ont \u00e9t\u00e9 trouv\u00e9s. Vouliez-vous dire l'un de ceux-ci ? -fuzzyNothingFound=Je n''ai rien trouv\u00e9 pour `{0}`. +permsNotAdded={0} n''est pas dans {1}, c''est b\u00eate \u00af\\_(\u30c4)_/\u00af +fuzzyMultiple=J'ai trouv\u00e9 un tas de trucs sympa \! Y en a un qui t'int\u00e9resse ? +fuzzyNothingFound=Je n''ai rien trouv\u00e9 pour `{0}` \u00af\\_(\u30c4)_/\u00af cmdPermsTooLow=Vous n''avez pas l''autorisation d''ex\u00e9cuter cette commande \! Cette commande requiert la permission `{0}` mais vous avez seulement la permission `{1}`. playersLimited=FredBoat est actuellement \u00e0 plein r\u00e9gime \! Le bot est actuellement r\u00e9gl\u00e9 pour lire seulement jusqu''\u00e0 `{0}` streams, faute de quoi nous risquerions une d\u00e9connexion de Discord \u00e0 cause de la surcharge du r\u00e9seau.\nSi vous voulez nous aider \u00e0 augmenter la limite ou si vous souhaitez utiliser notre bot non-surcharg\u00e9, soutenez notre travail sur Patreon \: {1}\n\nD\u00e9sol\u00e9s pour le d\u00e9rangement \! Vous devriez essayer plus tard. Ce message n''appara\u00eet habituellement qu''aux heures de pointe. -tryLater=Veuillez essayer plus tard. +tryLater=Retente ta chance plus tard \:p skipUserSingle=Pass\u00e9 {0} ajout\u00e9 par {1}. skipUserMultiple=Pass\u00e9 {0} piste(s) ajout\u00e9e(s) par {1}. skipUsersMultiple=Pass\u00e9 {0} piste(s) ajout\u00e9e(s) par {1} utilisateur(s). skipUserNoTracks=Aucun des utilisateurs mentionn\u00e9s n'a de piste dans la file d'attente. voteSkipAdded=Ton vote a bien \u00e9t\u00e9 ajout\u00e9. De toute fa\u00e7on, cette musique craint... -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! +voteSkipRemoved=Votre vote a \u00e9t\u00e9 retir\u00e9 \! +voteSkipNotFound=Vous n'avez pas vot\u00e9 pour passer cette musique \! voteSkipAlreadyVoted=Vous avez d\u00e9j\u00e0 vot\u00e9 pour passer cette musique \! voteSkipSkipping={0} a vot\u00e9 pour passer. Passage de la piste {1}. voteSkipNotEnough={0} a vot\u00e9 pour passer. Au moins {1} vote(s) n\u00e9cessaire(s). voteSkipEmbedNoVotes=Pas encore de votes pour passer cette piste. voteSkipEmbedVoters={0} sur {1} ont vot\u00e9 pour passer la musique en cours mathOperationResult=Le r\u00e9sultat est -mathOperationDivisionByZeroError=Je ne peux pas diviser par z\u00e9ro. +mathOperationDivisionByZeroError=Je ne peux pas diviser par z\u00e9ro (\u2686\u256d\u256e\u2686) mathOperationInfinity=Le nombre est trop grand pour \u00eatre affich\u00e9 \! prefix=Pr\u00e9fixe prefixGuild=Le pr\u00e9fixe pour cette guilde est {0} @@ -332,6 +337,6 @@ modulesCommands=Dites {0} pour afficher les commandes d''un module, ou {1} pour modulesEnabledInGuild=J'ai accept\u00e9 ces fichus modules pour c'te guilde \: modulesHowTo=Pour me donner plus de boulot ou m''en retirer, m\u00eame si je sais que tu vas m''en donner, c''est {0} parseNotAUser=Votre entr\u00e9e {0} n''a renvoy\u00e9 \u00e0 un utilisateur Discord. -parseNotAMember={0} est pas sur ce serveur, et tant mieux \! \=v\= +parseNotAMember={0} n''est pas sur ce serveur, et tant mieux \! \=v\= parseSnowflakeIdHelp=T''as un probl\u00e8me avec l''ID d''un utilisateur, d''un channel ou aut''chose? Bah r''gardes la doc de Discord sur ce lien {0} diff --git a/FredBoat/src/main/resources/lang/ga_IE.properties b/FredBoat/src/main/resources/lang/ga_IE.properties index df4fe7ee9..408788bdb 100644 --- a/FredBoat/src/main/resources/lang/ga_IE.properties +++ b/FredBoat/src/main/resources/lang/ga_IE.properties @@ -62,7 +62,7 @@ npDescription=Cur s\u00edos npLoadedSoundcloud=[{0}/{1}] a th\u00f3g m\u00e9 \u00f3 Soundcloud npLoadedBandcamp={0} a th\u00f3g m\u00e9 \u00f3 Bandcamp npLoadedTwitch=L\u00f3d\u00e1il as Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=T\u00e1 an cead seo uaim len \u00e9 sin a dh\u00e9anamh\: permissionMissingInvoker=You need the following permission to perform that action\: permissionEmbedLinks=Embed Links @@ -92,6 +92,11 @@ loadPlaylistTooMany=Chuir {0} tracks. D'' aimisigh iomarca tracks ag th\u00e1isp loadErrorCommon=Error occurred when loading info for `{0}`\:\n{1} loadErrorSusp=Suspicious error when loading info for `{0}`. loadQueueTrackLimit=You can''t add tracks to a queue with more than {0} tracks\! This is to prevent abuse. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=About to load playlist **{0}** with up to `{1}` tracks. This may take a while, please be patient. playerUserNotInChannel=Caithfidh t\u00fa ag bheith i channel gl\u00f3ir ar dt\u00fas. playerJoinConnectDenied=I am not permitted to connect to that voice channel. diff --git a/FredBoat/src/main/resources/lang/he_IL.properties b/FredBoat/src/main/resources/lang/he_IL.properties index 12244bfb8..cb0efb5fe 100644 --- a/FredBoat/src/main/resources/lang/he_IL.properties +++ b/FredBoat/src/main/resources/lang/he_IL.properties @@ -62,7 +62,7 @@ npDescription=\u05ea\u05d9\u05d0\u05d5\u05e8 npLoadedSoundcloud=[{0}/{1}]\n\n\u05e0\u05d8\u05e2\u05df \u05de\u05e1\u05d0\u05d5\u05e0\u05d3\u05e7\u05dc\u05d0\u05d5\u05d3 npLoadedBandcamp={0} \u05e0\u05d8\u05e2\u05df \u05de\u05d1\u05d0\u05e0\u05d3\u05e7\u05d0\u05de\u05e4 npLoadedTwitch=\u05e0\u05d8\u05e2\u05df \u05de\u05d8\u05d5\u05d5\u05d9\u05e5' -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=\u05d0\u05e0\u05d9 \u05e6\u05e8\u05d9\u05da \u05d0\u05ea \u05d4\u05e7\u05d9\u05e9\u05d5\u05e8\u05d9\u05dd \u05d4\u05d1\u05d0\u05d9\u05dd \u05db\u05d3\u05d9 \u05dc\u05d1\u05e6\u05e2 \u05e4\u05e2\u05d5\u05dc\u05d4 \u05d6\u05d5\: permissionMissingInvoker=\u05d0\u05ea\u05d4 \u05e6\u05e8\u05d9\u05da \u05d0\u05ea \u05d4\u05d4\u05e8\u05e9\u05d0\u05d5\u05ea \u05d4\u05d1\u05d0\u05d5\u05ea \u05db\u05d3\u05d9 \u05dc\u05d1\u05e6\u05e2 \u05e4\u05e2\u05d5\u05dc\u05d4 \u05d6\u05d5\: permissionEmbedLinks=\u05dc\u05e4\u05e8\u05e1\u05dd \u05e7\u05d9\u05e9\u05d5\u05e8\u05d9\u05dd @@ -92,6 +92,11 @@ loadPlaylistTooMany=\u05e0\u05d5\u05e1\u05e3 {0} \u05e8\u05e6\u05d5\u05e2\u05d5\ loadErrorCommon=\u05d0\u05d9\u05e8\u05e2\u05d4 \u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05e2\u05ea \u05d8\u05e2\u05d9\u05e0\u05ea \u05de\u05d9\u05d3\u05e2 \u05e2\u05d1\u05d5\u05e8 ''{0} `\:\n{1} loadErrorSusp=\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d7\u05e9\u05d5\u05d3\u05d4 \u05d1\u05e2\u05ea \u05d8\u05e2\u05d9\u05e0\u05ea \u05de\u05d9\u05d3\u05e2 \u05e2\u05d1\u05d5\u05e8 ''{0}''. loadQueueTrackLimit=\u05dc\u05d0 \u05e0\u05d9\u05ea\u05df \u05dc\u05d4\u05d5\u05e1\u05d9\u05e3 \u05e8\u05e6\u05d5\u05e2\u05d5\u05ea \u05dc\u05ea\u05d5\u05e8 \u05e2\u05dd \u05d9\u05d5\u05ea\u05e8 \u05de {0} \u05e8\u05e6\u05d5\u05e2\u05d5\u05ea\! \u05d6\u05d4 \u05db\u05d3\u05d9 \u05dc\u05de\u05e0\u05d5\u05e2 \u05d4\u05ea\u05e2\u05dc\u05dc\u05d5\u05ea. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=\u05db\u05d3\u05d9 \u05dc\u05d8\u05e2\u05d5\u05df \u05d0\u05ea \u05e8\u05e9\u05d9\u05de\u05ea \u05d4\u05d4\u05e9\u05de\u05e2\u05d4 * *{0}* * \u05e2\u05dd \u05e2\u05d3 ''{1}'' \u05e8\u05e6\u05d5\u05e2\u05d5\u05ea. \u05e4\u05e2\u05d5\u05dc\u05d4 \u05d6\u05d5 \u05e2\u05e9\u05d5\u05d9\u05d4 \u05dc\u05d4\u05d9\u05de\u05e9\u05da \u05d6\u05de\u05df, \u05d0\u05e0\u05d0 \u05d4\u05de\u05ea\u05d9\u05e0\u05d5 \u05d1\u05e1\u05d1\u05dc\u05e0\u05d5\u05ea. playerUserNotInChannel=\u05e2\u05dc\u05d9\u05da \u05dc\u05d4\u05d9\u05db\u05e0\u05e1 \u05dc\u05e2\u05e8\u05d5\u05e5 \u05e7\u05d5\u05dc\u05d9 \u05e7\u05d5\u05d3\u05dd. playerJoinConnectDenied=\u05d0\u05e0\u05d9 \u05dc\u05d0 \u05de\u05d5\u05e8\u05e9\u05d4 \u05dc\u05d4\u05d9\u05db\u05e0\u05e1 \u05dc\u05e2\u05e8\u05d5\u05e5 \u05d4\u05e7\u05d5\u05dc \u05d4\u05d6\u05d4. diff --git a/FredBoat/src/main/resources/lang/hr_HR.properties b/FredBoat/src/main/resources/lang/hr_HR.properties index 212ff5565..3eb40f5cd 100644 --- a/FredBoat/src/main/resources/lang/hr_HR.properties +++ b/FredBoat/src/main/resources/lang/hr_HR.properties @@ -62,7 +62,7 @@ npDescription=Opis npLoadedSoundcloud=[{0}/{1}] \n\nU\u010ditano sa SoundClouda npLoadedBandcamp={0}\n\nU\u010ditano sa Bandcampa npLoadedTwitch=U\u010ditano sa Twitcha -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Trebam sljede\u0107e dozvole za izvo\u0111enje te akcije\: permissionMissingInvoker=Moras imati dozvolu da bi izveu tu radnju\: permissionEmbedLinks=Ugra\u0111ene poveznice @@ -92,6 +92,11 @@ loadPlaylistTooMany=Dodano {0} pjesama. Prona\u0161ao previ\u0161e pjesama za pr loadErrorCommon=Dogodila se pogre\u0161ka prilikom u\u010ditavanja informacija za ''{0}''\:{1} loadErrorSusp=Sumnjiva pogre\u0161ka prilikom u\u010ditavanja informacija za ''{0}''. loadQueueTrackLimit=Ne mo\u017eete dodati pjesmu u raspored sa vi\u0161e od {0} pjesama. Ovo je da se sprije\u010di zloupotreba. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Upravo \u0107u u\u010ditati listu pjesama **{0}** sa brojem pjesama\: `{1}`. Ovo mo\u017ee potrajati stoga budite strpljivi. playerUserNotInChannel=Prvo se morate pridru\u017eiti govornome kanalu. playerJoinConnectDenied=Nemam dozvolu spojiti se na taj glasovni kanal. diff --git a/FredBoat/src/main/resources/lang/hu_HU.properties b/FredBoat/src/main/resources/lang/hu_HU.properties index 691af0670..b411cba8d 100644 --- a/FredBoat/src/main/resources/lang/hu_HU.properties +++ b/FredBoat/src/main/resources/lang/hu_HU.properties @@ -62,7 +62,7 @@ npDescription=Le\u00edr\u00e1s npLoadedSoundcloud=[{0}/{1}]\n\nSoundcloud-r\u00f3l bet\u00f6ltve npLoadedBandcamp={0}\n\nBandcamp-r\u0151l bet\u00f6ltve npLoadedTwitch=Twitch-r\u0151l bet\u00f6ltve -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=A k\u00f6vetkez\u0151 jogosults\u00e1gra van sz\u00fcks\u00e9gem a m\u0171velet v\u00e9grehajt\u00e1s\u00e1hoz\: permissionMissingInvoker=A k\u00f6vetkez\u0151 jogosults\u00e1gra van sz\u00fcks\u00e9gem a m\u0171velet v\u00e9grehajt\u00e1s\u00e1hoz\: permissionEmbedLinks=Be\u00e1gyazott Linkek @@ -92,6 +92,11 @@ loadPlaylistTooMany={0} zene hozz\u00e1adva. T\u00fal sok zen\u00e9t tal\u00e1lt loadErrorCommon=Hiba t\u00f6rt\u00e9nt az inform\u00e1ci\u00f3 bet\u00f6lt\u00e9sekor innen\:`{0}`\:\n{1} loadErrorSusp=Gyan\u00fas hiba `{0}` bet\u00f6lt\u00e9se k\u00f6zben. loadQueueTrackLimit=Nem adhatsz hozz\u00e1 t\u00f6bb, mint {0} sz\u00e1mot a list\u00e1hoz\! Ez a vissza\u00e9l\u00e9sek elker\u00fcl\u00e9se \u00e9rdek\u00e9ben van. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=\u00c9ppen most t\u00f6lt\u00f6m be a(z) **{0}** lej\u00e1tsz\u00e1si list\u00e1t `{1}` zenesz\u00e1mmal. Ez eltarthat egy kis ideig, k\u00e9rlek l\u00e9gy t\u00fcrelmes. playerUserNotInChannel=El\u0151bb csatlakozz egy hang csatorn\u00e1hoz. playerJoinConnectDenied=Nem csatlakozhatok ahhoz a hang csatorn\u00e1hoz. diff --git a/FredBoat/src/main/resources/lang/id_ID.properties b/FredBoat/src/main/resources/lang/id_ID.properties index 1b5a1b66e..dcef73e4e 100644 --- a/FredBoat/src/main/resources/lang/id_ID.properties +++ b/FredBoat/src/main/resources/lang/id_ID.properties @@ -6,7 +6,7 @@ playWillNowPlay=Pemutar Musik Dimulai. playSearching=Mencari link youtube untuk `{q}`... playYoutubeSearchError=Terjadi kesalahan dalam pencarian. Silahkan gunakan link playSearchNoResults=Tidak ada hasil untuk {q} -playSelectVideo=Harap Untuk memilih video dengan perintah `;;play n`{0} +playSelectVideo=**Pilih trek dengan perintah `{0}play 1-5`** joinJoining=Tergabung {0} joinErrorAlreadyJoining=Terjadi kesalahan. tidak dapat tersambung {0} karena telah bergabung dengan channel tersebut. Silahkan coba lagi. pauseAlreadyPaused=Pemutar telah dihentikan sementara. @@ -41,8 +41,8 @@ volumeSyntax=Gunakan `;;volume <0-150>`. default volume adalah {0}%\nVolume pemu volumeSuccess=Volume dirubah dari **{0}%** ke **{1}%**. exportEmpty=Tidak ada apapun untuk dimainkan. antrian lagu kosong. exportPlaylistResulted=Mengambil playlist\: {0}\nkamu dapat menggunakan URL ini untuk memainkan playlist. -exportPlaylistFail=Failed to upload playlist to paste services. -listShowShuffled=Menampilkan Playlist Acak.\n\n +exportPlaylistFail=Gagal untuk mengunggah daftar putar ke layanan bloknot. +listShowShuffled=Menampilkan daftar putar acak. listShowRepeatSingle=Mengulang trek saat ini. listShowRepeatAll=Mengulang antrian lagu saat ini. listShowHistory=Menampilkan trek dalam sejarah. @@ -52,19 +52,19 @@ listStreamsOnlyMultiple=Ada **{0} ** stream {1} dalam antrian. listStreamsOrTracksSingle=Ada **{0} ** {1} dengan waktu tersisa ** [{2}] **{3} dalam antrian. listStreamsOrTracksMultiple=Ada **{0} ** {1} dengan waktu tersisa ** [{2}] **{3} dalam antrian. streamSingular=stream -streamPlural=\ +streamPlural=aliran listAsWellAsLiveStreams=serta **{0}** sedang diputar {1} trackSingular=track trackPlural=tracks npNotPlaying=Tidak sedang memainkan apapun. -npNotInHistory=Saat ini tidak ada jejak dalam sejarah. +npNotInHistory=Saat ini tidak ada trek dalam sejarah. npDescription=Deskripsi npLoadedSoundcloud=[{0}/{1}]\n\nmengambil dari Soundcloud npLoadedBandcamp={0}\n\nMengambil dari Bandcamp npLoadedTwitch=Mengambil dari Twitch -npRequestedBy= -permissionMissingBot=Aku memerlukan izin berikut untuk melakukan tindakan\: -permissionMissingInvoker=You need the following permission to perform that action\: +npRequestedBy=Requested by {0} +permissionMissingBot=Saya memerlukan izin berikut untuk melakukan tindakan\: +permissionMissingInvoker=Anda memerlukan izin berikut untuk melakukan tindakan berikut\: permissionEmbedLinks=Cantumkan Tautan rating=Rating listeners=Pendengar @@ -81,7 +81,7 @@ restartSuccess=**{0}** telah di restart. queueEmpty=Antrian lagu kosong. rewSuccess=Memutar balik **{0}** ke {1}. seekSuccess=Mencari **{0} ** untuk {1}. -seekDeniedLiveTrack=Anda tidak bisa mencari lagu live. +seekDeniedLiveTrack=Anda tidak bisa mencari trek yang disiarkan secara langsung. loadPlaySplitListFail=Link tersebut mengarah ke Playlist. Bukan track. Gunakan `;;play`. loadListSuccess=Menemukan dan menambahkan `{0}` dari playlist **{1}**. loadNoMatches=Tidak ada Audio ditemukan dari `{0}`. @@ -92,6 +92,11 @@ loadPlaylistTooMany=Trek {0} ditambahkan. Ditemukan juga banyak trek untuk ditam loadErrorCommon=Terjadi kesalahan ketika Mencari info untuk `{0}`\:\n{1} loadErrorSusp=Terjadi kesalahan ketika loading info untuk ''{0}''. loadQueueTrackLimit=Kamu tidak dapat menambahkan trek ke antrian lebih dari {0} trek\! Ini untuk mencegah penyalahgunaan. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Tentang memuat playlist **{0}** sampai `{1}` trek. Ini memerlukan waktu, harap bersabar. playerUserNotInChannel=Anda harus berada pada Voice channel terlebih dahulu. playerJoinConnectDenied=Aku tidak diperbolehkan untuk terhubung ke channel tersebut. @@ -150,7 +155,7 @@ modUnbanFail=Failed to unban {0} modUnbanFailNotBanned=User {0} is not banned in this guild. modKeepMessages=Add {0} to not delete any messages. modActionTargetDmKicked=Ahoy\! You have been kicked from the guild {0} by user {1}. -modActionTargetDmBanned=Ahoy\! You have been banned from the guild {0} by user {1}. +modActionTargetDmBanned=Ahoy\! Anda telah dicekal dari klan {0} oleh pengguna {1}. kickSuccess=Pengguna {0} telah diusir. kickFail=Gagal untuk diusir {0} kickFailSelf=Kamu tidak bisa mengusir dirimu sendiri. @@ -273,7 +278,7 @@ helpUserInfoCommand=Menampilkan informasi yang diketahui bot tentang diri kamu a helpPerms=Membolehkan Anggota Untuk Mengakses Dan Peran Untuk Peringkat. {0} helpPrefixCommand=Tetapkan awalan untuk guild ini. helpVoteSkip=Pilih untuk melewatkan lagu yang sedang diputar. Membutuhkan 50% dari semua pengguna di voice chat untuk memilih. -helpUnvoteSkip=Remove your vote to skip the current song. +helpUnvoteSkip=Menghapus suara Anda untuk melewatkan lagu saat ini. helpMathOperationAdd=Cetak jumlah num1 dan num2. helpMathOperationSub=Cetak perbedaan penguraian num2 dari num1. helpMathOperationMult=Cetak produk num1 * num2. @@ -302,8 +307,8 @@ skipUserMultiple=Melewati {0} trek yang ditambahkan oleh {1}. skipUsersMultiple=Melewati {0} trek yang ditambahkan oleh {1} pengguna. skipUserNoTracks=Tidak satu pun pengguna yang disebutkan memiliki antrean. voteSkipAdded=Suara Anda telah ditambahkan\! -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! +voteSkipRemoved=Suara Anda telah dihapus\! +voteSkipNotFound=Anda belum membuat suara untuk melewati lagu ini\! voteSkipAlreadyVoted=Anda telah memilih untuk melewati jalur ini\! voteSkipSkipping={0} telah memilih untuk melompati Melewati lintasan {1}. voteSkipNotEnough={0} telah memilih untuk melompati Paling sedikit dibutuhkan {1}. @@ -315,7 +320,7 @@ mathOperationInfinity=Angkanya terlalu besar untuk ditampilkan\! prefix=Awalan prefixGuild=Awalan untuk guild ini adalah {0} prefixShowAgain=Anda bisa menunjukkan awalan kapan saja dengan menyebutkan saya. -moduleAdmin=Administration +moduleAdmin=Administrasi moduleInfo=Information moduleConfig=Configuration moduleMusic=Music @@ -325,8 +330,8 @@ moduleFun=Fun moduleLocked=The {0} module is locked and cannot be enabled/disabled. moduleCantParse=No such module. Show a list of available modules with {0} moduleStatus=Module Status -moduleDisable=Disabled module {0}. -moduleEnable=Enabled module {0}. +moduleDisable=Modul yang dinonaktifkan {0}. +moduleEnable=Modul yang diaktifkan {0}. moduleShowCommands=Say {0} to see the commands of this module. modulesCommands=Say {0} to show commands for a module, or {1} to show all commands. modulesEnabledInGuild=Enabled modules for this guild\: diff --git a/FredBoat/src/main/resources/lang/it_IT.properties b/FredBoat/src/main/resources/lang/it_IT.properties index 1ddd96d92..16b34e5be 100644 --- a/FredBoat/src/main/resources/lang/it_IT.properties +++ b/FredBoat/src/main/resources/lang/it_IT.properties @@ -46,7 +46,7 @@ listShowShuffled=Mostrando la playlist shuffle. listShowRepeatSingle=In ripetizione della traccia corrente. listShowRepeatAll=Ripetendo la lista corrente. listShowHistory=Sto mostrando i brani nella cronologia. -listAddedBy=**{0}**aggiunto da **{1}** `{2}` +listAddedBy=**{0}** aggiunto da **{1}** `[{2}]` listStreamsOnlySingle=C''\u00e8 **{0}** live {1} nella coda di riproduzione. listStreamsOnlyMultiple=Ci sono **{0}** {1} nella coda di riproduzione. listStreamsOrTracksSingle=C''\u00e8 **{0}** {1} con una lunghezza rimanente di **[{2}]** {3} nella coda di riproduzione. @@ -62,7 +62,7 @@ npDescription=Descrizione npLoadedSoundcloud=[{0}/{1}]\n\nCaricato da SoundCloud npLoadedBandcamp={0}\n\nCaricato da Bandcamp npLoadedTwitch=Caricato da Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Ho bisogno della seguente autorizzazione per eseguire tale azione\: permissionMissingInvoker=Hai bisogno del seguente permesso per eseguire tale azione\: permissionEmbedLinks=Incorpora i collegamenti @@ -92,6 +92,11 @@ loadPlaylistTooMany=Aggiunta/e {0} traccia/e. Sono state trovate troppe tracce p loadErrorCommon=Si \u00e8 verificato un errore durante il caricamento delle info relative a `{0}`\:\n{1} loadErrorSusp=Errore sospetto durante il caricamento delle informazioni relative a `{0}`. loadQueueTrackLimit=Non \u00e8 possibile aggiungere brani a una coda con pi\u00f9 di {0} brani\! Ci\u00f2 \u00e8 per prevenire l''abuso. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Sto per caricare la playlist ** {0} ** con fino a `{1}` brani. Questo potrebbe richiedere un po ''di tempo, per favore essere paziente.\n \nContesto | Contesto di richiest. playerUserNotInChannel=Devi prima unirti ad un canale vocale. playerJoinConnectDenied=Non ho i permessi necessari per unirmi a quel canale vocale. @@ -227,8 +232,8 @@ helpCredits=Creato da Fre_d e collaboratori open source helpSent=La documentazione \u00e8 stata inviata ai vostri DM\! helpProperUsage=Utilizzo corretto\: helpCommandOwnerRestricted=Questo comando \u00e8 limitato al proprietario del bot. -helpConfigCommand=Visualizza la configurazione di questa gilda o regolare le impostazioni. -helpLanguageCommand=Visualizzare lingue disponibili o impostare una lingua per questa gilda. +helpConfigCommand=Mostra la configurazione di questa gilda o regola le impostazioni. +helpLanguageCommand=Mostra le lingue disponibili o imposta una lingua per questa gilda. helpModules=Mostra, attiva o disattiva moduli dei comandi per questo server. helpHardbanCommand=Espelli un utente e elimina I suoi messagi dal giorno precedente. helpKickCommand=Caccia un utente da questa gilda. @@ -251,7 +256,7 @@ helpUnpauseCommand=Riattivare il bot. helpVolumeCommand=Cambia il volume. I valori sono 0-150 e 100 \u00e8 il valore predefinito. Il comando volume \u00e8 obsoleto sul pubblico bot. helpExportCommand=Esporta la coda di riproduzione attuale su un link di wastebin, pu\u00f2 essere usato pi\u00f9 tardi come playlist. helpGensokyoRadioCommand=Visualizza il brano giocato su gensokyoradio.net -helpListCommand=Visualizzare un elenco delle attuali canzoni nella playlist. +helpListCommand=Mostra un elenco dei brani correnti nella playlist. helpHistoryCommand=Per mostrare la lista delle canzoni nella cronologia della playlist. helpNowplayingCommand=Visualizzare la canzone attualmente in riproduzione. helpForwardCommand=Avanti la traccia di un determinato periodo di tempo. Esempio\: @@ -268,12 +273,12 @@ helpInviteCommand=Post invita i link per questo bot. helpMALCommand=Ricerca MyAnimeList e visualizzare un anime o un profilo di un utente. helpMusicHelpCommand=Mostra musica comandi e loro uso. helpSayCommand=Fare il bot eco qualcosa. -helpServerInfoCommand=Visualizzare alcune statistiche circa questa gilda. +helpServerInfoCommand=Mostra alcune statistiche su questa gilda. helpUserInfoCommand=Visualizzare le informazioni su di te o un utente noto al bot. helpPerms=Concedi ai ruoli e membri autorizzati il rank {0}. helpPrefixCommand=Imposta il prefisso per questa gilda. helpVoteSkip=Votare per saltare il brano corrente. Ha bisogno del 50% di tutti gli utenti nella chat vocale per saltare. -helpUnvoteSkip=Remove your vote to skip the current song. +helpUnvoteSkip=Rimuove il tuo voto per saltare la canzone corrente. helpMathOperationAdd=Ti dice la somma del numero 1 e del numero 2. helpMathOperationSub=Ti dice la differenza tra il numero 1 e il numero 2. helpMathOperationMult=Ti dice il prodotto del numero 1 moltiplicato per il numero 2. @@ -289,7 +294,7 @@ listPageNum=Pagina * *{0} * * di * *{1} * *. permsListTitle=Utenti e ruoli con i permessi {0} permsAdded={0} aggiunta a {1}. permsRemoved={0} rimosso da {1}. -permsFailSelfDemotion=Non puoi rimuoverlo perch\u00e9 ti riconoscera come se non sei un admin\! +permsFailSelfDemotion=Non puoi rimuoverlo poich\u00e9 ti priverebbe dei permessi da amministratore\! permsAlreadyAdded={0} gi\u00e0 aggiunto a {1} permsNotAdded={0} non \u00e8 in {1} fuzzyMultiple=Pi\u00f9 elementi sono stati trovati. Volevi dire uno di questi? @@ -302,8 +307,8 @@ skipUserMultiple=Ho saltato {0} brani aggiunti da {1}. skipUsersMultiple=Ho saltato {0} brani aggiunti da {1} utenti. skipUserNoTracks=Nessuno degli utenti citati hanno un brano in coda. voteSkipAdded=Il tuo voto \u00e8 stato aggiunto\! -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! +voteSkipRemoved=Il tuo voto \u00e8 stato rimosso\! +voteSkipNotFound=Non hai votato per saltare questa traccia\! voteSkipAlreadyVoted=Hai gi\u00e0 votato per saltare questo brano\! voteSkipSkipping={0} hanno votato per saltare. Sto saltando traccia {1}. voteSkipNotEnough={0} hanno votato per saltare. Almeno {1} necessari. diff --git a/FredBoat/src/main/resources/lang/ja_JP.properties b/FredBoat/src/main/resources/lang/ja_JP.properties index 55a0b5b26..3f57ba285 100644 --- a/FredBoat/src/main/resources/lang/ja_JP.properties +++ b/FredBoat/src/main/resources/lang/ja_JP.properties @@ -62,7 +62,7 @@ npDescription=\u8aac\u660e npLoadedSoundcloud=[{0}/{1}] \nSoundcloud \u304b\u3089\u8aad\u307f\u8fbc\u307e\u308c\u307e\u3059 npLoadedBandcamp={0} \u306fBandcamp \u304b\u3089\u8aad\u307f\u8fbc\u307e\u308c\u307e\u3059 npLoadedTwitch=Twitch\u304b\u3089\u8aad\u307f\u8fbc\u307e\u308c\u307e\u3059 -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=\u305d\u306e\u30a2\u30af\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3059\u308b\u305f\u3081\u306b\u306f\u3001\u79c1\u306b\u3053\u308c\u3089\u306e\u6a29\u9650\u3092\u4e0e\u3048\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\: permissionMissingInvoker=\u305d\u306e\u30a2\u30af\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3059\u308b\u305f\u3081\u306b\u306f\u3001\u79c1\u306b\u3053\u308c\u3089\u306e\u6a29\u9650\u3092\u4e0e\u3048\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\: permissionEmbedLinks=\u30ea\u30f3\u30af\u3092\u57cb\u3081\u8fbc\u3080 @@ -92,6 +92,11 @@ loadPlaylistTooMany={0} \u30c8\u30e9\u30c3\u30af\u3092\u8ffd\u52a0\u3057\u307e\u loadErrorCommon=`{0}`\u306e\u60c5\u5831\u3092\u8aad\u307f\u8fbc\u307f\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\:\n{1} loadErrorSusp=''{0}''\u306e\u60c5\u5831\u3092\u30ed\u30fc\u30c9\u3059\u308b\u3068\u304d\u306b\u4e0d\u5be9\u306a\u30a8\u30e9\u30fc\u304c\u767a\u751f \u3002 loadQueueTrackLimit=\u4e71\u7528\u9632\u6b62\u306e\u305f\u3081\u3001{0} \u30c8\u30e9\u30c3\u30af\u4ee5\u4e0a\u3001\u30ad\u30e5\u30fc\u306b\u30c8\u30e9\u30c3\u30af\u3092\u8ffd\u52a0\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093\! +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=\u30d7\u30ec\u30a4\u30ea\u30b9\u30c8 * *{0} * * ( ''{1}'' \u30c8\u30e9\u30c3\u30af) \u3092\u30ed\u30fc\u30c9\u3057\u307e\u3059\u3002\u3053\u308c\u306b\u306f\u3001\u3057\u3070\u3089\u304f\u6642\u9593\u304c\u304b\u304b\u308a\u307e\u3059\u3002\u3054\u4e86\u627f\u304f\u3060\u3055\u3044\u3002 playerUserNotInChannel=\u306f\u3058\u3081\u306b\u3001\u97f3\u58f0\u30c1\u30e3\u30cd\u30eb\u306b\u5165\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002 playerJoinConnectDenied=\u97f3\u58f0\u30c1\u30e3\u30f3\u30cd\u30eb\u3078\u306e\u63a5\u7d9a\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093\u3088 @@ -220,7 +225,7 @@ commandsMoreHelp={0} \u3068\u767a\u8a00\u3059\u308b\u3053\u3068\u3067\u3001\u727 commandsModulesHint={0} \u3067\u30e2\u30b8\u30e5\u30fc\u30eb\u3092\u6709\u52b9\u307e\u305f\u306f\u7121\u52b9\u306b\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002 helpUnknownCommand=\u4e0d\u660e\u306a\u30b3\u30de\u30f3\u30c9\u3002 helpDocsLocation=\u4ed5\u69d8\u66f8\u306f\u3053\u3061\u3089\: -helpBotInvite=\u30dc\u30c3\u30c8\u3092\u3042\u306a\u305f\u306e\u30b5\u30fc\u30d0\u30fc\u306b\u8ffd\u52a0\u3057\u305f\u3044\u3067\u3059\u304b? \u3082\u3057\u3042\u306a\u305f\u304c\u30b5\u30fc\u30d0\u30fc\u306e\u7ba1\u7406\u306e\u6a29\u9650\u3092\u304a\u6301\u3061\u3067\u3057\u305f\u3089\u3001\u3053\u3061\u3089\u304b\u3089\u30dc\u30c3\u30c8\u3092\u62db\u5f85\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\: +helpBotInvite=\u30dc\u30c3\u30c8\u3092\u3042\u306a\u305f\u306e\u30b5\u30fc\u30d0\u30fc\u306b\u8ffd\u52a0\u3057\u305f\u3044\u3067\u3059\u304b? \u3082\u3057\u3042\u306a\u305f\u304c\u30b5\u30fc\u30d0\u30fc\u7ba1\u7406\u306e\u6a29\u9650\u3092\u304a\u6301\u3061\u3067\u3057\u305f\u3089\u3001\u3053\u3061\u3089\u304b\u3089\u30dc\u30c3\u30c8\u3092\u62db\u5f85\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\: helpHangoutInvite=\u30dc\u30c3\u30c8\u306b\u95a2\u3057\u3066\u52a9\u3051\u304c\u5fc5\u8981\u3067\u3059\u304b? \u305d\u308c\u3068\u3082\u30a2\u30a4\u30c7\u30a2\u3092\u304a\u6301\u3061\u3067\u3059\u304b? \u3082\u3057\u304f\u306f\u4ed6\u306e\u4eba\u3068\u8a71\u304c\u3057\u305f\u3044\u3067\u3059\u304b? \u305d\u308c\u306a\u3089\u3070\u662f\u975eFredBoat\u30b3\u30df\u30e5\u30cb\u30c6\u30a3\u306b\u53c2\u52a0\u3057\u307e\u3057\u3087\u3046\! helpNoDmCommands=\u30c0\u30a4\u30ec\u30af\u30c8\u30e1\u30c3\u30bb\u30fc\u30b8\u3067\u306f\u30dc\u30c3\u30c8\u306e\u30b3\u30de\u30f3\u30c9\u3092\u4f7f\u3046\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093\u3002 helpCredits=Fre_d\u3068\u30aa\u30fc\u30d7\u30f3\u30bd\u30fc\u30b9\u8ca2\u732e\u8005\u306b\u3088\u3063\u3066\u4f5c\u6210\u3055\u308c\u307e\u3057\u305f\u3002 @@ -273,11 +278,11 @@ helpUserInfoCommand=\u81ea\u5206\u307e\u305f\u306f\u30dc\u30c3\u30c8\u306b\u77e5 helpPerms={0} \u30e9\u30f3\u30af\u306e\u30db\u30ef\u30a4\u30c8\u30ea\u30b9\u30c8\u30e1\u30f3\u30d0\u30fc\u3068\u30ed\u30fc\u30eb\u3092\u8a31\u53ef\u3057\u307e\u3059\u3002 helpPrefixCommand=\u3053\u306e\u30ae\u30eb\u30c9\u306e\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u3092\u8a2d\u5b9a\u3057\u307e\u3059\u3002 helpVoteSkip=\u73fe\u5728\u306e\u66f2\u3092\u30b9\u30ad\u30c3\u30d7\u3059\u308b\u305f\u3081\u306b\u6295\u7968\u3057\u3066\u304f\u3060\u3055\u3044\u3002\u30dc\u30a4\u30b9\u30c1\u30e3\u30c3\u30c8\u306e\u5168\u30e6\u30fc\u30b6\u30fc\u306e50%\u304c\u6295\u7968\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002 -helpUnvoteSkip=Remove your vote to skip the current song. +helpUnvoteSkip=\u73fe\u5728\u306e\u66f2\u306e\u30b9\u30ad\u30c3\u30d7\u3078\u306e\u3042\u306a\u305f\u306e\u6295\u7968\u3092\u524a\u9664\u3057\u307e\u3059 helpMathOperationAdd=num1\u3068num2\u306e\u5408\u8a08\u3092\u51fa\u529b\u3057\u307e\u3059\u3002 helpMathOperationSub=num2\u304b\u3089num1\u3092\u5f15\u3044\u305f\u5dee\u3092\u51fa\u529b\u3057\u307e\u3059\u3002 helpMathOperationMult=num1 * num2\u306e\u7a4d\u3092\u51fa\u529b\u3057\u307e\u3059\u3002 -helpMathOperationDiv=Print the quotient of dividing num1 by num2. +helpMathOperationDiv=num1\u3092num2\u3067\u5272\u3063\u305f\u5546\u3092\u51fa\u529b\u3057\u307e\u3059\u3002 helpMathOperationMod=num1\u3092num2\u3067\u5272\u3063\u305f\u6b8b\u308a\u306e\u90e8\u5206\u3092\u51fa\u529b\u3057\u307e\u3059\u3002 helpMathOperationPerc=num2\u3067num1\u3067\u8868\u3055\u308c\u308b\u5272\u5408\u3092\u8868\u793a\u3057\u307e\u3059\u3002 helpMathOperationSqrt=num\u306e\u5e73\u65b9\u6839\u3092\u51fa\u529b\u3057\u307e\u3059\u3002 @@ -287,7 +292,7 @@ destroyHelp=\u30d7\u30ec\u30a4\u30e4\u30fc\u3092\u30ea\u30bb\u30c3\u30c8\u3057\u destroySucc=\u30d7\u30ec\u30a4\u30e4\u30fc\u3092\u30ea\u30bb\u30c3\u30c8\u3057\u3001\u30ad\u30e5\u30fc\u3092\u30af\u30ea\u30a2\u3057\u307e\u3059\u3002 listPageNum=**{1}**\u30da\u30fc\u30b8\u4e2d**{0}**\u30da\u30fc\u30b8\u3092\u8868\u793a permsListTitle={0}\u500b\u306e\u6a29\u9650\u3092\u6301\u3064\u30e6\u30fc\u30b6\u30fc\u3068\u5f79\u8077 -permsAdded=` {0} `\u3092{1} \u306b\u8ffd\u52a0\u3057\u307e\u3057\u305f\u3002 +permsAdded=`{0}`\u3092`{1}`\u306b\u8ffd\u52a0\u3057\u307e\u3057\u305f\u3002 permsRemoved={1} \u304b\u3089{0} \u3092\u524a\u9664\u3057\u307e\u3057\u305f\u3002 permsFailSelfDemotion=\u3042\u306a\u305f\u306f\u7ba1\u7406\u8005\u6a29\u9650\u306a\u3057\u3067\u3042\u306a\u305f\u3092\u30ec\u30f3\u30c0\u30ea\u30f3\u30b0\u3059\u308b\u306e\u3067\u3001\u3053\u308c\u3092\u524a\u9664\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093\uff01 permsAlreadyAdded={0} \u306f\u65e2\u306b{1} \u306b\u8ffd\u52a0\u3055\u308c\u3066\u3044\u307e\u3059 @@ -295,15 +300,15 @@ permsNotAdded={0} \u306f{1} \u306b\u306f\u3042\u308a\u307e\u305b\u3093 fuzzyMultiple=\u8907\u6570\u306e\u30a2\u30a4\u30c6\u30e0\u304c\u898b\u3064\u304b\u308a\u307e\u3057\u305f\u3002\u3053\u308c\u3089\u306e\u3044\u305a\u308c\u304b\u3092\u610f\u5473\u3057\u307e\u3057\u305f\u304b\uff1f fuzzyNothingFound=` {0} `\u306b\u4f55\u3082\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002 cmdPermsTooLow=\u3053\u306e\u30b3\u30de\u30f3\u30c9\u3092\u5b9f\u884c\u3059\u308b\u6a29\u9650\u304c\u3042\u308a\u307e\u305b\u3093\! \u3053\u306e\u30b3\u30de\u30f3\u30c9\u306b\u306f`{0}`\u304c\u5fc5\u8981\u3067\u3059\u304c\u3001\u3042\u306a\u305f\u306f`{1}`\u3057\u304b\u6301\u3063\u3066\u3044\u307e\u305b\u3093\u3002 -playersLimited=FredBoat\u306f\u73fe\u5728\u5bb9\u91cf\u304c\u6700\u5927\u3067\u3059\! \u30dc\u30c3\u30c8\u306f\u73fe\u5728 `{0}`\u30b9\u30c8\u30ea\u30fc\u30e0\u307e\u3067\u518d\u751f\u3059\u308b\u3088\u3046\u306b\u56fa\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u305d\u3046\u3057\u306a\u3044\u3068\u3001\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u8ca0\u8377\u306e\u4e0b\u3067Discord\u304b\u3089\u5207\u65ad\u3059\u308b\u5371\u967a\u304c\u3042\u308a\u307e\u3059\u3002\u3042\u306a\u305f\u304c\u79c1\u305f\u3061\u306e\u9650\u754c\u3092\u5897\u3084\u3059\u306e\u3092\u624b\u4f1d\u3063\u3066\u304f\u308c\u308b\u4eba\u3084\u3001\u8d85\u904e\u5bc6\u306a\u30dc\u30c3\u30c8\u3092\u4f7f\u7528\u3057\u305f\u3044\u5834\u5408\u306f\u3001Patreon\u306e\u4f5c\u696d\u3092\u30b5\u30dd\u30fc\u30c8\u3057\u3066\u304f\u3060\u3055\u3044\:\n {1}\n\n\u3054\u8ff7\u60d1\u3092\u304a\u304b\u3051\u3057\u3066\u7533\u3057\u8a33\u3042\u308a\u307e\u305b\u3093\! \u5f8c\u3067\u3082\u3046\u4e00\u5ea6\u3084\u308a\u76f4\u3057\u305f\u3044\u3068\u601d\u3046\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002\u3053\u306e\u30e1\u30c3\u30bb\u30fc\u30b8\u306f\u901a\u5e38\u3001\u30d4\u30fc\u30af\u6642\u306b\u306e\u307f\u8868\u793a\u3055\u308c\u307e\u3059\u3002 +playersLimited=FredBoat\u306f\u73fe\u5728\u5bb9\u91cf\u304c\u6700\u5927\u3067\u3059\! \n\u30dc\u30c3\u30c8\u306f\u73fe\u5728 `{0}`\u30b9\u30c8\u30ea\u30fc\u30e0\u307e\u3067\u518d\u751f\u3059\u308b\u3088\u3046\u306b\u56fa\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u305d\u3046\u3057\u306a\u3044\u3068\u3001\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u8ca0\u8377\u306e\u4e0b\u3067Discord\u304b\u3089\u5207\u65ad\u3059\u308b\u5371\u967a\u304c\u3042\u308a\u307e\u3059\u3002\u3042\u306a\u305f\u304c\u79c1\u305f\u3061\u306e\u9650\u754c\u3092\u5897\u3084\u3059\u306e\u3092\u624b\u4f1d\u3063\u3066\u304f\u308c\u308b\u4eba\u3084\u3001\u8d85\u904e\u5bc6\u306a\u30dc\u30c3\u30c8\u3092\u4f7f\u7528\u3057\u305f\u3044\u5834\u5408\u306f\u3001Patreon\u306e\u4f5c\u696d\u3092\u30b5\u30dd\u30fc\u30c8\u3057\u3066\u304f\u3060\u3055\u3044\:\n {1}\n\n\u3054\u8ff7\u60d1\u3092\u304a\u304b\u3051\u3057\u3066\u7533\u3057\u8a33\u3042\u308a\u307e\u305b\u3093\! \u5f8c\u3067\u3082\u3046\u4e00\u5ea6\u3084\u308a\u76f4\u3057\u305f\u3044\u3068\u601d\u3046\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002\u3053\u306e\u30e1\u30c3\u30bb\u30fc\u30b8\u306f\u901a\u5e38\u3001\u30d4\u30fc\u30af\u6642\u306b\u306e\u307f\u8868\u793a\u3055\u308c\u307e\u3059\u3002 tryLater=\u5f8c\u3067\u3082\u3046\u4e00\u5ea6\u304a\u8a66\u3057\u304f\u3060\u3055\u3044\u3002 skipUserSingle={1}\u306b\u8ffd\u52a0\u3055\u308c\u305f{0}\u3092\u30b9\u30ad\u30c3\u30d7\u3057\u307e\u3057\u305f\u3002 skipUserMultiple={1} \u3067\u8ffd\u52a0\u3055\u308c\u305f{0} \u30c8\u30e9\u30c3\u30af\u3092\u30b9\u30ad\u30c3\u30d7\u3057\u307e\u3057\u305f\u3002 skipUsersMultiple={1}\u4eba\u306e\u30e6\u30fc\u30b6\u30fc\u304c\u8ffd\u52a0\u3057\u305f{0}\u66f2\u3092\u30b9\u30ad\u30c3\u30d7\u3057\u307e\u3057\u305f\u3002 skipUserNoTracks=\u30e1\u30f3\u30b7\u30e7\u30f3\u3055\u308c\u305f\u30e6\u30fc\u30b6\u30fc\u306e\u3044\u305a\u308c\u3082\u3001\u30c8\u30e9\u30c3\u30af\u304c\u4e88\u7d04\u306b\u5165\u308c\u3089\u308c\u3066\u3044\u306a\u3044\u3002 voteSkipAdded=\u3042\u306a\u305f\u306e\u6295\u7968\u304c\u8ffd\u52a0\u3055\u308c\u307e\u3057\u305f\! -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! +voteSkipRemoved=\u3042\u306a\u305f\u306e\u6295\u7968\u306f\u524a\u9664\u3055\u308c\u307e\u3057\u305f\uff01 +voteSkipNotFound=\u3042\u306a\u305f\u306f\u3053\u306e\u30c8\u30e9\u30c3\u30af\u306e\u30b9\u30ad\u30c3\u30d7\u3078\u306e\u6295\u7968\u3092\u884c\u3063\u3066\u3044\u307e\u305b\u3093\uff01 voteSkipAlreadyVoted=\u3042\u306a\u305f\u306f\u3059\u3067\u306b\u3053\u306e\u30c8\u30e9\u30c3\u30af\u3092\u30b9\u30ad\u30c3\u30d7\u3059\u308b\u3053\u3068\u306b\u6295\u7968\u3057\u3066\u3044\u307e\u3059\uff01 voteSkipSkipping={0} \u306f\u30b9\u30ad\u30c3\u30d7\u3059\u308b\u3088\u3046\u6295\u7968\u3057\u307e\u3057\u305f\u3002\u30c8\u30e9\u30c3\u30af{1} \u3092\u30b9\u30ad\u30c3\u30d7\u3057\u3066\u3044\u307e\u3059\u3002 voteSkipNotEnough={0} \u306f\u30b9\u30ad\u30c3\u30d7\u3059\u308b\u3088\u3046\u6295\u7968\u3057\u307e\u3057\u305f\u3002\u5c11\u306a\u304f\u3068\u3082{1} \u306f\u5fc5\u8981\u3067\u3059\u3002 diff --git a/FredBoat/src/main/resources/lang/ko_KR.properties b/FredBoat/src/main/resources/lang/ko_KR.properties index 2016976ff..400e863c8 100644 --- a/FredBoat/src/main/resources/lang/ko_KR.properties +++ b/FredBoat/src/main/resources/lang/ko_KR.properties @@ -62,7 +62,7 @@ npDescription=\uc124\uba85 npLoadedSoundcloud=[{0}/{1}]\n\nSoundCloud\uc5d0\uc11c \ubd88\ub7ec\uc634 npLoadedBandcamp={0}\n\nBandcamp\uc5d0\uc11c \ubd88\ub7ec\uc634 npLoadedTwitch=Twitch\uc5d0\uc11c \ubd88\ub7ec\uc634 -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=\uc774 \uc791\uc5c5\uc744 \uc218\ud589\ud558\ub824\uba74 \ub2e4\uc74c\uacfc \uac19\uc740 \uad8c\ud55c\uc774 \ud544\uc694\ud569\ub2c8\ub2e4\: permissionMissingInvoker=\ud574\ub2f9 \uc791\uc5c5\uc744 \uc218\ud589 \ud558\ub824\uba74 \ub2e4\uc74c \uc0ac\uc6a9 \uad8c\ud55c\uc774 \ud544\uc694 \ud569\ub2c8\ub2e4. permissionEmbedLinks=\ub0b4\uc7a5\ub41c \ub9c1\ud06c @@ -92,6 +92,11 @@ loadPlaylistTooMany={0} \uac1c\uc758 \uc74c\uc545\uc744 \ucd94\uac00\ud588\uc2b5 loadErrorCommon=`{0}`\uc744(\ub97c) \uc9c4\ud589\ud558\ub294 \uc911 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4\:\n{1} loadErrorSusp=`{0}`\uc5d0\uc11c \uc624\ub958\uac00 \uc758\uc2ec\ub429\ub2c8\ub2e4. loadQueueTrackLimit=\ud2b8\ub799\uc744 {0}\uac1c \uc774\uc0c1\uc758 \ud2b8\ub799\uc774 \uc788\ub294 \ub300\uae30 \uc5f4\uc5d0 \ucd94\uac00\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4\! \uc774\uac83\uc740 \ub0a8\uc6a9 \ubc29\uc9c0\ud558\uae30 \uc704\ud568\uc785\ub2c8\ub2e4. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=\uc7ac\uc0dd \ubaa9\ub85d **{0}** \uc5d0 \ub300\ud55c \ucd5c\ub300 "{1}" \ud2b8\ub799\uc744 \ub85c\ub4dc\ud558\ub294 \uc911\uc785\ub2c8\ub2e4. \uc7a0\uc2dc\ub9cc \uae30\ub2e4\ub824 \uc8fc\uc2ed\uc2dc\uc624. playerUserNotInChannel=\ub2f9\uc2e0\uc740 \uc6b0\uc120 \uc74c\uc131 \ucc44\ub110\uc5d0 \ub4e4\uc5b4\uc640\uc57c \ud569\ub2c8\ub2e4. playerJoinConnectDenied=\uc74c\uc131 \ucc44\ub110\uc5d0 \ub4e4\uc5b4\uac08 \uad8c\ud55c\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. diff --git a/FredBoat/src/main/resources/lang/ms_MY.properties b/FredBoat/src/main/resources/lang/ms_MY.properties index d1fbdf798..829d6badd 100644 --- a/FredBoat/src/main/resources/lang/ms_MY.properties +++ b/FredBoat/src/main/resources/lang/ms_MY.properties @@ -62,7 +62,7 @@ npDescription=Keterangan npLoadedSoundcloud=[{0}/{1}]\n\nDimuatkan dari Soundcloud npLoadedBandcamp={0}\n\nDimuatkan dari Bandcamp npLoadedTwitch=Dimuatkan dari Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Saya memerlukan kebenaran berikut untuk melaksanakan tindakan tersebut\: permissionMissingInvoker=Anda memerlukan kebenaran berikut untuk melaksanakan tindakan tersebut\: permissionEmbedLinks=Pautan Benaman @@ -92,6 +92,11 @@ loadPlaylistTooMany={0} lagu telah dimasukkan. Terlalu banyak lagu yang dijumpai loadErrorCommon=Ralat ketika memuatkan maklumat `{0}`\:\n{1} loadErrorSusp=Ralat mencurigakan ketika memuatkan maklumat `{0}`. loadQueueTrackLimit=Anda tidak boleh menambah lagu ke dalam senarai menunggu yang ada lebih dari {0} lagu\! Ini kerana kami perlu elakkan penyalahgunaan. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Sedang memuatkan senarai main **{0}** dengan `{1}` buah lagu. Ia mungkin mengambil sedikit masa, harap bersabar. playerUserNotInChannel=Anda mesti sertai saluran suara dahulu. playerJoinConnectDenied=Saya tidak dibenarkan untuk menyambung ke saluran suara anda. diff --git a/FredBoat/src/main/resources/lang/nl_NL.properties b/FredBoat/src/main/resources/lang/nl_NL.properties index 9f99e3b94..91f9c21c4 100644 --- a/FredBoat/src/main/resources/lang/nl_NL.properties +++ b/FredBoat/src/main/resources/lang/nl_NL.properties @@ -62,7 +62,7 @@ npDescription=Beschrijving npLoadedSoundcloud=[{0}/{1}] \n\nGeladen vanuit Soundcloud npLoadedBandcamp={0}\n\nGeladen vanuit Bandcamp npLoadedTwitch=Geladen vanuit Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Ik heb de volgende toestemming nodig om die opdracht uit te voeren\: permissionMissingInvoker=Je hebt de volgende toestemming nodig om die opdracht uit te voeren\: permissionEmbedLinks=Voeg links toe @@ -92,6 +92,11 @@ loadPlaylistTooMany={0} nummers toegevoegd. Te veel nummers gevonden om weer te loadErrorCommon=Er is een fout opgetreden bij het laden van info voor `{0}`\: {1} loadErrorSusp=Verdachte fout bij het laden van info voor "{0}". loadQueueTrackLimit=U kunt geen nummers toevoegen aan een nummerreeks met meer dan {0} nummers\! Dit is om misbruik te voorkomen. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Zodadelijk wordt de afspeellijst **{0} ** met tot ''{1}'' nummers geladen. Dit kan een tijdje duren, wees geduldig. playerUserNotInChannel=Je moet je eerst bij een spraakkanaal aansluiten. playerJoinConnectDenied=Ik heb geen toestemming om met dat spraakkanaal te verbinden. @@ -181,7 +186,7 @@ langSuccess=Overgeschakeld naar spraak {0}. langInfo=FredBoat ondersteunt meerdere door gebruikers toegevoegde talen die je met deze opdracht kan selecteren. Administrators op deze server kunnen een taal selecteren met ";;lang " Hier is een volledige lijst met ondersteunde talen\: langDisclaimer=Vertalingen kunnen niet 100% correct of volledig zijn. Ontbrekende vertalingen kunnen worden bijgedragen bij . loadSingleTrack=**{0}** is aan je wachtrij toegevoegd. -loadSingleTrackFirst=**{0}** has been added to the top of the queue. +loadSingleTrackFirst=**{0}** is toegevoegd aan de top van de wachtrij. loadSingleTrackAndPlay=**{0} ** zal nu spelen. invite=Uitnodigingslink voor **{0}**\: ratelimitedCommandsUser=Je verstuurt te snel opdrachten\! Doe alsjeblieft iets rustiger aan. @@ -239,7 +244,7 @@ helpJoinCommand=Voeg de bot aan je huidige stemkanaal toe. helpLeaveCommand=Laat de bot je huidige stemkanaal verlaten. helpPauseCommand=Pauzeer de Bot. helpPlayCommand=Speel muziek van de gegeven URL of zoek naar een nummer. Bezoek alsjeblieft {0} voor een volledige lijst van bronnen -helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} +helpPlayNextCommand=Speel muziek van de opgegeven URL of zoek naar een track. Tracks worden toegevoegd aan de top van de wachtrij. Voor een volledige lijst van bronnen bezoek {0} helpPlaySplitCommand=Verdeel een YouTube video in een nummerreeks voorzien in zijn descriptie. helpRepeatCommand=Schakel tussen herhaal modi. helpReshuffleCommand=Schuifel de huidige nummerreeks opnieuw. @@ -273,7 +278,7 @@ helpUserInfoCommand=Toon informatie over jezelf of een gebruiker die bekend is m helpPerms=Laat de {0} rang leden en rollen op de witte lijst zetten. helpPrefixCommand=Stel de prefix in voor deze guild. helpVoteSkip=Stem om het huidige nummer over te slaan. Minstens 50% van alle gebruikers in het spraakkanaal moeten ermee instemmen. -helpUnvoteSkip=Remove your vote to skip the current song. +helpUnvoteSkip=Verwijder je stem om het huidige nummer over te slaan. helpMathOperationAdd=Print de som van num1 en num2. helpMathOperationSub=Print het verschil tussen het aftrekken van num2 en van num1. helpMathOperationMult=Print het product van num1*num2. @@ -302,8 +307,8 @@ skipUserMultiple=Overgeslagen {0} nummers toegevoegd door {1}. skipUsersMultiple={0} nummers overgeslagen toegevoegd door {1} gebruikers. skipUserNoTracks=Geen van de genoemde gebruikers hebben nummers in de wachtrij gezet. voteSkipAdded=Jouw stem is toegevoegd\! -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! +voteSkipRemoved=Je stem is verwijderd\! +voteSkipNotFound=Je hebt nog niet gestemd om deze track over te slaan\! voteSkipAlreadyVoted=Je hebt al gestemd om dit nummer over te slaan\! voteSkipSkipping={0} heeft gestemd om over te slaan. {1} wordt overgeslagen. voteSkipNotEnough={0} heeft gestemd om over te slaan. Nog {1} stemmen nodig om het nummer te kunnen overslaan. diff --git a/FredBoat/src/main/resources/lang/no_NO.properties b/FredBoat/src/main/resources/lang/no_NO.properties index 4f1a87d03..a47ec82ad 100644 --- a/FredBoat/src/main/resources/lang/no_NO.properties +++ b/FredBoat/src/main/resources/lang/no_NO.properties @@ -62,7 +62,7 @@ npDescription=Beskrivelse npLoadedSoundcloud=[{0}/{1}] Lastet fra Soundcloud npLoadedBandcamp={0} lastet inn fra Bandcamp npLoadedTwitch=Lastet inn fra Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Jeg trenger f\u00f8lgende tillatelse for \u00e5 utf\u00f8re handlingen\: permissionMissingInvoker=You need the following permission to perform that action\: permissionEmbedLinks=Opprette lenker @@ -92,6 +92,11 @@ loadPlaylistTooMany=Lagt til {0} sanger. Fant for mange sanger til at de kan vis loadErrorCommon=Feil oppstod under innlasting informasjon for ''{0}''\:{1} loadErrorSusp=Mistenkelige feil ved lasting informasjon for ''{0}''. loadQueueTrackLimit=Du kan ikke legge til flere sanger i en k\u00f8 som inneholder {0} sanger\! Dette for \u00e5 forhindre misbruk. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Spillelisten **{0}** med opptil `{1}` sanger lastes n\u00e5 inn. Dette kan ta litt tid, vennligst vent. playerUserNotInChannel=Du m\u00e5 bli en stemme kanal f\u00f8rst. playerJoinConnectDenied=Jeg har ikke tillatelse til \u00e5 koble til den talekanalen. diff --git a/FredBoat/src/main/resources/lang/pl_PL.properties b/FredBoat/src/main/resources/lang/pl_PL.properties index 8c0a88d4c..7a4d2c435 100644 --- a/FredBoat/src/main/resources/lang/pl_PL.properties +++ b/FredBoat/src/main/resources/lang/pl_PL.properties @@ -62,7 +62,7 @@ npDescription=Opis npLoadedSoundcloud=[{0}/{1}]\n\nZa\u0142adowano z Soundcloud''a npLoadedBandcamp={0}\n\nZa\u0142adowano z Bandcamp''a npLoadedTwitch=Za\u0142adowano z Twitch'a -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Potrzebuj\u0119 nast\u0119puj\u0105cych uprawnie\u0144 do wykonania tej akcji\: permissionMissingInvoker=Musisz mie\u0107 nast\u0119puj\u0105ce uprawnienia do wykonania tej akcji\: permissionEmbedLinks=Osadzone \u0142\u0105cza @@ -92,6 +92,11 @@ loadPlaylistTooMany=Dodano {0} utwory/\u00f3w. Znaleziono zbyt wiele utwor\u00f3 loadErrorCommon=Wyst\u0105pi\u0142 b\u0142\u0105d podczas \u0142adowania informacji dla `{0}`\:\n{1} loadErrorSusp=Podejrzany b\u0142\u0105d podczas \u0142adowania informacji dla `{0}`. loadQueueTrackLimit=Nie mo\u017cesz doda\u0107 utwor\u00f3w do kolejki je\u017celi ma wi\u0119cej ni\u017c {0} utwor\u00f3w\! Ma to zapobiec Nadu\u017cyciom. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Zaczynam \u0142adowa\u0107 Playlist\u0119 **{0}** z `{1}`utworami. To mo\u017ce potrwa\u0107 troch\u0119, prosz\u0119 o cierpliwo\u015b\u0107. playerUserNotInChannel=Najpierw musisz do\u0142\u0105czy\u0107 do kana\u0142u g\u0142osowego. playerJoinConnectDenied=Nie mam pozwolenia by po\u0142\u0105czy\u0107 si\u0119 z tym kana\u0142em g\u0142osowym. @@ -181,7 +186,7 @@ langSuccess=Prze\u0142\u0105czono na j\u0119zyk {0}. langInfo=FredBoat obs\u0142uguje kilka j\u0119zyk\u00f3w przekazanych przez u\u017cytkownika, kt\u00f3re mo\u017cna wybra\u0107 z tego polecenia. Administratorzy na tym serwerze mog\u0105 wybra\u0107 j\u0119zyk z '; lang ' tu znajduje si\u0119 pe\u0142na lista obs\u0142ugiwanych j\u0119zyk\u00f3w\: langDisclaimer=T\u0142umaczenie mo\u017ce nie by\u0107 100% dok\u0142adne i kompletne. Brakuj\u0105ce t\u0142umaczenia mo\u017cna wysy\u0142a\u0107 na < https\://crowdin.com/project/fredboat >. loadSingleTrack=**{0}** zosta\u0142 dodany do kolejki. -loadSingleTrackFirst=**{0}** has been added to the top of the queue. +loadSingleTrackFirst=**{0}** zosta\u0142 dodany do g\u00f3rnej kolejki. loadSingleTrackAndPlay=**{0}** zostanie odtworzony. invite=Zapraszaj\u0105cy link **{0}**\: ratelimitedCommandsUser=Wysy\u0142asz komendy za szybko\! Zwolnij troch\u0119. diff --git a/FredBoat/src/main/resources/lang/pt_BR.properties b/FredBoat/src/main/resources/lang/pt_BR.properties index b73da5abd..f0497eca2 100644 --- a/FredBoat/src/main/resources/lang/pt_BR.properties +++ b/FredBoat/src/main/resources/lang/pt_BR.properties @@ -62,10 +62,10 @@ npDescription=Descri\u00e7\u00e3o npLoadedSoundcloud=[{0}/{1}] \n\nCarregado do Soundcloud npLoadedBandcamp={0} \n\nCarregado do Bandcamp npLoadedTwitch=Carregado do Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Eu preciso de permiss\u00e3o para executar essa a\u00e7\u00e3o a seguir\: permissionMissingInvoker=Eu preciso de permiss\u00e3o para executar essa a\u00e7\u00e3o a seguir\: -permissionEmbedLinks=Inserir Links de Embeds +permissionEmbedLinks=Inserir Links rating=Notas listeners=Ouvintes year=Ano @@ -92,6 +92,11 @@ loadPlaylistTooMany=Adicionadas {0} faixas. Encontradas muitas faixas para exibi loadErrorCommon=Erro encontrado carregando informa\u00e7\u00e3o para `{0}`\:\n{1} loadErrorSusp=Erro suspeito ao carregar a informa\u00e7\u00e3o para ''{0}''. loadQueueTrackLimit=Voc\u00ea n\u00e3o pode adicionar faixas para uma fila com mais de {0} faixas\! Isso \u00e9 para evitar abusos. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Prestes a carregar a lista de reprodu\u00e7\u00e3o * *{0} * * com at\u00e9 faixas de ''{1}''. Isto pode demorar um pouco, por favor, seja paciente. playerUserNotInChannel=Primeiro voc\u00ea deve se juntar a um canal. playerJoinConnectDenied=N\u00e3o estou autorizado a conectar-me a esse canal. @@ -103,7 +108,7 @@ shutdownRestarting=FredBoat\u266a\u266a est\u00e1 reiniciando. Isto deve demorar shutdownIndef=FredBoat\u266a\u266a est\u00e1 se desligando. Quando o voltar vai carregar a lista de m\u00fasica atual. shutdownPersistenceFail=Ocorreu um erro ao gravar o arquivo de persist\u00eancia\: {0} reloadSuccess=Carregando lista. `{0}` faixas encontradas. -trackAnnounce=Agora jogando * *{0}* *. Solicitado por\: * *{1} * *. +trackAnnounce=Agora tocando **{0}**. Solicitado por\: **{1}**. cmdAccessDenied=N\u00e3o est\u00e1 autorizado a usar esse comando\! malRevealAnime={0}\: Pesquisa revelou um anime.\n malTitle={0}**T\u00edtulo\: **{1}\n @@ -239,7 +244,7 @@ helpJoinCommand=Fazer o bot entrar no seu canal de voz atual. helpLeaveCommand=Fazer o bot deixar o canal de voz atual. helpPauseCommand=Pausa o reprodutor. helpPlayCommand=Reproduza a m\u00fasica a partir do URL fornecido ou procure por uma faixa. Para a lista completa dos recursos por favor visite {0} -helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} +helpPlayNextCommand=Toca m\u00fasica de determinado URL ou busca por uma faixa. Para obter uma lista completa de fontes, visite {0} helpPlaySplitCommand=Divide um v\u00eddeo do YouTube em uma tracklist fornecida na sua descri\u00e7\u00e3o. helpRepeatCommand=Alternar entre modos de repeti\u00e7\u00e3o. helpReshuffleCommand=Reembaralhar a fila atual. @@ -273,7 +278,7 @@ helpUserInfoCommand=Exibe informa\u00e7\u00f5es sobre voc\u00ea ou um usu\u00e1r helpPerms=Permite que membros de whitelisting e fun\u00e7\u00f5es para o posto de {0}. helpPrefixCommand=Defina o prefixo para essa guilda. helpVoteSkip=Vote para ignorar a m\u00fasica atual. Precisa que 50% de todos os usu\u00e1rios no bate-papo votem. -helpUnvoteSkip=Remove your vote to skip the current song. +helpUnvoteSkip=Remove seu voto para pular a m\u00fasica atual. helpMathOperationAdd=Imprime a soma do num1 e num2. helpMathOperationSub=Mostra a diferen\u00e7a entre a sub-faixa num2 para a num1. helpMathOperationMult=Imprime o produto de num1 * num2. @@ -302,8 +307,8 @@ skipUserMultiple={0} faixas adicionadas por {1} foram puladas. skipUsersMultiple={0} faixas adicionadas por {1} usu\u00e1rios foram puladas. skipUserNoTracks=Nenhum dos usu\u00e1rios mencionados tem faixas na fila. voteSkipAdded=O seu voto foi adicionado\! -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! +voteSkipRemoved=Seu voto foi removido\! +voteSkipNotFound=Voc\u00ea n\u00e3o votou para pular essa faixa\! voteSkipAlreadyVoted=Voc\u00ea j\u00e1 votou para pular essa faixa\! voteSkipSkipping={0} votou para pular. Pulando a faixa {1}. voteSkipNotEnough={0} votaram para pular. Pelo menos {1} s\u00e3o necess\u00e1rios. diff --git a/FredBoat/src/main/resources/lang/pt_PT.properties b/FredBoat/src/main/resources/lang/pt_PT.properties index e45ab03b4..dd9fb49f2 100644 --- a/FredBoat/src/main/resources/lang/pt_PT.properties +++ b/FredBoat/src/main/resources/lang/pt_PT.properties @@ -62,9 +62,9 @@ npDescription=Descri\u00e7\u00e3o npLoadedSoundcloud=[{0}/{1}] \n\nCarregado do Soundcloud npLoadedBandcamp={0} \n\nCarregado do Bandcamp npLoadedTwitch=Carregado do Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Eu preciso da seguinte permiss\u00e3o para executar essa a\u00e7\u00e3o\: -permissionMissingInvoker=Eu preciso de permiss\u00e3o para executar essa a\u00e7\u00e3o a seguir\: +permissionMissingInvoker=Voc\u00ea precisa da seguinte permiss\u00e3o para executar essa a\u00e7\u00e3o\: permissionEmbedLinks=Liga\u00e7\u00f5es Incorporadas rating=Classifica\u00e7\u00e3o listeners=Ouvintes @@ -92,6 +92,11 @@ loadPlaylistTooMany=Adicionadas {0} faixas. Encontradas demasiadas faixas para e loadErrorCommon=Ocorreu um erro ao carregar informa\u00e7\u00e3o para `{0}`\:\n{1} loadErrorSusp=Erro suspeito ao carregar a informa\u00e7\u00e3o para ''{0}''. loadQueueTrackLimit=Voc\u00ea n\u00e3o pode adicionar faixas para uma fila com mais de {0} faixas\! Isso \u00e9 para evitar abusos. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Prestes a carregar a lista de reprodu\u00e7\u00e3o * *{0} * * com at\u00e9 ''{1}'' faixas. Isto pode demorar um pouco, por favor, seja paciente. playerUserNotInChannel=Primeiro tem que entrar num canal de voz. playerJoinConnectDenied=N\u00e3o estou autorizado a conectar-me a esse canal. @@ -103,7 +108,7 @@ shutdownRestarting=FredBoat\u266a\u266a est\u00e1 a reiniciar. Isto deve demorar shutdownIndef=FredBoat\u266a\u266a est\u00e1 a desligar-se. Quando o bot voltar vai carregar a lista de m\u00fasica actual. shutdownPersistenceFail=Ocorreu um erro ao gravar o arquivo de persist\u00eancia\: {0} reloadSuccess=A carregar lista. `{0}` faixas encontradas. -trackAnnounce=pt_PT - Portugu\u00eas +trackAnnounce=Agora reproduzindo **{0}**. Solicitado por\: **{1}**. cmdAccessDenied=N\u00e3o est\u00e1 autorizado a usar esse comando\! malRevealAnime={0}\: A pesquisa revelou um anime.\n malTitle={0}**T\u00edtulo\: **{1}\n @@ -140,18 +145,18 @@ configSetTo=agora est\u00e1 definido para `{0}`. configUnknownKey={0}\: Chave desconhecida. configMustBeBoolean={0}\: O valor deve ser verdadeiro ou falso. modReason=Raz\u00e3o -modAuditLogMessage=Action issued by {0} against {1} -modAudioLogNoReason=No reason provided. +modAuditLogMessage=A\u00e7\u00e3o emitida por {0} contra {1} +modAudioLogNoReason=Nenhuma raz\u00e3o dada. modFailUserHierarchy=Voc\u00ea n\u00e3o tem um cargo maior do que {0}. modFailBotHierarchy=Eu preciso ter um papel maior que {0}. -modBanlistFail=Failed to fetch the ban list. +modBanlistFail=Falha ao buscar a lista de banimentos. modBanFail=O banimento falhou {0} -modUnbanFail=Failed to unban {0} -modUnbanFailNotBanned=User {0} is not banned in this guild. -modKeepMessages=Add {0} to not delete any messages. -modActionTargetDmKicked=Ahoy\! You have been kicked from the guild {0} by user {1}. -modActionTargetDmBanned=Ahoy\! You have been banned from the guild {0} by user {1}. -kickSuccess=User {0} has been kicked. +modUnbanFail=Falha ao desbanir {0} +modUnbanFailNotBanned=O utilizador {0} n\u00e3o est\u00e1 banido neste servidor. +modKeepMessages=Adicione {0} para n\u00e3o excluir nenhuma mensagem. +modActionTargetDmKicked=Ahoy\! Voc\u00ea foi expulso da guild {0} pelo usu\u00e1rio {1}. +modActionTargetDmBanned=Ahoy\! Voc\u00ea foi banido da guild {0} pelo usu\u00e1rio {1}. +kickSuccess=Usu\u00e1rio {0} foi expulso. kickFail=Falha ao expulsar {0} kickFailSelf=Voc\u00ea n\u00e3o se pode expulsar. kickFailOwner=Voc\u00ea n\u00e3o pode expulsar o dono do servidor. @@ -160,11 +165,11 @@ softbanSuccess=User {0} has been softbanned. softbanFailSelf=Voc\u00ea n\u00e3o pode cometer um softban a si mesmo. softbanFailOwner=Voc\u00ea n\u00e3o pode cometer um softban ao dono do servidor. softbanFailMyself=Eu n\u00e3o posso cometer um softban a mim mesmo. -hardbanSuccess=User {0} has been banned. +hardbanSuccess=O utilizador {0} foi banido. hardbanFailSelf=Voc\u00ea n\u00e3o se pode banir a si mesmo. hardbanFailOwner=Voc\u00ea n\u00e3o pode banir o dono do servidor. hardbanFailMyself=Eu n\u00e3o me posso banir. -unbanSuccess=User {0} has been unbanned. +unbanSuccess=O usu\u00e1rio {0} foi desbanido. getidSuccess=A identifica\u00e7\u00e3o deste servidor \u00e9 {0}. A identifica\u00e7\u00e3o deste canal de texto \u00e9 {1}. statsParagraph=\ Este bot foi executado por {0} dias, {1} horas, {2} minutos e {3} segundos.\nEste bot executou {4} comandos nesta sess\u00e3o. statsRate={0}Taxa de {1} comandos por hora @@ -181,7 +186,7 @@ langSuccess=Mudou para falar {0}. langInfo=FredBoat suporta v\u00e1rios idiomas fornecidos pelos usu\u00e1rios que pode seleccionar com este comando. \nOs Administradores neste servidor podem seleccionar o idioma com `;;lang ` Aqui est\u00e1 a lista completa de idiomas\: langDisclaimer=As tradu\u00e7\u00f5es poder\u00e3o n\u00e3o estar 100% completas ou exactas. Tradu\u00e7\u00f5es em falta podem ser fornecidas em . loadSingleTrack=* *{0} * * foi adicionado \u00e0 fila. -loadSingleTrackFirst=**{0}** has been added to the top of the queue. +loadSingleTrackFirst=**{0}** foi adicionado ao topo da fila. loadSingleTrackAndPlay=* *{0} * * vai agora ser reproduzido. invite=Endere\u00e7o de convite para **{0} **\: ratelimitedCommandsUser=Est\u00e1s a enviar comandos demasiado r\u00e1pido\! Por favor, abranda. @@ -230,16 +235,16 @@ helpCommandOwnerRestricted=Este comando \u00e9 restrito para o dono do bot. helpConfigCommand=Mostre a configura\u00e7\u00e3o deste servidor ou ajuste as configura\u00e7\u00f5es. helpLanguageCommand=Mostrar l\u00ednguas dispon\u00edveis ou definir um idioma para este servidor. helpModules=Mostrar, ativar ou desativar m\u00f3dulos de comando para este servidor. -helpHardbanCommand=Ban a user and delete their messages from the last day. +helpHardbanCommand=Bana um usu\u00e1rio e exclua suas mensagens do \u00faltimo dia. helpKickCommand=Expulsar um usu\u00e1rio deste servidor. -helpSoftbanCommand=Softban a user by kicking them and deleting their messages from the last day. -helpUnbanCommand=Unban a previously banned user. +helpSoftbanCommand=Proibir um usu\u00e1rio expulsando-o e excluindo suas mensagens do \u00faltimo dia. +helpUnbanCommand=Desbanir um usu\u00e1rio banido anteriormente. helpMusicCommandsHeader=Comandos de M\u00fasica do FredBoat helpJoinCommand=Fa\u00e7a o bot entrar no seu canal de voz atual. helpLeaveCommand=Fa\u00e7a o bot sair do canal de voz atual. helpPauseCommand=Pause o reprodutor. helpPlayCommand=Toque m\u00fasica pelo URL ou pesquisa pela faixa. Para uma lista cheia de fontes visite {0} -helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} +helpPlayNextCommand=Reproduzir m\u00fasica do URL dado ou procurar uma faixa. As faixas s\u00e3o adicionadas ao topo da fila. Para uma lista completa de fontes, por favor visite {0} helpPlaySplitCommand=Dividir um v\u00eddeo do YouTube em uma lista de reprodu\u00e7\u00e3o fornecido na sua descri\u00e7\u00e3o. helpRepeatCommand=Alternar entre modos de repeti\u00e7\u00e3o. helpReshuffleCommand=Reorganizar a fila atual. @@ -249,7 +254,7 @@ helpSkipCommand=Ignore a m\u00fasica atual, a can\u00e7\u00e3o de n'th na fila o helpStopCommand=Pare o reprodutor e limpe a lista de reprodu\u00e7\u00e3o. Reservado para moderadores com permiss\u00e3o de gerir mensagens. helpUnpauseCommand=Retomar o reprodutor. helpVolumeCommand=Altera o volume. Os valores s\u00e3o 0-150 e 100 \u00e9 o padr\u00e3o. O comando de volume \u00e9 depreciado no bot p\u00fablico. -helpExportCommand=Export the current queue to a wastebin link, can be later used as a playlist. +helpExportCommand=Exportar a fila atual para um link, pode ser usado mais tarde como uma playlist. helpGensokyoRadioCommand=Mostrar a atual m\u00fasica tocada na gensokyoradio.net helpListCommand=Exibir uma lista de m\u00fasicas atuais na lista de reprodu\u00e7\u00e3o. helpHistoryCommand=Mostra uma lista de musicas no hist\u00f3rico da lista de reprodu\u00e7\u00e3o. @@ -273,7 +278,7 @@ helpUserInfoCommand=Exibir informa\u00e7\u00f5es sobre voc\u00ea ou um usu\u00e1 helpPerms=Permite que membros e cargos da whitelist para o posto de {0}. helpPrefixCommand=Definir o prefixo deste servidor. helpVoteSkip=Vota\u00e7\u00e3o para ignorar a m\u00fasica atual. Precisa de 50% de todos os usu\u00e1rios no canal para votar. -helpUnvoteSkip=Remove your vote to skip the current song. +helpUnvoteSkip=Remova seu voto para saltar a m\u00fasica atual. helpMathOperationAdd=Imprimir a soma de num1 e num2. helpMathOperationSub=Imprimir a diferen\u00e7a de subtra\u00e7\u00e3o de num2 pelo num1. helpMathOperationMult=Imprimir o produto de num1*num2. @@ -302,8 +307,8 @@ skipUserMultiple=Ignoradas {0} m\u00fasica adicionadas por {1}. skipUsersMultiple=Ignoradas {0} m\u00fasicas adicionadas por {1} utilizadores. skipUserNoTracks=Nenhum dos utilizadores mencionados tem m\u00fasicas em espera. voteSkipAdded=O teu voto foi adicionado\! -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! +voteSkipRemoved=O seu voto foi eliminado\! +voteSkipNotFound=Voc\u00ea n\u00e3o votou para saltar esta faixa\! voteSkipAlreadyVoted=Voc\u00ea j\u00e1 votou para saltar esta faixa\! voteSkipSkipping={0} votaram para passar \u00e1 frente. M\u00fasica ultrapassada {1}. voteSkipNotEnough={0} votaram para passar \u00e1 frente. Pelo menos {1} necess\u00e1rios. @@ -331,7 +336,7 @@ moduleShowCommands=Utiliza {0} para ver os comandos deste m\u00f3dulo. modulesCommands=Utiliza {0} para ver os comandos de um m\u00f3dulo, ou {1} para mostrar todos os comandos. modulesEnabledInGuild=M\u00f3dulos ativos para este servidor\: modulesHowTo=Utiliza {0} para ativar/desativar m\u00f3dulos. -parseNotAUser=Your input {0} did not yield a Discord user. -parseNotAMember=User {0} is not a member of this guild. -parseSnowflakeIdHelp=Having trouble getting the id of a user/message/channel/something else? Check out the Discord docs at {0} +parseNotAUser=Sua entrada {0} n\u00e3o produziu um usu\u00e1rio do Discord. +parseNotAMember=Usu\u00e1rio {0} n\u00e3o \u00e9 um membro desta guilda. +parseSnowflakeIdHelp=Tendo dificuldade em obter o id de um usu\u00e1rio/mensagem/canal/outra coisa? Confira os documentos do Discord em {0} diff --git a/FredBoat/src/main/resources/lang/ro_RO.properties b/FredBoat/src/main/resources/lang/ro_RO.properties index 5ceb9e310..be41e2809 100644 --- a/FredBoat/src/main/resources/lang/ro_RO.properties +++ b/FredBoat/src/main/resources/lang/ro_RO.properties @@ -62,7 +62,7 @@ npDescription=Descriere npLoadedSoundcloud=[{0}/{1}]\n\n \u00cenc\u0103rcat de pe Soundcloud npLoadedBandcamp={0}\n\n\u00cenc\u0103rcat de pe Bandcamp npLoadedTwitch=\u00cenc\u0103rcat de pe Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Am nevoie de urm\u0103toarea permisiune pentru a efectua aceast\u0103 ac\u0163iune\: permissionMissingInvoker=Ai nevoie de urm\u0103toarea permisiune pentru a putea efectua aceast\u0103 ac\u021biune\: permissionEmbedLinks=Embed Links @@ -92,6 +92,11 @@ loadPlaylistTooMany=S-au ad\u0103ugat {0} piese. S-au g\u0103sit prea multe pies loadErrorCommon=A ap\u0103rut o eroare la \u00eenc\u0103rcarea informa\u021biei pentru `{0}`\:\n{1} loadErrorSusp=A ap\u0103rut o eroarea suspicioas\u0103 c\u00e2nd \u00eenc\u0103rcam informa\u021bia pentru `{0}`. loadQueueTrackLimit=Nu pute\u021bi ad\u0103uga piese la o list\u0103 de redare cu mai mult de {0} piese\! Acest lucru este pentru a preveni abuzurile. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Voi \u00eencepe s\u0103 \u00eencarc lista de redare **{0}** cu p\u00e2n\u0103 la `{1}` piese. Acest lucru ar putea s\u0103 dureze mai mult timp, v\u0103 rog s\u0103 fi\u021bi r\u0103bd\u0103tori. playerUserNotInChannel=Mai \u00eent\u00e2i trebuie s\u0103 intri \u00eentr-un canal de voce. playerJoinConnectDenied=Nu-mi este permis s\u0103 m\u0103 conectez la acel canal de voce. @@ -273,7 +278,7 @@ helpUserInfoCommand=Afi\u015feaz\u0103 informa\u0163ii despre tine sau despre un helpPerms=Permite ad\u0103ugarea membrilor la lista alb\u0103 \u0219i a rolurilor pentru rangul {0}. helpPrefixCommand=Seteaz\u0103 prefixul pentru serverul acesta. helpVoteSkip=Voteaz\u0103 pentru a s\u0103ri piesa curent\u0103. Este necesar c\u0103 50% din utilizatorii din chatul vocal s\u0103 voteze. -helpUnvoteSkip=Remove your vote to skip the current song. +helpUnvoteSkip=Elimin\u0103 votul t\u0103u pentru a s\u0103ri piesa curent\u0103. helpMathOperationAdd=Scrie suma lui num1 cu num2. helpMathOperationSub=Scrie diferen\u021ba sc\u0103derii lui num2 din num1. helpMathOperationMult=Scrie produsul ecua\u021biei num1*num2. @@ -302,8 +307,8 @@ skipUserMultiple=Au fost s\u0103rite {0} piese ad\u0103ugate de {1}. skipUsersMultiple=Au fost s\u0103rite {0} piese ad\u0103ugate de {1} utilizatori. skipUserNoTracks=Niciunul dintre utilizatorii men\u021biona\u021bi nu are piese \u00een lista de redare. voteSkipAdded=Votul t\u0103u a fost ad\u0103ugat\! -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! +voteSkipRemoved=Votul t\u0103u a fost eliminat\! +voteSkipNotFound=Nu ai votat pentru a s\u0103ri peste aceast\u0103 pies\u0103\! voteSkipAlreadyVoted=Deja ai votat s\u0103 sari peste aceast\u0103 pies\u0103\! voteSkipSkipping={0} au votat s\u0103 sar\u0103 piesa. Piesa {1} va fi s\u0103rit\u0103. voteSkipNotEnough={0} au votat s\u0103 sar\u0103 piesa. Mai este nevoie de cel pu\u021bin {1}. diff --git a/FredBoat/src/main/resources/lang/ru_RU.properties b/FredBoat/src/main/resources/lang/ru_RU.properties index 9af9f6c00..c795f80af 100644 --- a/FredBoat/src/main/resources/lang/ru_RU.properties +++ b/FredBoat/src/main/resources/lang/ru_RU.properties @@ -62,7 +62,7 @@ npDescription=\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 npLoadedSoundcloud=[{0}/{1}]\n\n\u0417\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043e \u0438\u0437 Soundcloud npLoadedBandcamp={0}\n\n\u0417\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043e \u0438\u0437 Bandcamp npLoadedTwitch=\u0417\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043e \u0438\u0437 Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=\u041c\u043d\u0435 \u043d\u0443\u0436\u043d\u044b \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f\: permissionMissingInvoker=\u0412\u0430\u043c \u043d\u0443\u0436\u043d\u044b \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f\: permissionEmbedLinks=\u0412\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0435 \u0441\u0441\u044b\u043b\u043a\u0438 @@ -92,6 +92,11 @@ loadPlaylistTooMany=\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e {0} \ loadErrorCommon=\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e `{0}`\: {1} loadErrorSusp=\u041f\u043e\u0434\u043e\u0437\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e `{0}`. loadQueueTrackLimit=\u0412\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0442\u0440\u0435\u043a\u0438 \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0431\u043e\u043b\u0435\u0435 \u0447\u0435\u043c {0} \u0442\u0440\u0435\u043a\u043e\u0432. \u0417\u043b\u043e\u0443\u043f\u043e\u0442\u0440\u0435\u0431\u043b\u044f\u0442\u044c \u043d\u0435 \u0441\u0442\u043e\u0438\u0442. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=\u0417\u0430\u0433\u0440\u0443\u0436\u0430\u044e \u043f\u043b\u0435\u0439\u043b\u0438\u0441\u0442 **{0}** \u0441 {1} \u0442\u0440\u0435\u043a\u0430\u043c\u0438. \u042d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043d\u044f\u0442\u044c \u043a\u0430\u043a\u043e\u0435-\u0442\u043e \u0432\u0440\u0435\u043c\u044f, \u043f\u043e\u0434\u043e\u0436\u0434\u0438\u0442\u0435. playerUserNotInChannel=\u0421\u043f\u0435\u0440\u0432\u0430 \u0412\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0437\u0430\u0439\u0442\u0438 \u043d\u0430 \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0439 \u043a\u0430\u043d\u0430\u043b. playerJoinConnectDenied=\u041c\u043d\u0435 \u0437\u0430\u043f\u0440\u0435\u0449\u0435\u043d\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0442\u044c\u0441\u044f \u043a \u044d\u0442\u043e\u043c\u0443 \u043a\u0430\u043d\u0430\u043b\u0443. @@ -273,7 +278,7 @@ helpUserInfoCommand=\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0438\u043 helpPerms=\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0432\u0430\u0439\u0442-\u043b\u0438\u0441\u0442 \u0434\u043b\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0438 \u0440\u043e\u043b\u0435\u0439 {0} \u0440\u0430\u043d\u0433\u0430. helpPrefixCommand=\u0417\u0430\u0434\u0430\u0439\u0442\u0435 \u043f\u0440\u0435\u0444\u0438\u043a\u0441 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430. helpVoteSkip=\u0413\u043e\u043b\u043e\u0441\u043e\u0432\u0430\u043d\u0438\u0435, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0442\u0435\u043a\u0443\u0449\u0443\u044e \u043f\u0435\u0441\u043d\u044e. \u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f 50% \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0434\u043b\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f. -helpUnvoteSkip=Remove your vote to skip the current song. +helpUnvoteSkip=\u0423\u0434\u0430\u043b\u0438\u0442\u0435 \u0432\u0430\u0448 \u0433\u043e\u043b\u043e\u0441 \u0434\u043b\u044f \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u043f\u0435\u0441\u043d\u0438. helpMathOperationAdd=\u0412\u044b\u0432\u043e\u0434\u0438\u0442 \u0441\u0443\u043c\u043c\u0443 1 \u0438 2 \u0447\u0438\u0441\u043b\u0430. helpMathOperationSub=\u0412\u044b\u0447\u0438\u0442\u0430\u0435\u0442 \u0438\u0437 num1 num2. helpMathOperationMult=\u0423\u043c\u043d\u043e\u0436\u0430\u0435\u0442 num1 \u043d\u0430 num2. @@ -302,8 +307,8 @@ skipUserMultiple=\u041f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0 skipUsersMultiple=\u041f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0435 {0} \u0442\u0440\u0435\u043a\u0438, \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c\u0438 {1}. skipUserNoTracks=\u041d\u0438 \u043e\u0434\u0438\u043d \u0438\u0437 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0438\u043c\u0435\u044e\u0442 \u043b\u044e\u0431\u044b\u0445 \u0442\u0440\u0435\u043a\u043e\u0432 \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u0438. voteSkipAdded=\u0412\u0430\u0448 \u0433\u043e\u043b\u043e\u0441 \u0431\u044b\u043b \u0443\u0447\u0442\u0435\u043d\! -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! +voteSkipRemoved=\u0412\u0430\u0448 \u0433\u043e\u043b\u043e\u0441 \u0431\u044b\u043b \u0443\u0434\u0430\u043b\u0435\u043d\! +voteSkipNotFound=\u0412\u044b \u043d\u0435 \u043f\u0440\u043e\u0433\u043e\u043b\u043e\u0441\u043e\u0432\u0430\u043b\u0438 \u0434\u043b\u044f \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0442\u0440\u0435\u043a\u0430\! voteSkipAlreadyVoted=\u0412\u044b \u0443\u0436\u0435 \u043f\u0440\u043e\u0433\u043e\u043b\u043e\u0441\u043e\u0432\u0430\u043b\u0438, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u044d\u0442\u043e\u0442 \u0442\u0440\u0435\u043a\! voteSkipSkipping={0} \u043f\u0440\u043e\u0433\u043e\u043b\u043e\u0441\u043e\u0432\u0430\u043b\u0438 \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u044c. \u041f\u0440\u043e\u043f\u0443\u0441\u043a {1} \u0442\u0440\u0435\u043a\u0430. voteSkipNotEnough={0} \u043f\u0440\u043e\u0433\u043e\u043b\u043e\u0441\u043e\u0432\u0430\u043b\u0438 \u0437\u0430 \u043f\u0440\u043e\u043f\u0443\u0441\u043a. \u041d\u0443\u0436\u043d\u043e \u0435\u0449\u0451 {1} \u0433\u043e\u043b\u043e\u0441\u043e\u0432. diff --git a/FredBoat/src/main/resources/lang/sk_SK.properties b/FredBoat/src/main/resources/lang/sk_SK.properties index 3e0b8e070..ddfc39baa 100644 --- a/FredBoat/src/main/resources/lang/sk_SK.properties +++ b/FredBoat/src/main/resources/lang/sk_SK.properties @@ -62,7 +62,7 @@ npDescription=Popis npLoadedSoundcloud=[{0}/{1}] \n\n Na\u010d\u00edtan\u00e9 zo Soundcloud npLoadedBandcamp={0} \n\nNa\u010d\u00edtan\u00e9 z Bandcampu npLoadedTwitch=Na\u010d\u00edtanie z Twitchu -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Potrebujem nasleduj\u00face povolenie aby som mohol spravi\u0165 t\u00fato akciu\: permissionMissingInvoker=Potrebujete n\u00e1sleduj\u00face povolenie na t\u00fato akciu\: permissionEmbedLinks=Vlo\u017eien\u00e9 odkazy @@ -92,6 +92,11 @@ loadPlaylistTooMany=Pridan\u00fdch {0} skladieb. N\u00e1jden\u00fdch prive\u013e loadErrorCommon=Vyskytla sa chyba pri na\u010d\u00edtan\u00ed info pre `{0}`\:\n{1} loadErrorSusp=Podozriv\u00e1 chyba pri na\u010d\u00edtan\u00ed inform\u00e1ci\u00ed pre `{0}`. loadQueueTrackLimit=Nie je mo\u017en\u00e9 prida\u0165 skladby do frontu s viac ako {0} stopami\! To m\u00e1 zabr\u00e1ni\u0165 zneu\u017eitiu. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Inform\u00e1cie o na\u010d\u00edtan\u00ed zoznamu skladieb ** {0} ** a\u017e po stopy "{1}". M\u00f4\u017ee to chv\u00ed\u013eu trva\u0165, bu\u010fte trpezliv\u00ed. playerUserNotInChannel=Najprv sa mus\u00edte pripoji\u0165 do hlasov\u00e9ho kan\u00e1lu. playerJoinConnectDenied=Nem\u00e1m povolenie sa pripoji\u0165 do tohto hlasov\u00e9ho kan\u00e1lu. diff --git a/FredBoat/src/main/resources/lang/sr_SP.properties b/FredBoat/src/main/resources/lang/sr_SP.properties index 3bfe00c42..71b0182b7 100644 --- a/FredBoat/src/main/resources/lang/sr_SP.properties +++ b/FredBoat/src/main/resources/lang/sr_SP.properties @@ -62,7 +62,7 @@ npDescription=\u041e\u043f\u0438\u0441 npLoadedSoundcloud=[{0}/{1}]\n\n\u0423\u0447\u0438\u0442\u0430\u043d\u043e \u0438\u0437 SoundCloud-\u0430 npLoadedBandcamp={0}\n\n\u0423\u0447\u0438\u0442\u0430\u043d\u043e \u0441\u0430 Bandcamp-\u0430 npLoadedTwitch=\u0423\u0447\u0438\u0442\u0430\u043d\u043e \u0438\u0437 Twitch-\u0430 -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=\u041f\u043e\u0442\u0440\u0435\u0431\u043d\u0435 \u0441\u0443 \u043c\u0438 \u0441\u0435\u043b\u0435\u0434\u0435\u045b\u0435 \u0434\u043e\u0437\u0432\u043e\u043b\u0435 \u0437\u0430 \u0438\u0437\u0432\u043e\u0452\u0435\u043d\u0435 \u0442\u0435 \u0430\u043a\u0446\u0438\u0458\u0435\: permissionMissingInvoker=\u041c\u043e\u0440\u0430\u0442\u0435 \u0438\u043c\u0430\u0442\u0438 \u0441\u043b\u0435\u0434\u0435\u045b\u0443 \u0434\u043e\u0437\u0432\u043e\u043b\u0443 \u0434\u0430 \u0431\u0438\u0441\u0442\u0435 \u0438\u0437\u0432\u0435\u043b\u0438 \u0442\u0443 \u0440\u0430\u0434\u045a\u0443\: permissionEmbedLinks=\u0423\u0433\u0440\u0430\u0452\u0435\u043d\u0435 \u0432\u0435\u0437\u0435 @@ -92,6 +92,11 @@ loadPlaylistTooMany=\u0414\u043e\u0434\u0430\u043d\u043e {0} \u0442\u0440\u0430\ loadErrorCommon=\u0414\u0435\u0441\u0438\u043b\u0430 \u0441\u0435 \u0433\u0440\u0435\u0448\u043a\u0430 \u043f\u0440\u0438\u043b\u0438\u043a\u043e\u043c \u0443\u0447\u0438\u0442\u0430\u0432\u0430\u045a\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0458\u0430 \u0437\u0430 `{0}`\:\n{1} loadErrorSusp=\u0421\u0443\u043c\u045a\u0438\u0432\u0430 \u0433\u0440\u0435\u0448\u043a\u0430 \u043f\u0440\u0438\u043b\u0438\u043a\u043e\u043c \u0443\u0447\u0438\u0442\u0430\u0432\u0430\u045a\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0458\u0430 \u0437\u0430 `{0}`. loadQueueTrackLimit=\u041d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 \u0442\u0440\u0430\u043a\u0443 \u0443 \u0440\u0435\u0434 \u0447\u0435\u043a\u0430\u045a\u0430 \u0441\u0430 \u0432\u0438\u0448\u0435 \u043e\u0434 {0} \u0442\u0440\u0430\u043a\u0430\! \u041e\u0432\u043e \u0458\u0435 \u0434\u0430 \u0441\u0435 \u0441\u043f\u0440\u0435\u0447\u0438 \u0437\u043b\u043e\u0443\u043f\u043e\u0442\u0440\u0435\u0431\u0430. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=\u0423\u0447\u0438\u0442\u0430\u0432\u0430\u045a\u0435 \u043f\u043b\u0435\u0458\u043b\u0438\u0441\u0442\u0435 **{0}** \u0441\u0430 \u0431\u0440\u043e\u0458\u0435\u043c \u0434\u043e `{1}` \u0442\u0440\u0430\u043a\u0430. \u041e\u0432\u043e \u043c\u043e\u0436\u0435 \u043f\u043e\u0442\u0440\u0430\u0458\u0430\u0442\u0438, \u043c\u043e\u043b\u0438\u043c\u043e \u0412\u0430\u0441 \u0431\u0443\u0434\u0438\u0442\u0435 \u0441\u0442\u0440\u043f\u0459\u0438\u0432\u0438. playerUserNotInChannel=\u041f\u0440\u0432\u043e \u0441\u0435 \u043c\u043e\u0440\u0430\u0442\u0435 \u043f\u0440\u0438\u0434\u0440\u0443\u0436\u0438\u0442\u0438 \u0433\u043e\u0432\u043e\u0440\u043d\u043e\u043c \u043a\u0430\u043d\u0430\u043b\u0443. playerJoinConnectDenied=\u041d\u0435\u043c\u0430\u043c \u0434\u043e\u0437\u0432\u043e\u043b\u0443 \u0434\u0430 \u0441\u0435 \u043f\u043e\u0432\u0435\u0436\u0435\u043c \u043d\u0430 \u0442\u0430\u0458 \u0433\u043e\u0432\u043e\u0440\u043d\u0438 \u043a\u0430\u043d\u0430\u043b. @@ -273,7 +278,7 @@ helpUserInfoCommand=\u041f\u0440\u0438\u043a\u0430\u0437 \u0438\u043d\u0444\u043 helpPerms=\u041e\u043c\u043e\u0433\u0443\u045b\u0430\u0432\u0430 \u0447\u043b\u0430\u043d\u043e\u0432\u0435 \u0438 \u0443\u043b\u043e\u0433\u0435 \u0437\u0430 \u0434\u043e\u043f\u0443\u0448\u0442\u0430\u045a\u0435 \u0437\u0430 {0} \u0440\u0430\u043d\u0433. helpPrefixCommand=\u041f\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u0435 \u043f\u0440\u0435\u0444\u0438\u043a\u0441 \u0437\u0430 \u043e\u0432\u043e \u0443\u0434\u0440\u0443\u0436\u0435\u045a\u0435. helpVoteSkip=\u0413\u043b\u0430\u0441\u0430\u0458\u0442\u0435 \u0437\u0430 \u043f\u0440\u0435\u0441\u043a\u0430\u043a\u0430\u045a\u0435 \u0442\u0440\u0435\u043d\u0443\u0442\u043d\u0435 \u043f\u0435\u0441\u043c\u0435. \u041f\u043e\u0442\u0440\u0435\u0431\u043d\u043e \u0458\u0435 50% \u043e\u0434 \u0441\u0432\u0438\u0445 \u043a\u043e\u0440\u0438\u0441\u043d\u0438\u043a\u0430 \u0443 \u0433\u043b\u0430\u0441\u043e\u0432\u043d\u043e\u043c \u0447\u0435\u0442\u0443 \u0437\u0430 \u0433\u043b\u0430\u0441\u0430\u045a\u0435. -helpUnvoteSkip=Remove your vote to skip the current song. +helpUnvoteSkip=\u0423\u043a\u043b\u043e\u043d\u0438\u0442\u0435 \u0412\u0430\u0448 \u0433\u043b\u0430\u0441 \u0434\u0430 \u043f\u0440\u0435\u0441\u043a\u043e\u0447\u0438\u0442\u0435 \u0442\u0440\u0435\u043d\u0443\u0442\u043d\u0443 \u043f\u0435\u0441\u043c\u0443. helpMathOperationAdd=\u0418\u0441\u043f\u0438\u0448\u0438\u0442\u0435 \u0437\u0431\u0438\u0440 \u043e\u0434 num1 \u0438 num2. helpMathOperationSub=\u0418\u0441\u043f\u0438\u0448\u0438\u0442\u0435 \u0440\u0430\u0437\u043b\u0438\u043a\u0443 \u043e\u0434\u0443\u0437\u0438\u043c\u0430\u045a\u0430 num2 \u043e\u0434 num1. helpMathOperationMult=\u0418\u0441\u043f\u0438\u0448\u0438\u0442\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434 \u043e\u0434 num1*num2. @@ -302,8 +307,8 @@ skipUserMultiple=\u041f\u0440\u0435\u0441\u043a\u043e\u0447\u0435\u043d\u043e {0 skipUsersMultiple=\u041f\u0440\u0435\u0441\u043a\u043e\u0447\u0435\u043d\u043e {0} \u0442\u0440\u0430\u043a\u0430 \u0434\u043e\u0434\u0430\u043d\u043e \u043e\u0434 \u0441\u0442\u0440\u0430\u043d\u0435 {1} \u043a\u043e\u0440\u0438\u0441\u043d\u0438\u043a\u0430. skipUserNoTracks=\u041d\u0438\u0458\u0435\u0434\u0430\u043d \u043e\u0434 \u043f\u043e\u043c\u0435\u043d\u0443\u0442\u0438\u0445 \u043a\u043e\u0440\u0438\u0441\u043d\u0438\u043a\u0430 \u043d\u0435\u043c\u0430 \u0442\u0440\u0430\u043a\u0443 \u0443 \u0440\u0435\u0434\u0443 \u0447\u0435\u043a\u0430\u045a\u0430. voteSkipAdded=\u0412\u0430\u0448 \u0433\u043b\u0430\u0441 \u0458\u0435 \u0437\u0430\u0431\u0435\u043b\u0435\u0436\u0435\u043d\! -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! +voteSkipRemoved=\u0412\u0430\u0448 \u0433\u043b\u0430\u0441 \u0458\u0435 \u0443\u043a\u043b\u043e\u045a\u0435\u043d\! +voteSkipNotFound=\u041d\u0438\u0441\u0442\u0435 \u0433\u043b\u0430\u0441\u0430\u043b\u0438 \u0437\u0430 \u043f\u0440\u0435\u0441\u043a\u0430\u043a\u0430\u045a\u0435 \u043e\u0432\u0435 \u0442\u0440\u0430\u043a\u0435\! voteSkipAlreadyVoted=\u0412\u0435\u045b \u0441\u0442\u0435 \u0433\u043b\u0430\u0441\u0430\u043b\u0438 \u0437\u0430 \u043f\u0440\u0435\u0441\u043a\u0430\u043a\u0430\u045a\u0435 \u043e\u0432\u0435 \u0442\u0440\u0430\u043a\u0435\! voteSkipSkipping={0} \u0458\u0435 \u0433\u043b\u0430\u0441\u0430\u043b\u043e \u0437\u0430 \u043f\u0440\u0435\u0441\u043a\u0430\u043a\u0430\u045a\u0435. \u041f\u0440\u0435\u0441\u043a\u0430\u043a\u0430\u045a\u0435 \u0442\u0440\u0430\u043a\u0435 {1}. voteSkipNotEnough={0} \u0458\u0435 \u0433\u043b\u0430\u0441\u0430\u043b\u043e \u0437\u0430 \u043f\u0440\u0435\u0441\u043a\u0430\u043a\u0430\u045a\u0435. \u041d\u0430\u0458\u043c\u0430\u045a\u0435 {1} \u043f\u043e\u0442\u0440\u0435\u0431\u043d\u043e. diff --git a/FredBoat/src/main/resources/lang/sv_SE.properties b/FredBoat/src/main/resources/lang/sv_SE.properties index bd15ed121..27d164b43 100644 --- a/FredBoat/src/main/resources/lang/sv_SE.properties +++ b/FredBoat/src/main/resources/lang/sv_SE.properties @@ -62,7 +62,7 @@ npDescription=Beskrivning npLoadedSoundcloud=[{0}/{1}]\n\nLaddas fr\u00e5n Soundcloud npLoadedBandcamp={0}\n\nLaddad fr\u00e5n Bandcamp npLoadedTwitch=Laddad fr\u00e5n Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Jag beh\u00f6ver f\u00f6ljande till\u00e5telse f\u00f6r att utf\u00f6ra den uppgiften\: permissionMissingInvoker=Du beh\u00f6ver f\u00f6ljande beh\u00f6righet f\u00f6r att utf\u00f6ra \u00e5tg\u00e4rden\: permissionEmbedLinks=B\u00e4dda in l\u00e4nkar @@ -92,6 +92,11 @@ loadPlaylistTooMany=Lade till {0} l\u00e5tar. Hittade f\u00f6r m\u00e5nga l\u00e loadErrorCommon=Fel uppstod n\u00e4r info laddades f\u00f6r `{0}`\:\n{1} loadErrorSusp=Misst\u00e4nktsamt fel uppst\u00e5d n\u00e4r info laddades f\u00f6r `{0}`. loadQueueTrackLimit=Du f\u00e5r inte l\u00e4gga till l\u00e5tar till en k\u00f6 med mer \u00e4n {0} l\u00e5tar\! Detta \u00e4r f\u00f6r att f\u00f6rhindra missbruk. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=F\u00f6rbereder f\u00f6r att ladda spellista **{0}** med upp till `{1}` l\u00e5tar. Detta kan ta en stund, v\u00e4nligen var t\u00e5lmodig. playerUserNotInChannel=Du m\u00e5ste ansluta till en r\u00f6stkanal f\u00f6rst. playerJoinConnectDenied=Jag har inte till\u00e5telse till att ansluta till den r\u00f6stkanalen. @@ -103,7 +108,7 @@ shutdownRestarting=FredBoat\u266a\u266a startar om. Detta b\u00f6r endast ta en shutdownIndef=FredBoat\u266a\u266a st\u00e4nger av. N\u00e4r den kommer tillbaka kommer den nuvarande spellistan laddas om. shutdownPersistenceFail=Fel uppstod vid sparande av persistence-fil\: {0} reloadSuccess=Laddar om spellistan. `{0}` l\u00e5tar hittades. -trackAnnounce=Now playing **{0}**. Requested by\: **{1}**. +trackAnnounce=Spelar nu **{0}**. Beg\u00e4rd av **{1}**. cmdAccessDenied=Du har inte tillst\u00e5nd att anv\u00e4nda det kommandot\! malRevealAnime={0}\: S\u00f6kningen hittade en anime.\n malTitle={0}**Titel\: **{1}\n @@ -181,7 +186,7 @@ langSuccess=Bytte till att prata {0}. langInfo=FredBoat st\u00f6der flera anv\u00e4ndar-gjorda spr\u00e5k som du kan v\u00e4lja med detta kommando. Admins p\u00e5 denna server kan v\u00e4lja ett spr\u00e5k med `;;lang ` H\u00e4r \u00e4r den fulla listan av st\u00f6dda spr\u00e5k\: langDisclaimer=\u00d6vers\u00e4ttningarna kanske inte \u00e4r 100% s\u00e4kra eller kompletta. Saknade \u00f6vers\u00e4ttningar kan bidras med p\u00e5 . loadSingleTrack=**{0}** har lagts till i k\u00f6n. -loadSingleTrackFirst=**{0}** has been added to the top of the queue. +loadSingleTrackFirst=**{0}** har lagts till p\u00e5 toppen av k\u00f6n. loadSingleTrackAndPlay=**{0}** kommer nu spelas. invite=Inbjudningsl\u00e4nk f\u00f6r **{0}**\: ratelimitedCommandsUser=Du skickar kommandon f\u00f6r snabbt\! Ta det lite lugnt grabben. @@ -239,7 +244,7 @@ helpJoinCommand=F\u00e5 botten att ansluta till din nuvarande r\u00f6stkanal. helpLeaveCommand=F\u00e5 botten att l\u00e4mna den nuvarande r\u00f6stkanalen. helpPauseCommand=Pausa spelaren. helpPlayCommand=Spela upp musik fr\u00e5n den angivna URLen eller s\u00f6k efter ett sp\u00e5r. F\u00f6r en full lista av k\u00e4llor v\u00e4nligen bes\u00f6k {0} -helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} +helpPlayNextCommand=Spela musik fr\u00e5n den utgivna URL eller s\u00f6k efter en l\u00e5t. L\u00e5tar \u00e4r tillagda i toppen av k\u00f6n. F\u00f6r en full lista av k\u00e4llor v\u00e4nligen bes\u00f6k {0} helpPlaySplitCommand=Dela upp en YouTube-video i en l\u00e5tlista angiven i beskrivningen. helpRepeatCommand=V\u00e4xla mellan upprepningsl\u00e4gen. helpReshuffleCommand=Blandar om den aktuella k\u00f6n. @@ -249,7 +254,7 @@ helpSkipCommand=Skippar den nuvarande l\u00e5ten, den n\: te l\u00e5ten i k\u00f helpStopCommand=Stoppa spelaren och rensa spellistan. Reserverade f\u00f6r moderatorer med befogenheten "hantera meddelanden". helpUnpauseCommand=\u00c5teraktivera spelaren. helpVolumeCommand=\u00c4ndrar volym. V\u00e4rden \u00e4r 0-150 och 100 \u00e4r standard. Kommandot volym \u00e4r otillg\u00e4ngligt f\u00f6r den offentliga boten. -helpExportCommand=Export the current queue to a wastebin link, can be later used as a playlist. +helpExportCommand=Exportera den nuvarande k\u00f6n till en papperskorgs l\u00e4nk, kan senare anv\u00e4ndas som en spellista. helpGensokyoRadioCommand=Visa den nuvarande l\u00e5ten som spelas p\u00e5 gensokyoradio.net helpListCommand=Visa en lista \u00f6ver de nuvarande l\u00e5tarna i spellistan. helpHistoryCommand=Visar en lista av l\u00e5tarna i spellistan historia. @@ -273,7 +278,7 @@ helpUserInfoCommand=Visa information om dig sj\u00e4lv eller en anv\u00e4ndare s helpPerms=Till\u00e5ter vitlistning medlemmar och roller f\u00f6r {0} rang. helpPrefixCommand=S\u00e4tt prefixen f\u00f6r denna server. helpVoteSkip=R\u00f6sta f\u00f6r att hoppa \u00f6ver den nuvarande l\u00e5ten. 50% av alla anv\u00e4ndare m\u00e5ste r\u00f6sta f\u00f6r att det ska funka. -helpUnvoteSkip=Remove your vote to skip the current song. +helpUnvoteSkip=Ta bort din r\u00f6st f\u00f6r att hoppa \u00f6ver den nuvarande s\u00e5ngen. helpMathOperationAdd=Skriv ut summan av num1 och num2. helpMathOperationSub=Skriv ut differensen av att subtrahera num2 fr\u00e5n num1. helpMathOperationMult=Skriv ut produkten av num1*num2. @@ -302,8 +307,8 @@ skipUserMultiple=Hoppade \u00f6ver {0} l\u00e5tar tillagda av {1}. skipUsersMultiple=Hoppade \u00f6ver {0} l\u00e5tar tillagda av {1} anv\u00e4ndare. skipUserNoTracks=Inga av de n\u00e4mnda anv\u00e4ndarna har n\u00e5gra sp\u00e5r i k\u00f6n. voteSkipAdded=Din r\u00f6st har r\u00e4knats\! -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! +voteSkipRemoved=Din r\u00f6st har tagits bort\! +voteSkipNotFound=Du har inte r\u00f6stat f\u00f6r att hoppa \u00f6ver denna l\u00e5t\! voteSkipAlreadyVoted=Du har redan r\u00f6stat f\u00f6r att hoppa \u00f6ver denna l\u00e5t\! voteSkipSkipping={0} har r\u00f6stad f\u00f6r att hoppa \u00f6ver. Hoppar \u00f6ver l\u00e5t {1}. voteSkipNotEnough={0} har r\u00f6stat f\u00f6r att hoppa \u00f6ver. \u00c5tminstone {1} r\u00f6ster kr\u00e4vs. diff --git a/FredBoat/src/main/resources/lang/th_TH.properties b/FredBoat/src/main/resources/lang/th_TH.properties index e96cb4bee..4d7c2246c 100644 --- a/FredBoat/src/main/resources/lang/th_TH.properties +++ b/FredBoat/src/main/resources/lang/th_TH.properties @@ -1,6 +1,6 @@ #X-Generator: crowdin.com playQueueEmpty=\u0e1a\u0e2d\u0e15\u0e44\u0e21\u0e48\u0e44\u0e14\u0e49\u0e40\u0e1b\u0e34\u0e14\u0e40\u0e1e\u0e25\u0e07\u0e2d\u0e22\u0e48\u0e39 \u0e43\u0e0a\u0e49\u0e04\u0e33\u0e2a\u0e31\u0e48\u0e07\u0e19\u0e35\u0e49\u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e40\u0e1e\u0e34\u0e48\u0e21\u0e40\u0e1e\u0e25\u0e07\u0e40\u0e02\u0e49\u0e32\u0e44\u0e1b\u0e43\u0e19\u0e04\u0e34\u0e27\!\n;;play <\u0e25\u0e34\u0e49\u0e07 \u0e2b\u0e23\u0e37\u0e2d \u0e40\u0e1e\u0e25\u0e07\u0e17\u0e35\u0e48\u0e15\u0e49\u0e2d\u0e07\u0e01\u0e32\u0e23\u0e43\u0e2b\u0e49\u0e04\u0e49\u0e19\u0e2b\u0e32> -playAlreadyPlaying=\u0e1a\u0e2d\u0e15\u0e01\u0e33\u0e25\u0e31\u0e07\u0e2b\u0e22\u0e38\u0e14\u0e40\u0e1b\u0e34\u0e14\u0e40\u0e1e\u0e25\u0e07\u0e2d\u0e22\u0e48\u0e39\u0e40\u0e40\u0e25\u0e49\u0e27\u0e08\u0e49\u0e32 +playAlreadyPlaying=\u0e1a\u0e2d\u0e15\u0e01\u0e33\u0e25\u0e31\u0e07\u0e40\u0e1b\u0e34\u0e14\u0e40\u0e1e\u0e25\u0e07\u0e2d\u0e22\u0e48\u0e39\! playVCEmpty=\u0e44\u0e21\u0e48\u0e21\u0e35\u0e43\u0e04\u0e23\u0e2d\u0e22\u0e48\u0e39\u0e43\u0e19\u0e2b\u0e49\u0e2d\u0e07\u0e19\u0e35\u0e49 playWillNowPlay=\u0e44\u0e14\u0e49\u0e40\u0e27\u0e25\u0e32\u0e40\u0e1b\u0e34\u0e14\u0e40\u0e1e\u0e25\u0e07\! playSearching=\u0e01\u0e33\u0e25\u0e31\u0e07\u0e04\u0e49\u0e19\u0e2b\u0e32 `{q}` \u0e43\u0e19Youtube @@ -62,7 +62,7 @@ npDescription=\u0e04\u0e33\u0e2d\u0e18\u0e34\u0e1a\u0e32\u0e22 npLoadedSoundcloud=\u0e42\u0e2b\u0e25\u0e14 [{0}/{1}] \u0e08\u0e32\u0e01 Soundcloud \u0e40\u0e40\u0e25\u0e49\u0e27 npLoadedBandcamp=\u0e42\u0e2b\u0e25\u0e14 {0} \u0e08\u0e32\u0e01 Bandcamp \u0e40\u0e40\u0e25\u0e49\u0e27 npLoadedTwitch=\u0e42\u0e2b\u0e25\u0e14\u0e08\u0e32\u0e01 Twitch \u0e40\u0e40\u0e25\u0e49\u0e27 -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=\u0e15\u0e49\u0e2d\u0e07\u0e21\u0e35\u0e01\u0e32\u0e23\u0e2d\u0e19\u0e38\u0e0d\u0e32\u0e15\u0e34\u0e43\u0e2b\u0e49\u0e1c\u0e21\u0e17\u0e33\u0e21\u0e31\u0e19\u0e19\u0e30\u0e1e\u0e35\u0e48 permissionMissingInvoker=\u0e04\u0e38\u0e13\u0e44\u0e21\u0e48\u0e21\u0e35\u0e2a\u0e34\u0e17\u0e18\u0e34\u0e4c\u0e01\u0e23\u0e30\u0e17\u0e33\u0e01\u0e32\u0e23\u0e15\u0e48\u0e2d\u0e44\u0e1b\u0e19\u0e35\u0e49\: permissionEmbedLinks=\u0e1d\u0e31\u0e07\u0e40\u0e0a\u0e37\u0e48\u0e2d\u0e21\u0e42\u0e22\u0e07\u0e40\u0e02\u0e49\u0e32\u0e44\u0e1b\u0e41\u0e25\u0e49\u0e27 @@ -92,6 +92,11 @@ loadPlaylistTooMany=\u0e1e\u0e1a\u0e41\u0e17\u0e23\u0e47\u0e04\u0e08\u0e33\u0e19 loadErrorCommon=\u0e21\u0e35\u0e02\u0e49\u0e2d\u0e1c\u0e34\u0e14\u0e1e\u0e25\u0e32\u0e14\u0e40\u0e01\u0e34\u0e14\u0e02\u0e36\u0e49\u0e19\u0e43\u0e19\u0e02\u0e13\u0e30\u0e42\u0e2b\u0e25\u0e14\u0e02\u0e49\u0e2d\u0e21\u0e39\u0e25\u0e08\u0e32\u0e01 `{0}`\:\n{1} loadErrorSusp=\u0e40\u0e01\u0e34\u0e14\u0e02\u0e49\u0e2d\u0e1c\u0e34\u0e14\u0e1e\u0e25\u0e32\u0e14\u0e23\u0e30\u0e2b\u0e27\u0e48\u0e32\u0e07\u0e01\u0e32\u0e23\u0e42\u0e2b\u0e25\u0e14\u0e02\u0e49\u0e2d\u0e21\u0e39\u0e25\u0e02\u0e2d\u0e07 `{0}` loadQueueTrackLimit=\u0e04\u0e38\u0e13\u0e44\u0e21\u0e48\u0e2a\u0e32\u0e21\u0e32\u0e23\u0e16\u0e40\u0e1e\u0e34\u0e48\u0e21\u0e40\u0e1e\u0e25\u0e07\u0e25\u0e07\u0e43\u0e19\u0e04\u0e34\u0e27\u0e19\u0e35\u0e49 {0} \u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e1b\u0e49\u0e2d\u0e07\u0e01\u0e31\u0e19\u0e01\u0e32\u0e23\u0e25\u0e30\u0e40\u0e21\u0e34\u0e14\u0e25\u0e34\u0e02\u0e2a\u0e34\u0e17\u0e18\u0e4c +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\u0e17\u0e35\u0e48\u0e40\u0e25\u0e48\u0e19 **{0} ** \u0e16\u0e36\u0e07\u0e40\u0e1e\u0e25\u0e07 ''{1}'' \u0e19\u0e35\u0e49\u0e2d\u0e32\u0e08\u0e43\u0e0a\u0e49\u0e40\u0e27\u0e25\u0e32\u0e2a\u0e31\u0e01\u0e04\u0e23\u0e39\u0e48 playerUserNotInChannel=\u0e15\u0e49\u0e2d\u0e07\u0e40\u0e02\u0e49\u0e32\u0e44\u0e1b\u0e43\u0e19\u0e2b\u0e49\u0e2d\u0e07\u0e1e\u0e38\u0e14\u0e04\u0e38\u0e22\u0e01\u0e48\u0e2d\u0e19\u0e19\u0e30\n playerJoinConnectDenied=\u0e0a\u0e31\u0e49\u0e19\u0e40\u0e02\u0e49\u0e32\u0e2b\u0e49\u0e2d\u0e07\u0e19\u0e31\u0e49\u0e19\u0e44\u0e21\u0e48\u0e44\u0e14\u0e49\u0e19\u0e48\u0e30 @@ -103,7 +108,7 @@ shutdownRestarting=\u0e01\u0e33\u0e25\u0e31\u0e07\u0e23\u0e35\u0e2a\u0e15\u0e32\ shutdownIndef=\u0e01\u0e33\u0e25\u0e31\u0e07\u0e1b\u0e34\u0e14 FredBoat\u266a\u266a \u0e2d\u0e22\u0e48\u0e39 \u0e1e\u0e2d\u0e1a\u0e2d\u0e15\u0e01\u0e25\u0e31\u0e1a\u0e21\u0e32\u0e08\u0e30\u0e42\u0e2b\u0e25\u0e14\u0e40\u0e1e\u0e25\u0e22\u0e4c\u0e25\u0e34\u0e2a\u0e15\u0e4c\u0e43\u0e2b\u0e49\u0e43\u0e2b\u0e21\u0e48 shutdownPersistenceFail=\u0e40\u0e01\u0e34\u0e14\u0e02\u0e49\u0e2d\u0e1c\u0e34\u0e14\u0e1e\u0e25\u0e32\u0e14\u0e02\u0e13\u0e30\u0e1a\u0e31\u0e19\u0e17\u0e36\u0e01\u0e44\u0e1f\u0e25\u0e4c\: {0} reloadSuccess=\u0e01\u0e33\u0e25\u0e31\u0e07\u0e42\u0e2b\u0e25\u0e14\u0e40\u0e1e\u0e25\u0e4c\u0e25\u0e34\u0e2a\u0e15 \u0e1e\u0e1a\u0e40\u0e1e\u0e25\u0e07 `{0}` \u0e40\u0e1e\u0e25\u0e07 -trackAnnounce=Now playing **{0}**. Requested by\: **{1}**. +trackAnnounce=\u0e15\u0e2d\u0e19\u0e19\u0e35\u0e49 \u0e40\u0e25\u0e48\u0e19 **{0}** \u0e42\u0e14\u0e22\: **{1} ** cmdAccessDenied=\u0e04\u0e38\u0e13\u0e44\u0e21\u0e48\u0e44\u0e14\u0e49\u0e23\u0e31\u0e1a\u0e2d\u0e19\u0e38\u0e0d\u0e32\u0e15\u0e43\u0e2b\u0e49\u0e43\u0e0a\u0e49\u0e04\u0e33\u0e2a\u0e31\u0e48\u0e07\u0e19\u0e35\u0e49\! malRevealAnime={0}\: \u0e04\u0e49\u0e19\u0e2b\u0e32\u0e08\u0e32\u0e01\u0e2d\u0e19\u0e34\u0e40\u0e21\u0e30\u0e17\u0e35\u0e48\u0e40\u0e1b\u0e34\u0e14\u0e40\u0e1c\u0e22.\n malTitle={0}**\u0e0a\u0e37\u0e48\u0e2d\u0e40\u0e23\u0e37\u0e48\u0e2d\u0e07\: **{1}\n @@ -181,7 +186,7 @@ langSuccess=\u0e40\u0e1b\u0e25\u0e35\u0e48\u0e22\u0e19\u0e40\u0e1b\u0e47\u0e19\u langInfo=FredBoat \u0e2a\u0e19\u0e31\u0e1a\u0e2a\u0e19\u0e38\u0e19\u0e43\u0e0a\u0e49\u0e20\u0e32\u0e29\u0e32\u0e15\u0e48\u0e32\u0e07 \u0e46 \u0e17\u0e35\u0e48\u0e04\u0e38\u0e13\u0e2a\u0e32\u0e21\u0e32\u0e23\u0e16\u0e40\u0e25\u0e37\u0e2d\u0e01 \u0e14\u0e49\u0e27\u0e22\u0e04\u0e33\u0e2a\u0e31\u0e48\u0e07\u0e19\u0e35\u0e49 \u0e1c\u0e39\u0e49\u0e14\u0e39\u0e41\u0e25\u0e23\u0e30\u0e1a\u0e1a\u0e1a\u0e19\u0e40\u0e0b\u0e34\u0e23\u0e4c\u0e1f\u0e40\u0e27\u0e2d\u0e23\u0e4c\u0e19\u0e35\u0e49\u0e2a\u0e32\u0e21\u0e32\u0e23\u0e16\u0e40\u0e25\u0e37\u0e2d\u0e01\u0e20\u0e32\u0e29\u0e32\u0e01\u0e31\u0e1a ';;lang ' \u0e19\u0e35\u0e48\u0e04\u0e37\u0e2d\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\u0e20\u0e32\u0e29\u0e32\u0e17\u0e35\u0e48\u0e2a\u0e19\u0e31\u0e1a\u0e2a\u0e19\u0e38\u0e19\: langDisclaimer=\u0e04\u0e33\u0e41\u0e1b\u0e25\u0e2d\u0e32\u0e08\u0e44\u0e21\u0e48\u0e44\u0e14\u0e49 100% \u0e16\u0e39\u0e01\u0e15\u0e49\u0e2d\u0e07 \u0e2b\u0e23\u0e37\u0e2d\u0e2a\u0e21\u0e1a\u0e39\u0e23\u0e13\u0e4c \u0e2d\u0e32\u0e08\u0e21\u0e35\u0e2a\u0e48\u0e27\u0e19\u0e2b\u0e32\u0e22\u0e44\u0e1b\u0e41\u0e1b\u0e25\u0e17\u0e35\u0e48 loadSingleTrack=**{0} ** \u0e16\u0e39\u0e01\u0e40\u0e1e\u0e34\u0e48\u0e21\u0e25\u0e07\u0e43\u0e19\u0e04\u0e34\u0e27 -loadSingleTrackFirst=**{0}** has been added to the top of the queue. +loadSingleTrackFirst={0}** \u0e44\u0e14\u0e49\u0e16\u0e39\u0e01\u0e40\u0e1e\u0e34\u0e48\u0e21\u0e44\u0e1b\u0e22\u0e31\u0e07\u0e14\u0e49\u0e32\u0e19\u0e1a\u0e19\u0e02\u0e2d\u0e07\u0e04\u0e34\u0e27 loadSingleTrackAndPlay=**{0} ** \u0e08\u0e30\u0e40\u0e25\u0e48\u0e19 invite=\u0e25\u0e34\u0e07\u0e01\u0e4c\u0e40\u0e0a\u0e34\u0e0d\u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a **{0} **\: ratelimitedCommandsUser=\u0e04\u0e38\u0e13\u0e2a\u0e48\u0e07\u0e04\u0e33\u0e2a\u0e31\u0e48\u0e07\u0e40\u0e23\u0e47\u0e27\u0e40\u0e01\u0e34\u0e19\u0e44\u0e1b\u0e41\u0e25\u0e49\u0e27\! \u0e0a\u0e49\u0e32\u0e25\u0e07\u0e2b\u0e19\u0e48\u0e2d\u0e22\u0e44\u0e14\u0e49\u0e44\u0e2b\u0e21. @@ -239,7 +244,7 @@ helpJoinCommand=\u0e17\u0e33\u0e43\u0e2b\u0e49\u0e1a\u0e2d\u0e17\u0e40\u0e02\u0e helpLeaveCommand=\u0e17\u0e33\u0e43\u0e2b\u0e49\u0e1a\u0e2d\u0e17\u0e2d\u0e2d\u0e01\u0e08\u0e32\u0e01\u0e0a\u0e48\u0e2d\u0e07\u0e2a\u0e31\u0e0d\u0e0d\u0e32\u0e13\u0e40\u0e2a\u0e35\u0e22\u0e07 helpPauseCommand=\u0e2b\u0e22\u0e38\u0e14\u0e40\u0e25\u0e48\u0e19 helpPlayCommand=\u0e40\u0e25\u0e48\u0e19\u0e40\u0e1e\u0e25\u0e07\u0e08\u0e32\u0e01 URL \u0e17\u0e35\u0e48\u0e01\u0e33\u0e2b\u0e19\u0e14\u0e2b\u0e23\u0e37\u0e2d\u0e04\u0e49\u0e19\u0e2b\u0e32\u0e08\u0e32\u0e01\u0e04\u0e33\u0e2a\u0e31\u0e48\u0e07\u0e19\u0e35\u0e49 \u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a\u0e40\u0e27\u0e47\u0e1a\u0e44\u0e0b\u0e15\u0e4c\u0e17\u0e35\u0e48\u0e23\u0e2d\u0e07\u0e23\u0e31\u0e1a \u0e42\u0e1b\u0e23\u0e14\u0e14\u0e39\u0e17\u0e35\u0e48 {0} -helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} +helpPlayNextCommand=\u0e40\u0e25\u0e48\u0e19\u0e40\u0e1e\u0e25\u0e07\u0e08\u0e32\u0e01 URL \u0e17\u0e35\u0e48\u0e01\u0e33\u0e2b\u0e19\u0e14\u0e2b\u0e23\u0e37\u0e2d\u0e04\u0e49\u0e19\u0e2b\u0e32\u0e01\u0e32\u0e23\u0e15\u0e34\u0e14\u0e15\u0e32\u0e21 \u0e21\u0e35\u0e40\u0e1e\u0e34\u0e48\u0e21\u0e40\u0e1e\u0e25\u0e07\u0e02\u0e2d\u0e07\u0e04\u0e34\u0e27 \u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a\u0e23\u0e32\u0e22\u0e0a\u0e37\u0e48\u0e2d\u0e02\u0e2d\u0e07\u0e41\u0e2b\u0e25\u0e48\u0e07\u0e02\u0e49\u0e2d\u0e21\u0e39\u0e25\u0e42\u0e1b\u0e23\u0e14\u0e40\u0e22\u0e35\u0e48\u0e22\u0e21\u0e0a\u0e21 {0} helpPlaySplitCommand=\u0e41\u0e1a\u0e48\u0e07\u0e27\u0e34\u0e14\u0e35\u0e42\u0e2d YouTube tracklist \u0e17\u0e35\u0e48\u0e43\u0e2b\u0e49\u0e44\u0e27\u0e49\u0e43\u0e19\u0e04\u0e33\u0e2d\u0e18\u0e34\u0e1a\u0e32\u0e22\u0e02\u0e2d\u0e07\u0e21\u0e31\u0e19 helpRepeatCommand=\u0e2a\u0e25\u0e31\u0e1a\u0e23\u0e30\u0e2b\u0e27\u0e48\u0e32\u0e07\u0e42\u0e2b\u0e21\u0e14\u0e0b\u0e49\u0e33 helpReshuffleCommand=\u0e1e\u0e34\u0e08\u0e32\u0e23\u0e13\u0e32\u0e04\u0e34\u0e27\u0e1b\u0e31\u0e08\u0e08\u0e38\u0e1a\u0e31\u0e19 @@ -249,7 +254,7 @@ helpSkipCommand=\u0e02\u0e49\u0e32\u0e21\u0e40\u0e1e\u0e25\u0e07\u0e1b\u0e31\u0e helpStopCommand=\u0e2b\u0e22\u0e38\u0e14\u0e40\u0e25\u0e48\u0e19 \u0e41\u0e25\u0e30\u0e22\u0e01\u0e40\u0e25\u0e34\u0e01\u0e40\u0e25\u0e37\u0e2d\u0e01\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\u0e17\u0e35\u0e48\u0e40\u0e25\u0e48\u0e19 \u0e2a\u0e07\u0e27\u0e19\u0e44\u0e27\u0e49\u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a\u0e1c\u0e39\u0e49\u0e17\u0e35\u0e48\u0e21\u0e35\u0e2a\u0e34\u0e17\u0e18\u0e34\u0e4c\u0e08\u0e31\u0e14\u0e01\u0e32\u0e23\u0e02\u0e49\u0e2d\u0e04\u0e27\u0e32\u0e21 helpUnpauseCommand=\u0e40\u0e25\u0e34\u0e01\u0e1e\u0e31\u0e01\u0e40\u0e04\u0e23\u0e37\u0e48\u0e2d\u0e07\u0e40\u0e25\u0e48\u0e19 helpVolumeCommand=\u0e40\u0e1b\u0e25\u0e35\u0e48\u0e22\u0e19\u0e23\u0e30\u0e14\u0e31\u0e1a\u0e40\u0e2a\u0e35\u0e22\u0e07 \u0e04\u0e48\u0e32\u0e40\u0e1b\u0e47\u0e19 0-150 \u0e41\u0e25\u0e30 100 \u0e40\u0e1b\u0e47\u0e19\u0e04\u0e48\u0e32\u0e40\u0e23\u0e34\u0e48\u0e21\u0e15\u0e49\u0e19 \u0e04\u0e33\u0e2a\u0e31\u0e48\u0e07\u0e40\u0e2a\u0e35\u0e22\u0e07\u0e44\u0e21\u0e48\u0e2a\u0e19\u0e31\u0e1a\u0e2a\u0e19\u0e38\u0e19\u0e41\u0e25\u0e49\u0e27\u0e1a\u0e19\u0e1a\u0e2d\u0e2a\u0e32\u0e18\u0e32\u0e23\u0e13\u0e30 -helpExportCommand=Export the current queue to a wastebin link, can be later used as a playlist. +helpExportCommand=\u0e2a\u0e48\u0e07\u0e04\u0e34\u0e27\u0e1b\u0e31\u0e08\u0e08\u0e38\u0e1a\u0e31\u0e19\u0e44\u0e1b\u0e40\u0e1b\u0e47\u0e19\u0e25\u0e34\u0e07\u0e04\u0e4c wastebin \u0e20\u0e32\u0e22\u0e2b\u0e25\u0e31\u0e07\u0e43\u0e0a\u0e49\u0e40\u0e1b\u0e47\u0e19\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\u0e40\u0e25\u0e48\u0e19 helpGensokyoRadioCommand=\u0e41\u0e2a\u0e14\u0e07\u0e40\u0e1e\u0e25\u0e07\u0e1b\u0e31\u0e08\u0e08\u0e38\u0e1a\u0e31\u0e19\u0e17\u0e35\u0e48\u0e40\u0e25\u0e48\u0e19\u0e1a\u0e19 gensokyoradio.net helpListCommand=\u0e41\u0e2a\u0e14\u0e07\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\u0e40\u0e1e\u0e25\u0e07\u0e1b\u0e31\u0e08\u0e08\u0e38\u0e1a\u0e31\u0e19\u0e43\u0e19\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\u0e40\u0e25\u0e48\u0e19 helpHistoryCommand=\u0e41\u0e2a\u0e14\u0e07\u0e1b\u0e23\u0e30\u0e27\u0e31\u0e15\u0e34\u0e40\u0e1e\u0e25\u0e07\u0e43\u0e19\u0e40\u0e1e\u0e25\u0e22\u0e4c\u0e25\u0e34\u0e2a\u0e15\u0e4c @@ -273,7 +278,7 @@ helpUserInfoCommand=\u0e41\u0e2a\u0e14\u0e07\u0e02\u0e49\u0e2d\u0e21\u0e39\u0e25 helpPerms=\u0e2d\u0e19\u0e38\u0e0d\u0e32\u0e15\u0e43\u0e2b\u0e49\u0e22\u0e39\u0e2a\u0e40\u0e0b\u0e2d\u0e23\u0e4c\u0e17\u0e35\u0e48\u0e44\u0e14\u0e49\u0e23\u0e31\u0e1a\u0e01\u0e32\u0e23\u0e2d\u0e19\u0e38\u0e0d\u0e32\u0e15\u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a\u0e22\u0e28 {0} helpPrefixCommand=\u0e15\u0e31\u0e49\u0e07\u0e04\u0e48\u0e32\u0e04\u0e33\u0e19\u0e33\u0e2b\u0e19\u0e49\u0e32\u0e04\u0e33\u0e2a\u0e31\u0e48\u0e07\u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a\u0e40\u0e0b\u0e34\u0e23\u0e4c\u0e1f\u0e40\u0e27\u0e2d\u0e23\u0e4c\u0e19\u0e35\u0e49 helpVoteSkip=\u0e01\u0e32\u0e23\u0e42\u0e2b\u0e27\u0e15\u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e02\u0e49\u0e32\u0e21\u0e40\u0e1e\u0e25\u0e07\u0e1b\u0e31\u0e08\u0e08\u0e38\u0e1a\u0e31\u0e19\u0e19\u0e31\u0e49\u0e19 \u0e15\u0e49\u0e2d\u0e07\u0e01\u0e32\u0e23\u0e40\u0e2a\u0e35\u0e22\u0e07\u0e42\u0e2b\u0e27\u0e15 50% \u0e08\u0e32\u0e01\u0e22\u0e39\u0e2a\u0e40\u0e0b\u0e2d\u0e23\u0e4c\u0e43\u0e19\u0e2b\u0e49\u0e2d\u0e07\u0e1e\u0e39\u0e14\u0e04\u0e38\u0e22\u0e1b\u0e31\u0e08\u0e08\u0e38\u0e1a\u0e31\u0e19 -helpUnvoteSkip=Remove your vote to skip the current song. +helpUnvoteSkip=\u0e25\u0e1a\u0e04\u0e30\u0e41\u0e19\u0e19\u0e40\u0e2a\u0e35\u0e22\u0e07\u0e02\u0e2d\u0e07\u0e04\u0e38\u0e13\u0e17\u0e35\u0e48\u0e08\u0e30\u0e02\u0e49\u0e32\u0e21\u0e40\u0e1e\u0e25\u0e07\u0e1b\u0e31\u0e08\u0e08\u0e38\u0e1a\u0e31\u0e19\u0e2d\u0e2d\u0e01\u0e44\u0e1b helpMathOperationAdd=\u0e42\u0e0a\u0e27\u0e4c\u0e1c\u0e25\u0e23\u0e27\u0e21\u0e02\u0e2d\u0e07 num1 \u0e41\u0e25\u0e30 num2 helpMathOperationSub=\u0e41\u0e2a\u0e14\u0e07\u0e1c\u0e25\u0e15\u0e48\u0e32\u0e07\u0e02\u0e2d\u0e07 num2 \u0e41\u0e25\u0e30 num1 helpMathOperationMult=\u0e42\u0e0a\u0e27\u0e4c\u0e1c\u0e25\u0e34\u0e15\u0e20\u0e31\u0e13\u0e11\u0e4c\u0e02\u0e2d\u0e07 num1 * num2 @@ -302,8 +307,8 @@ skipUserMultiple=\u0e02\u0e49\u0e32\u0e21\u0e40\u0e1e\u0e25\u0e07\u0e08\u0e33\u0 skipUsersMultiple=\u0e02\u0e49\u0e32\u0e21\u0e40\u0e1e\u0e25\u0e07\u0e08\u0e33\u0e19\u0e27\u0e19 {0} \u0e40\u0e1e\u0e25\u0e07\u0e17\u0e35\u0e48\u0e40\u0e1e\u0e34\u0e48\u0e21\u0e42\u0e14\u0e22\u0e1c\u0e39\u0e49\u0e43\u0e0a\u0e49 {1} skipUserNoTracks=\u0e1c\u0e39\u0e49\u0e43\u0e0a\u0e49\u0e17\u0e35\u0e48\u0e41\u0e17\u0e47\u0e01\u0e44\u0e21\u0e48\u0e44\u0e14\u0e49\u0e40\u0e1e\u0e34\u0e48\u0e21\u0e40\u0e1e\u0e25\u0e07\u0e25\u0e07\u0e43\u0e19\u0e04\u0e34\u0e27 voteSkipAdded=\u0e42\u0e2b\u0e27\u0e15\u0e02\u0e2d\u0e07\u0e04\u0e38\u0e13\u0e44\u0e14\u0e49\u0e16\u0e39\u0e01\u0e40\u0e1e\u0e34\u0e48\u0e21\u0e41\u0e25\u0e49\u0e27 -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! +voteSkipRemoved=\u0e04\u0e30\u0e41\u0e19\u0e19\u0e40\u0e2a\u0e35\u0e22\u0e07\u0e02\u0e2d\u0e07\u0e04\u0e38\u0e13\u0e44\u0e14\u0e49\u0e16\u0e39\u0e01\u0e40\u0e2d\u0e32\u0e2d\u0e2d\u0e01\u0e41\u0e25\u0e49\u0e27 +voteSkipNotFound=\u0e04\u0e38\u0e13\u0e44\u0e21\u0e48\u0e44\u0e14\u0e49\u0e42\u0e2b\u0e27\u0e15\u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e02\u0e49\u0e32\u0e21\u0e40\u0e1e\u0e25\u0e07\u0e19\u0e35\u0e49\u0e41\u0e25\u0e49\u0e27 voteSkipAlreadyVoted=\u0e04\u0e38\u0e13\u0e44\u0e14\u0e49\u0e42\u0e2b\u0e27\u0e15\u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e02\u0e49\u0e32\u0e21\u0e40\u0e1e\u0e25\u0e07\u0e19\u0e35\u0e49\u0e41\u0e25\u0e49\u0e27 voteSkipSkipping=\u0e04\u0e38\u0e13 {0} \u0e44\u0e14\u0e49\u0e17\u0e33\u0e01\u0e32\u0e23\u0e42\u0e2b\u0e27\u0e15\u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e02\u0e49\u0e32\u0e21\u0e40\u0e1e\u0e25\u0e07 {1} \u0e41\u0e25\u0e49\u0e27 voteSkipNotEnough=\u0e04\u0e38\u0e13 {0} \u0e44\u0e14\u0e49\u0e17\u0e33\u0e01\u0e32\u0e23\u0e42\u0e2b\u0e27\u0e15\u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e02\u0e49\u0e32\u0e21\u0e40\u0e1e\u0e25\u0e07\u0e19\u0e35\u0e49\u0e41\u0e25\u0e49\u0e27 \u0e40\u0e2b\u0e25\u0e37\u0e2d\u0e2d\u0e35\u0e01 {1} \u0e42\u0e2b\u0e27\u0e15 diff --git a/FredBoat/src/main/resources/lang/tr_TR.properties b/FredBoat/src/main/resources/lang/tr_TR.properties index 8e343feb9..935c621bd 100644 --- a/FredBoat/src/main/resources/lang/tr_TR.properties +++ b/FredBoat/src/main/resources/lang/tr_TR.properties @@ -62,7 +62,7 @@ npDescription=A\u00e7\u0131klama npLoadedSoundcloud=[{0}/{1}]\n\nSoundcloud''dan y\u00fcklendi npLoadedBandcamp={0}\n\nBandcamp''ten y\u00fcklendi npLoadedTwitch=Twitch'ten y\u00fcklendi -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=Bu i\u015flemi yerine getirebilmek i\u00e7in \u015fu izne ihtiyac\u0131m var\: permissionMissingInvoker=Bu i\u015flemi ger\u00e7ekle\u015ftirmek i\u00e7in a\u015fa\u011f\u0131daki izne ihtiyac\u0131n\u0131z var\: permissionEmbedLinks=G\u00f6mme Ba\u011flant\u0131lar @@ -92,6 +92,11 @@ loadPlaylistTooMany={0} par\u00e7a eklendi. G\u00f6sterilebilece\u011finden \u00 loadErrorCommon=`{0}` i\u00e7in bilgi y\u00fcklenirken bir hata olu\u015ftu\:\n{1} loadErrorSusp=`{0}` i\u00e7in bilgi y\u00fcklenirken \u015f\u00fcpheli bir hata olu\u015ftu. loadQueueTrackLimit={0} par\u00e7adan fazla kuyru\u011fa par\u00e7a ekleyemezsiniz\! Bu, k\u00f6t\u00fcye kullan\u0131m\u0131 \u00f6nlemek i\u00e7in mevcut. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Oynatma listesi **{0}**, `{1}`ye kadar par\u00e7a, y\u00fcklenecek. Bu biraz zaman alabilir, l\u00fctfen sab\u0131rl\u0131 olun. playerUserNotInChannel=\u00d6nce sesli bir kanala kat\u0131lman\u0131z gerekmektedir. playerJoinConnectDenied=Bu sesli kanala ba\u011flanabilmek i\u00e7in yeterli yetkiye sahip de\u011filim. @@ -181,7 +186,7 @@ langSuccess={0}''ye ge\u00e7ildi. langInfo=Fredboat, kullan\u0131c\u0131lar\u0131n kat\u0131l\u0131m\u0131yla terc\u00fcme edilmi\u015f \u00e7oklu dil destekler. Y\u00f6neticiler `;;lang ` komutunu kullanarak diller aras\u0131nda ge\u00e7i\u015f yapabilirsiniz. \u0130\u015fte dillerin bir listesi\: langDisclaimer=\u00c7eviriler %100 do\u011fru veya tam olmayabilir. Yanl\u0131\u015f veya eksik \u00e7evirilere yard\u0131mda bulunmak i\u00e7in . loadSingleTrack=**{0}** s\u0131raya eklendi. -loadSingleTrackFirst=**{0}** has been added to the top of the queue. +loadSingleTrackFirst=**{0}** listenin ba\u015f\u0131na eklenildi. loadSingleTrackAndPlay=**{0}** \u015fimdi oynat\u0131lacak. invite=**{0}** i\u00e7in davet ba\u011flant\u0131s\u0131\: ratelimitedCommandsUser=\u00c7ok h\u0131zl\u0131 komut g\u00f6nderiyorsunuz\! L\u00fctfen biraz daha yava\u015f olun. @@ -273,7 +278,7 @@ helpUserInfoCommand=Kendiniz veya bot i\u00e7in bilinen bir kullan\u0131c\u0131 helpPerms={0} s\u0131ralamas\u0131 i\u00e7in beyaz listeye eklenen \u00fcyelere ve rollere izin verir. helpPrefixCommand=L\u00fctfen sunucunuz i\u00e7in bir "k\u0131sayol" ayarlay\u0131n. helpVoteSkip=\u00c7alan \u015fark\u0131y\u0131 atlamak i\u00e7in oylama yap. Sohbetteki kullan\u0131c\u0131lar\u0131n en az %50'sinin oyu gereklidir. -helpUnvoteSkip=Remove your vote to skip the current song. +helpUnvoteSkip=\u015eimdiki \u015fark\u0131y\u0131 ge\u00e7mek i\u00e7in olan oyunuzu silin. helpMathOperationAdd=Num1 + num2 sonucunu yazd\u0131r. helpMathOperationSub=Num1 - num2 aras\u0131ndaki farkl\u0131l\u0131klar\u0131 yazd\u0131r. helpMathOperationMult=Num1 * num2 sonucunu yazd\u0131r. @@ -302,8 +307,8 @@ skipUserMultiple={1} taraf\u0131ndan eklenen {0} par\u00e7a atland\u0131. skipUsersMultiple={1} kullan\u0131c\u0131 taraf\u0131ndan eklenen {0} par\u00e7a atland\u0131. skipUserNoTracks=Belirtilen kullan\u0131c\u0131lar taraf\u0131ndan s\u0131raya eklenmi\u015f par\u00e7a yok. voteSkipAdded=Oyunuz eklendi\! -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! +voteSkipRemoved=Oyunuz silindi\! +voteSkipNotFound=Bu \u015fark\u0131y\u0131 ge\u00e7mek i\u00e7in oy kullanmad\u0131n\u0131z\! voteSkipAlreadyVoted=Bu par\u00e7ay\u0131 atlamak i\u00e7in zaten oy kulland\u0131n\u0131z\! voteSkipSkipping={0} ki\u015fi atlanmas\u0131 i\u00e7in oy kulland\u0131. {1} par\u00e7as\u0131 atlan\u0131yor. voteSkipNotEnough={0} ki\u015fi atlanmas\u0131 i\u00e7in oy kulland\u0131. {1} oya daha ihtiya\u00e7 var. diff --git a/FredBoat/src/main/resources/lang/uk_UA.properties b/FredBoat/src/main/resources/lang/uk_UA.properties index ef6aae1b5..4fb10c483 100644 --- a/FredBoat/src/main/resources/lang/uk_UA.properties +++ b/FredBoat/src/main/resources/lang/uk_UA.properties @@ -62,7 +62,7 @@ npDescription=\u041e\u043f\u0438\u0441 npLoadedSoundcloud=[{0}/{1}]\n\n\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043e \u0437 Soundcloud npLoadedBandcamp={0}\n\n\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043e \u0437 Bandcamp npLoadedTwitch=\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043e \u0437 Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=\u041c\u0435\u043d\u0456 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u0456 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0456 \u0434\u043e\u0437\u0432\u043e\u043b\u0438 \u0434\u043b\u044f \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u043d\u044f \u0446\u0456\u0454\u0457 \u0434\u0456\u0457\: permissionMissingInvoker=\u0412\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u0456 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0456 \u0434\u043e\u0437\u0432\u043e\u043b\u0438 \u0434\u043b\u044f \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u043d\u044f \u0446\u0456\u0454\u0457 \u0434\u0456\u0457\: permissionEmbedLinks=\u0412\u0431\u0443\u0434\u043e\u0432\u0430\u043d\u0456 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f @@ -92,6 +92,11 @@ loadPlaylistTooMany=\u0414\u043e\u0434\u0430\u043d\u043e {0} \u0442\u0440\u0435\ loadErrorCommon=\u0412\u0456\u0434\u0431\u0443\u043b\u0430\u0441\u044f \u043f\u043e\u043c\u0438\u043b\u043a\u0430 \u043f\u0440\u0438 \u0437\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u0456 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457 \u043f\u0440\u043e `{0}`\:\n{1} loadErrorSusp=\u0412\u0438\u043d\u0438\u043a\u043b\u0430 \u043f\u0456\u0434\u043e\u0437\u0440\u0456\u043b\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430 \u043f\u0440\u0438 \u0437\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u0456 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457 \u043f\u0440\u043e `{0}`. loadQueueTrackLimit=\u0412\u0438 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 \u0442\u0440\u0435\u043a\u0438 \u0434\u043e \u0447\u0435\u0440\u0433\u0438, \u0443 \u044f\u043a\u0456\u0439 \u0431\u0456\u043b\u044c\u0448\u0435 \u0437\u0430 {0} \u0442\u0440\u0435\u043a\u0456\u0432\! \u0426\u0435 \u0434\u043b\u044f \u0437\u0430\u043f\u043e\u0431\u0456\u0433\u0430\u043d\u043d\u044f \u0437\u043b\u043e\u0432\u0436\u0438\u0432\u0430\u043d\u043d\u044e. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0443\u044e \u043f\u043b\u0435\u0439\u043b\u0456\u0441\u0442 **{0}** \u0456\u0437 `{1}` \u0442\u0440\u0435\u043a\u0430\u043c\u0438. \u0426\u0435 \u043c\u043e\u0436\u0435 \u0437\u0430\u0439\u043d\u044f\u0442\u0438 \u043f\u0435\u0432\u043d\u0438\u0439 \u0447\u0430\u0441, \u0431\u0443\u0434\u044c\u0442\u0435 \u0442\u0435\u0440\u043f\u043b\u044f\u0447\u0438\u043c\u0438. playerUserNotInChannel=\u0421\u043f\u043e\u0447\u0430\u0442\u043a\u0443 \u0432\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0443\u0432\u0456\u0439\u0442\u0438 \u0434\u043e \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0433\u043e \u043a\u0430\u043d\u0430\u043b\u0443. playerJoinConnectDenied=\u0423 \u043d\u0435 \u043c\u0430\u044e \u043f\u0440\u0430\u0432 \u0437\u0430\u0445\u043e\u0434\u0438\u0442\u0438 \u0443 \u0446\u0435\u0439 \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u0438\u0439 \u043a\u0430\u043d\u0430\u043b. diff --git a/FredBoat/src/main/resources/lang/vi_VN.properties b/FredBoat/src/main/resources/lang/vi_VN.properties index 2f745de93..9745ce39a 100644 --- a/FredBoat/src/main/resources/lang/vi_VN.properties +++ b/FredBoat/src/main/resources/lang/vi_VN.properties @@ -62,7 +62,7 @@ npDescription=Mi\u00eau t\u1ea3 npLoadedSoundcloud=[{0}/{1}]\n\n\u0110\u00e3 n\u1ea1p t\u1eeb Soundcloud npLoadedBandcamp={0}\n\n\u0110\u00e3 n\u1ea1p t\u1eeb Bandcamp npLoadedTwitch=\u0110\u00e3 n\u1ea1p t\u1eeb Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=T\u00f4i c\u1ea7n quy\u1ec1n sau \u0111\u1ec3 l\u00e0m h\u00e0nh \u0111\u1ed9ng \u0111\u00f3\: permissionMissingInvoker=B\u1ea1n c\u1ea7n c\u00f3 quy\u1ec1n sau \u0111\u1ec3 th\u1ef1c hi\u1ec7n h\u00e0nh \u0111\u1ed9ng \u0111\u00f3\: permissionEmbedLinks=Nh\u00fang li\u00ean k\u1ebft @@ -92,6 +92,11 @@ loadPlaylistTooMany=\u0110\u00e3 th\u00eam {0} b\u00e0i h\u00e1t. T\u00ecm th\u1 loadErrorCommon=X\u1ea3y ra l\u1ed7i khi n\u1ea1p th\u00f4ng tin cho `{0}`\:\n{1} loadErrorSusp=L\u1ed7i nghi ng\u1edd khi n\u1ea1p th\u00f4ng tin cho `{0}`. loadQueueTrackLimit=B\u1ea1n kh\u00f4ng th\u1ec3 th\u00eam b\u00e0i h\u00e1t trong h\u00e0ng ch\u1edd v\u1edbi h\u01a1n {0} b\u00e0i\! \u0110\u1ec3 tr\u00e1nh l\u1ea1m d\u1ee5ng. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Chu\u1ea9n b\u1ecb n\u1ea1p danh s\u00e1ch ph\u00e1t **{0}** v\u1edbi `{1}` b\u00e0i h\u00e1t. C\u00f3 th\u1ec3 m\u1ea5t m\u1ed9t l\u00fac, xin h\u00e3y ki\u00ean nh\u1eabn. playerUserNotInChannel=B\u1ea1n ph\u1ea3i tham gia k\u00eanh tho\u1ea1i tr\u01b0\u01a1\u0301c. playerJoinConnectDenied=T\u00f4i kh\u00f4ng \u0111\u01b0\u1ee3c ph\u00e9p k\u1ebft n\u1ed1i v\u1edbi k\u00eanh tho\u1ea1i \u0111\u00f3. @@ -181,7 +186,7 @@ langSuccess=Chuy\u1ec3n sang n\u00f3i {0}. langInfo=FredBoat h\u1ed7 tr\u1ee3 nhi\u1ec1u ng\u00f4n ng\u1eef \u0111\u00f3ng g\u00f3p t\u1eeb ng\u01b0\u1eddi d\u00f9ng m\u00e0 b\u1ea1n c\u00f3 th\u1ec3 ch\u1ecdn v\u1edbi l\u1ec7nh n\u00e0y. Qu\u1ea3n tr\u1ecb vi\u00ean tr\u00ean m\u00e1y ch\u1ee7 n\u00e0y c\u00f3 th\u1ec3 ch\u1ecdn m\u1ed9t ng\u00f4n ng\u1eef v\u1edbi `;;lang `d\u01b0\u1edbi \u0111\u00e2y l\u00e0 danh s\u00e1ch c\u00e1c ng\u00f4n ng\u1eef \u0111\u01b0\u1ee3c h\u1ed7 tr\u1ee3 \u0111\u1ea7y \u0111\u1ee7\: langDisclaimer=B\u1ea3n d\u1ecbch c\u00f3 th\u1ec3 kh\u00f4ng ch\u00ednh x\u00e1c ho\u1eb7c \u0111\u1ea7y \u0111\u1ee7 100%. Thi\u1ebfu b\u1ea3n d\u1ecbch c\u00f3 th\u1ec3 \u0111\u01b0\u1ee3c \u0111\u00f3ng g\u00f3p t\u1ea1i . loadSingleTrack=**{0}** \u0111\u00e3 \u0111\u01b0\u1ee3c th\u00eam v\u00e0o h\u00e0ng ch\u1edd. -loadSingleTrackFirst=**{0}** has been added to the top of the queue. +loadSingleTrackFirst=**{1}** \u0111\u00e3 \u0111\u01b0\u1ee3c th\u00eam v\u00e0o h\u00e0ng ch\u1edd. loadSingleTrackAndPlay=**{0}** s\u1ebd ph\u00e1t ngay b\u00e2y gi\u1edd. invite=M\u1eddi li\u00ean k\u1ebft cho **{0}**\: ratelimitedCommandsUser=B\u1ea1n \u0111ang g\u1eedi l\u1ec7nh qu\u00e1 nhanh\! Vui l\u00f2ng ch\u1eadm l\u1ea1i. diff --git a/FredBoat/src/main/resources/lang/yo_NG.properties b/FredBoat/src/main/resources/lang/yo_NG.properties index a2b711284..f00808073 100644 --- a/FredBoat/src/main/resources/lang/yo_NG.properties +++ b/FredBoat/src/main/resources/lang/yo_NG.properties @@ -62,7 +62,7 @@ npDescription=Description npLoadedSoundcloud=[{0}/{1}]\n\nLoaded from Soundcloud npLoadedBandcamp={0}\n\nLoaded from Bandcamp npLoadedTwitch=Loaded from Twitch -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=I need the following permission to perform that action\: permissionMissingInvoker=You need the following permission to perform that action\: permissionEmbedLinks=Embed Links @@ -92,6 +92,11 @@ loadPlaylistTooMany=Added {0} tracks. Found too many tracks to display. loadErrorCommon=Error occurred when loading info for `{0}`\:\n{1} loadErrorSusp=Suspicious error when loading info for `{0}`. loadQueueTrackLimit=You can''t add tracks to a queue with more than {0} tracks\! This is to prevent abuse. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=About to load playlist **{0}** with up to `{1}` tracks. This may take a while, please be patient. playerUserNotInChannel=You must join a voice channel first. playerJoinConnectDenied=I am not permitted to connect to that voice channel. diff --git a/FredBoat/src/main/resources/lang/zh_CN.properties b/FredBoat/src/main/resources/lang/zh_CN.properties index 3d8666812..a58856b09 100644 --- a/FredBoat/src/main/resources/lang/zh_CN.properties +++ b/FredBoat/src/main/resources/lang/zh_CN.properties @@ -62,7 +62,7 @@ npDescription=\u8bf4\u660e npLoadedSoundcloud=[{0}/{1}]\n\n\u5df2\u4ece Soundcloud \u52a0\u8f7d\u5b8c\u6210 npLoadedBandcamp={0}\n\n\u5df2\u4ece Bandcamp \u52a0\u8f7d\u5b8c\u6210 npLoadedTwitch=\u5df2\u4ece Twitch \u52a0\u8f7d\u5b8c\u6210 -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=\u6211\u9700\u8981\u4ee5\u4e0b\u6743\u9650\u624d\u80fd\u6267\u884c\u8be5\u64cd\u4f5c\: permissionMissingInvoker=\u60a8\u9700\u8981\u4ee5\u4e0b\u6743\u9650\u624d\u80fd\u6267\u884c\u8be5\u64cd\u4f5c\uff1a permissionEmbedLinks=\u5d4c\u5165\u94fe\u63a5 @@ -92,6 +92,11 @@ loadPlaylistTooMany=\u5df2\u6dfb\u52a0 {0} \u9996\u6b4c\u66f2\u3002\u6b4c\u66f2\ loadErrorCommon=\u52a0\u8f7d `{0}` \u7684\u4fe1\u606f\u65f6\u53d1\u751f\u9519\u8bef\uff1a\n{1} loadErrorSusp=\u52a0\u8f7d `{0}` \u7684\u4fe1\u606f\u65f6\u610f\u601d\u53d1\u751f\u9519\u8bef\u3002 loadQueueTrackLimit=\u60a8\u4e0d\u80fd\u5c06\u66f2\u76ee\u6dfb\u52a0\u5230\u8d85\u8fc7{0} \u8f68\u9053\u7684\u961f\u5217\u4e2d\! \u8fd9\u662f\u4e3a\u4e86\u9632\u6b62\u6ee5\u7528\u3002 +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=\u5373\u5c06\u52a0\u8f7d\u64ad\u653e\u5217\u8868 **{0} ** \u4e0e\u591a\u8fbe ''{1} '' \u8f68\u9053\u3002\u8fd9\u53ef\u80fd\u9700\u8981\u4e00\u6bb5\u65f6\u95f4, \u8bf7\u8010\u5fc3\u7b49\u5f85\u3002 playerUserNotInChannel=\u60a8\u5fc5\u987b\u5148\u52a0\u5165\u4e00\u4e2a\u8bed\u97f3\u804a\u5929\u5ba4 playerJoinConnectDenied=\u6ca1\u6709\u6743\u9650\u8fde\u63a5\u81f3\u8be5\u8bed\u97f3\u9891\u9053 diff --git a/FredBoat/src/main/resources/lang/zh_TW.properties b/FredBoat/src/main/resources/lang/zh_TW.properties index b0064842b..47a1355dc 100644 --- a/FredBoat/src/main/resources/lang/zh_TW.properties +++ b/FredBoat/src/main/resources/lang/zh_TW.properties @@ -1,6 +1,6 @@ #X-Generator: crowdin.com playQueueEmpty=\u64ad\u653e\u5668\u76ee\u524d\u6c92\u6709\u5728\u64ad\u653e\u6b4c\u66f2\u3002\u4f7f\u7528\u4e0b\u9762\u7684\u6307\u4ee4\u6dfb\u52a0\u4e00\u9996\u6b4c\: \n;;play -playAlreadyPlaying=\u64ad\u653e\u5668\u5df2\u7d93\u5728\u64a5\u653e\u4e86 +playAlreadyPlaying=\u64ad\u653e\u5668\u5df2\u7d93\u5728\u64ad\u653e\u4e86\u3002 playVCEmpty=\u8a9e\u97f3\u983b\u9053\u76ee\u524d\u6c92\u6709\u7528\u6236 playWillNowPlay=\u5373\u5c07\u958b\u59cb\u64ad\u653e playSearching=\u6b63\u5728\u5f9e Youtube \u4e2d\u641c\u5c0b `{q}`... @@ -13,7 +13,7 @@ pauseAlreadyPaused=\u64ad\u653e\u5668\u5df2\u7d93\u66ab\u505c\u64ad\u653e\u4e86 pauseSuccess=\u64ad\u653e\u5668\u5df2\u7d93\u66ab\u505c\u3002\u60a8\u53ef\u4ee5\u4f7f\u7528 `{0}unpause` \u53d6\u6d88\u66ab\u505c repeatOnSingle=\u64ad\u653e\u5668\u5c07\u91cd\u8986\u64ad\u653e\u7576\u524d\u66f2\u76ee\u3002 repeatOnAll=\u64ad\u653e\u5668\u5c07\u91cd\u8986\u64ad\u653e\u6b64\u64ad\u653e\u5e8f\u5217 -repeatOff=\u64ad\u653e\u5668\u5c07\u95dc\u9589\u91cd\u8907\u64a5\u653e\u6a21\u5f0f\u3002 +repeatOff=\u64ad\u653e\u5668\u5c07\u95dc\u9589\u91cd\u8907\u64ad\u653e\u6a21\u5f0f\u3002 selectSuccess=\u5df2\u7d93\u9078\u64c7\u7b2c **\#{0}** \u9996\u6b4c\: **{1}** ({2}) selectInterval=\u6578\u5b57\u5fc5\u9808\u5728 1~{0} \u4e4b\u9593 selectSelectionNotGiven=\u4f60\u5fc5\u9808\u5148\u6709\u9078\u9805\u624d\u53ef\u4ee5\u9032\u884c\u9078\u64c7 @@ -62,7 +62,7 @@ npDescription=\u8a73\u60c5 npLoadedSoundcloud=[{0}/{1}]\n\n\u5df2\u5f9e Soundcloud \u4e2d\u8f09\u5165 npLoadedBandcamp={0} \n\n\u5df2\u5f9e Bandcamp \u4e2d\u8f09\u5165 npLoadedTwitch=\u5df2\u5f9e Twitch \u4e2d\u8f09\u5165 -npRequestedBy= +npRequestedBy=Requested by {0} permissionMissingBot=\u6211\u9700\u8981\u4e0b\u5217\u6b0a\u9650\u4f86\u57f7\u884c\u6b64\u64cd\u4f5c\: permissionMissingInvoker=\u4f60\u9700\u8981\u4e0b\u5217\u6b0a\u9650\u4f86\u57f7\u884c\u6b64\u64cd\u4f5c\: permissionEmbedLinks=\u5167\u5d4c\u9023\u7d50 @@ -80,7 +80,7 @@ fwdSuccess=\u6b63\u5728\u5c07 **{0}** \u5feb\u8f49 {1}. restartSuccess=**{0}** \u5df2\u91cd\u65b0\u555f\u52d5\u3002 queueEmpty=\u64ad\u653e\u5e8f\u5217\u662f\u7a7a\u7684 rewSuccess=\u6b63\u5728\u5c07 **{0}** \u5012\u5e36 {1} -seekSuccess=\u6b63\u5728\u5c07 **{0}** \u64a5\u653e\u6642\u9593\u79fb\u52d5\u5230 {1} +seekSuccess=\u6b63\u5728\u5c07 **{0}** \u64ad\u653e\u6642\u9593\u79fb\u52d5\u5230 {1}\u3002 seekDeniedLiveTrack=\u4f60\u4e0d\u80fd\u79fb\u52d5\u5be6\u6cc1\u7684\u6642\u9593\u8ef8\u3002 loadPlaySplitListFail=\u8a72\u9023\u7d50\u6307\u5411\u4e00\u500b\u64ad\u653e\u6e05\u55ae\uff0c\u4e0d\u662f\u4e00\u9996\u6b4c\u3002\u8acb\u4f7f\u7528 `;;play` loadListSuccess=\u5df2\u5f9e\u64ad\u653e\u6e05\u55ae **{1}** \u65b0\u589e `{0}` \u9996\u6b4c @@ -92,6 +92,11 @@ loadPlaylistTooMany=\u5df2\u65b0\u589e {0} \u500b\u66f2\u76ee, \u767c\u73fe\u592 loadErrorCommon=\u5728\u8f09\u5165 `{0}` \u7684\u8cc7\u8a0a\u6642\u767c\u751f\u932f\u8aa4\uff1a\n{1} loadErrorSusp=\u5728\u8f09\u5165 `{0}` \u7684\u8cc7\u8a0a\u6642\u7591\u4f3c\u767c\u751f\u932f\u8aa4\u3002 loadQueueTrackLimit=\u70ba\u4e86\u9632\u6b62\u6feb\u7528\uff0c\u60a8\u7121\u6cd5\u5728\u591a\u65bc {0} \u500b\u66f2\u76ee\u6642\u65b0\u589e\u66f2\u76ee\u5230\u5e8f\u5217\u4e2d +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=\u5373\u5c07\u8b80\u53d6\u64ad\u653e\u6e05\u55ae **{0}** \u4e2d\u7684 `{1}` \u500b\u66f2\u76ee. \u9019\u9700\u8981\u4e00\u9ede\u6642\u9593\uff0c\u8acb\u8010\u5fc3\u7b49\u5019. playerUserNotInChannel=\u4f60\u5fc5\u9808\u5148\u52a0\u5165\u8a9e\u97f3\u983b\u9053 playerJoinConnectDenied=\u6211\u6c92\u6709\u6b0a\u9650\u9032\u5165\u90a3\u500b\u8a9e\u97f3\u983b\u9053 @@ -103,7 +108,7 @@ shutdownRestarting=\u6b63\u5728\u91cd\u555fFredBoat\u266a\u266a\u3002\u9019\u61c shutdownIndef=\u6b63\u5728\u95dc\u9589 FredBoat\u266a\u266a\u3002Bot\u958b\u555f\u6642\u6703\u91cd\u65b0\u8f09\u5165\u76ee\u524d\u7684\u6e05\u55ae shutdownPersistenceFail=\u5132\u5b58\u6c38\u4e45\u6027\u6a94\u6848\u6642\u767c\u751f\u932f\u8aa4\uff1a{0} reloadSuccess=\u6b63\u5728\u91cd\u65b0\u8f09\u5165\u6e05\u55ae\u3002\u5df2\u627e\u5230 `{0}` \u500b\u66f2\u76ee -trackAnnounce=\u73fe\u5728\u6b63\u5728\u64a5\u653e\u7531 **{1}** \u9ede\u64ad\u7684 **{0}**\u3002 +trackAnnounce=\u73fe\u5728\u6b63\u5728\u64ad\u653e\u7531 **{1}** \u9ede\u64ad\u7684 **{0}**\u3002 cmdAccessDenied=\u4f60\u6c92\u6709\u6b0a\u9650\u4f7f\u7528\u9019\u500b\u6307\u4ee4 malRevealAnime={0}\: \u641c\u5c0b\u986f\u793a\u7684\u52d5\u6f2b\n malTitle={0}**\u6a19\u984c\uff1a**{1}\n @@ -147,10 +152,10 @@ modFailBotHierarchy=\u6211\u5fc5\u9808\u6709\u6bd4 {0} \u66f4\u9ad8\u7684\u8eab\ modBanlistFail=\u7121\u6cd5\u53d6\u5f97\u9ed1\u540d\u55ae modBanFail=\u7121\u6cd5\u5c01\u9396 {0} modUnbanFail=\u7121\u6cd5\u89e3\u9664\u5c01\u9396 {0} -modUnbanFailNotBanned=\u4f7f\u7528\u8005 {0} \u6c92\u6709\u5728\u9019\u4f3a\u670d\u5668\u88ab\u5c01\u9396\u3002 +modUnbanFailNotBanned=\u4f7f\u7528\u8005 {0} \u6c92\u6709\u5728\u9019\u7fa4\u7d44\u88ab\u5c01\u9396\u3002 modKeepMessages=\u7528 {0} \u4f7f\u8a0a\u606f\u4e0d\u6703\u88ab\u522a\u9664\u3002 -modActionTargetDmKicked=\u55d0\uff01\u4f60\u88ab {1} \u5f9e\u4f3a\u670d\u5668 {0} \u8e22\u4e86\u51fa\u4f86\u3002 -modActionTargetDmBanned=\u55d0\uff01{1} \u628a\u4f60\u5728\u4f3a\u670d\u5668 {0} \u4e2d\u5c01\u9396\u4e86\u3002 +modActionTargetDmKicked=\u55d0\uff01\u4f60\u88ab {1} \u5f9e\u7fa4\u7d44 {0} \u8e22\u4e86\u51fa\u4f86\u3002 +modActionTargetDmBanned=\u55d0\uff01 {1} \u628a\u4f60\u5728\u7fa4\u7d44 {0} \u4e2d\u5c01\u9396\u4e86\u3002 kickSuccess=\u7528\u6236 {0} \u5df2\u88ab\u8e22\u9664\u3002 kickFail=\u7121\u6cd5\u8e22\u9664 {0} kickFailSelf=\u60a8\u4e0d\u80fd\u8e22\u9664\u81ea\u5df1 @@ -165,7 +170,7 @@ hardbanFailSelf=\u60a8\u4e0d\u80fd\u5c01\u9396\u81ea\u5df1 hardbanFailOwner=\u60a8\u4e0d\u80fd\u5c01\u9396\u4f3a\u670d\u4e3b hardbanFailMyself=\u6211\u4e0d\u80fd\u5c01\u9396\u6211\u81ea\u5df1 unbanSuccess=\u4f7f\u7528\u8005 {0} \u5df2\u88ab\u89e3\u9664\u5c01\u9396\u3002 -getidSuccess=\u9019\u500b\u4f3a\u670d\u5668\u7684 id\u662f {0}\u3002\u9019\u500b\u6587\u5b57\u983b\u9053\u7684ID\u662f {1}\u3002 +getidSuccess=\u9019\u500b\u7fa4\u7d44\u7684 id\u662f {0}\u3002\u9019\u500b\u6587\u5b57\u983b\u9053\u7684ID\u662f {1}\u3002 statsParagraph=\ \u6b64Bot\u5df2\u57f7\u884c\u4e86 {0} \u5929 {1} \u5c0f\u6642 {2} \u5206\u9418 {3} \u79d2\u3002\n\u6b64Bot\u5df2\u7d93\u5728\u6b64\u6703\u8a71\u57f7\u884c\u4e86 {4} \u500b\u6307\u4ee4 statsRate={0} \u9019\u662f\u6bcf\u5c0f\u6642 {1} \u500b\u6307\u4ee4\u7684\u6bd4\u7387 catgirlFail=\u5f9e {0} \u63d0\u53d6\u5716\u7247\u5931\u6557 @@ -185,7 +190,7 @@ loadSingleTrackFirst=**{0}** \u5df2\u52a0\u5230\u64ad\u653e\u5e8f\u5217\u7684\u7 loadSingleTrackAndPlay=\u5373\u5c07\u64ad\u653e **{0}** invite=**{0}** \u7684\u9080\u8acb\u9023\u7d50 ratelimitedCommandsUser=\u4f60\u7684\u6307\u4ee4\u9001\u7684\u592a\u5feb\u4e86\uff01\u8acb\u6162\u6162\u4f86\u3002 -ratelimitedCommandsGuild=\u9019\u500b\u4f3a\u670d\u5668\u7684\u6307\u4ee4\u9001\u7684\u592a\u5feb\u4e86\uff01\u8acb\u6162\u6162\u4f86\u3002 +ratelimitedCommandsGuild=\u9019\u500b\u7fa4\u7d44\u7684\u6307\u4ee4\u9001\u7684\u592a\u5feb\u4e86\uff01\u8acb\u6162\u6162\u4f86\u3002 ratelimitedSkipCommand=\u60a8\u53ef\u4ee5\u901a\u904e\u4f7f\u7528\u6b64\u547d\u4ee4\u8df3\u904e\u8d85\u904e\u4e00\u9996\u6216\u8457\u591a\u9996\u6b4c\uff1a {0} ratelimitedGuildSlowLoadingPlaylist=\u6b64\u4f3a\u670d\u5668\u4e0d\u5141\u8a31\u5728\u6b64\u6642\u65b0\u589e\u66f4\u591a\u64ad\u653e\u6e05\u55ae\u3002\u8acb\u4e0d\u8981\u5237\u5f88\u9577\u7684\u64ad\u653e\u6e05\u55ae unblacklisted=\u5f9e\u9ed1\u540d\u55ae\u79fb\u9664 {0} @@ -195,7 +200,7 @@ serverinfoTotalUsers=\u7528\u6236\u7e3d\u6578\uff1a serverinfoRoles=\u8eab\u4efd\uff1a serverinfoText=\u6587\u5b57\u983b\u9053\uff1a serverinfoVoice=\u8a9e\u97f3\u983b\u9053\uff1a -serverinfoGuildID=\u4f3a\u670d\u5668ID\: +serverinfoGuildID=\u7fa4\u7d44ID\: serverinfoCreationDate=\u5efa\u7acb\u65e5\u671f\: serverinfoOwner=\u64c1\u6709\u8005\uff1a serverinfoVLv=\u9a57\u8b49\u7d1a\u5225\ufe30 @@ -220,31 +225,31 @@ commandsMoreHelp=\u4f7f\u7528 {0} \u4ee5\u7372\u5f97\u7279\u5b9a\u6307\u4ee4\u76 commandsModulesHint=\u60a8\u53ef\u4ee5\u4f7f\u7528{0} \u555f\u7528\u548c\u7981\u7528\u5176\u4ed6\u6a21\u7d44 helpUnknownCommand=\u672a\u77e5\u7684\u6307\u4ee4 helpDocsLocation=\u627e\u4e0d\u5230\u6587\u4ef6 -helpBotInvite=\u8981\u5c07 FredBoat \u6dfb\u52a0\u5230\u60a8\u7684\u4f3a\u670d\u5668\u55ce\uff1f\u5982\u679c\u60a8\u6709\u7ba1\u7406\u4f3a\u670d\u5668\u7684\u8a31\u53ef\u6b0a, \u60a8\u53ef\u4ee5\u9080\u8acb FredBoat\: +helpBotInvite=\u8981\u5c07 FredBoat \u6dfb\u52a0\u5230\u60a8\u7684\u7fa4\u7d44\u55ce\uff1f\u5982\u679c\u60a8\u6709\u7ba1\u7406\u7fa4\u7d44\u7684\u8a31\u53ef\u6b0a, \u60a8\u53ef\u4ee5\u9080\u8acb FredBoat\: helpHangoutInvite=\u9700\u8981\u5e6b\u52a9\u6216\u6709\u4efb\u4f55\u60f3\u6cd5\uff1f\u4e5f\u8a31\u4f60\u53ea\u662f\u60f3\u56db\u8655\u73a9\uff1f\u5feb\u4f86\u52a0\u5165 FredBoat \u793e\u7fa4\! helpNoDmCommands=\u60a8\u4e0d\u80fd\u901a\u904e\u79c1\u8a0a\u767c\u9001 FredBoat \u6307\u4ee4\u3002 helpCredits=\u7531 Fre_d \u548c\u958b\u6e90\u8005\u5275\u5efa helpSent=\u6587\u4ef6\u5df2\u7d93\u79c1\u8a0a\u7d66\u4f60 helpProperUsage=\u6b63\u78ba\u7528\u6cd5\: helpCommandOwnerRestricted=\u6b64\u6307\u4ee4\u53ea\u9650Bot\u7684\u64c1\u6709\u8005\u4f7f\u7528 -helpConfigCommand=\u986f\u793a\u6216\u8abf\u6574\u4f3a\u670d\u5668\u7684\u8a2d\u5b9a -helpLanguageCommand=\u986f\u793a\u53ef\u7528\u8a9e\u8a00\u6216\u70ba\u9019\u500b\u4f3a\u670d\u5668\u8a2d\u5b9a\u4e00\u500b\u8a9e\u8a00 -helpModules=\u986f\u793a\u3001\u555f\u7528\u6216\u7981\u7528\u8a72\u4f3a\u670d\u5668\u7684\u6a21\u7d44\u3002 +helpConfigCommand=\u986f\u793a\u6216\u8abf\u6574\u7fa4\u7d44\u7684\u8a2d\u5b9a +helpLanguageCommand=\u986f\u793a\u53ef\u7528\u7684\u8a9e\u8a00\uff0c\u6216\u662f\u70ba\u6b64\u7fa4\u7d44\u8a2d\u5b9a\u8a9e\u8a00\u3002 +helpModules=\u986f\u793a\u3001\u555f\u7528\u6216\u7981\u7528\u8a72\u7fa4\u7d44\u7684\u6a21\u7d44\u3002 helpHardbanCommand=\u5c01\u9396\u4f7f\u7528\u8005\u4e26\u522a\u9664\u6b64\u4f7f\u7528\u8005\u4e00\u5929\u5167\u7684\u4fe1\u606f -helpKickCommand=\u5f9e\u4f3a\u670d\u5668\u8e22\u9664\u4f7f\u7528\u8005 +helpKickCommand=\u5f9e\u7fa4\u7d44\u8e22\u9664\u4f7f\u7528\u8005 helpSoftbanCommand=\u66ab\u6642\u5c01\u9396\u4e26\u8e22\u9664\u4f7f\u7528\u8005\uff0c\u4e26\u522a\u9664\u6b64\u4f7f\u7528\u8005\u4e00\u5929\u5167\u7684\u4fe1\u606f helpUnbanCommand=\u89e3\u9664\u524d\u4e00\u500b\u88ab\u5c01\u9396\u7684\u4f7f\u7528\u8005 helpMusicCommandsHeader=FredBoat \u97f3\u6a02\u6307\u4ee4 helpJoinCommand=\u8b93 Bot \u52a0\u5165\u60a8\u76ee\u524d\u7684\u8a9e\u97f3\u983b\u9053 helpLeaveCommand=\u8b93 Bot \u96e2\u958b\u60a8\u76ee\u524d\u7684\u8a9e\u97f3\u983b\u9053 helpPauseCommand=\u6682\u505c\u64ad\u653e -helpPlayCommand=\u7528\u9023\u7d50\u64a5\u653e\u97f3\u6a02\u6216\u5229\u7528\u95dc\u9375\u5b57\u641c\u5c0b\u66f2\u76ee\u3002\u5982\u6b32\u7372\u5f97\u5b8c\u6574\u7684\u8cc7\u6599, \u8acb\u8a2a\u554f{0} -helpPlayNextCommand=\u7528\u9023\u7d50\u64a5\u653e\u97f3\u6a02\u6216\u5229\u7528\u95dc\u9375\u5b57\u641c\u5c0b\u66f2\u76ee\uff0c\u4e26\u653e\u7f6e\u5728\u7b2c\u4e00\u9806\u4f4d\u64ad\u653e\u3002\u5982\u6b32\u7372\u5f97\u5b8c\u6574\u7684\u8cc7\u6599, \u8acb\u8a2a\u554f {0} +helpPlayCommand=\u7528\u9023\u7d50\u64ad\u653e\u97f3\u6a02\u6216\u5229\u7528\u95dc\u9375\u5b57\u641c\u5c0b\u66f2\u76ee\u3002\u5982\u6b32\u7372\u5f97\u5b8c\u6574\u7684\u8cc7\u6599, \u8acb\u8a2a\u554f{0} +helpPlayNextCommand=\u7528\u9023\u7d50\u64ad\u653e\u97f3\u6a02\u6216\u5229\u7528\u95dc\u9375\u5b57\u641c\u5c0b\u66f2\u76ee\uff0c\u4e26\u653e\u7f6e\u5728\u7b2c\u4e00\u9806\u4f4d\u64ad\u653e\u3002\u5982\u6b32\u7372\u5f97\u5b8c\u6574\u7684\u8cc7\u6599, \u8acb\u8a2a\u554f {0} helpPlaySplitCommand=\u5c07\u5f71\u7247\u6839\u64daYoutube\u5b8c\u6574\u8cc7\u8a0a\u4e2d\u7684\u6642\u9593\u9ede\u5206\u5272\u6210\u4e0d\u540c\u66f2\u76ee\u3002 -helpRepeatCommand=\u958b\u555f/\u95dc\u9589\u91cd\u8907\u64a5\u653e\u6a21\u5f0f +helpRepeatCommand=\u5207\u63db\u91cd\u8907\u64ad\u653e\u6a21\u5f0f\u3002 helpReshuffleCommand=\u96a8\u6a5f\u6392\u5217\u64ad\u653e\u5e8f\u5217 helpSelectCommand=\u8acb\u5f9e\u641c\u5c0b\u7d50\u679c\u9078\u4e00\u500b\u66f2\u76ee\u4f86\u64ad\u653e -helpShuffleCommand=\u958b\u555f/\u95dc\u9589\u96a8\u6a5f\u64a5\u653e\u6a21\u5f0f\n(\u53ea\u5c0d\u76ee\u524d\u5df2\u5728\u5e8f\u5217\u4e2d\u7684\u66f2\u76ee\u6709\u6548) +helpShuffleCommand=\u5207\u63db\u76ee\u524d\u5e8f\u5217\u7684\u96a8\u6a5f\u64ad\u653e\u6a21\u5f0f\u3002 helpSkipCommand=\u8df3\u904e\u7576\u524d\u7684\u6b4c\u66f2, \u6216\u5728\u4f47\u5217\u4e2d\u7684 n \u9996\u6b4c\u66f2, \u6216\u662f\u6e05\u55ae\u4e0a\u7531 n \u5230 m\u7684\u6b4c\u66f2, \u6216\u67d0\u4f7f\u7528\u8005\u63d0\u4f9b\u7684\u6240\u6709\u6b4c\u66f2\u3002\u8acb\u659f\u914c\u4f7f\u7528\u3002 helpStopCommand=\u505c\u6b62\u64ad\u653e\u5668\u4e26\u6e05\u9664\u64ad\u653e\u6e05\u55ae\u3002\u53ea\u6709\u7ba1\u7406\u8005\u6b0a\u9650\u624d\u80fd\u4f7f\u7528\u3002 helpUnpauseCommand=\u7e7c\u7e8c\u64ad\u653e\u3002 @@ -252,7 +257,7 @@ helpVolumeCommand=\u66f4\u6539\u64ad\u653e\u5668\u97f3\u91cf\u3002\u503c\u70ba 0 helpExportCommand=\u532f\u51fa\u73fe\u5728\u7684\u64ad\u653e\u5e8f\u5217\u5230 wastebin\uff0c\u6b64\u9023\u7d50\u4ee5\u5f8c\u53ef\u4ee5\u505a\u70ba\u64ad\u653e\u6e05\u55ae\u4f7f\u7528\u3002 helpGensokyoRadioCommand=\u986f\u793a\u76ee\u524d\u5728 gensokyoradio.net \u64ad\u653e\u7684\u6b4c\u66f2 helpListCommand=\u986f\u793a\u76ee\u524d\u64ad\u653e\u6e05\u55ae\u7684\u6240\u6709\u6b4c\u66f2 -helpHistoryCommand=\u986f\u793a\u5df2\u7d93\u64a5\u653e\u904e\u7684\u6b4c\u66f2\u6e05\u55ae\u3002 +helpHistoryCommand=\u986f\u793a\u5df2\u7d93\u64ad\u653e\u904e\u7684\u6b4c\u66f2\u6e05\u55ae\u3002 helpNowplayingCommand=\u986f\u793a\u76ee\u524d\u64ad\u653e\u7684\u6b4c\u66f2 helpForwardCommand=\u5c07\u97f3\u8ecc\u5feb\u8f49\u4e00\u6bb5\u6642\u9593\uff0c\u4f8b\u5982\: helpRestartCommand=\u91cd\u65b0\u64ad\u653e\u6b64\u66f2\u76ee @@ -268,12 +273,12 @@ helpInviteCommand=\u8cbc\u4e0a\u6b64Bot\u7684\u9080\u8acb\u9023\u7d50 helpMALCommand=\u641c\u5c0b MyAnimeList \u4e26\u986f\u793a\u4e00\u500b\u52d5\u6f2b\u6216\u4f7f\u7528\u8005\u7684\u8cc7\u8a0a helpMusicHelpCommand=\u986f\u793a\u97f3\u6a02\u6307\u4ee4\u53ca\u7528\u6cd5 helpSayCommand=\u8b93 Bot \u91cd\u8907\u60a8\u8aaa\u7684\u5b57 -helpServerInfoCommand=\u986f\u793a\u6b64\u4f3a\u670d\u5668\u7684\u4e00\u4e9b\u72c0\u614b +helpServerInfoCommand=\u986f\u793a\u6b64\u7fa4\u7d44\u7684\u4e00\u4e9b\u72c0\u614b helpUserInfoCommand=\u986f\u793a\u95dc\u65bc\u60a8\u81ea\u5df1\u6216Bot\u5df2\u77e5\u7684\u4f7f\u7528\u8005\u7684\u8cc7\u8a0a helpPerms=\u6dfb\u52a0\u6216\u79fb\u9664\u4f7f\u7528\u8005\u7684 {0} \u8eab\u5206\u3002 -helpPrefixCommand=\u8a2d\u7f6e\u8a72\u4f3a\u670d\u5668\u7684\u6307\u4ee4\u524d\u7db4\u3002 +helpPrefixCommand=\u8a2d\u7f6e\u8a72\u7fa4\u7d44\u7684\u6307\u4ee4\u524d\u7db4\u3002 helpVoteSkip=\u6295\u7968\u8df3\u904e\u7576\u524d\u7684\u6b4c\u66f2\u3002\u9700\u8981\u7576\u524d\u983b\u9053\u4e2d 50% \u7684\u4f7f\u7528\u8005\u6295\u7968\u3002 -helpUnvoteSkip=Remove your vote to skip the current song. +helpUnvoteSkip=\u5ee2\u9664\u4f60\u70ba\u8df3\u904e\u6b4c\u66f2\u6240\u6295\u7684\u7968\u3002 helpMathOperationAdd=\u986f\u793a num1 \u548c num2 \u7684\u548c\u3002 helpMathOperationSub=\u986f\u793a num1 \u6e1b num2 \u7684\u5dee\u3002 helpMathOperationMult=\u986f\u793a num1\u4e58num2 \u7684\u4e58\u7a4d\u3002 @@ -302,8 +307,8 @@ skipUserMultiple=\u8df3\u904e\u7531{1} \u6dfb\u52a0\u7684{0} \u9996\u6b4c\u66f2\ skipUsersMultiple=\u8df3\u904e\u4e86\u7531{1} \u6dfb\u52a0\u7684{0} \u66f2\u76ee\u3002 skipUserNoTracks=\u8a72\u4f7f\u7528\u8005\u4e26\u6c92\u6709\u6b4c\u66f2\u5728\u5e8f\u5217\u4e2d\u3002 voteSkipAdded=\u4f60\u5df2\u7d93\u6295\u904e\u7968\u4e86\! -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! +voteSkipRemoved=\u60a8\u7684\u6295\u7968\u5df2\u88ab\u5ee2\u9664\u3002 +voteSkipNotFound=\u4f60\u9084\u6c92\u6709\u53c3\u8207\u9019\u6b21\u6295\u7968\u3002 voteSkipAlreadyVoted=\u4f60\u5df2\u7d93\u6295\u904e\u7968\u4e86\! voteSkipSkipping={0} \u7684\u4f7f\u7528\u8005\u6c7a\u5b9a\u8df3\u904e\u66f2\u76ee\uff0c\u6b63\u5728\u8df3\u904e{1}\u3002 voteSkipNotEnough={0} \u5df2\u7d93\u6295\u7968\u8df3\u904e\uff0c\u81f3\u5c11\u9700\u8981 {1} @@ -313,7 +318,7 @@ mathOperationResult=\u7d50\u679c\u662f mathOperationDivisionByZeroError=\u4e0d\u80fd\u9664\u4ee5 0\u3002 mathOperationInfinity=\u6578\u5b57\u904e\u5927\u7121\u6cd5\u986f\u793a\! prefix=\u6307\u4ee4\u524d\u7db4 -prefixGuild=\u9019\u500b\u4f3a\u670d\u5668\u7684\u6307\u4ee4\u524d\u7db4\u662f{0} +prefixGuild=\u9019\u500b\u7fa4\u7d44\u7684\u6307\u4ee4\u524d\u7db4\u662f{0} prefixShowAgain=\u4f60\u96a8\u6642\u53ef\u4ee5\u900f\u904e\u6307\u4ee4\u524d\u7db4\u4f86\u901a\u77e5\u6211(\u9810\u8a2d\: ;;) moduleAdmin=\u7ba1\u7406\u54e1 moduleInfo=\u8cc7\u8a0a @@ -329,9 +334,9 @@ moduleDisable=\u5df2\u505c\u7528\u6a21\u7d44 {0}\u3002 moduleEnable=\u5df2\u555f\u7528 {0} \u6a21\u7d44\u3002 moduleShowCommands=\u7528 {0} \u4f86\u67e5\u770b\u9019\u500b\u6a21\u7d44\u7684\u547d\u4ee4\u3002 modulesCommands=\u7528 {0} \u986f\u793a\u4e00\u500b\u6a21\u7d44\u7684\u547d\u4ee4\uff0c\u6216\u8005 {1} \u986f\u793a\u6240\u6709\u7684\u547d\u4ee4\u3002 -modulesEnabledInGuild=\u70ba\u9019\u500b\u4f3a\u670d\u5668\u555f\u7528\u6a21\u7d44\uff1a +modulesEnabledInGuild=\u70ba\u9019\u500b\u7fa4\u7d44\u555f\u7528\u6a21\u7d44\uff1a modulesHowTo=\u7528 {0} \u555f\u7528/\u7981\u7528\u6a21\u7d44\u3002 parseNotAUser=\u60a8\u8f38\u5165\u7684 {0} \u627e\u4e0d\u5230\u7b26\u5408\u7684Discord\u4f7f\u7528\u8005\u3002 -parseNotAMember=\u4f7f\u7528\u8005 {0} \u4e0d\u662f\u9019\u7684\u4f3a\u670d\u5668\u7684\u6210\u54e1\u3002 +parseNotAMember=\u4f7f\u7528\u8005 {0} \u4e0d\u662f\u9019\u7684\u7fa4\u7d44\u7684\u6210\u54e1\u3002 parseSnowflakeIdHelp=\u627e\u4e0d\u5230\u4f7f\u7528\u8005/\u6d88\u606f/\u983b\u9053/\u5176\u4ed6\u5167\u5bb9\u7684 id \u55ce\uff1f\u77a7\u77a7 {0} \u7684Discord\u6a94\u6848\u5427 From 11197796a202d4f6c8b8aefb7afb40c001812139 Mon Sep 17 00:00:00 2001 From: Nanabell Date: Tue, 2 Apr 2019 17:45:21 +0200 Subject: [PATCH 160/172] Fix GuildPlayer#queue() starting to play track --- .../java/fredboat/audio/player/GuildPlayer.kt | 5 ++--- .../fredboat/audio/player/guildPlayerUtils.kt | 2 +- .../java/fredboat/audio/queue/audioLoading.kt | 21 +++++++++---------- .../command/music/control/SelectCommand.kt | 1 + .../fredboat/testutil/util/playerUtils.kt | 1 + 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index 47db661fa..ab850dfa8 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -229,7 +229,6 @@ class GuildPlayer( queueHandler.add(atc) if (isPlaying) updateClients() - play() } /** Add a bunch of tracks to the track provider */ @@ -249,10 +248,10 @@ class GuildPlayer( return status } - suspend fun queueLimited(tracks: List, isPriority: Boolean): List { + suspend fun queueLimited(tracks: List): List { val states = queueLimiter.isQueueLimited(tracks, this) - audioTrackProvider.addAll(states.filter { it.canQueue }.map { it.atc }) + queueAll(states.filter { it.canQueue }.map { it.atc }) return states } diff --git a/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt b/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt index 3eda78661..3d9ec39af 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt @@ -72,7 +72,7 @@ val GuildPlayer.isHistoryQueueEmpty: Boolean get() = historyQueue.isEmpty() fun GuildPlayer.getUserTrackCount(userId: Long): Int { - var trackCount = audioTrackProvider.getCountByUser(userId) + var trackCount = queueHandler.queue.filter { it.userId == userId }.size if (player.playingTrack != null && internalContext?.userId == userId) trackCount++ return trackCount } diff --git a/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt b/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt index 5c3ed11af..e0f2dcff7 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt @@ -199,20 +199,17 @@ private class ResultHandler(val loader: AudioLoader, val context: IdentifierCont val atc = AudioTrackContext(at, context.member, context.isPriority) GlobalScope.mono { loader.player.queueLimited(atc) }.subscribe { if (it.canQueue) { - context.reply(if (loader.player.trackCount == 1) + context.reply(if (!loader.player.isPlaying) context.i18nFormat("loadSingleTrackAndPlay", TextUtils.escapeAndDefuse(at.info.title)) else context.i18nFormat(if (context.isPriority) "loadSingleTrackFirst" else "loadSingleTrack", TextUtils.escapeAndDefuse(at.info.title)) ) + loader.player.play() } else { context.replyWithMention(it.errorMessage) } } - //FIXME THIS NEEDS TO BE TAKEN A LOOK AT AFTER MERGE - if (!loader.player.isPaused) { - loader.player.play() - } } } catch (th: Throwable) { loader.handleThrowable(context, th) @@ -235,7 +232,7 @@ private class ResultHandler(val loader: AudioLoader, val context: IdentifierCont toAdd.add(AudioPlaylistContext(at, context.member, context.isPriority)) } - GlobalScope.mono { loader.player.queueLimited(toAdd, context.isPriority) }.subscribe { + GlobalScope.mono { loader.player.queueLimited(toAdd) }.subscribe { if (it.isPlaylistDisabledError) { context.replyWithMention(context.i18n(it.playlistDisabledError)) return@subscribe @@ -250,9 +247,7 @@ private class ResultHandler(val loader: AudioLoader, val context: IdentifierCont } if (it.successful.isNotEmpty()) { - if (!loader.player.isPaused) { - loader.player.play() - } + loader.player.play() } } } catch (th: Throwable) { @@ -331,10 +326,10 @@ private class ResultHandler(val loader: AudioLoader, val context: IdentifierCont list.add(atc) } - GlobalScope.mono { loader.player.queueLimited(list, context.isPriority) }.subscribe { + GlobalScope.mono { loader.player.queueLimited(list) }.subscribe { var mb = localMessageBuilder().append(ic.i18n("loadFollowingTracksAdded")).append("\n") - for (atc in it.filter { status -> status.canQueue }.map { status -> status.atc }) { + for (atc in it.filter { status -> status.canQueue }.map { status -> status.atc }) { mb.append("`[") .append(TextUtils.formatTime(atc.effectiveDuration)) .append("]` ") @@ -349,6 +344,10 @@ private class ResultHandler(val loader: AudioLoader, val context: IdentifierCont } context.reply(mb.build()) + + if (it.any { status -> status.canQueue }) { + loader.player.play() + } } } } diff --git a/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.kt index 4abac0ec7..600d70987 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.kt @@ -116,6 +116,7 @@ class SelectCommand(private val videoSelectionCache: VideoSelectionCache, name: outputMsgBuilder.append(msg) player.queueLimited(AudioTrackContext(selectedTracks[i]!!, invoker, selection.isPriority)) + player.play() } videoSelectionCache.remove(invoker) diff --git a/FredBoat/src/test/java/fredboat/testutil/util/playerUtils.kt b/FredBoat/src/test/java/fredboat/testutil/util/playerUtils.kt index a9a3dc3b2..97e6148ab 100644 --- a/FredBoat/src/test/java/fredboat/testutil/util/playerUtils.kt +++ b/FredBoat/src/test/java/fredboat/testutil/util/playerUtils.kt @@ -36,6 +36,7 @@ fun Guild.queue( } player.queue(AudioTrackContext(track, member)) + player.play() return player } From 9d1ded32d0a399941bbc6c0af6286c52b345d8ce Mon Sep 17 00:00:00 2001 From: Marlon Haenen Date: Sun, 7 Apr 2019 14:27:04 +0200 Subject: [PATCH 161/172] Add RoundRobin-On/Off translations --- .../fredboat/command/music/control/RoundRobbinCommand.kt | 6 +++++- FredBoat/src/main/resources/lang/en_US.properties | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/command/music/control/RoundRobbinCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/RoundRobbinCommand.kt index 0bcf9bb16..25f4c21d3 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/RoundRobbinCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/RoundRobbinCommand.kt @@ -1,5 +1,6 @@ package fredboat.command.music.control +import fredboat.commandmeta.CommandInitializer import fredboat.commandmeta.abs.Command import fredboat.commandmeta.abs.CommandContext import fredboat.commandmeta.abs.ICommandRestricted @@ -16,7 +17,10 @@ class RoundRobbinCommand(name: String, vararg aliases: String) : Command(name, * val player = getBotController().playerRegistry.awaitPlayer(context.guild) player.isRoundRobin = !player.isRoundRobin - context.reply(if (player.isRoundRobin) "roundRobinOn" else "roundRobinOff") + context.reply(if (player.isRoundRobin) + context.i18nFormat("roundRobinOn", "`${context.prefix}${CommandInitializer.HELP_COMM_NAME} ${context.trigger}`") + else + context.i18n("roundRobinOff")) } override fun help(context: Context): String { diff --git a/FredBoat/src/main/resources/lang/en_US.properties b/FredBoat/src/main/resources/lang/en_US.properties index e25e3c84c..a1c2b74e8 100644 --- a/FredBoat/src/main/resources/lang/en_US.properties +++ b/FredBoat/src/main/resources/lang/en_US.properties @@ -21,9 +21,9 @@ shuffleOn=The player is now shuffled. shuffleOff=The player is no longer shuffled. reshufflePlaylist=Queue reshuffled. reshufflePlayerNotShuffling=You must first turn on shuffle mode. -roundRobinOn=The player is now in Round-Robin mode. -roundRobinOff=The player is no longer in Round-Robin mode. -listShowRoundRobin=Showing round robin playlist. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing Round-Robin playlist. skipEmpty=The queue is empty\! skipOutOfBounds=Can''t remove track number {0} when there are only {1} tracks. skipNumberTooLow=Given number must be greater than 0. From f5d8907bcce4f022cb385a0eb690fb25097609e1 Mon Sep 17 00:00:00 2001 From: Marlon Haenen Date: Sun, 7 Apr 2019 14:27:35 +0200 Subject: [PATCH 162/172] Add Add help Translation String --- .../java/fredboat/command/music/control/RoundRobbinCommand.kt | 2 +- FredBoat/src/main/resources/lang/en_US.properties | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/FredBoat/src/main/java/fredboat/command/music/control/RoundRobbinCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/RoundRobbinCommand.kt index 25f4c21d3..51e420ba2 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/RoundRobbinCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/RoundRobbinCommand.kt @@ -24,6 +24,6 @@ class RoundRobbinCommand(name: String, vararg aliases: String) : Command(name, * } override fun help(context: Context): String { - return "{0}{1}\n# " + context.i18n("helpRoundRobinCommand") + return "{0}{1}\n#" + context.i18n("helpRoundRobinCommand") } } \ No newline at end of file diff --git a/FredBoat/src/main/resources/lang/en_US.properties b/FredBoat/src/main/resources/lang/en_US.properties index a1c2b74e8..af3ad8f42 100644 --- a/FredBoat/src/main/resources/lang/en_US.properties +++ b/FredBoat/src/main/resources/lang/en_US.properties @@ -255,6 +255,7 @@ helpRepeatCommand=Toggle between repeat modes. helpReshuffleCommand=Reshuffle the current queue. helpSelectCommand=Select one of the offered tracks after a search to play. helpShuffleCommand=Toggle shuffle mode for the current queue. +helpRoundRobinCommand=Toggle "Round Robin" / "Fair Queue" mode.\n#In this mode the queue will be sorted into rounds whereby it is guaranteed that every unique users can place once per round. helpSkipCommand=Skip the current song, the n\'th song in the queue, all songs from n to m, or all songs from mentioned users. Please use in moderation. helpStopCommand=Stop the player and clear the playlist. Reserved for moderators with Manage Messages permission. helpUnpauseCommand=Unpause the player. From 37b448282afc6fbb481378332058a05ab08277a1 Mon Sep 17 00:00:00 2001 From: "Frederik Ar. Mikkelsen" Date: Wed, 1 May 2019 10:11:36 +0200 Subject: [PATCH 163/172] Fix compile error --- .../main/java/fredboat/command/music/control/ReplayCommand.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FredBoat/src/main/java/fredboat/command/music/control/ReplayCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/ReplayCommand.kt index 4a50da214..a30d97413 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/ReplayCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/ReplayCommand.kt @@ -36,7 +36,7 @@ class ReplayCommand(name: String, vararg aliases: String) : Command(name, *alias player.skip() } - player.loadAll(toQueue, true) + player.queueAll(toQueue) context.reply(context.i18nFormat("replayWillNowReplay", "**${last.effectiveTitle.escapeAndDefuse()}**")) } From afeb3622ff7f3a059914c5e9a36ff1f5bad59f4b Mon Sep 17 00:00:00 2001 From: "Frederik Ar. Mikkelsen" Date: Wed, 1 May 2019 10:17:46 +0200 Subject: [PATCH 164/172] Source string tweaks --- FredBoat/src/main/resources/lang/en_US.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FredBoat/src/main/resources/lang/en_US.properties b/FredBoat/src/main/resources/lang/en_US.properties index c470bb2fb..d4a7f172b 100644 --- a/FredBoat/src/main/resources/lang/en_US.properties +++ b/FredBoat/src/main/resources/lang/en_US.properties @@ -8,7 +8,7 @@ playYoutubeSearchError=An error occurred when searching YouTube. Consider linkin playSearchNoResults=No results for `{q}` playSelectVideo=**Please select a track with the `{0}play 1-5` command\:** replayWillNowReplay=Track {0} will now be replayed. -replayHistoryEmpty=No Track in History to replay! +replayHistoryEmpty=There is no track in history to replay. joinJoining=Joining {0} joinErrorAlreadyJoining=An error occurred. Couldn''t join {0} because I am already trying to connect to that channel. Please try again. pauseAlreadyPaused=The player is already paused. @@ -248,7 +248,7 @@ helpJoinCommand=Make the bot join your current voice channel. helpLeaveCommand=Make the bot leave the current voice channel. helpPauseCommand=Pause the player. helpPlayCommand=Play music from the given URL or search for a track. For a full list of sources please visit {0} -helpReplayCommand=Replay the most recent track in the history. +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Split a YouTube video into a tracklist provided in it\'s description. helpRepeatCommand=Toggle between repeat modes. From 450cf6d16b0f25f833ceb7c1a902c86a91072fc2 Mon Sep 17 00:00:00 2001 From: "Frederik Ar. Mikkelsen" Date: Wed, 1 May 2019 10:51:50 +0200 Subject: [PATCH 165/172] =?UTF-8?q?Rename=20=E2=80=9Ctbd=E2=80=9D=20packag?= =?UTF-8?q?e=20to=20=E2=80=9Chandlers=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt | 2 +- .../src/main/java/fredboat/audio/player/guildPlayerUtils.kt | 2 +- FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt | 2 +- .../fredboat/audio/queue/{tbd => handlers}/IQueueHandler.kt | 2 +- .../audio/queue/{tbd => handlers}/IRepeatableQueueHandler.kt | 2 +- .../audio/queue/{tbd => handlers}/IRoundRobinQueueHandler.kt | 2 +- .../audio/queue/{tbd => handlers}/IShufflableQueueHandler.kt | 2 +- .../audio/queue/{tbd => handlers}/RepeatableQueueHandler.kt | 2 +- .../audio/queue/{tbd => handlers}/RoundRobinQueueHandler.kt | 2 +- .../audio/queue/{tbd => handlers}/ShufflableQueueHandler.kt | 2 +- .../audio/queue/{tbd => handlers}/SimpleQueueHandler.kt | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) rename FredBoat/src/main/java/fredboat/audio/queue/{tbd => handlers}/IQueueHandler.kt (98%) rename FredBoat/src/main/java/fredboat/audio/queue/{tbd => handlers}/IRepeatableQueueHandler.kt (85%) rename FredBoat/src/main/java/fredboat/audio/queue/{tbd => handlers}/IRoundRobinQueueHandler.kt (87%) rename FredBoat/src/main/java/fredboat/audio/queue/{tbd => handlers}/IShufflableQueueHandler.kt (87%) rename FredBoat/src/main/java/fredboat/audio/queue/{tbd => handlers}/RepeatableQueueHandler.kt (96%) rename FredBoat/src/main/java/fredboat/audio/queue/{tbd => handlers}/RoundRobinQueueHandler.kt (99%) rename FredBoat/src/main/java/fredboat/audio/queue/{tbd => handlers}/ShufflableQueueHandler.kt (98%) rename FredBoat/src/main/java/fredboat/audio/queue/{tbd => handlers}/SimpleQueueHandler.kt (97%) diff --git a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt index ab850dfa8..d6b7ac5db 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -34,7 +34,7 @@ import fredboat.audio.lavalink.SentinelLink import fredboat.audio.queue.* import fredboat.audio.queue.limiter.QueueLimitStatus import fredboat.audio.queue.limiter.QueueLimiter -import fredboat.audio.queue.tbd.* +import fredboat.audio.queue.handlers.* import fredboat.command.music.control.VoteSkipCommand import fredboat.commandmeta.MessagingException import fredboat.commandmeta.abs.CommandContext diff --git a/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt b/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt index 3d9ec39af..d934c20c5 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt @@ -3,7 +3,7 @@ package fredboat.audio.player import com.fredboat.sentinel.entities.ShardStatus import com.google.common.collect.Lists import fredboat.audio.queue.AudioTrackContext -import fredboat.audio.queue.tbd.IQueueHandler +import fredboat.audio.queue.handlers.IQueueHandler import fredboat.commandmeta.MessagingException import fredboat.commandmeta.abs.CommandContext import fredboat.definitions.PermissionLevel diff --git a/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt b/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt index e0f2dcff7..49d10415d 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt @@ -37,7 +37,7 @@ import fredboat.audio.queue.limiter.errored import fredboat.audio.queue.limiter.isPlaylistDisabledError import fredboat.audio.queue.limiter.playlistDisabledError import fredboat.audio.queue.limiter.successful -import fredboat.audio.queue.tbd.IQueueHandler +import fredboat.audio.queue.handlers.IQueueHandler import fredboat.audio.source.PlaylistImportSourceManager import fredboat.audio.source.PlaylistImporter import fredboat.audio.source.SpotifyPlaylistSourceManager diff --git a/FredBoat/src/main/java/fredboat/audio/queue/tbd/IQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/handlers/IQueueHandler.kt similarity index 98% rename from FredBoat/src/main/java/fredboat/audio/queue/tbd/IQueueHandler.kt rename to FredBoat/src/main/java/fredboat/audio/queue/handlers/IQueueHandler.kt index e205d0275..0c11ab875 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/tbd/IQueueHandler.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/handlers/IQueueHandler.kt @@ -1,4 +1,4 @@ -package fredboat.audio.queue.tbd +package fredboat.audio.queue.handlers import fredboat.audio.queue.AudioTrackContext import org.bson.types.ObjectId diff --git a/FredBoat/src/main/java/fredboat/audio/queue/tbd/IRepeatableQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/handlers/IRepeatableQueueHandler.kt similarity index 85% rename from FredBoat/src/main/java/fredboat/audio/queue/tbd/IRepeatableQueueHandler.kt rename to FredBoat/src/main/java/fredboat/audio/queue/handlers/IRepeatableQueueHandler.kt index 0c3d7fb19..dda49bf5d 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/tbd/IRepeatableQueueHandler.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/handlers/IRepeatableQueueHandler.kt @@ -1,4 +1,4 @@ -package fredboat.audio.queue.tbd +package fredboat.audio.queue.handlers import fredboat.audio.queue.AudioTrackContext import fredboat.definitions.RepeatMode diff --git a/FredBoat/src/main/java/fredboat/audio/queue/tbd/IRoundRobinQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/handlers/IRoundRobinQueueHandler.kt similarity index 87% rename from FredBoat/src/main/java/fredboat/audio/queue/tbd/IRoundRobinQueueHandler.kt rename to FredBoat/src/main/java/fredboat/audio/queue/handlers/IRoundRobinQueueHandler.kt index 6023ea793..e605457e5 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/tbd/IRoundRobinQueueHandler.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/handlers/IRoundRobinQueueHandler.kt @@ -1,4 +1,4 @@ -package fredboat.audio.queue.tbd +package fredboat.audio.queue.handlers import fredboat.audio.queue.AudioTrackContext import java.util.concurrent.ConcurrentLinkedDeque diff --git a/FredBoat/src/main/java/fredboat/audio/queue/tbd/IShufflableQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/handlers/IShufflableQueueHandler.kt similarity index 87% rename from FredBoat/src/main/java/fredboat/audio/queue/tbd/IShufflableQueueHandler.kt rename to FredBoat/src/main/java/fredboat/audio/queue/handlers/IShufflableQueueHandler.kt index 196db5b7c..2c8387c66 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/tbd/IShufflableQueueHandler.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/handlers/IShufflableQueueHandler.kt @@ -1,4 +1,4 @@ -package fredboat.audio.queue.tbd +package fredboat.audio.queue.handlers import fredboat.audio.queue.AudioTrackContext import java.util.concurrent.ConcurrentLinkedDeque diff --git a/FredBoat/src/main/java/fredboat/audio/queue/tbd/RepeatableQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/handlers/RepeatableQueueHandler.kt similarity index 96% rename from FredBoat/src/main/java/fredboat/audio/queue/tbd/RepeatableQueueHandler.kt rename to FredBoat/src/main/java/fredboat/audio/queue/handlers/RepeatableQueueHandler.kt index 8e2fa545b..d2e41f7d5 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/tbd/RepeatableQueueHandler.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/handlers/RepeatableQueueHandler.kt @@ -1,4 +1,4 @@ -package fredboat.audio.queue.tbd +package fredboat.audio.queue.handlers import fredboat.audio.player.GuildPlayer import fredboat.audio.queue.AudioTrackContext diff --git a/FredBoat/src/main/java/fredboat/audio/queue/tbd/RoundRobinQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/handlers/RoundRobinQueueHandler.kt similarity index 99% rename from FredBoat/src/main/java/fredboat/audio/queue/tbd/RoundRobinQueueHandler.kt rename to FredBoat/src/main/java/fredboat/audio/queue/handlers/RoundRobinQueueHandler.kt index adf06891c..9ae7fcb35 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/tbd/RoundRobinQueueHandler.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/handlers/RoundRobinQueueHandler.kt @@ -1,4 +1,4 @@ -package fredboat.audio.queue.tbd +package fredboat.audio.queue.handlers import com.google.common.collect.Iterators import fredboat.audio.player.GuildPlayer diff --git a/FredBoat/src/main/java/fredboat/audio/queue/tbd/ShufflableQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/handlers/ShufflableQueueHandler.kt similarity index 98% rename from FredBoat/src/main/java/fredboat/audio/queue/tbd/ShufflableQueueHandler.kt rename to FredBoat/src/main/java/fredboat/audio/queue/handlers/ShufflableQueueHandler.kt index b00f3f9da..86232fa10 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/tbd/ShufflableQueueHandler.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/handlers/ShufflableQueueHandler.kt @@ -1,4 +1,4 @@ -package fredboat.audio.queue.tbd +package fredboat.audio.queue.handlers import fredboat.audio.queue.AudioTrackContext import java.util.* diff --git a/FredBoat/src/main/java/fredboat/audio/queue/tbd/SimpleQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/handlers/SimpleQueueHandler.kt similarity index 97% rename from FredBoat/src/main/java/fredboat/audio/queue/tbd/SimpleQueueHandler.kt rename to FredBoat/src/main/java/fredboat/audio/queue/handlers/SimpleQueueHandler.kt index 68f34c645..cae6901d3 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/tbd/SimpleQueueHandler.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/handlers/SimpleQueueHandler.kt @@ -1,4 +1,4 @@ -package fredboat.audio.queue.tbd +package fredboat.audio.queue.handlers import fredboat.audio.queue.AudioTrackContext import org.slf4j.Logger From 941899b63ec3353211d954520189bf5e1dfc0d19 Mon Sep 17 00:00:00 2001 From: "Frederik Ar. Mikkelsen" Date: Wed, 1 May 2019 11:08:39 +0200 Subject: [PATCH 166/172] RR queue source string changes --- FredBoat/src/main/resources/lang/en_US.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FredBoat/src/main/resources/lang/en_US.properties b/FredBoat/src/main/resources/lang/en_US.properties index 540132cb1..96a181f8a 100644 --- a/FredBoat/src/main/resources/lang/en_US.properties +++ b/FredBoat/src/main/resources/lang/en_US.properties @@ -25,7 +25,7 @@ reshufflePlaylist=Queue reshuffled. reshufflePlayerNotShuffling=You must first turn on shuffle mode. roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. -listShowRoundRobin=Showing Round-Robin playlist. +listShowRoundRobin=Showing fair queue. skipEmpty=The queue is empty\! skipOutOfBounds=Can''t remove track number {0} when there are only {1} tracks. skipNumberTooLow=Given number must be greater than 0. @@ -258,7 +258,7 @@ helpRepeatCommand=Toggle between repeat modes. helpReshuffleCommand=Reshuffle the current queue. helpSelectCommand=Select one of the offered tracks after a search to play. helpShuffleCommand=Toggle shuffle mode for the current queue. -helpRoundRobinCommand=Toggle "Round Robin" / "Fair Queue" mode.\n#In this mode the queue will be sorted into rounds whereby it is guaranteed that every unique users can place once per round. +helpRoundRobinCommand=Toggle ”Fair Queue“ (Round Robin) mode.\n#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Skip the current song, the n\'th song in the queue, all songs from n to m, or all songs from mentioned users. Please use in moderation. helpStopCommand=Stop the player and clear the playlist. Reserved for moderators with Manage Messages permission. helpUnpauseCommand=Unpause the player. From 5cfe879a95562484661ff480b955eddd316f7092 Mon Sep 17 00:00:00 2001 From: "Frederik Ar. Mikkelsen" Date: Wed, 1 May 2019 11:24:20 +0200 Subject: [PATCH 167/172] Update translations --- .../src/main/resources/lang/af_ZA.properties | 8 +- .../src/main/resources/lang/ar_SA.properties | 12 +- .../src/main/resources/lang/ast_ES.properties | 8 +- .../src/main/resources/lang/bg_BG.properties | 8 +- .../src/main/resources/lang/bn_BD.properties | 8 +- .../src/main/resources/lang/ca_ES.properties | 36 +- .../src/main/resources/lang/ceb_PH.properties | 8 +- .../src/main/resources/lang/cs_CZ.properties | 12 +- .../src/main/resources/lang/cy_GB.properties | 8 +- .../src/main/resources/lang/da_DK.properties | 8 +- .../src/main/resources/lang/de_DE.properties | 18 +- .../src/main/resources/lang/el_GR.properties | 8 +- .../src/main/resources/lang/en_PT.properties | 8 +- .../src/main/resources/lang/en_TS.properties | 8 +- .../src/main/resources/lang/es_ES.properties | 22 +- .../src/main/resources/lang/et_EE.properties | 8 +- .../src/main/resources/lang/fa_IR.properties | 8 +- .../src/main/resources/lang/fi_FI.properties | 8 +- .../src/main/resources/lang/fil_PH.properties | 8 +- .../src/main/resources/lang/fr_FR.properties | 22 +- .../src/main/resources/lang/fr_TS.properties | 78 ++-- .../src/main/resources/lang/ga_IE.properties | 8 +- .../src/main/resources/lang/he_IL.properties | 8 +- .../src/main/resources/lang/hr_HR.properties | 8 +- .../src/main/resources/lang/hu_HU.properties | 30 +- .../src/main/resources/lang/id_ID.properties | 8 +- .../src/main/resources/lang/it_IT.properties | 20 +- .../src/main/resources/lang/ja_JP.properties | 8 +- .../src/main/resources/lang/ko_KR.properties | 12 +- .../src/main/resources/lang/ms_MY.properties | 8 +- .../src/main/resources/lang/nl_NL.properties | 20 +- .../src/main/resources/lang/no_NO.properties | 8 +- .../src/main/resources/lang/pl_PL.properties | 26 +- .../src/main/resources/lang/pt_BR.properties | 20 +- .../src/main/resources/lang/pt_PT.properties | 8 +- .../src/main/resources/lang/ro_RO.properties | 20 +- .../src/main/resources/lang/ru_RU.properties | 22 +- .../src/main/resources/lang/sk_SK.properties | 8 +- .../src/main/resources/lang/sq_AL.properties | 348 ++++++++++++++++++ .../src/main/resources/lang/sr_SP.properties | 10 +- .../src/main/resources/lang/sv_SE.properties | 8 +- .../src/main/resources/lang/th_TH.properties | 8 +- .../src/main/resources/lang/tr_TR.properties | 18 +- .../src/main/resources/lang/uk_UA.properties | 130 +++---- .../src/main/resources/lang/vi_VN.properties | 8 +- .../src/main/resources/lang/yo_NG.properties | 8 +- .../src/main/resources/lang/zh_CN.properties | 8 +- .../src/main/resources/lang/zh_TW.properties | 10 +- 48 files changed, 870 insertions(+), 240 deletions(-) create mode 100644 FredBoat/src/main/resources/lang/sq_AL.properties diff --git a/FredBoat/src/main/resources/lang/af_ZA.properties b/FredBoat/src/main/resources/lang/af_ZA.properties index a69f30971..ea72fbb93 100644 --- a/FredBoat/src/main/resources/lang/af_ZA.properties +++ b/FredBoat/src/main/resources/lang/af_ZA.properties @@ -7,6 +7,8 @@ playSearching=Soek YouTube vir die volgende ''{q}''... playYoutubeSearchError='n Fout het voorgekom in YouTube se soek tog. Oorweeg om eerder 'n direkte web band met audio-bronne te gebruik. \n```\n;;play ``` playSearchNoResults=Geen resultate vir ''{q}'' playSelectVideo=* * Kies ''n lied nommer met die ''{0}play n` opdrag\: * * +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Aansluiting by {0} joinErrorAlreadyJoining=''n Fout het voorgekom. Kon nie aansluit by {0} omdat ek reeds probeer koppel aan daardie kanaal. Probeer asseblief weer. pauseAlreadyPaused=Die speler is reeds op breek gesit. @@ -21,6 +23,9 @@ shuffleOn=Die speler is nou geskommeld. shuffleOff=Die speler is nie meer geskommeld nie. reshufflePlaylist=Reeks is weer geskommel. reshufflePlayerNotShuffling=Jy moet eers die skommel mode aanskakel. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=Die reeks is leeg\! skipOutOfBounds=Kan nie die lied nommer verwyder nie {0} wanneer daar slegs {1} snitte. skipNumberTooLow=Gegewe getal moet groter wees as 0. @@ -244,12 +249,14 @@ helpJoinCommand=Maak die bot saam met jou huidige stem kanaal. helpLeaveCommand=Maak die bot laat die huidige stem kanaal. helpPauseCommand=Laat die speler wag. helpPlayCommand=Play music from the given URL or search for a track. For a full list of sources please visit {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Verdeel 'n YouTube video in 'n tracklist voorsien in dit se beskrywing. helpRepeatCommand=Wissel tussen herhaal modes. helpReshuffleCommand=Reshuffle die huidige waglys. helpSelectCommand=Kies een van die aangebied snitte na 'n soektog om te speel. helpShuffleCommand=Wissel skommel modus vir die huidige waglys. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Skip the current song, the n'th song in the queue, all songs from n to m, or all songs from mentioned users. Please use in moderation. helpStopCommand=Stop die speler en duidelik die speellys. Gereserveer vir moderators met bestuur boodskappe toestemming. helpUnpauseCommand=Unpause die speler. @@ -339,4 +346,3 @@ modulesHowTo=Say {0} to enable/disable modules. parseNotAUser=Your input {0} did not yield a Discord user. parseNotAMember=User {0} is not a member of this guild. parseSnowflakeIdHelp=Having trouble getting the id of a user/message/channel/something else? Check out the Discord docs at {0} - diff --git a/FredBoat/src/main/resources/lang/ar_SA.properties b/FredBoat/src/main/resources/lang/ar_SA.properties index 63f7cff59..b01d872df 100644 --- a/FredBoat/src/main/resources/lang/ar_SA.properties +++ b/FredBoat/src/main/resources/lang/ar_SA.properties @@ -7,6 +7,8 @@ playSearching=\u0627\u0644\u0628\u062d\u062b \u0641\u064a \u064a\u0648\u062a\u06 playYoutubeSearchError=\u062d\u062f\u062b \u062e\u0637\u0623 \u0639\u0646\u062f \u0627\u0644\u0628\u062d\u062b \u0641\u064a \u064a\u0648\u062a\u064a\u0648\u0628\u060c \u0627\u0633\u062a\u062e\u062f\u0645 \u061b\u061bplay <\u0627\u0644\u0631\u0627\u0628\u0637> \u0628\u062f\u0644\u0627 \u0645\u0646 \u0630\u0644\u0643 playSearchNoResults=\u0644\u0627 \u062a\u0648\u062c\u062f \u0646\u062a\u0627\u0626\u062c \u0644 ''{q}'' playSelectVideo=* * \u0627\u0644\u0631\u062c\u0627\u0621 \u062a\u062d\u062f\u064a\u062f \u0627\u0644\u0645\u0631\u0627\u062f \u0628\u0627\u0633\u062a\u062e\u062f\u0627\u0645 \u0627\u0644\u0623\u0645\u0631`{0}play 1-5`\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=\u0627\u0644\u0625\u0646\u0636\u0645\u0627\u0645 \u0627\u0644\u0649 {0} joinErrorAlreadyJoining=\u062d\u062f\u062b \u062e\u0637\u0623. \u0644\u0645 \u0623\u0633\u062a\u0637\u0639 \u0627\u0644\u0627\u0646\u0636\u0645\u0627\u0645 \u0625\u0644\u0649 {0} \u0644\u0623\u0646\u0646\u064a \u0623\u062d\u0627\u0648\u0644 \u0627\u0644\u0627\u062a\u0635\u0627\u0644 \u0628\u0647\u0630\u0647 \u0627\u0644\u0642\u0646\u0627\u0629 \u0641\u064a \u0627\u0644\u0648\u0642\u062a \u0627\u0644\u062d\u0627\u0644\u064a. \u0627\u0644\u0631\u062c\u0627\u0621 \u0627\u0644\u0645\u062d\u0627\u0648\u0644\u0629 \u0645\u0631\u0629 \u0623\u062e\u0631\u0649. pauseAlreadyPaused=\u0627\u0644\u0627\u0639\u0628 \u0645\u062a\u0648\u0642\u0641 \u062d\u0627\u0644\u064a\u0627. @@ -21,6 +23,9 @@ shuffleOn=\u062a\u0645 \u062e\u0644\u0637 \u0627\u0644\u0645\u0634\u063a\u0644. shuffleOff=\u0644\u0645 \u064a\u0639\u062f \u0627\u0644\u0645\u0634\u063a\u0644 \u0645\u062e\u0644\u0648\u0637. reshufflePlaylist=\u0642\u0627\u0626\u0645\u0629 \u0627\u0644\u0627\u0646\u062a\u0638\u0627\u0631 \u0642\u062f \u062a\u0645 \u0625\u0639\u0627\u062f\u0629 \u062e\u0644\u0637\u0647\u0627. reshufflePlayerNotShuffling=\u064a\u062c\u0628 \u0623\u0648\u0644\u0627\u064b \u062a\u0634\u063a\u064a\u0644 \u0648\u0636\u0639 \u0627\u0644\u062e\u0644\u0637. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=\u0642\u0627\u0626\u0645\u0629 \u0627\u0644\u0627\u0646\u062a\u0638\u0627\u0631 \u0641\u0627\u0631\u063a\u0629\! skipOutOfBounds=\u0644\u0627 \u064a\u0645\u0643\u0646 \u0625\u0632\u0627\u0644\u0629 \u0627\u0644\u0645\u0633\u0627\u0631 \u0631\u0642\u0645 {0} \u0639\u0646\u062f\u0645\u0627 \u064a\u0643\u0648\u0646 \u0647\u0646\u0627\u0643 \u0645\u0633\u0627\u0631{1} \u0641\u0642\u0637 . skipNumberTooLow=\u0627\u0644\u0639\u062f\u062f \u0627\u0644\u0645\u0639\u0637\u0649 \u064a\u062c\u0628 \u0623\u0646 \u064a\u0643\u0648\u0646 \u0623\u0643\u0628\u0631 \u0645\u0646 0. @@ -62,7 +67,7 @@ npDescription=\u0627\u0644\u0648\u0635\u0641 npLoadedSoundcloud=[{0}/{1}] \u062a\u062d\u0645\u064a\u0644 \u0645\u0646 \u0633\u0648\u0646\u062f\u0643\u0644\u0648\u062f npLoadedBandcamp={0} \u062a\u062d\u0645\u064a\u0644 \u0645\u0646 Bandcamp npLoadedTwitch=\u062a\u0645 \u062a\u062d\u0645\u064a\u0644\u0647 \u0645\u0646 \u0646\u0634\u0644 -npRequestedBy=Requested by {0} +npRequestedBy=Requested by {0}\n permissionMissingBot=\u0623\u0646\u0627 \u0628\u062d\u0627\u062c\u0629 \u0625\u0644\u0649 \u0625\u0630\u0646 \u0627\u0644\u062a\u0627\u0644\u064a\u0629 \u0644\u0644\u0642\u064a\u0627\u0645 \u0628\u0647\u0630\u0627 \u0627\u0644\u0625\u062c\u0631\u0627\u0621\: permissionMissingInvoker=\u0623\u0646\u0627 \u0628\u062d\u0627\u062c\u0629 \u0625\u0644\u0649 \u0625\u0630\u0646 \u0627\u0644\u062a\u0627\u0644\u064a\u0629 \u0644\u0644\u0642\u064a\u0627\u0645 \u0628\u0647\u0630\u0627 \u0627\u0644\u0625\u062c\u0631\u0627\u0621\: i need this comment to do this action\: permissionEmbedLinks=\u062a\u0636\u0645\u064a\u0646 \u0627\u0644\u0631\u0627\u0628\u0637 @@ -92,7 +97,7 @@ loadPlaylistTooMany=\u0648\u0623\u0636\u0627\u0641 {0} \u0627\u0644\u0645\u0633\ loadErrorCommon=\u062d\u062f\u062b \u062e\u0637\u0623 \u0623\u062b\u0646\u0627\u0621 \u062a\u062d\u0645\u064a\u0644 \u0645\u0639\u0644\u0648\u0645\u0627\u062a \u0639\u0646 ''{0}''\:{1} loadErrorSusp=\u0627\u0644\u0645\u0634\u0628\u0648\u0647\u0629 \u0645\u0646 \u0627\u0644\u062e\u0637\u0623 \u0639\u0646\u062f \u062a\u062d\u0645\u064a\u0644 \u0645\u0639\u0644\u0648\u0645\u0627\u062a \u0639\u0646 ''{0}''. loadQueueTrackLimit=\u0644\u0627 \u064a\u0645\u0643\u0646\u0643 \u0625\u0636\u0627\u0641\u0629 \u0645\u0633\u0627\u0631\u0627\u062a \u0625\u0644\u0649 \u0642\u0627\u0626\u0645\u0629 \u0627\u0646\u062a\u0638\u0627\u0631 \u0645\u0639 \u0623\u0643\u062b\u0631 \u0645\u0646 \u0627\u0644\u0645\u0633\u0627\u0631\u0627\u062a {0}\! \u0648\u0647\u0630\u0627 \u0644\u0645\u0646\u0639 \u0627\u0644\u0627\u0639\u062a\u062f\u0627\u0621. -loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadPlaylistDisabled=\u0647\u0630\u0627 \u0627\u0644\u0645\u0644\u0642\u0645 \u0628\u062a\u0639\u0637\u064a\u0644 \u0625\u0639\u062f\u0627\u062f \u0642\u0648\u0627\u0626\u0645 \u0627\u0644\u062a\u0634\u063a\u064a\u0644 \u0641\u064a \u0642\u0627\u0626\u0645\u0629 \u0627\u0646\u062a\u0638\u0627\u0631. \u0627\u0644\u0631\u062c\u0627\u0621 \u0627\u0644\u0627\u0646\u062a\u0638\u0627\u0631 \u0639\u0646 \u0643\u0644 \u0645\u0633\u0627\u0631 \u0639\u0644\u0649 \u062d\u062f\u0629. loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. @@ -244,12 +249,14 @@ helpJoinCommand=\u062c\u0639\u0644 \u0628\u0648\u062a \u0627\u0644\u0627\u0646\u helpLeaveCommand=\u062c\u0639\u0644 \u0628\u0648\u062a \u0645\u063a\u0627\u062f\u0631\u0629 \u0627\u0644\u0642\u0646\u0627\u0629 \u0627\u0644\u0635\u0648\u062a\u064a\u0629 \u0627\u0644\u062d\u0627\u0644\u064a\u0629. helpPauseCommand=\u0625\u064a\u0642\u0627\u0641 \u0627\u0644\u0644\u0627\u0639\u0628. helpPlayCommand=\u062a\u0634\u063a\u064a\u0644 \u0627\u0644\u0645\u0648\u0633\u064a\u0642\u0649 \u0645\u0646 \u0639\u0646\u0648\u0627\u0646 URL \u0645\u0639\u064a\u0646 \u0623\u0648 \u0627\u0644\u0628\u062d\u062b \u0639\u0646 \u0645\u0633\u0627\u0631. \u0644\u0644\u062d\u0635\u0648\u0644 \u0639\u0644\u0649 \u0642\u0627\u0626\u0645\u0629 \u0643\u0627\u0645\u0644\u0629 \u0645\u0646 \u0627\u0644\u0645\u0635\u0627\u062f\u0631 \u064a\u0631\u062c\u0649 \u0632\u064a\u0627\u0631\u0629 +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=\u0645\u0648\u0633\u064a\u0642\u0649 helpPlaySplitCommand=\u062a\u0642\u0633\u064a\u0645 \u0641\u064a\u062f\u064a\u0648 \u064a\u0648\u062a\u064a\u0648\u0628 \u0625\u0644\u0649 \u0627\u0644\u062a\u0633\u0637\u064a\u0628 \u0627\u0644\u0645\u0646\u0635\u0648\u0635 \u0639\u0644\u064a\u0647\u0627 \u0641\u064a \u0648\u0635\u0641 \u0623\u0646\u0647\u0627. helpRepeatCommand=\u0627\u0644\u062a\u0628\u062f\u064a\u0644 \u0628\u064a\u0646 \u0648\u0636\u0639\u064a \u062a\u0643\u0631\u0627\u0631. helpReshuffleCommand=\u062a\u0639\u062f\u064a\u0644 \u0642\u0627\u0626\u0645\u0629 \u0627\u0644\u0627\u0646\u062a\u0638\u0627\u0631 \u0627\u0644\u062d\u0627\u0644\u064a\u0629. helpSelectCommand=\u062d\u062f\u062f \u0623\u062d\u062f \u0627\u0644\u0645\u0633\u0627\u0631\u0627\u062a \u0627\u0644\u0645\u0642\u062f\u0645\u0629 \u0628\u0639\u062f \u0628\u062d\u062b \u0644\u0644\u0639\u0628. helpShuffleCommand=\u062a\u0628\u062f\u064a\u0644 \u0648\u0636\u0639 \u0627\u0644\u0645\u0631\u0627\u0648\u063a\u0629 \u0644\u0642\u0627\u0626\u0645\u0629 \u0627\u0644\u0627\u0646\u062a\u0638\u0627\u0631 \u0627\u0644\u062d\u0627\u0644\u064a\u0629. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=\u062a\u062e\u0637\u064a \u0627\u0644\u0623\u063a\u0646\u064a\u0629 \u0627\u0644\u062d\u0627\u0644\u064a\u0629\u060c \u0623\u063a\u0646\u064a\u0629 n&\#39; th \u0641\u064a \u0642\u0627\u0626\u0645\u0629 \u0627\u0644\u0627\u0646\u062a\u0638\u0627\u0631\u060c \u062c\u0645\u064a\u0639 \u0627\u0644\u0623\u063a\u0627\u0646\u064a \u0645\u0646 n \u0625\u0644\u0649 m\u060c \u0623\u0648 \u062c\u0645\u064a\u0639 \u0627\u0644\u0623\u063a\u0627\u0646\u064a \u0645\u0646 \u0627\u0644\u0645\u0633\u062a\u062e\u062f\u0645\u064a\u0646 \u0627\u0644\u0645\u0630\u0643\u0648\u0631\u0629. \u0627\u0644\u0631\u062c\u0627\u0621 \u0627\u0633\u062a\u062e\u062f\u0627\u0645 \u0644\u0644\u0627\u0639\u062a\u062f\u0627\u0644. helpStopCommand=\u0625\u064a\u0642\u0627\u0641 \u0627\u0644\u0644\u0627\u0639\u0628 \u0648\u0645\u0633\u062d \u0642\u0627\u0626\u0645\u0629 \u0627\u0644\u062a\u0634\u063a\u064a\u0644. \u0645\u062d\u0641\u0648\u0638\u0629 \u0644\u0644\u0645\u0634\u0631\u0641\u064a\u0646 \u0645\u0639 "\u0625\u062f\u0627\u0631\u0629 \u0631\u0633\u0627\u0626\u0644" \u0627\u0644\u0625\u0630\u0646. helpUnpauseCommand=\u0625\u0644\u063a\u0627\u0621 \u0627\u0644\u0625\u064a\u0642\u0627\u0641 \u0627\u0644\u0645\u0624\u0642\u062a \u0644\u0644\u0627\u0639\u0628. @@ -339,4 +346,3 @@ modulesHowTo=\u0642\u0644 {0} \u0644\u062a\u0645\u0643\u064a\u0646 \u0623\u0648 parseNotAUser=\u0648\u0644\u0645 \u062a\u0633\u0641\u0631 \u0639\u0646 \u0627\u0644\u0625\u062f\u062e\u0627\u0644 \u0627\u0644\u062e\u0627\u0635 \u0628\u0643 {0} \u0645\u0633\u062a\u062e\u062f\u0645 \u0634\u0642\u0627\u0642. parseNotAMember=\u0627\u0644\u0645\u0633\u062a\u062e\u062f\u0645 {0} \u0644\u064a\u0633 \u0639\u0636\u0648\u0627 \u0641\u064a \u0647\u0630\u0647 \u0627\u0644\u0646\u0642\u0627\u0628\u0629. parseSnowflakeIdHelp=\u062a\u0648\u0627\u062c\u0647 \u0635\u0639\u0648\u0628\u0629 \u0641\u064a \u0627\u0644\u062d\u0635\u0648\u0644 \u0639\u0644\u0649 \u0645\u0639\u0631\u0641 \u0627\u0644\u0645\u0633\u062a\u062e\u062f\u0645/\u0631\u0633\u0627\u0644\u0629/\u0627\u0644\u0642\u0646\u0627\u0629/\u0634\u064a\u0621 \u0622\u062e\u0631\u061f \u0627\u0644\u062a\u062d\u0642\u0642 \u0645\u0646 \u0645\u0633\u062a\u0646\u062f\u0627\u062a \u0627\u0644\u0641\u062a\u0646\u0629 {0} - diff --git a/FredBoat/src/main/resources/lang/ast_ES.properties b/FredBoat/src/main/resources/lang/ast_ES.properties index fa3b08e8a..191abda27 100644 --- a/FredBoat/src/main/resources/lang/ast_ES.properties +++ b/FredBoat/src/main/resources/lang/ast_ES.properties @@ -7,6 +7,8 @@ playSearching=Searching YouTube for `{q}`... playYoutubeSearchError=An error occurred when searching YouTube. Consider linking directly to audio sources instead.\n```\n;;play ``` playSearchNoResults=Nun hai resultaos pa \u00ab{q}\u00bb playSelectVideo=**Please select a track with the `{0}play 1-5` command\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Joining {0} joinErrorAlreadyJoining=An error occurred. Couldn''t join {0} because I am already trying to connect to that channel. Please try again. pauseAlreadyPaused=El reproductor y\u00e1 ta pos\u00e1u. @@ -21,6 +23,9 @@ shuffleOn=Agora'l reproductor reproduz al debalu. shuffleOff=El reproductor y\u00e1 nun reproduz al debalu m\u00e1s. reshufflePlaylist=Queue reshuffled. reshufflePlayerNotShuffling=You must first turn on shuffle mode. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=\u00a1La cola ta balera\! skipOutOfBounds=Can''t remove track number {0} when there are only {1} tracks. skipNumberTooLow=Given number must be greater than 0. @@ -244,12 +249,14 @@ helpJoinCommand=Fai que'l rob\u00f3 se xuna a la canal de voz actual. helpLeaveCommand=Fai que'l rob\u00f3 marche de la canal de voz actual. helpPauseCommand=Posa'l reproductor. helpPlayCommand=Play music from the given URL or search for a track. For a full list of sources please visit {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Split a YouTube video into a tracklist provided in it's description. helpRepeatCommand=Alterna ente los moos de repitici\u00f3n. helpReshuffleCommand=Reshuffle the current queue. helpSelectCommand=Select one of the offered tracks after a search to play. helpShuffleCommand=Toggle shuffle mode for the current queue. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Skip the current song, the n'th song in the queue, all songs from n to m, or all songs from mentioned users. Please use in moderation. helpStopCommand=Stop the player and clear the playlist. Reserved for moderators with Manage Messages permission. helpUnpauseCommand=Unpause the player. @@ -339,4 +346,3 @@ modulesHowTo=Di {0} pa des/activar m\u00f3dulos. parseNotAUser=Your input {0} did not yield a Discord user. parseNotAMember=L''usuariu {0} nun ye miembru d''esta cla. parseSnowflakeIdHelp=\u00bfTienes problemes pa consiguir la ID d''un usuariu/mensaxe/canal u otra cosa? \u00c9cha-y un g\u00fceyada a la documentaci\u00f3n de Discord en {0} - diff --git a/FredBoat/src/main/resources/lang/bg_BG.properties b/FredBoat/src/main/resources/lang/bg_BG.properties index fb03cfcb4..abe9903cb 100644 --- a/FredBoat/src/main/resources/lang/bg_BG.properties +++ b/FredBoat/src/main/resources/lang/bg_BG.properties @@ -7,6 +7,8 @@ playSearching=\u0422\u044a\u0440\u0441\u0435\u043d\u0435 \u0432 YouTube \u0437\u playYoutubeSearchError=\u0412\u044a\u0437\u043d\u0438\u043a\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430 \u0432 \u0442\u044a\u0440\u0441\u0435\u043d\u0435\u0442\u043e. \u041e\u0431\u043c\u0438\u0441\u043b\u0435\u0442\u0435 \u0434\u0430 \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043b\u0438\u043d\u043a\u0430 \u043d\u0430 \u043f\u0435\u0441\u0435\u043d\u0442\u0430 \u0434\u0438\u0440\u0435\u043a\u0442\u043d\u043e.\n```\n;;play ``` playSearchNoResults=\u041d\u044f\u043c\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u0440\u0435\u0437\u0443\u043b\u0442\u0430\u0442\u0438 \u0437\u0430 `{q}` playSelectVideo=**\u041c\u043e\u043b\u044f \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u043f\u0435\u0441\u0435\u043d \u0441 `{0};;play 1-5` \u043a\u043e\u043c\u0430\u043d\u0434\u0430\u0442\u0430\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=\u0412\u043b\u0438\u0437\u0430\u043d\u0435 \u0432 {0} joinErrorAlreadyJoining=\u0412\u044a\u0437\u043d\u0438\u043a\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430. \u041d\u0435 \u043c\u043e\u0436\u0435\u0445 \u0434\u0430 \u0432\u043b\u044f\u0437\u0430 \u0432 {0} \u0437\u0430\u0449\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0441\u0435 \u043e\u043f\u0438\u0442\u0432\u0430\u043c \u0434\u0430 \u0432\u043b\u044f\u0437\u0430 \u0432 \u0442\u043e\u0437\u0438 \u043a\u0430\u043d\u0430\u043b. \u041c\u043e\u043b\u044f \u043e\u043f\u0438\u0442\u0430\u0439\u0442\u0435 \u043e\u0442\u043d\u043e\u0432\u043e. pauseAlreadyPaused=\u041f\u043b\u0435\u044a\u0440\u0430 \u0432\u0435\u0447\u0435 \u0431\u0435\u0448\u0435 \u0432 \u043f\u0430\u0443\u0437\u0430. @@ -21,6 +23,9 @@ shuffleOn=\u041f\u043b\u0435\u0439\u044a\u0440\u0430 \u0432\u0435\u0447\u0435 \u shuffleOff=\u041f\u043b\u0435\u0439\u044a\u0440\u0430 \u0432\u0435\u0447\u0435 \u043d\u0435 \u0435 \u0440\u0430\u0437\u0431\u044a\u0440\u043a\u0430\u043d. reshufflePlaylist=\u041e\u043f\u0430\u0448\u043a\u0430\u0442\u0430 \u0440\u0430\u0437\u0431\u044a\u0440\u043a\u0430\u043d\u0430. reshufflePlayerNotShuffling=\u0422\u0440\u044f\u0431\u0432\u0430 \u043f\u044a\u0440\u0432\u043e \u0434\u0430 \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u0435 \u0440\u0435\u0436\u0438\u043c \u0440\u0430\u0437\u0431\u044a\u0440\u043a\u0432\u0430\u043d\u0435. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=\u041e\u043f\u0430\u0448\u043a\u0430\u0442\u0430 \u0435 \u043f\u0440\u0430\u0437\u043d\u0430\! skipOutOfBounds=\u041d\u0435 \u043c\u043e\u0436\u0435 \u0434\u0430 \u043f\u0440\u0435\u043c\u0430\u0445\u043d\u0435\u0442\u0435 \u043f\u0435\u0441\u0435\u043d \u043d\u043e\u043c\u0435\u0440 {0} \u043a\u043e\u0433\u0430\u0442\u043e \u0438\u043c\u0430 \u0441\u0430\u043c\u043e {1} \u043f\u0435\u0441\u043d\u0438. skipNumberTooLow=\u0414\u0430\u0434\u0435\u043d\u043e\u0442\u043e \u0447\u0438\u0441\u043b\u043e \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u0435 \u043f\u043e-\u0433\u043e\u043b\u044f\u043c\u043e \u043e\u0442 0. @@ -244,12 +249,14 @@ helpJoinCommand=\u041d\u0430\u043a\u0430\u0440\u0430\u0439\u0442\u0435 \u0431\u0 helpLeaveCommand=\u041d\u0430\u043a\u0430\u0440\u0430\u0439\u0442\u0435 \u0431\u043e\u0442\u0430 \u0434\u0430 \u043d\u0430\u043f\u0443\u0441\u043d\u0435 \u0441\u0435\u0433\u0430\u0448\u043d\u0438\u044f \u0433\u043b\u0430\u0441\u043e\u0432 \u043a\u0430\u043d\u0430\u043b. helpPauseCommand=\u041f\u0430\u0443\u0437\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u043f\u043b\u0435\u044a\u0440\u0430. helpPlayCommand=\u041f\u0443\u0441\u043d\u0435\u0442\u0435 \u043c\u0443\u0437\u0438\u043a\u0430 \u043e\u0442 \u0434\u0430\u0434\u0435\u043d\u0438\u044f URL \u0438\u043b\u0438 \u043f\u043e\u0442\u044a\u0440\u0441\u0435\u0442\u0435 \u043f\u0435\u0441\u0435\u043d. \u0417\u0430 \u043f\u044a\u043b\u0435\u043d \u0441\u043f\u0438\u0441\u044a\u043a \u043d\u0430 \u0438\u0437\u0442\u043e\u0447\u043d\u0438\u0446\u0438, \u043c\u043e\u043b\u044f \u043f\u043e\u0441\u0435\u0442\u0435\u0442\u0435 {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=\u0420\u0430\u0437\u0434\u0435\u043b\u0435\u0442\u0435 YouTube \u0432\u0438\u0434\u0435\u043e \u0432 \u0442\u0440\u0430\u043a\u043b\u0438\u0441\u0442 \u0434\u0430\u0434\u0435\u043d \u0432 \u043d\u0435\u0433\u043e\u0432\u043e\u0442\u043e \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435. helpRepeatCommand=\u041f\u0440\u0435\u0432\u043a\u043b\u044e\u0447\u0432\u0430\u043d\u0435 \u043c\u0435\u0436\u0434\u0443 \u0440\u0435\u0436\u0438\u043c\u0438 \u043d\u0430 \u043f\u043e\u0432\u0442\u0430\u0440\u044f\u043d\u0435. helpReshuffleCommand=\u041f\u0440\u0435\u0443\u0441\u0442\u0440\u043e\u0439\u0432\u0430\u043d\u0435 \u043d\u0430 \u0441\u0435\u0433\u0430\u0448\u043d\u0430\u0442\u0430 \u043e\u043f\u0430\u0448\u043a\u0430\u0442\u0430. helpSelectCommand=\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0435\u0434\u0438\u043d \u043e\u0442 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u0438\u0442\u0435 \u043f\u0435\u0441\u043d\u0438 \u0441\u043b\u0435\u0434 \u0442\u044a\u0440\u0441\u0435\u043d\u0435\u0442\u043e \u0437\u0430 \u0438\u0437\u043f\u044a\u043b\u043d\u0435\u043d\u0438\u0435. helpShuffleCommand=\u0412\u043a\u043b\u044e\u0447\u0432\u0430\u043d\u0435/\u0438\u0437\u043a\u043b\u044e\u0447\u0432\u0430\u043d\u0435 \u043d\u0430 \u0440\u0430\u0437\u0431\u044a\u0440\u043a\u0432\u0430\u0449\u0438\u044f \u0440\u0435\u0436\u0438\u043c \u0432 \u0441\u0435\u0433\u0430\u0448\u043d\u0430\u0442\u0430 \u043e\u043f\u0430\u0448\u043a\u0430. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=\u041f\u0440\u043e\u043f\u0443\u0441\u043d\u0435\u0442\u0435 \u0442\u0435\u043a\u0443\u0449\u0430\u0442\u0430 \u043f\u0435\u0441\u0435\u043d, n \u043f\u0435\u0441\u0435\u043d \u0432 \u043e\u043f\u0430\u0448\u043a\u0430\u0442\u0430 \u0438\u043b\u0438 \u0432\u0441\u0438\u0447\u043a\u0438 \u043f\u0435\u0441\u043d\u0438 \u043e\u0442 n \u0434\u043e m. \u041c\u043e\u043b\u044f \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0439\u0442\u0435 \u0432 \u0443\u043c\u0435\u0440\u0435\u043d\u043e\u0441\u0442. helpStopCommand=\u0421\u043f\u0440\u0435\u0442\u0435 \u043f\u043b\u0435\u044a\u0440\u0430 \u0438 \u0438\u0437\u0447\u0438\u0441\u0442\u0435\u0442\u0435 \u043f\u043b\u0430\u0439\u043b\u0438\u0441\u0442\u0430. \u0420\u0435\u0437\u0435\u0440\u0432\u0438\u0440\u0430\u043d\u043e \u0437\u0430 \u043c\u043e\u0434\u0435\u0440\u0430\u0442\u043e\u0440\u0438 \u0441 Manage Messages (\u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0437\u0430 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u0430 \u0441\u044a\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u0442\u0430). helpUnpauseCommand=\u042a\u043d\u043f\u0430\u0443\u0437\u0438\u0440\u0430\u043d\u0435 (\u043f\u0440\u043e\u0434\u044a\u043b\u0436\u0430\u0432\u0430\u043d\u0435) \u043d\u0430 \u043f\u043b\u0435\u044a\u0440\u0430. @@ -339,4 +346,3 @@ modulesHowTo=\u041d\u0430\u043f\u0438\u0448\u0438 {0} \u0437\u0430 \u0434\u0430 parseNotAUser=\u0422\u043e\u0432\u0430, \u043a\u043e\u0435\u0442\u043e \u043d\u0430\u043f\u0438\u0441\u0430 {0} \u043d\u0435 \u043e\u0442\u043a\u0440\u0438 \u043f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b \u0432 \u0414\u0438\u0441\u043a\u043e\u0440\u0434. parseNotAMember=\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b {0} \u043d\u0435 \u0435 \u0447\u043b\u0435\u043d \u043d\u0430 \u0442\u043e\u0437\u0438 \u0433\u0438\u043b\u0434\u0438\u044f. parseSnowflakeIdHelp=\u0418\u043c\u0430 \u043f\u0440\u043e\u0431\u043b\u0435\u043c \u0434\u0430 \u043c\u0430\u0445\u043d\u0435\u0448 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u044f\u0442\u0430 \u043d\u0430 \u043f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b/\u0441\u044a\u043e\u0431\u0449\u0435\u043d\u0438\u0435/\u043a\u0430\u043d\u0430\u043b/\u043d\u0435\u0449\u043e \u0434\u0440\u0443\u0433\u043e? \u0412\u0438\u0436\u0442\u0435 \u043d\u0430 \u0434\u044e\u043a\u043e\u043c\u0435\u043d\u0442\u0438\u0442\u0435 \u043d\u0430 \u0414\u0438\u0441\u043a\u043e\u0440\u0434 \u0432 {0} - diff --git a/FredBoat/src/main/resources/lang/bn_BD.properties b/FredBoat/src/main/resources/lang/bn_BD.properties index 3674c2282..6338b6f82 100644 --- a/FredBoat/src/main/resources/lang/bn_BD.properties +++ b/FredBoat/src/main/resources/lang/bn_BD.properties @@ -7,6 +7,8 @@ playSearching=\u09b2\u09c1\u0995\u09bf\u0982 \u09ab\u09b0 `{q}`\u0987\u09a8 YouT playYoutubeSearchError=\u0987\u0989\u099f\u09bf\u0989\u09ac \u09b8\u09a8\u09cd\u09a7\u09be\u09a8 \u0995\u09b0\u09be\u09b0 \u09b8\u09ae\u09af\u09bc \u098f\u0995\u099f\u09bf \u09a4\u09cd\u09b0\u09c1\u099f\u09bf \u09b8\u0982\u0998\u099f\u09bf\u09a4 \u09b9\u09af\u09bc\u09c7\u099b\u09c7\u0964 \u098f\u09b0 \u09aa\u09b0\u09bf\u09ac\u09b0\u09cd\u09a4\u09c7 \u09b8\u09b0\u09be\u09b8\u09b0\u09bf \u0985\u09a1\u09bf\u0993 \u0989\u09ce\u09b8\u09c7\u09b0 \u09b8\u09be\u09a5\u09c7 \u09ac\u09bf\u09ac\u09c7\u099a\u09a8\u09be \u0995\u09b0\u09ac\u09cb\u0964 ``` ;; \u0996\u09c7\u09b2\u09be "\u0964 playSearchNoResults=''{q}''-\u098f\u09b0 \u099c\u09a8\u09cd\u09af \u0995\u09cb\u09a8\u09cb \u09ab\u09b2\u09be\u09ab\u09b2 \u09a8\u09c7\u0987 playSelectVideo=* * \u09a6\u09af\u09bc\u09be \u0995\u09b0\u09c7 \u099f\u09cd\u09b0\u09cd\u09af\u09be\u0995 \u09a6\u09bf\u09af\u09bc\u09c7 \u09a8\u09bf\u09b0\u09cd\u09ac\u09be\u099a\u09a8 ''{0}play 1-5'' \u0995\u09ae\u09be\u09a8\u09cd\u09a1\: * * +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=\u098f\u09a8\u09cd\u099f\u09be\u09b0\u09bf\u0982 {0} joinErrorAlreadyJoining=\u098f\u0995\u099f\u09bf \u09a4\u09cd\u09b0\u09c1\u099f\u09bf \u09b8\u0982\u0998\u099f\u09bf\u09a4 \u09b9\u09af\u09bc\u09c7\u099b\u09c7\u0964 {0} \u09af\u09cb\u0997 \u09a6\u09bf\u09a4\u09c7 \u09aa\u09be\u09b0\u09b2\u09be\u09ae \u09a8\u09be \u0995\u09be\u09b0\u09a8 \u0986\u09ae\u09bf \u0987\u09a4\u09bf\u09ae\u09a7\u09cd\u09af\u09c7\u0987 \u0993\u0987 \u099a\u09cd\u09af\u09be\u09a8\u09c7\u09b2\u09c7\u09b0 \u09b8\u09be\u09a5\u09c7 \u09b8\u0982\u09af\u09cb\u0997 \u0995\u09b0\u09be\u09b0 \u099a\u09c7\u09b7\u09cd\u099f\u09be \u0995\u09b0\u099b\u09bf\u0964 \u0985\u09a8\u09c1\u0997\u09cd\u09b0\u09b9 \u0995\u09b0\u09c7 \u0986\u09ac\u09be\u09b0 \u099a\u09c7\u09b7\u09cd\u099f\u09be \u0995\u09b0\u09c1\u09a8\u0964. pauseAlreadyPaused=\u09aa\u09cd\u09b2\u09c7\u09af\u09bc\u09be\u09b0 \u0987\u09a4\u09bf\u09ae\u09a7\u09cd\u09af\u09c7 \u09a5\u09be\u09ae\u09be\u09a8\u09cb \u09b9\u09af\u09bc\u09c7\u099b\u09c7. @@ -21,6 +23,9 @@ shuffleOn=\u09b8\u09cd\u09ac\u09aa \u0995\u09ae\u09aa\u09cd\u09b2\u09bf\u099f shuffleOff=\u09b8\u09cd\u09ac\u09aa \u0995\u09ae\u09aa\u09cd\u09b2\u09bf\u099f. reshufflePlaylist=\u09b8\u09be\u09b0\u09bf \u09b8\u09cd\u09a5\u09b2\u09c7\u0964. reshufflePlayerNotShuffling=\u0986\u09aa\u09a8\u09bf \u09aa\u09cd\u09b0\u09a5\u09ae\u09c7 \u0993\u09b2\u099f\u09aa\u09be\u09b2\u099f \u0995\u09b0\u09be\u09b0 \u09ae\u09cb\u09a1 \u098f \u099a\u09be\u09b2\u09c1 \u0995\u09b0\u09a4\u09c7 \u09b9\u09ac\u09c7\u0964. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=\u0987\u099f \u0987\u099c \u098f\u09ae\u09cd\u09aa\u099f\u09bf\! skipOutOfBounds=\u099f\u09cd\u09b0\u09cd\u09af\u09be\u0995 \u09a8\u09ae\u09cd\u09ac\u09b0 {0} \u09af\u0996\u09a8 {1} \u099f\u09cd\u09b0\u09be\u0995 \u098f\u0995\u09ae\u09be\u09a4\u09cd\u09b0 \u0985\u09aa\u09b8\u09be\u09b0\u09a3 \u0995\u09b0\u09a4\u09c7 \u09aa\u09be\u09b0\u099b\u09bf \u09a8\u09be\u0964. skipNumberTooLow=\u09a8\u09ae\u09cd\u09ac\u09b0 \u09a6\u09c7\u0993\u09af\u09bc\u09be 0 \u099a\u09c7\u09af\u09bc\u09c7 \u09ac\u09c7\u09b6\u09bf \u09b9\u09ac\u09c7\u0964\! @@ -244,12 +249,14 @@ helpJoinCommand=Make the bot join your current voice channel. helpLeaveCommand=Make the bot leave the current voice channel. helpPauseCommand=Pause the player. helpPlayCommand=Play music from the given URL or search for a track. For a full list of sources please visit {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Split a YouTube video into a tracklist provided in it's description. helpRepeatCommand=Toggle between repeat modes. helpReshuffleCommand=Reshuffle the current queue. helpSelectCommand=Select one of the offered tracks after a search to play. helpShuffleCommand=Toggle shuffle mode for the current queue. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Skip the current song, the n'th song in the queue, all songs from n to m, or all songs from mentioned users. Please use in moderation. helpStopCommand=Stop the player and clear the playlist. Reserved for moderators with Manage Messages permission. helpUnpauseCommand=Unpause the player. @@ -339,4 +346,3 @@ modulesHowTo=Say {0} to enable/disable modules. parseNotAUser=Your input {0} did not yield a Discord user. parseNotAMember=User {0} is not a member of this guild. parseSnowflakeIdHelp=Having trouble getting the id of a user/message/channel/something else? Check out the Discord docs at {0} - diff --git a/FredBoat/src/main/resources/lang/ca_ES.properties b/FredBoat/src/main/resources/lang/ca_ES.properties index ba1b8aa2c..c7594cd4e 100644 --- a/FredBoat/src/main/resources/lang/ca_ES.properties +++ b/FredBoat/src/main/resources/lang/ca_ES.properties @@ -7,6 +7,8 @@ playSearching=Buscant al YouTube `{q}`... playYoutubeSearchError=S'ha produ\u00eft un error buscant al YouTube. Consideri enviant un enlla\u00e7 directe a l'arxiu d'audio.\n```\n;;play ``` playSearchNoResults=No hi ha resultats per a `{q}` playSelectVideo=**Si us play seleccioni una pista amb el comand `{0}playn`\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Incorporar-se a {0} joinErrorAlreadyJoining=S''ha produ\u00eft un eror. No he pogut unirme a {0} per que ja estic intentant connectarme a aquest canal. Si us plau, torneu-ho a provar. pauseAlreadyPaused=El reproductor ja esta en pausa. @@ -21,6 +23,9 @@ shuffleOn=El reproductor est\u00e0 en mode de barreja. shuffleOff=El reproductor ja no est\u00e0 en mode de barreja. reshufflePlaylist=Cua reorganitzada. reshufflePlayerNotShuffling=Heu d'activar el mode aleatori. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=La cua \u00e9s buida\! skipOutOfBounds=No es pot suprimir la pista n\u00famero {0} quan nom\u00e9s hi ha/n {1} pistes. skipNumberTooLow=El nombre a de ser major que 0. @@ -62,7 +67,7 @@ npDescription=Descripci\u00f3 npLoadedSoundcloud=[{0}/{1}]\n\nCarregat de Soundcloud npLoadedBandcamp={0}\n\nCarregat de Bandcamp npLoadedTwitch=Carregat de Twitch -npRequestedBy=Requested by {0} +npRequestedBy=Demanat per {0} permissionMissingBot=Necessito el seg\u00fcent perm\u00eds per poder fer aquesta acci\u00f3\: permissionMissingInvoker=Necesitas permisos para realizar esa acci\u00f3n\: permissionEmbedLinks=Incrustar enlla\u00e7os @@ -92,11 +97,11 @@ loadPlaylistTooMany=Afegides {0} pistes. Hi han massa pistes per mostrar. loadErrorCommon=S''ha produ\u00eft un error en carregar la informaci\u00f3 per a `{0}`\:\n{1} loadErrorSusp=Sospit\u00f3s error en carregar la informaci\u00f3 per a `{0}`. loadQueueTrackLimit=No pots afegir pistes a una cua amb m\u00e9s de {0} pistes\! Aix\u00f2 es per evitar abusos. -loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. -loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. -loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. -loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. -loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! +loadPlaylistDisabled=Aquest servidor ha deshabilitat afegir llistes de reproducci\u00f3 a la cua. Si us plau, afegiu les pistes individualment. +loadMaxTracksExceeded=Aquest servidor no permet afegir mes de {0} pistes a la cua. +loadMaxUserTracksExceeded=Aquest servidor no et permet tenir mes de {0} pistes a la cua. +loadMaxTrackLengthExceeded=Aquest servidor no permet afegir pistes de duraci\u00f3 superior a {0}. Si us plau, afegiu pistes mes curtes. +loadPlaylistGeneralError=No s'han afegit pistes perqu\u00e8 aquest servidor imposa restriccions de cua\! loadAnnouncePlaylist=A punt de carregar la llista de reproducci\u00f3 **{0}** amb `{1}` pistes. Aix\u00f2 pot tardar una estona, si us plau, sigui pacient. playerUserNotInChannel=Primer has de unirte a un canal de veu. playerJoinConnectDenied=No tinc perm\u00eds per connectar-me a aquest canal de veu. @@ -149,23 +154,23 @@ modAuditLogMessage=Acci\u00f3n emitida por {0} contra {1} modAudioLogNoReason=No se proporcion\u00f3 ninguna raz\u00f3n. modFailUserHierarchy=No tens un rol major que {0}. modFailBotHierarchy=Necessito tenir un rol major que {0}. -modBanlistFail=Failed to fetch the ban list. +modBanlistFail=No s'ha pogut obtenir la llista de expulsats. modBanFail=Fallida al bannear {0} modUnbanFail=Failed to unban {0} -modUnbanFailNotBanned=User {0} is not banned in this guild. -modKeepMessages=Add {0} to not delete any messages. -modActionTargetDmKicked=Ahoy\! You have been kicked from the guild {0} by user {1}. -modActionTargetDmBanned=Ahoy\! You have been banned from the guild {0} by user {1}. -kickSuccess=User {0} has been kicked. +modUnbanFailNotBanned=L''usuari {0} no est\u00e0 expulsat d''aquest servidor. +modKeepMessages=Afegeix {0} per no suprimir tots els missatges. +modActionTargetDmKicked=Apa\! L''usuari {1} l''ha fet fora de {0}. +modActionTargetDmBanned=Apa\! L''usuari {1} l''ha expulsat de {0}. +kickSuccess=L''usuari {0} ha estat fet fora. kickFail=Fallida al expulsar {0} kickFailSelf=No pots expulsar-te a tu mateix. kickFailOwner=No pots expulsar a l'administrador del servidor. kickFailMyself=No puc expulsar-me a mi mateix. -softbanSuccess=User {0} has been softbanned. +softbanSuccess=L''usuari {0} ha estat "softbanned". softbanFailSelf=No pots expulsar-te a tu mateix. softbanFailOwner=No pots expulsar a l'administrador del servidor. softbanFailMyself=No pots expulsar-te a tu mateix. -hardbanSuccess=User {0} has been banned. +hardbanSuccess=L''usuari {0} ha estat expulsat. hardbanFailSelf=No pots expulsar-te a tu mateix. hardbanFailOwner=No pots expulsar a l'administrador del servidor. hardbanFailMyself=No pots expulsar-te a tu mateix. @@ -244,12 +249,14 @@ helpJoinCommand=Make the bot join your current voice channel. helpLeaveCommand=Make the bot leave the current voice channel. helpPauseCommand=Pausa el reproductor. helpPlayCommand=Play music from the given URL or search for a track. For a full list of sources please visit {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Split a YouTube video into a tracklist provided in it's description. helpRepeatCommand=Canviar entre modes de repetici\u00f3. helpReshuffleCommand=Reshuffle the current queue. helpSelectCommand=Select one of the offered tracks after a search to play. helpShuffleCommand=Toggle shuffle mode for the current queue. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Skip the current song, the n'th song in the queue, all songs from n to m, or all songs from mentioned users. Please use in moderation. helpStopCommand=Stop the player and clear the playlist. Reserved for moderators with Manage Messages permission. helpUnpauseCommand=Anul\u00b7leu la pausa el jugador. @@ -339,4 +346,3 @@ modulesHowTo=Say {0} to enable/disable modules. parseNotAUser=Your input {0} did not yield a Discord user. parseNotAMember=User {0} is not a member of this guild. parseSnowflakeIdHelp=Having trouble getting the id of a user/message/channel/something else? Check out the Discord docs at {0} - diff --git a/FredBoat/src/main/resources/lang/ceb_PH.properties b/FredBoat/src/main/resources/lang/ceb_PH.properties index d5f65fdcb..87bc1609b 100644 --- a/FredBoat/src/main/resources/lang/ceb_PH.properties +++ b/FredBoat/src/main/resources/lang/ceb_PH.properties @@ -7,6 +7,8 @@ playSearching=Ge pangita ang Youtube para {q}... playYoutubeSearchError=Ug sayup ang nahitabong kanus-a nagpangita sa YouTube. Bilang ang linking na deritso sa sawong tinub-an hinuon. playSearchNoResults=Walay resulta para {q} playSelectVideo=**Palihug kong pili sa dalan uban ang {0}dula 1-5 mando\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Pag apil{0} joinErrorAlreadyJoining=Ug ang mali nahitabo. Dili maka apil {0} kay ako naka apil og ni subay og konekta para sa channel. Palihug ko utro. pauseAlreadyPaused=Ang tigdula kay andam i-paused. @@ -21,6 +23,9 @@ shuffleOn=Ang tigdula mao ron ang karaw. shuffleOff=Ang player mao dili na taas na ma karaw. reshufflePlaylist=Queue balik og karaw. reshufflePlayerNotShuffling=Ikaw kinahanglan mag una og karaw paagi. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=Ang queue kay nahutdan\! skipOutOfBounds=Dili maka tangtang sa dalan ang numero{0} kay naay lang {1} dalan. skipNumberTooLow=Nahatagan og numero kinahanglan na dako-dako kaysa zero. @@ -244,12 +249,14 @@ helpJoinCommand=Paapila ang bot sa imong kasamtangan nga voice channel. helpLeaveCommand=Pahawaa ang bot sa kasamtangan nga voice channel. helpPauseCommand=Hunonga ang tukar. helpPlayCommand=Magpatukar og musika gikan sa gihatag nga URL o mangita sa track. Alang sa kompleto nga lista sa mga tinubdan palihug pagbisita sa {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Split a YouTube video into a tracklist provided in it's description. helpRepeatCommand=Toggle between repeat modes. helpReshuffleCommand=Reshuffle the current queue. helpSelectCommand=Select one of the offered tracks after a search to play. helpShuffleCommand=Toggle shuffle mode for the current queue. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Skip the current song, the n'th song in the queue, all songs from n to m, or all songs from mentioned users. Please use in moderation. helpStopCommand=Stop the player and clear the playlist. Reserved for moderators with Manage Messages permission. helpUnpauseCommand=Unpause the player. @@ -339,4 +346,3 @@ modulesHowTo=Say {0} to enable/disable modules. parseNotAUser=Your input {0} did not yield a Discord user. parseNotAMember=User {0} is not a member of this guild. parseSnowflakeIdHelp=Having trouble getting the id of a user/message/channel/something else? Check out the Discord docs at {0} - diff --git a/FredBoat/src/main/resources/lang/cs_CZ.properties b/FredBoat/src/main/resources/lang/cs_CZ.properties index a366a9cab..8c20bffbc 100644 --- a/FredBoat/src/main/resources/lang/cs_CZ.properties +++ b/FredBoat/src/main/resources/lang/cs_CZ.properties @@ -7,6 +7,8 @@ playSearching=Hled\u00e1m `{q}` na YouTube... playYoutubeSearchError=Nastala chyba p\u0159i vyhled\u00e1v\u00e1n\u00ed na YouTube. Zkuste m\u00edsto toho pou\u017e\u00edt p\u0159\u00edm\u00fd odkaz.\n```\n;;play ``` playSearchNoResults=\u017d\u00e1dn\u00e9 v\u00fdsledky pro `{q}` playSelectVideo=**Pros\u00edm vyberte stopu p\u0159\u00edkazem\: `{0}play 1-5` ** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=P\u0159ipojuji se k {0} joinErrorAlreadyJoining=Do\u0161lo k chyb\u011b. Nemohu se p\u0159ipojit k {0}, jeliko\u017e se ji\u017e sna\u017e\u00edm p\u0159ipojit k tomuto kan\u00e1lu. Pros\u00edm opakujte akci. pauseAlreadyPaused=P\u0159ehr\u00e1va\u010d je ji\u017e pozastaven. @@ -21,6 +23,9 @@ shuffleOn=P\u0159ehr\u00e1va\u010d bude p\u0159ehr\u00e1vat n\u00e1hodn\u011b. shuffleOff=P\u0159ehr\u00e1va\u010d nebude d\u00e1le p\u0159ehr\u00e1vat n\u00e1hodn\u011b. reshufflePlaylist=Fronta skladeb prom\u00edch\u00e1na. reshufflePlayerNotShuffling=Je t\u0159eba nejprve zapnout re\u017eim n\u00e1hodn\u00e9ho p\u0159ehr\u00e1v\u00e1n\u00ed. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=Fronta je pr\u00e1zdn\u00e1\! skipOutOfBounds=Stopu \u010d\u00edslo {0} nelze odebrat, pokud existuje pouze {1} stop. skipNumberTooLow=Dan\u00e9 \u010d\u00edslo mus\u00ed b\u00fdt v\u011bt\u0161\u00ed ne\u017e 0. @@ -244,12 +249,14 @@ helpJoinCommand=P\u0159inutit bota aby se p\u0159ipojil do aktu\u00e1ln\u00edho helpLeaveCommand=P\u0159inutit bota aby se odpojil z aktu\u00e1ln\u00edho hlasov\u00e9ho kan\u00e1lu. helpPauseCommand=Pozastavit p\u0159ehr\u00e1va\u010d. helpPlayCommand=P\u0159ehr\u00e1vejte hudbu z dan\u00e9 adresy nebo hledat skladbu. Pro \u00fapln\u00fd seznam zdroj\u016f pros\u00edm nav\u0161tivte {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Rozd\u011blte YouTube video na seznam skladeb v jejich popisku. helpRepeatCommand=P\u0159ep\u00edn\u00e1n\u00ed mezi re\u017eimy opakov\u00e1n\u00ed. helpReshuffleCommand=P\u0159em\u00edchat aktu\u00e1ln\u00ed frontu. helpSelectCommand=Vyberte jednu z nab\u00edzen\u00fdch skladeb po vyhled\u00e1n\u00ed k p\u0159ehr\u00e1n\u00ed. helpShuffleCommand=P\u0159epnout na re\u017eim n\u00e1hodn\u00e9ho p\u0159ehr\u00e1v\u00e1n\u00ed pro aktu\u00e1ln\u00ed frontu. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=P\u0159esko\u010dit aktu\u00e1ln\u00ed hudbu, n-tou hudbu ve front\u011b, v\u0161echny hudby od n do m nebo v\u0161echnu hudbu od zm\u00edn\u011bn\u00fdch u\u017eivatel\u016f. Pros\u00edm pou\u017eijte v moderov\u00e1n\u00ed. helpStopCommand=Zastavit p\u0159ehr\u00e1va\u010d a vymazat seznam. Vyhrazeno pro moder\u00e1tory s Manage Messages opr\u00e1vn\u011bn\u00edm. helpUnpauseCommand=Spustit p\u0159ehr\u00e1va\u010d. @@ -307,8 +314,8 @@ skipUserMultiple=P\u0159esko\u010deno {0} skladeb p\u0159idan\u00fdch {1}. skipUsersMultiple=P\u0159esko\u010deno {0} skladeb p\u0159idan\u00fdch {1}ti u\u017eivateli. skipUserNoTracks=\u017d\u00e1dn\u00fd z uveden\u00fdch u\u017eivatel\u016f nem\u00e1 jak\u00e9koli skladby ve front\u011b. voteSkipAdded=V\u00e1\u0161 hlas byl p\u0159id\u00e1n\! -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! +voteSkipRemoved=Tv\u016fj hlas byl odstran\u011bn\! +voteSkipNotFound=Je\u0161t\u011b jste nehlasovali pro p\u0159esko\u010den\u00ed t\u00e9to skladby\! voteSkipAlreadyVoted=U\u017e jste hlasovali pro p\u0159esko\u010den\u00ed t\u00e9to skladby\! voteSkipSkipping={0} hlasovalo pro p\u0159esko\u010den\u00ed. P\u0159eskakuji skladbu {1}. voteSkipNotEnough={0} hlasovalo pro p\u0159esko\u010den\u00ed. Alespo\u0148 {1} pot\u0159eba. @@ -339,4 +346,3 @@ modulesHowTo=Napi\u0161te {0} pro povolen\u00ed/zak\u00e1z\u00e1n\u00ed modul\u0 parseNotAUser=Tv\u016fj vstup {0} u\u017eivatel Discordu neposkytl. parseNotAMember=U\u017eivatel {0} nen\u00ed \u010dlenem tohoto serveru. parseSnowflakeIdHelp=M\u00e1\u0161 pot\u00ed\u017ee p\u0159i z\u00edsk\u00e1v\u00e1n\u00ed id u\u017eivatele/zpr\u00e1vy/n\u011b\u010deho jin\u00e9ho? Pod\u00edvej se na Discord dokumentaci\: {0} - diff --git a/FredBoat/src/main/resources/lang/cy_GB.properties b/FredBoat/src/main/resources/lang/cy_GB.properties index 11353d508..93d78fba3 100644 --- a/FredBoat/src/main/resources/lang/cy_GB.properties +++ b/FredBoat/src/main/resources/lang/cy_GB.properties @@ -7,6 +7,8 @@ playSearching=Chwilio YouTube am ''{q}''... playYoutubeSearchError=Digwyddodd gwall wrth chwilio YouTube. Ystyriwch gysylltu''n uniongyrchol \u00e2 ffynonellau sain yn lle hynny. ``` ;; chwarae '''' '' playSearchNoResults=Dim canlyniadau ar gyfer ''{q}'' playSelectVideo=** Dewiswch trac gyda''r gorchymyn ''{0}play n''\: ** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Ymuno \u00e2 {0} joinErrorAlreadyJoining=Digwyddodd gwall. Ni allai ymuno \u00e2 {0} oherwydd ydwyf eisoes yn ceisio cysylltu \u00e2''r sianel honno. Rhowch gynnig arni eto. pauseAlreadyPaused=Mae'r chwaraewr wedi'i rewi eisoes. @@ -21,6 +23,9 @@ shuffleOn=Bellach mae chwaraewr yn cymysgu. shuffleOff=Nid yw'r chwaraewr yn cymysgu yn bellach. reshufflePlaylist=Ciw eich ad-drefnu. reshufflePlayerNotShuffling=Yn gyntaf, mae'n rhaid ichi ddefnyddio modd cymysgu. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=Mae'r ciw yn wag\! skipOutOfBounds=Ni ellir dileu trac rhif {0} pan {1} trac sydd yn bresenol. skipNumberTooLow=Rhaid rhoi nifer sy'n fwy na 0. @@ -244,12 +249,14 @@ helpJoinCommand=Gwneud y bot ymuno \u00e2 sianel presennol eich llais. helpLeaveCommand=Gwneud y bot ymuno \u00e2 sianel presennol eich llais. helpPauseCommand=Unpause y chwaraewr. helpPlayCommand=Play music from the given URL or search for a track. For a full list of sources please visit {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Rhannu fideo YouTube yn tracklist yn ei disgrifiad. helpRepeatCommand=Toglo rhwng moddau mynych. helpReshuffleCommand=Ad-drefnu y ciw presennol. helpSelectCommand=Dewiswch un o'r traciau cynnig ar \u00f4l chwilio i chwarae. helpShuffleCommand=Toglo'r modd cymysgu ar gyfer y ciw presennol. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Hepgor y g\u00e2n presennol, y g\u00e2n n'th yn y ciw, holl ganeuon o n i m, neu holl ganeuon gan ddefnyddwyr grybwyllir. Defnyddiwch yn gymedrol. helpStopCommand=Atal y chwaraewr a clir y rhestr chwarae. A gedwir ar gyfer safonwyr gyda chaniat\u00e2d negeseuon rheoli. helpUnpauseCommand=Unpause y chwaraewr. @@ -339,4 +346,3 @@ modulesHowTo=Say {0} to enable/disable modules. parseNotAUser=Your input {0} did not yield a Discord user. parseNotAMember=User {0} is not a member of this guild. parseSnowflakeIdHelp=Having trouble getting the id of a user/message/channel/something else? Check out the Discord docs at {0} - diff --git a/FredBoat/src/main/resources/lang/da_DK.properties b/FredBoat/src/main/resources/lang/da_DK.properties index 1ad51d259..29f7f06ef 100644 --- a/FredBoat/src/main/resources/lang/da_DK.properties +++ b/FredBoat/src/main/resources/lang/da_DK.properties @@ -7,6 +7,8 @@ playSearching=S\u00f8ger p\u00e5 youtube for `{q}`... playYoutubeSearchError=Der opstod en fejl under s\u00f8gning p\u00e5 YouTube. Overvej at linke direkte til lydkilder i stedet\:\n```\n;;play '''' '' playSearchNoResults=Ingen resultater for ''{q}'' playSelectVideo=**V\u00e6lg en sang ved at skrive `{0}play n` command\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Forbinder til {0} joinErrorAlreadyJoining=Der opstod en fejl. Kunne ikke joine {0}, fordi jeg allerede fors\u00f8ger at oprette forbindelse til denne kanal. Pr\u00f8v venligst igen. pauseAlreadyPaused=Spilleren er allerede sat p\u00e5 pause. @@ -21,6 +23,9 @@ shuffleOn=Afspilleren er nu shufflet. shuffleOff=Afspilleren er ikke l\u00e6ngere shufflet. reshufflePlaylist=K\u00f8en blandet. reshufflePlayerNotShuffling=Du skal f\u00f8rst aktivere shuffle mode. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=K\u00f8en er tom\! skipOutOfBounds=Kan ikke fjerne spor nummer {0}, n\u00e5r der er kun er {1} spor. skipNumberTooLow=Det givne nummer skal v\u00e6re st\u00f8rre end 0. @@ -244,12 +249,14 @@ helpJoinCommand=F\u00e5 botten til at joine din tale kanal. helpLeaveCommand=F\u00e5 botten til at leave din tale kanal. helpPauseCommand=Pause musikken. helpPlayCommand=Afspil musik fra den givne URL eller S\u00f8g efter et spor. For en komplet liste over kilder kan du bes\u00f8ge {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Opdele en YouTube-video i en playliste i sin beskrivelse. helpRepeatCommand=Skift mellem gentagelsesfunktioner. helpReshuffleCommand=Shuffle nuv\u00e6rende k\u00f8en. helpSelectCommand=V\u00e6lg en af de tilbudte sange efter en s\u00f8gning for at spille. helpShuffleCommand=Skift shuffle tilstand for den aktuelle k\u00f8. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Springe den aktuelle sang, n'th sang i k\u00f8en, alle sange fra n til m eller alle sange fra n\u00e6vnte brugere. Brug i moderation. helpStopCommand=Stop spilleren og fjern afspilningslisten. Forbeholdt redakt\u00f8rer med Administrer meddelelser om tilladelse. helpUnpauseCommand=Genoptag spilleren. @@ -339,4 +346,3 @@ modulesHowTo=Sige {0} for at aktivere/deaktivere moduler. parseNotAUser=Dit input {0} ikke give en uenighed bruger. parseNotAMember=Bruger {0} er ikke Banned i denne guild. parseSnowflakeIdHelp=At have problemer med at f\u00e5 et id for en bruger/meddelelse/kanal/noget andet? Tjek splid docs p\u00e5 {0} - diff --git a/FredBoat/src/main/resources/lang/de_DE.properties b/FredBoat/src/main/resources/lang/de_DE.properties index 5803b9411..a59b85a74 100644 --- a/FredBoat/src/main/resources/lang/de_DE.properties +++ b/FredBoat/src/main/resources/lang/de_DE.properties @@ -7,6 +7,8 @@ playSearching=YouTube wird nach `{q}` durchsucht... playYoutubeSearchError=Ein Fehler ist w\u00e4hrend der YouTube-Suche aufgetreten. Erw\u00e4ge stattdessen, Audioquellen direkt zu verlinken.\n```\n;;play ``` playSearchNoResults=Keine Ergebnisse f\u00fcr `{q}` playSelectVideo=** Bitte w\u00e4hle einen Track mit dem Befehl "{0}play n" aus\: ** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Verbinde zu {0} joinErrorAlreadyJoining=Es ist ein Fehler aufgetreten. Konnte nicht zu {0} verbinden, weil ich bereits versuche, mich mit dem Kanal zu verbinden. Bitte versuche es erneut. pauseAlreadyPaused=Der Spieler ist bereits angehalten. @@ -21,6 +23,9 @@ shuffleOn=Der Spieler ist nun gemischt. shuffleOff=Der Spieler ist nicht mehr gemischt. reshufflePlaylist=Warteschlange neu gemischt. reshufflePlayerNotShuffling=Du musst zuerst Shuffle-Modus aktivieren. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=Die Warteschlange ist leer\! skipOutOfBounds=Track Nummer {0} kann nicht entfernt werden, es gibt nur {1} Tracks. skipNumberTooLow=Gegebene Zahl muss gr\u00f6\u00dfer als 0 sein. @@ -62,7 +67,7 @@ npDescription=Beschreibung npLoadedSoundcloud=[{0}/{1}]\n\nGeladen von Soundcloud npLoadedBandcamp={0} aus Bandcamp geladen npLoadedTwitch=Von Twitch geladen -npRequestedBy=Requested by {0} +npRequestedBy=Angefordert von {0} permissionMissingBot=Ich brauche die folgenden Berechtigungen damit ich diese Aktion ausf\u00fchren kann\: permissionMissingInvoker=Du brauchst die folgenden Berechtigung damit sie diese Aktion ausf\u00fchren k\u00f6nnen\: permissionEmbedLinks=Links einbinden @@ -92,10 +97,10 @@ loadPlaylistTooMany={0} Tracks hinzugef\u00fcgt. Zu viele Tracks gefunden, um si loadErrorCommon=Fehler aufgetreten beim Laden der Informationen f\u00fcr `{0}`\:\n{1} loadErrorSusp=Verd\u00e4chtiger Fehler beim Laden der Informationen f\u00fcr `{0}`. loadQueueTrackLimit=Du kannst keine Lieder zu einer Warteschlange die {0} Lieder beinhaltet hinzuf\u00fcgen, dies ist dazu da um Missbrauch zu verhindern. -loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. -loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. -loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. -loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistDisabled=Dieser Server erlaubt keine Anforderung von ganzen Playlists. Bitte fordere jeden Track einzeln an. +loadMaxTracksExceeded=Dieser Server erlaubt nicht mehr als {0} anzufordern. +loadMaxUserTracksExceeded=Dieser Server erlaubt es nicht mehr als {0} Titel in der Warteschlange zu haben. +loadMaxTrackLengthExceeded=Dieser Server erlaubt es dir nicht, Titel l\u00e4nger als {0} abzuspielen. Bitte versuche einen k\u00fcrzeren Titel. loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=Die Playlist **{0}** mit bis zu `{1}` Liedern wird geladen. Dies kann eine Weile dauern, bitte habe etwas Geduld. playerUserNotInChannel=Du musst zuerst einem Sprachkanal beitreten. @@ -244,12 +249,14 @@ helpJoinCommand=Lass den Bot sich mit deinem aktuellen Sprachkanal verbinden. helpLeaveCommand=Lass den Bot deinem aktuellen Sprachkanal verlassen. helpPauseCommand=Halte den Spieler an. helpPlayCommand=Gebe Musik von der angegebenen URL wieder oder suche nach einem Lied. Eine vollst\u00e4ndige Liste der Quellen findest Du auf {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Spiele musik durch eine URL ab oder suche direkt nach dem Lied. Lieder werden an den Anfang der Warteliste hinzugef\u00fcgt. Um eine liste der gesamten Quellen zu sehen, besuchen Sie bitte {0} helpPlaySplitCommand=Teile ein YouTube-Video in eine Tracklist, welche in dessen Beschreibung angegeben ist. helpRepeatCommand=Umschalten der Wiederholungsmodi. helpReshuffleCommand=Mische die Lieder f\u00fcr die aktuelle Warteschlange neu. helpSelectCommand=W\u00e4hle nach einer Suche einen der angezeigten Tracks zum Abspielen aus. helpShuffleCommand=Schalte den shuffle-Modus f\u00fcr die aktuelle Warteschlange ein. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=\u00dcberspringt den aktuellen Song, den n-ten Song in der Warteschlange oder alle Songs von n bis m. Bitte in Ma\u00dfen verwenden. helpStopCommand=Stoppe den Spieler und leere die Wiedergabeliste. Reserviert f\u00fcr Moderatoren mit Berechtigung zum Verwalten von Nachrichten. helpUnpauseCommand=Setze den Spieler fort. @@ -339,4 +346,3 @@ modulesHowTo=Sage {0}, um Module zu aktivieren/deaktivieren. parseNotAUser=Ihre Eingabe {0} entspricht keinem registrierten Discord Benutzer. parseNotAMember=Benutzer {0} ist kein Mitglied dieser Gilde. parseSnowflakeIdHelp=Haben Sie Probleme, die Id eines Benutzers / Nachricht / Kanals / etwas anderes zu bekommen? \u00dcberpr\u00fcfen Sie die Discord-Dokumente um {0} - diff --git a/FredBoat/src/main/resources/lang/el_GR.properties b/FredBoat/src/main/resources/lang/el_GR.properties index caa1d4111..4264f7fc7 100644 --- a/FredBoat/src/main/resources/lang/el_GR.properties +++ b/FredBoat/src/main/resources/lang/el_GR.properties @@ -7,6 +7,8 @@ playSearching=\u0391\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7 \u03c3\u03c playYoutubeSearchError=\u03a0\u03b1\u03c1\u03bf\u03c5\u03c3\u03b9\u03ac\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ad\u03bd\u03b1 \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03c3\u03c4\u03b7\u03bd \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7 \u03c3\u03c4\u03bf YouTube. \u03a0\u03c1\u03bf\u03c4\u03b9\u03bc\u03ae\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c4\u03b5 \u03ac\u03bc\u03b5\u03c3\u03b5\u03c2 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03b9\u03c2 \u03b1\u03bd\u03c4\u03af \u03b1\u03c5\u03c4\u03cc\u03c5.\n```\n;;play ``` playSearchNoResults=\u0394\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b1\u03bd \u03b1\u03c0\u03bf\u03c4\u03b5\u03bb\u03ad\u03c3\u03bc\u03b1\u03c4\u03b1 \u03b3\u03b9\u03b1 `{q}` playSelectVideo=**\u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b5\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03ba\u03bf\u03bc\u03bc\u03ac\u03c4\u03b9 \u03bc\u03b5 \u03c4\u03b7\u03bd \u03b5\u03bd\u03c4\u03bf\u03bb\u03ae `{0}play n`\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=\u03a3\u03c5\u03bd\u03b4\u03ad\u03c9\u03bc\u03b1\u03b9 {0} joinErrorAlreadyJoining=\u03a0\u03b1\u03c1\u03bf\u03c5\u03c3\u03b9\u03ac\u03c3\u03c4\u03b7\u03ba\u03b5 \u03ad\u03bd\u03b1 \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1. \u0394\u03b5\u03bd \u03bc\u03c0\u03cc\u03c1\u03b5\u03c3\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03ce \u03c3\u03c4\u03bf {0}, \u03b5\u03c0\u03b5\u03b9\u03b4\u03ae \u03ae\u03b4\u03b7 \u03c0\u03c1\u03bf\u03c3\u03c0\u03b1\u03b8\u03ce \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03ce. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03b1\u03c1\u03b3\u03cc\u03c4\u03b5\u03c1\u03b1. pauseAlreadyPaused=\u039f \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03cc\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03b4\u03b9\u03b1\u03ba\u03bf\u03c0\u03b5\u03af. @@ -21,6 +23,9 @@ shuffleOn=\u039f \u03c0\u03b1\u03af\u03ba\u03c4\u03b7\u03c2 \u03b5\u03af\u03bd\u shuffleOff=\u039f \u03c0\u03b1\u03af\u03ba\u03c4\u03b7\u03c2 \u03b4\u03b5\u03bd \u03b1\u03bd\u03b1\u03ba\u03b1\u03c4\u03b5\u03cd\u03b5\u03c4\u03b1\u03b9 \u03c0\u03bb\u03ad\u03bf\u03bd. reshufflePlaylist=\u039f\u03c5\u03c1\u03ac \u03b1\u03bd\u03b1\u03ba\u03b1\u03c4\u03b5\u03cd\u03c4\u03b7\u03ba\u03b5 . reshufflePlayerNotShuffling=\u03a0\u03c1\u03ce\u03c4\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1 \u03c4\u03c5\u03c7\u03b1\u03af\u03b1\u03c2 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=\u0397 \u03bb\u03af\u03c3\u03c4\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ba\u03b5\u03bd\u03ae\! skipOutOfBounds=\u0394\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03ce \u03bd\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c1\u03b3\u03ae\u03c3\u03c9 \u03c4\u03bf \u03ba\u03bf\u03bc\u03bc\u03ac\u03c4\u03b9 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 {0} \u03cc\u03c4\u03b1\u03bd \u03c5\u03c0\u03ac\u03c1\u03c7\u03bf\u03c5\u03bd \u03bc\u03cc\u03bd\u03bf {1} \u03c4\u03c1\u03b1\u03b3\u03bf\u03cd\u03b4\u03b9\u03b1. skipNumberTooLow=\u039f \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03c0\u03bf\u03c5 \u03b4\u03ce\u03c3\u03b1\u03c4\u03b5 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03bc\u03b5\u03b3\u03b1\u03bb\u03cd\u03c4\u03b5\u03c1\u03bf\u03c2 \u03c4\u03bf\u03c5 0. @@ -244,12 +249,14 @@ helpJoinCommand=\u039a\u03ac\u03bd\u03c4\u03b5 \u03c4\u03bf bot \u03bd\u03b1 \u0 helpLeaveCommand=\u039a\u03ac\u03bd\u03c4\u03b5 \u03c4\u03bf bot \u03bd\u03b1 \u03c6\u03cd\u03b3\u03b5\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf voice channel \u03c3\u03b1\u03c2. helpPauseCommand=\u03a0\u03b1\u03cd\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2. helpPlayCommand=\u0391\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae \u03bc\u03bf\u03c5\u03c3\u03b9\u03ba\u03ae\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03b4\u03b9\u03b4\u03cc\u03bc\u03b5\u03bd\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03ae \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03ad\u03bd\u03b1 \u03ba\u03bf\u03bc\u03bc\u03ac\u03c4\u03b9. \u0393\u03b9\u03b1 \u03bc\u03b9\u03b1 \u03c0\u03bb\u03ae\u03c1\u03b7 \u03bb\u03af\u03c3\u03c4\u03b1 \u03c0\u03b7\u03b3\u03ce\u03bd \u03b5\u03c0\u03b9\u03c3\u03ba\u03b5\u03c6\u03b8\u03b5\u03af\u03c4\u03b5 {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=\u0391\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae \u03bc\u03bf\u03c5\u03c3\u03b9\u03ba\u03ae\u03c2 \u03b1\u03c0\u03cc \u03c4\u03b7 \u03b4\u03b5\u03b4\u03bf\u03bc\u03ad\u03bd\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL \u03ae \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7\u03c2 \u03b3\u03b9\u03b1 \u03bc\u03b9\u03b1 \u03c0\u03af\u03c3\u03c4\u03b1. \u039a\u03bf\u03bc\u03bc\u03ac\u03c4\u03b9\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c4\u03af\u03b8\u03b5\u03bd\u03c4\u03b1\u03b9 \u03c3\u03c4\u03b7\u03bd \u03ba\u03bf\u03c1\u03c5\u03c6\u03ae \u03c4\u03b7\u03c2 \u03bf\u03c5\u03c1\u03ac\u03c2. \u0393\u03b9\u03b1 \u03bc\u03b9\u03b1 \u03c0\u03bb\u03ae\u03c1\u03b7 \u03bb\u03af\u03c3\u03c4\u03b1 \u03c0\u03b7\u03b3\u03ce\u03bd \u03c0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b5\u03c0\u03b9\u03c3\u03ba\u03b5\u03c6\u03b8\u03b5\u03af\u03c4\u03b5 {0} helpPlaySplitCommand=\u0394\u03b9\u03b1\u03b9\u03c1\u03ad\u03c3\u03b5\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03b2\u03af\u03bd\u03c4\u03b5\u03bf YouTube \u03c3\u03b5 \u03bc\u03b9\u03b1 \u03bb\u03af\u03c3\u03c4\u03b1 \u03c4\u03c1\u03b1\u03b3\u03bf\u03c5\u03b4\u03b9\u03ce\u03bd \u03c0\u03bf\u03c5 \u03c0\u03b1\u03c1\u03ad\u03c7\u03b5\u03c4\u03b1\u03b9 \u03c3\u03c4\u03b7\u03bd \u03c0\u03b5\u03c1\u03b9\u03b3\u03c1\u03b1\u03c6\u03ae \u03c4\u03bf\u03c5. helpRepeatCommand=\u0395\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae \u03bc\u03b5\u03c4\u03b1\u03be\u03cd \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03b9\u03ce\u03bd \u03b5\u03c0\u03b1\u03bd\u03ac\u03bb\u03b7\u03c8\u03b7\u03c2. helpReshuffleCommand=\u0395\u03c0\u03b1\u03bd\u03ac\u03bb\u03b7\u03c8\u03b7 \u03c4\u03c5\u03c7\u03b1\u03af\u03b1\u03c2 \u03c3\u03b5\u03b9\u03c1\u03ac\u03c2 \u03c3\u03c4\u03b7\u03bd \u03c0\u03b1\u03c1\u03bf\u03cd\u03c3\u03b1 \u03bb\u03af\u03c3\u03c4\u03b1 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2. helpSelectCommand=\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03b1\u03c0\u03cc \u03c4\u03b1 \u03ba\u03bf\u03bc\u03bc\u03ac\u03c4\u03b9\u03b1 \u03c0\u03bf\u03c5 \u03c0\u03c1\u03bf\u03c3\u03c6\u03ad\u03c1\u03bf\u03bd\u03c4\u03b1\u03b9 \u03bc\u03b5\u03c4\u03ac \u03b1\u03c0\u03cc \u03bc\u03b9\u03b1 \u03b1\u03bd\u03b1\u03b6\u03ae\u03c4\u03b7\u03c3\u03b7 \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c4\u03b1 \u03c0\u03b1\u03af\u03be\u03b5\u03b9 \u03c4\u03bf bot. helpShuffleCommand=\u0395\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03c4\u03c5\u03c7\u03b1\u03af\u03b1\u03c2 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae\u03c2 \u03ba\u03bf\u03bc\u03bc\u03b1\u03c4\u03b9\u03bf\u03cd \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03c4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1 \u03bf\u03c5\u03c1\u03ac. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=\u03a0\u03b1\u03c1\u03b1\u03bb\u03b5\u03af\u03c8\u03b5\u03c4\u03b5 \u03c4\u03bf \u03c4\u03c1\u03ad\u03c7\u03bf\u03bd \u03c4\u03c1\u03b1\u03b3\u03bf\u03cd\u03b4\u03b9, \u03c4\u03bf \u03c4\u03c1\u03b1\u03b3\u03bf\u03cd\u03b4\u03b9 n'th \u03c3\u03c4\u03b7\u03bd \u03bf\u03c5\u03c1\u03ac \u03ae \u03cc\u03bb\u03b1 \u03c4\u03b1 \u03c4\u03c1\u03b1\u03b3\u03bf\u03cd\u03b4\u03b9\u03b1 \u03b1\u03c0\u03cc \u03c4\u03bf n \u03c3\u03c4\u03bf m \u03ae \u03c0\u03b1\u03c1\u03b1\u03bb\u03b5\u03af\u03c8\u03c4\u03b5 \u03cc\u03bb\u03b1 \u03c4\u03b1 \u03c4\u03c1\u03b1\u03b3\u03bf\u03cd\u03b4\u03b9\u03b1 \u03b5\u03bd\u03cc\u03c2. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03ce \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf \u03bc\u03b5 \u03bc\u03ad\u03c4\u03c1\u03bf. helpStopCommand=\u03a3\u03c4\u03b1\u03bc\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd player \u03ba\u03b1\u03b9 \u03b1\u03c0\u03b1\u03bb\u03bf\u03b9\u03c6\u03ae \u03c4\u03b7\u03c2 \u03bb\u03af\u03c3\u03c4\u03b1\u03c2 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2. \u03a0\u03c1\u03bf\u03bf\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03c3\u03c5\u03bd\u03c4\u03bf\u03bd\u03b9\u03c3\u03c4\u03ad\u03c2 \u03bc\u03b5 \u03ac\u03b4\u03b5\u03b9\u03b1 \u0394\u03b9\u03b1\u03c7\u03b5\u03af\u03c1\u03b9\u03c3\u03b7 \u03bc\u03b7\u03bd\u03c5\u03bc\u03ac\u03c4\u03c9\u03bd. helpUnpauseCommand=\u03a3\u03c5\u03bd\u03ad\u03c7\u03b9\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03b1\u03bd\u03b1\u03c0\u03b1\u03c1\u03b1\u03b3\u03c9\u03b3\u03ae\u03c2. @@ -339,4 +346,3 @@ modulesHowTo=\u03a0\u03b5\u03af\u03c4\u03b5 \u03c4\u03bf {0} \u03b3\u03b9\u03b1 parseNotAUser=\u0397 \u03b5\u03af\u03c3\u03bf\u03b4\u03bf\u03c2 {0} \u03c3\u03b1\u03c2 \u03b4\u03b5\u03bd \u03b1\u03c0\u03ad\u03b4\u03c9\u03c3\u03b5 \u03ad\u03bd\u03b1\u03bd \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 Discord. parseNotAMember=\u039f \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7\u03c2 {0} \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03bc\u03ad\u03bb\u03bf\u03c2 \u03b1\u03c5\u03c4\u03ae\u03c2 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03bd\u03c4\u03b5\u03c7\u03bd\u03af\u03b1\u03c2. parseSnowflakeIdHelp=\u0388\u03c7\u03b5\u03c4\u03b5 \u03c0\u03c1\u03cc\u03b2\u03bb\u03b7\u03bc\u03b1 \u03bc\u03b5 \u03c4\u03b7 \u03bb\u03ae\u03c8\u03b7 \u03c4\u03bf\u03c5 \u03b1\u03bd\u03b1\u03b3\u03bd\u03c9\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03bf\u03cd \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 / \u03bc\u03b7\u03bd\u03cd\u03bc\u03b1\u03c4\u03bf\u03c2 / \u03ba\u03b1\u03bd\u03b1\u03bb\u03b9\u03bf\u03cd / \u03ba\u03ac\u03c4\u03b9 \u03ac\u03bb\u03bb\u03bf; \u0395\u03bb\u03ad\u03b3\u03be\u03c4\u03b5 \u03c4\u03b1 \u03ad\u03b3\u03b3\u03c1\u03b1\u03c6\u03b1 \u03c4\u03bf\u03c5 Discord \u03c3\u03c4\u03bf {0} - diff --git a/FredBoat/src/main/resources/lang/en_PT.properties b/FredBoat/src/main/resources/lang/en_PT.properties index 947046447..10edae91d 100644 --- a/FredBoat/src/main/resources/lang/en_PT.properties +++ b/FredBoat/src/main/resources/lang/en_PT.properties @@ -7,6 +7,8 @@ playSearching=Lookin'' on map f''r ''{q}''... playYoutubeSearchError=Som't'in be wrong. Got ye coordinates in'te'd.\n```\n;;play ``` playSearchNoResults=Not''in be found in map f''r ''{q}'' playSelectVideo=**Choos'' yer shanty wit'' t'' `{0}play 1-5`command''\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=C''min'' upon {0} joinErrorAlreadyJoining=Som''t''n'' be off. Can''t com'' upon {0} I''m ''ready c''min'' upon t''e area. Keep y''r course mate. pauseAlreadyPaused=T'e musik be held bef'r' ye came. @@ -21,6 +23,9 @@ shuffleOn=T'e sound be rand'm. shuffleOff=T'e sound be in line. reshufflePlaylist=Queue reshuffled. reshufflePlayerNotShuffling=Get ye head out the sea and enable shuffle first. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=No sounds be 'n line\! skipOutOfBounds=Ye c''n''t stop sound {0} ther'' only be {1} sounds. skipNumberTooLow=Yer n'm'r mus' be big'r then 0. @@ -244,12 +249,14 @@ helpJoinCommand=Make the bot join your current voice channel. helpLeaveCommand=Make the bot leave the current voice channel. helpPauseCommand=Pause the player. helpPlayCommand=Play music from the given URL or search for a track. For a full list of sources please visit {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Split a YouTube video into a tracklist provided in it's description. helpRepeatCommand=Toggle between repeat modes. helpReshuffleCommand=Reshuffle the current queue. helpSelectCommand=Select one of the offered tracks after a search to play. helpShuffleCommand=Toggle shuffle mode for the current queue. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Skip the current song, the n'th song in the queue, all songs from n to m, or all songs from mentioned users. Please use in moderation. helpStopCommand=Stop the player and clear the playlist. Reserved for moderators with Manage Messages permission. helpUnpauseCommand=Unpause the player. @@ -339,4 +346,3 @@ modulesHowTo=Say {0} to enable/disable modules. parseNotAUser=Yer'' heartie {0} ain''t real. parseNotAMember=User {0} is not a member of this guild. parseSnowflakeIdHelp=Having trouble getting the id of a user/message/channel/something else? Check out the Discord docs at {0} - diff --git a/FredBoat/src/main/resources/lang/en_TS.properties b/FredBoat/src/main/resources/lang/en_TS.properties index ded724105..98e3fdcd2 100644 --- a/FredBoat/src/main/resources/lang/en_TS.properties +++ b/FredBoat/src/main/resources/lang/en_TS.properties @@ -7,6 +7,8 @@ playSearching=Fine, I''ll search YouTube for `{q}`, but I''m not doing it for yo playYoutubeSearchError=An error occurred when searching YouTube. I can't imagine it being anyone's fault but your's this time.\n```\n;;play ``` playSearchNoResults=I''d tell you what I found when searching for `{q}`, but then I''d have to kill you playSelectVideo=**Now....now... choose the song you want by doing`{0}play 1-5`\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Joining {0}... A-And it''s not because you told me to\! joinErrorAlreadyJoining=Don''t rush me\! I am already connecting to your stupid {0} or whatever channel\!\! \:unamused\: pauseAlreadyPaused=Guess what? It's already paused, you fool. @@ -21,6 +23,9 @@ shuffleOn=I'll mix the song list for you... but only once\! shuffleOff=But I just shuffled it\! Now you want me to sort it... Fine\! This is so labor intensive... I hate you\! reshufflePlaylist=Queue reshuffled. reshufflePlayerNotShuffling=You must first turn on shuffle mode. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=Why would you even want to skip a Track when the Queue is empty?\! Call me again when you've figured out how to actually add tracks. skipOutOfBounds=Are you blind\! Track {0} doesn''t exist\! chose between 1-{1} idiot\! skipNumberTooLow=What is this\!? Don't give me 0. @@ -244,12 +249,14 @@ helpJoinCommand=Make the bot join your current voice channel. helpLeaveCommand=Make the bot leave the current voice channel. helpPauseCommand=Pause the player. helpPlayCommand=Play music from the given URL or search for a track. For a full list of sources please visit {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Split a YouTube video into a tracklist provided in it's description. helpRepeatCommand=Toggle between repeat modes. helpReshuffleCommand=Reshuffle the current queue. helpSelectCommand=Select one of the offered tracks after a search to play. helpShuffleCommand=Toggle shuffle mode for the current queue. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Skip the current song, the n'th song in the queue, all songs from n to m, or all songs from mentioned users. Please use in moderation. helpStopCommand=Stop the player and clear the playlist. Reserved for moderators with Manage Messages permission. helpUnpauseCommand=Unpause the player. @@ -339,4 +346,3 @@ modulesHowTo=Say {0} to enable/disable modules. parseNotAUser=Your input {0} did not yield a Discord user. parseNotAMember=User {0} is not a member of this guild. parseSnowflakeIdHelp=Having trouble getting the id of a user/message/channel/something else? Check out the Discord docs at {0} - diff --git a/FredBoat/src/main/resources/lang/es_ES.properties b/FredBoat/src/main/resources/lang/es_ES.properties index ca0de6a0a..17f6b8178 100644 --- a/FredBoat/src/main/resources/lang/es_ES.properties +++ b/FredBoat/src/main/resources/lang/es_ES.properties @@ -7,6 +7,8 @@ playSearching=Buscando en YouTube ''{q}''... playYoutubeSearchError=Se ha producido un error al buscar en YouTube. Considere vincular directamente al audio. ``` ;;play ``` playSearchNoResults=No se han encontrado resultados para `{q}` playSelectVideo=**Por favor selecciona una canci\u00f3n con el comando `{0}play n`** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Uni\u00e9ndose a {0} joinErrorAlreadyJoining=Ha ocurrido un error. No se pudo unir a {0} porque ya estoy tratando de conectar a ese canal. Por favor int\u00e9ntalo de nuevo. pauseAlreadyPaused=El reproductor ya est\u00e1 pausado. @@ -21,6 +23,9 @@ shuffleOn=El reproductor est\u00e1 ahora en modo aleatorio. shuffleOff=El reproductor ya no est\u00e1 en modo aleatorio. reshufflePlaylist=Cola reorganizada. reshufflePlayerNotShuffling=Primero debes de activar el modo aleatorio. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=\u00a1La cola est\u00e1 vac\u00eda\! skipOutOfBounds=No se puede quitar la pista n\u00famero {0} cuando solo hay {1} pistas. skipNumberTooLow=El n\u00famero dado debe ser mayor que 0. @@ -62,7 +67,7 @@ npDescription=Descripci\u00f3n npLoadedSoundcloud=[{0}/{1}]\n\nCargado desde Soundcloud npLoadedBandcamp={0}\n\nCargado desde Bandcamp npLoadedTwitch=Cargado desde Twitch -npRequestedBy=Requested by {0} +npRequestedBy=Solicitado por {0} permissionMissingBot=Necesito el siguiente permiso para realizar esa acci\u00f3n\: permissionMissingInvoker=Necesita el siguiente permiso para realizar esa acci\u00f3n\: permissionEmbedLinks=Insertar enlaces @@ -92,11 +97,11 @@ loadPlaylistTooMany=Se han a\u00f1adido {0} pistas. Se han encontrado demasiadas loadErrorCommon=Hubo un error al cargar informaci\u00f3n desde `{0}`\:\n{1} loadErrorSusp=Sospechoso error al cargar informaci\u00f3n para `{0}`. loadQueueTrackLimit=\u00a1No se puede agregar pistas a una cola con m\u00e1s de {0} pistas\! Esto es para prevenir el abuso. -loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. -loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. -loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. -loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. -loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! +loadPlaylistDisabled=Este servidor ha deshabilitado la puesta en cola de listas de reproducci\u00f3n. Por favor, haga cola en cada pista individualmente. +loadMaxTracksExceeded=Este servidor no permite poner en cola m\u00e1s de {0} pistas. +loadMaxUserTracksExceeded=Este servidor no le permite tener m\u00e1s de {0} pistas en la cola. +loadMaxTrackLengthExceeded=Este servidor no le permite reproducir pistas m\u00e1s largas que {0}. Por favor, intente algo m\u00e1s corto. +loadPlaylistGeneralError=\u00a1Las pistas {0} no se agregaron porque este servidor impone restricciones de cola\! loadAnnouncePlaylist=Para cargar listas de reproducci\u00f3n **{0} ** con hasta pistas de ''{1}''. Esto puede tardar un poco, por favor se paciente. playerUserNotInChannel=Usted debe unirse a un canal de voz primero. playerJoinConnectDenied=No tengo permitido conectarme a ese canal de voz. @@ -108,7 +113,7 @@ shutdownRestarting=FredBoat\u266a\u266a se est\u00e1 reiniciando. Esto solo debe shutdownIndef=FredBoat\u266a\u266a se est\u00e1 apagando. Una vez que el bot regrese la lista de reproducci\u00f3n actual se reanudar\u00e1. shutdownPersistenceFail=Ha ocurrido un error guardando archivo de persistencia\: {0} reloadSuccess=Recargando lista de reproducci\u00f3n. `{0}` pistas encontradas. -trackAnnounce=Hola. +trackAnnounce=Reproducci\u00f3n **{0}**. Solicitado por\: **{1} **. cmdAccessDenied=\u00a1Usted no est\u00e1 autorizado para usar ese comando\! malRevealAnime={0}\: La b\u00fasqueda encontr\u00f3 un anime.\n malTitle={0}**T\u00edtulo\: **{1}\n @@ -244,12 +249,14 @@ helpJoinCommand=Haz que el bot se una a tu canal actual. helpLeaveCommand=Hacer que el bot deje el canal de voz actual. helpPauseCommand=Pausar el reproductor. helpPlayCommand=Reproduzca m\u00fasica desde una URL o busque una canci\u00f3n. Para una lista completa de fuentes, por favor visite {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Reproduce m\u00fasica a partir de la URL especificada o de una b\u00fasqueda. Las canciones son agregadas al comienzo de la cola.\nPara una lista completa de proveedores por favor visite {0} helpPlaySplitCommand=Dividir un video de YouTube a un tracklist en su descripci\u00f3n. helpRepeatCommand=Alternar entre los modos de repetici\u00f3n. helpReshuffleCommand=Mezclar la cola actual. helpSelectCommand=Selecciona una de las canciones ofrecidas. helpShuffleCommand=Activar el modo aleatorio para la cola actual. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Salta la canci\u00f3n actual, la en\u00e9sima canci\u00f3n en la cola, todas las canciones de n a m, o todas las canciones de los usuarios mencionados. Por favor use con moderaci\u00f3n. helpStopCommand=Detener el reproductor y borrar la lista de reproducci\u00f3n. Reservado a los moderadores con el permiso de administrar mensajes. helpUnpauseCommand=Activar el reproductor. @@ -339,4 +346,3 @@ modulesHowTo=Di {0} para activar/desactivar los m\u00f3dulos. parseNotAUser=Su entrada {0} no produjo un usuario de Discord. parseNotAMember=El usuario {0} no es miembro de este gremio. parseSnowflakeIdHelp=\u00bfTiene problemas para obtener la identificaci\u00f3n de un usuario / mensaje / canal / algo m\u00e1s? Consulte los documentos de Discordia en {0} - diff --git a/FredBoat/src/main/resources/lang/et_EE.properties b/FredBoat/src/main/resources/lang/et_EE.properties index 683a92ef0..43bacbac5 100644 --- a/FredBoat/src/main/resources/lang/et_EE.properties +++ b/FredBoat/src/main/resources/lang/et_EE.properties @@ -7,6 +7,8 @@ playSearching=Otsin YouTubest " {q} "... playYoutubeSearchError=Youtube otsimisel ilmnes t\u00f5rge. Proovige sidudes otse audio allikast s\u00fcntaksiga ;;m\u00e4ngi playSearchNoResults=Tulemusi ei leidnud {q} playSelectVideo=**Palun valige k\u00e4sk {0}m\u00e4ngi kommand** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Liitun {0} joinErrorAlreadyJoining=Ilmnes t\u00f5rge. Ei saa liituda {0} sest ma juba proovin liituda sellesse vestlusesse. Palun proovige uuesti. pauseAlreadyPaused=M\u00e4ngija on juba peatatud. @@ -21,6 +23,9 @@ shuffleOn=M\u00e4ngija on n\u00fc\u00fcd segatud. shuffleOff=M\u00e4ngija pole enam segatud. reshufflePlaylist=J\u00e4rjekord uuesti segatud. reshufflePlayerNotShuffling=Te peate esmalt l\u00fclitama segamis re\u017eiimi sisse. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=J\u00e4rjekord on t\u00fchi\! skipOutOfBounds=Ei saa eemaldada laulu number {0} kui seal on ainult {1} lood. skipNumberTooLow=Antud number peab olema suurem kui 0. @@ -244,12 +249,14 @@ helpJoinCommand=Kutsuge bot oma praegusesse h\u00e4\u00e4le kanalisse. helpLeaveCommand=Eemaldage bot oma praegusest h\u00e4\u00e4le kanalist. helpPauseCommand=Pause the player. helpPlayCommand=Play music from the given URL or search for a track. For a full list of sources please visit {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Split a YouTube video into a tracklist provided in it's description. helpRepeatCommand=Toggle between repeat modes. helpReshuffleCommand=Reshuffle the current queue. helpSelectCommand=Select one of the offered tracks after a search to play. helpShuffleCommand=Toggle shuffle mode for the current queue. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Skip the current song, the n'th song in the queue, all songs from n to m, or all songs from mentioned users. Please use in moderation. helpStopCommand=Stop the player and clear the playlist. Reserved for moderators with Manage Messages permission. helpUnpauseCommand=Unpause the player. @@ -339,4 +346,3 @@ modulesHowTo=Say {0} to enable/disable modules. parseNotAUser=Your input {0} did not yield a Discord user. parseNotAMember=User {0} is not a member of this guild. parseSnowflakeIdHelp=Having trouble getting the id of a user/message/channel/something else? Check out the Discord docs at {0} - diff --git a/FredBoat/src/main/resources/lang/fa_IR.properties b/FredBoat/src/main/resources/lang/fa_IR.properties index 0a7277b59..e9ef7cf04 100644 --- a/FredBoat/src/main/resources/lang/fa_IR.properties +++ b/FredBoat/src/main/resources/lang/fa_IR.properties @@ -7,6 +7,8 @@ playSearching=\u062f\u0631 \u062d\u0627\u0644 \u062c\u0633\u062a \u0648 \u062c\u playYoutubeSearchError=\u0645\u0634\u06a9\u0644\u06cc \u062f\u0631 \u062c\u0633\u062a \u0648 \u062c\u0648 \u062f\u0631 \u06cc\u0648\u062a\u06cc\u0648\u0628 \u0631\u062e \u062f\u0627\u062f. \u0644\u0637\u0641\u0627 \u0644\u06cc\u0646\u06a9\u06cc \u0645\u0633\u062a\u0642\u06cc\u0645 \u0628\u0647 \u0645\u0646\u0628\u0639 \u0635\u062f\u0627 \u0642\u0631\u0627\u0631 \u062f\u0647\u06cc\u062f.\n```\n;;play <\u0622\u062f\u0631\u0633 \u0633\u0627\u06cc\u062a>``` playSearchNoResults=\u0646\u062a\u06cc\u062c\u0647 \u0627\u06cc \u0628\u0631\u0627\u06cc `{q}` \u06cc\u0627\u0641\u062a \u0646\u0634\u062f playSelectVideo=**\u0644\u0637\u0641\u0627 \u0645\u0648\u0632\u06cc\u06a9\u06cc \u0631\u0627 \u0628\u0627 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0627\u0632 \u062f\u0633\u062a\u0648\u0631 `{0}play 1-5` \u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0646\u06cc\u062f.** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=\u062f\u0631 \u062d\u0627\u0644 \u067e\u06cc\u0648\u0633\u062a\u0646 \u0628\u0647 {0} joinErrorAlreadyJoining=\u062e\u0637\u0627\u06cc\u06cc \u0631\u062e \u062f\u0627\u062f. \u0646\u062a\u0648\u0627\u0646\u0633\u062a\u0645 \u0628\u0647 {0} \u0648\u0627\u0631\u062f \u0634\u0648\u0645 \u0686\u0648\u0646 \u0627\u0632 \u067e\u06cc\u0634\u062a\u0631 \u062f\u0631 \u062a\u0644\u0627\u0634 \u0628\u0631\u0627\u06cc \u0627\u062a\u0635\u0627\u0644 \u0628\u0647 \u0622\u0646 \u06a9\u0627\u0646\u0627\u0644 \u0647\u0633\u062a\u0645. \u0644\u0637\u0641\u0627 \u062f\u0648\u0628\u0627\u0631\u0647 \u0633\u0639\u06cc \u06a9\u0646\u06cc\u062f. pauseAlreadyPaused=\u067e\u0644\u06cc\u0631 \u0642\u0628\u0644\u0627 \u0645\u062a\u0648\u0642\u0641 \u0634\u062f\u0647 \u0627\u0633\u062a. @@ -21,6 +23,9 @@ shuffleOn=\u067e\u0644\u06cc\u0631 \u062f\u0631 \u062d\u0627\u0644\u062a Shuffle shuffleOff=\u067e\u0644\u06cc\u0631 \u062f\u06cc\u06af\u0631 \u062f\u0631 \u062d\u0627\u0644\u062a Shuffle \u0646\u06cc\u0633\u062a. reshufflePlaylist=\u0644\u06cc\u0633\u062a \u062f\u0648\u0628\u0627\u0631\u0647 Shuffle \u0634\u062f. reshufflePlayerNotShuffling=\u0627\u0628\u062a\u062f\u0627 \u0634\u0645\u0627 \u0628\u0627\u06cc\u062f \u062d\u0627\u0644\u062a Shuffle \u0631\u0627 \u0631\u0648\u0634\u0646 \u06a9\u0646\u06cc\u062f. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=\u0644\u06cc\u0633\u062a \u067e\u062e\u0634 \u062e\u0627\u0644\u06cc \u0627\u0633\u062a\! skipOutOfBounds=\u0646\u0645\u06cc\u062a\u0648\u0627\u0646\u06cc\u062f \u0622\u0647\u0646\u06af \u0634\u0645\u0627\u0631\u0647 {0} \u0631\u0627 \u062d\u0630\u0641 \u06a9\u0646\u06cc\u062f \u0647\u0646\u06af\u0627\u0645\u06cc \u06a9\u0647 \u0641\u0642\u0637 {1} \u0622\u0647\u0646\u06af \u0645\u0648\u062c\u0648\u062f \u0627\u0633\u062a. skipNumberTooLow=\u0639\u062f\u062f \u062f\u0627\u062f\u0647 \u0634\u062f\u0647 \u0628\u0627\u06cc\u062f \u0628\u0632\u0631\u06af\u062a\u0631 \u0627\u0632 0 \u0628\u0627\u0634\u062f. @@ -244,12 +249,14 @@ helpJoinCommand=Make the bot join your current voice channel. helpLeaveCommand=Make the bot leave the current voice channel. helpPauseCommand=\u062a\u0648\u0642\u0641 \u067e\u062e\u0634. helpPlayCommand=Play music from the given URL or search for a track. For a full list of sources please visit {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Split a YouTube video into a tracklist provided in it's description. helpRepeatCommand=Toggle between repeat modes. helpReshuffleCommand=Reshuffle the current queue. helpSelectCommand=Select one of the offered tracks after a search to play. helpShuffleCommand=Toggle shuffle mode for the current queue. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Skip the current song, the n'th song in the queue, all songs from n to m, or all songs from mentioned users. Please use in moderation. helpStopCommand=Stop the player and clear the playlist. Reserved for moderators with Manage Messages permission. helpUnpauseCommand=Unpause the player. @@ -339,4 +346,3 @@ modulesHowTo=Say {0} to enable/disable modules. parseNotAUser=Your input {0} did not yield a Discord user. parseNotAMember=User {0} is not a member of this guild. parseSnowflakeIdHelp=Having trouble getting the id of a user/message/channel/something else? Check out the Discord docs at {0} - diff --git a/FredBoat/src/main/resources/lang/fi_FI.properties b/FredBoat/src/main/resources/lang/fi_FI.properties index d4ac21e83..e17c7c44b 100644 --- a/FredBoat/src/main/resources/lang/fi_FI.properties +++ b/FredBoat/src/main/resources/lang/fi_FI.properties @@ -7,6 +7,8 @@ playSearching=Haetaan YouTubesta `{q}`... playYoutubeSearchError=Virhe kappaletta etsiess\u00e4. Yrit\u00e4 suoraa linkki\u00e4.\n```\n;;play ``` playSearchNoResults=Ei tuloksia haulle `{q}` playSelectVideo=**Valitse laulu `{0}play n` -komennolla\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Liityt\u00e4\u00e4n {0} joinErrorAlreadyJoining=Virhe liittyess\u00e4 kanavalle {0} koska yrit\u00e4n jo liitty\u00e4 tuolle kanavalle. Yrit\u00e4 uudelleen, kiitos. pauseAlreadyPaused=Soitin on jo keskeytetty. @@ -21,6 +23,9 @@ shuffleOn=Soittimen lista on nyt sekoitettu. shuffleOff=Soittimen lista ei ole en\u00e4\u00e4n sekoitettu. reshufflePlaylist=Soittolista sekoitettiin uudelleen. reshufflePlayerNotShuffling=Sinun pit\u00e4\u00e4 ensin ottaa k\u00e4ytt\u00f6\u00f6n sekoitustila. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=Soittolista on tyhj\u00e4\! skipOutOfBounds=Et voi poistaa kappaletta numero {0} koska jonossa on vain {1} kappaletta. skipNumberTooLow=Annetun numeron pit\u00e4\u00e4 olla isompi kuin 0. @@ -244,12 +249,14 @@ helpJoinCommand=Laita bot liittym\u00e4\u00e4n sinun k\u00e4yt\u00f6ss\u00e4si o helpLeaveCommand=Laita bot poistumaan t\u00e4m\u00e4nhetkiselt\u00e4 \u00e4\u00e4nikanavalta. helpPauseCommand=Pys\u00e4ytt\u00e4\u00e4 soittimen. helpPlayCommand=Soita musiikkia linkist\u00e4 tai hausta. Jos haluat t\u00e4yden listan l\u00e4hteist\u00e4, vieraile {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Jaa Youtube video kappalelistaan joka on tarjottu sen kuvauksessa. helpRepeatCommand=Vaihda toistomuotojen v\u00e4lill\u00e4. helpReshuffleCommand=Sekoita nykyinen soittolista. helpSelectCommand=Valitse yksi tarjotuista kappaleista haun j\u00e4lkeen soittaaksesi sen. helpShuffleCommand=Vaihda sekoitustila p\u00e4\u00e4lle nykyiselle soittolistalle. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Ohita nykyinen laulu, Muut laulut jonosta, tai kaikki laulut. K\u00e4yt\u00e4 moderaattorina. helpStopCommand=Pys\u00e4yt\u00e4 soitin ja raivaa t\u00e4m\u00e4nhetkinen soittolista. K\u00e4yt\u00f6ss\u00e4 moderaattoreille jotka pystyv\u00e4t hallitsemaan viestej\u00e4. helpUnpauseCommand=Anna soittimen jatkaa. @@ -339,4 +346,3 @@ modulesHowTo=Sano {0} ottaaksesi moduuleita k\u00e4ytt\u00f6\u00f6n/pois k\u00e4 parseNotAUser={0} ei vastaa Discord k\u00e4ytt\u00e4j\u00e4\u00e4. parseNotAMember=K\u00e4ytt\u00e4j\u00e4 {0} ei ole t\u00e4m\u00e4n palvelimen j\u00e4sen. parseSnowflakeIdHelp=Onko sinulla vaikeuksia saada k\u00e4ytt\u00e4j\u00e4n/viestin/kanavan/jonkin muun tunnus? Tutustu Discordin ohjeisiin t\u00e4\u00e4ll\u00e4 {0} - diff --git a/FredBoat/src/main/resources/lang/fil_PH.properties b/FredBoat/src/main/resources/lang/fil_PH.properties index 2a6ef8874..c5779f413 100644 --- a/FredBoat/src/main/resources/lang/fil_PH.properties +++ b/FredBoat/src/main/resources/lang/fil_PH.properties @@ -7,6 +7,8 @@ playSearching=Pagsasaliksik sa YouTube para sa mga ''{q}''... playYoutubeSearchError=Isang mali ang naganap kapag nagsasaliksik sa YouTube. Isiping nag-uugnay diretso sa audio na mga pinagkukunan sa halip. ``` ;; Maglaro ng ' ' playSearchNoResults=Walang resulta para sa `{q}` playSelectVideo=* * Mangyaring pumili ng isang tugtog gamit ang command na ''{0}play n''\: * * +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Pagsali {0} joinErrorAlreadyJoining=Isang mali ang naganap. Hindi makasali sa {0} dahil pinipilit kong kumonekta sa channel na iyon. Pakisubukang muli. pauseAlreadyPaused=Ang player ay huminto muna. @@ -21,6 +23,9 @@ shuffleOn=Ang player ay nakagulo shuffleOff=Ang player ay hindi na magulo reshufflePlaylist=Nahalo na po ang mga nakalistang tugtog. reshufflePlayerNotShuffling=Kailangan niyo po munang iturn-on ang shuffle mode. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=Walang nakalistang tugtog\! skipOutOfBounds=Hindi maitanggal ang tugtog bilng {0} dahil meron lamang {1} na tugtog. skipNumberTooLow=Ang numero ay kailangan na mas mataas sa 0. @@ -244,12 +249,14 @@ helpJoinCommand=Gumawa ang bot para sumali sa inyong kasalukuyang tsanel ng tini helpLeaveCommand=Nagawa na bot na iwanan ang kasalukuyang tsanel ng tinig. helpPauseCommand=Ihinto ang musiko. helpPlayCommand=Magpatugtog ng musika mula sa ibinigay na URL o kaya ay humanap sa trak. Para sa kabuong listahan ng mapagkukunan mangyaring bisitahin ang {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Itutog ang musika na galing sa URL o humanap ng track. Idinadagdag ang mga track sa taas ng pila. Para sa buong listahan ng pinagmulaan ng musika bisitahin ang {0} helpPlaySplitCommand=Nahati ang bidyo sa YouTube sa traklis na inilalalaan sa paglalarawan. helpRepeatCommand=Magpaulit-ulit sa pagitan ng inulit na modes. helpReshuffleCommand=Pag-aayos sa kasalukuyang pila. helpSelectCommand=Pumili ng isa sa mga inaalok ng traks pagkatapos ng paghahanap para patutugin. helpShuffleCommand=Toggle ayusin ang mode para sa kasalukuyang pila. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Laktawan ang kasalukuyang awitin, ang n'th na awitin sa mga pagpipilian, ang lahat ng awitin mula sa n to m, o kaya ang lahat ng awitin mula sa nabanggit ng mga gumagamit. Pakiusap gamitin lamang sa kahinahunan. helpStopCommand=Ihinto ang pagpapatugtutog at linisin ang listahan. Nakareserba para sa moderators sa pahintulot ng pangangasiwa ng mga mensahe. helpUnpauseCommand=Alisin sa pagkakahinto ang tugtog. @@ -339,4 +346,3 @@ modulesHowTo=Sabihin {0} para paganahin/huwag paganahin ang mga modyul. parseNotAUser=Iyong input na {0} ay hindi tunay na Discord user. parseNotAMember=Gumagamit na si {0} ay hindi isang miyembro ng guild na ito. parseSnowflakeIdHelp=Nagkakaproblema ka ba sa pagkuha ng id ng isang user/message/channel/may iba? Suriin ang mga documento para sa Discord sa {0} - diff --git a/FredBoat/src/main/resources/lang/fr_FR.properties b/FredBoat/src/main/resources/lang/fr_FR.properties index 60043deb0..5ecf6a106 100644 --- a/FredBoat/src/main/resources/lang/fr_FR.properties +++ b/FredBoat/src/main/resources/lang/fr_FR.properties @@ -7,6 +7,8 @@ playSearching=Recherche Youtube de `{q}`... playYoutubeSearchError=Une erreur est survenue au cours de la recherche Youtube. Veuillez consid\u00e9rer l'utilisation d'un lien direct vers la source audio.\n```\n;;play ``` playSearchNoResults=Aucun r\u00e9sultat pour `{q}` playSelectVideo=**Veuillez s\u00e9lectionner une piste avec la commande `{0}play n`\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Je rejoins le canal {0} joinErrorAlreadyJoining=Une erreur est survenue. Je n''ai pas pu rejoindre {0} car je tente d\u00e9j\u00e0 de me connecter \u00e0 ce canal actuellement. Veuillez essayez \u00e0 nouveau. pauseAlreadyPaused=Le lecteur est d\u00e9j\u00e0 sur pause. @@ -21,6 +23,9 @@ shuffleOn=Le lecteur est \u00e0 pr\u00e9sent en mode lecture al\u00e9atoire. shuffleOff=Le lecteur n'est plus en mode lecture al\u00e9atoire. reshufflePlaylist=File d'attente en mode al\u00e9atoire. reshufflePlayerNotShuffling=Vous devez d'abord passer en mode al\u00e9atoire. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=La file d\u2019attente est vide \! skipOutOfBounds=Impossible de supprimer la piste num\u00e9ro {0} lorsqu\u2019il y en a seulement {1}. skipNumberTooLow=Le nombre donn\u00e9 doit \u00eatre sup\u00e9rieur \u00e0 0. @@ -62,7 +67,7 @@ npDescription=Description npLoadedSoundcloud=[{0}/{1}] \n\nCharg\u00e9 depuis Soundcloud npLoadedBandcamp={0}\n\n charg\u00e9 depuis Bandcamp npLoadedTwitch=Charg\u00e9 depuis Twitch -npRequestedBy=Requested by {0} +npRequestedBy=Demand\u00e9 par {0} permissionMissingBot=J'ai besoin de l'autorisation suivante pour ex\u00e9cuter l'action suivante \: permissionMissingInvoker=Vous avez besoin de la permission suivante pour ex\u00e9cuter l'action suivante \: permissionEmbedLinks=Incorpore les liens @@ -92,11 +97,11 @@ loadPlaylistTooMany=Ajout de {0} pistes. Il y a trop de pistes ajout\u00e9es pou loadErrorCommon=Une erreur est survenue lors du chargement de `{0}`\:\n{1} loadErrorSusp=Une erreur bizarre est survenue lors du chargement de `{0}`. loadQueueTrackLimit=Vous ne pouvez ajouter des pistes \u00e0 une file de plus de {0}\u00a0titres \! Ceci permet d\u2019\u00e9viter les abus. -loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. -loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. -loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. -loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. -loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! +loadPlaylistDisabled=Ce serveur a d\u00e9sactiv\u00e9 la file d'attente \u00e0 partir de listes de lecture. Veuillez faire une file d'attente manuellement. +loadMaxTracksExceeded=Ce serveur ne permet pas de mettre plus de {0} titres en file d''attente. +loadMaxUserTracksExceeded=Ce serveur ne vous permet pas d''avoir plus de {0} pistes dans la file d''attente. +loadMaxTrackLengthExceeded=Ce serveur ne vous permet pas de lire des pistes plus longues que {0}. Veuillez essayer quelque chose de plus court. +loadPlaylistGeneralError={0} pistes n''ont pas \u00e9t\u00e9 ajout\u00e9es car ce serveur applique les restrictions de file d''attente \! loadAnnouncePlaylist=Liste de lecture en cours de chargement **{0} ** avec jusqu''\u00e0\u00a0`{1}` titres. Cela peut prendre un certain temps, merci de patienter. playerUserNotInChannel=Vous devez en premier lieu rejoindre un canal vocal. playerJoinConnectDenied=Je n'ai pas la permission de rejoindre ce canal vocal. @@ -244,12 +249,14 @@ helpJoinCommand=Fait venir le bot dans votre canal vocal actuel. helpLeaveCommand=Fait partir le bot de votre canal vocal actuel. helpPauseCommand=Met le lecteur en pause. helpPlayCommand=Joue de la musique \u00e0 partir d''un lien ou recherche une piste. Pour une liste compl\u00e8te des sources, veuillez visiter {0} -helpPlayNextCommand=Jouer la musique \u00e0 partir d''un URL ou rechercher une musique. Les musiques sont ajout\u00e9es au d\u00e9but de la liste. Pour une liste compl\u00e8te des sources, visitez {0} +helpReplayCommand=Replay the most recent track from history. +helpPlayNextCommand=Joue une musique \u00e0 partir d''une URL ou recherche une musique. Les musiques sont ajout\u00e9es au d\u00e9but de la liste. Pour une liste compl\u00e8te des sources, visitez {0} helpPlaySplitCommand=Divise une vid\u00e9o YouTube en une liste de lecture fournie depuis sa description. helpRepeatCommand=Bascule entre les modes de r\u00e9p\u00e9tition. helpReshuffleCommand=Remet en mode al\u00e9atoire la file d'attente actuelle. helpSelectCommand=S\u00e9lectionne et lance l'une des pistes propos\u00e9es apr\u00e8s une recherche. helpShuffleCommand=Bascule en mode al\u00e9atoire pour la file d\u2019attente actuelle. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Passe la musique actuelle, la n-i\u00e8me dans la queue, toutes les musiques de n \u00e0 m, ou toutes les musiques de l'utilisateur mentionn\u00e9. S'il vous pla\u00eet utilisez cette commande avec mod\u00e9ration. helpStopCommand=Arr\u00eate le lecteur et efface la liste de lecture. Ceci est r\u00e9serv\u00e9 aux mod\u00e9rateurs ayant l\u2019autorisation de g\u00e9rer les messages. helpUnpauseCommand=Relance le lecteur. @@ -339,4 +346,3 @@ modulesHowTo=Dites {0} pour activer/d\u00e9sactiver des modules. parseNotAUser=Votre entr\u00e9e {0} n''a renvoy\u00e9 \u00e0 un utilisateur Discord. parseNotAMember=L''utilisateur {0} n''est pas un membre de cette guilde. parseSnowflakeIdHelp=Vous avez des probl\u00e8mes avec l''identifiant d''un utilisateur, message, canal ou autre chose ? Vous pouvez trouver la documentation Discord ici \: {0} - diff --git a/FredBoat/src/main/resources/lang/fr_TS.properties b/FredBoat/src/main/resources/lang/fr_TS.properties index c59815d4e..85b579af4 100644 --- a/FredBoat/src/main/resources/lang/fr_TS.properties +++ b/FredBoat/src/main/resources/lang/fr_TS.properties @@ -7,6 +7,8 @@ playSearching=D'accord, je vais chercher \u00e7a sur Youtube... flemme... \:zzz\ playYoutubeSearchError=Une erreur est survenue au cours de la recherche sur Youtu... Attends, c'est ta faute. S\u00fbr que c'est ta faute.\n```\n;;play ``` playSearchNoResults=J'veux bien te dire ce que j'ai trouv\u00e9, mais je devrai t'\u00e9liminer ensuite. playSelectVideo=**Choisis vite une piste avec `{0}play 1-5` ou j''me tire \:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=J-je rejoins le salon {0}, mais c''est pas pour te faire plaisir \! >< joinErrorAlreadyJoining=AAAAAAH \u00e7a marche pas \! Comment tu veux que j''me connecte \u00e0 {0} alors que chuis d\u00e9j\u00e0 en train de le faire \! \:anger\: pauseAlreadyPaused=La musique est d\u00e9j\u00e0 en pause, baka. @@ -21,12 +23,15 @@ shuffleOn=(\u256f\u00b0\u25a1\u00b0\uff09\u256f\ufe35 \u253b\u2501\u253b Voil\u0 shuffleOff=D'abord faut m\u00e9langer, ensuite trier, tu l'fais expr\u00e8s ou quoi ? reshufflePlaylist=(\u30ce \u309c\u0414\u309c)\u30ce \ufe35 \u253b\u2501\u253b BAM \! Re-m\u00e9lang\u00e9e \! reshufflePlayerNotShuffling=\u00ab\u202fFred, rem\u00e9lange la playlist\u202f\u00bb et gnagnagna alors qu'elle est m\u00eame pas m\u00e9lang\u00e9e, bande de tar\u00e9s... +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=Ya rien dans ta liste, et puis c'est pas comme si j'en avait quelque chose \u00e0 faire -_- -skipOutOfBounds=Impossible de supprimer la piste num\u00e9ro {0} lorsqu\u2019il y en a seulement {1}. +skipOutOfBounds=Heu, j''peux pas retirer ta musique num\u00e9ro {0} dans une liste de {1} musiques hein >_< skipNumberTooLow=Donne un nombre plus grand, cr\u00e9tin. skipSuccess=Et bim, j''ai vir\u00e9 **{1}**, je l''aimais pas. Prochain truc qui vire \: toi. skipRangeInvalid=La plage pour cette piste est invalide. -skipRangeSuccess=Les pistes de \#{0} \u00e0 \#{1} ont \u00e9t\u00e9 supprim\u00e9es. +skipRangeSuccess=Hop, j''ai vir\u00e9 tout de \#{0} \u00e0 \#{1}. C''est \u00e7a en moins. skipTrackNotFound=Ya rien du tout \u00e0 passer, \u00e7a se voit non ?\! >.< stopAlreadyEmpty=Ouais c'est \u00e7a j'me casse, bye bye. stopEmptyOne=*Poof* j'ai **tu\u00e9** la file d'attente. Bon, yavait qu'une seule piste dedans mais quand m\u00eame \! @@ -41,7 +46,7 @@ volumeSyntax=*En train de hurler*\nUTILISEZ `;;volume <0-150>`. {0}% EST LA VALE volumeSuccess=Volume chang\u00e9 **%{0}** \u00e0 **{1}%**. exportEmpty=0 musiques \u00e0 exporter, comme ton 0 de Q.I. exportPlaylistResulted=Liste de lecture export\u00e9e \: {0} \nVous pouvez fournir cette URL pour jouer cette liste plus tard. -exportPlaylistFail=J'ai \u00e9chou\u00e9, mais c'est pas grave, on a qu'\u00e0 jouer aux \u00e9checs. +exportPlaylistFail=J'ai... pas r\u00e9ussi \u00e0... mettre la liste en ligne... ;_; listShowShuffled=Playlist que l\u00e0 j'te regardes signale une m\u00e9lang\u00e9e tu. listShowRepeatSingle=R\u00e9p\u00e9tition de la piste en cours. R\u00e9p\u00e9tition de la piste en cours. R\u00e9p\u00e9tition de la piste en cours. R\u00e9p\u00e9tition de la piste en cours. R\u00e9p\u00e9tition de la piste en cours... *continue* listShowRepeatAll=R\u00e9p\u00e9tition de la liste de lecture en cours. R\u00e9p\u00e9tition de la liste de lecture en cours. R\u00e9p\u00e9tition de la liste de lecture en cours. R\u00e9p\u00e9tition de la liste de lecture en cours... *continue* @@ -62,7 +67,7 @@ npDescription=Description npLoadedSoundcloud=[{0}/{1}]\n\nEt \u00e7a vient de Souncloud \! npLoadedBandcamp={0}\n\nCe truc vient de heu... Bandcamp... wow, impressionant... npLoadedTwitch=Charg\u00e9 depuis Twitch, beeerk -npRequestedBy=Requested by {0} +npRequestedBy=Demand\u00e9 par {0} permissionMissingBot=Donne-moi ces permissions tout de suite ou je vais m'\u00e9nerver \: permissionMissingInvoker=Donne-moi ces permissions sinon j'le fais pas \: permissionEmbedLinks=Int\u00e9grer des liens @@ -74,7 +79,7 @@ artist=Artiste circle=Cercle npLoadedFromHTTP={0}\n\n~~Vol\u00e9~~ charg\u00e9 depuis {1} npLoadedDefault={0}\n\n~~Vol\u00e9~~ charg\u00e9 depuis {1} -noneYet=Aucun pour le moment +noneYet=Que dalle pour l'instant npRatingRange={0}/5 (j''aurais donn\u00e9 0 perso) fwdSuccess=Avance **{0}** de {1}. restartSuccess=**{0}** a \u00e9t\u00e9 red\u00e9marr\u00e9, c''est reparti pour un tour. @@ -88,20 +93,20 @@ loadNoMatches=C'est tout sauf une musique, tu le fais expr\u00e8s ou t'es juste loadSplitNotYouTube=Nan, \u00e7a j'peux le faire qu'avec les vid\u00e9os de Youtube. Si t'es pas content t'as qu'\u00e0 faire `;;play` \u00e0 la place. loadSplitNotResolves=Impossible de r\u00e9soudre la liste de lecture de cette vid\u00e9o. Essayez d\u2019utiliser `;;play` \u00e0 la place. loadFollowingTracksAdded=Les \u00ab\u202fcompositions artistiques virtuoses\u202f\u00bb (berk) suivantes ont \u00e9t\u00e9 ajout\u00e9es \: -loadPlaylistTooMany=Ajout de {0} pistes. Il y a trop de pistes ajout\u00e9es pour qu''elles s''affichent toutes. +loadPlaylistTooMany=J''ai ajout\u00e9 {0} pistes, et \u00e7a fait beaucoup trop pour que j''affiche tout >_> loadErrorCommon=Une erreur est survenue lors du chargement de `{0}`\:\n{1} loadErrorSusp=Une erreur bizarre est survenue lors du chargement de `{0}`. loadQueueTrackLimit=Vous ne pouvez ajouter des pistes \u00e0 une file de plus de {0}\u00a0titres \! Ceci permet d\u2019\u00e9viter les abus. -loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. -loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. -loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. -loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. -loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! +loadPlaylistDisabled=Ce serveur a d\u00e9sactiv\u00e9 la file d'attente \u00e0 partir de listes de lecture. Veuillez faire une file d'attente manuellement. +loadMaxTracksExceeded=Ce serveur ne permet pas de mettre plus de {0} titres en file d''attente. +loadMaxUserTracksExceeded=Ce serveur ne vous permet pas d''avoir plus de {0} pistes dans la file d''attente. +loadMaxTrackLengthExceeded=Ce serveur ne vous permet pas de lire des pistes plus longues que {0}. Veuillez essayer quelque chose de plus court. +loadPlaylistGeneralError={0} pistes n''ont pas \u00e9t\u00e9 ajout\u00e9es car ce serveur applique les restrictions de file d''attente \! loadAnnouncePlaylist=Liste de lecture en cours de chargement **{0} ** avec jusqu''\u00e0\u00a0`{1}` titres. Cela peut prendre un certain temps, merci de patienter. playerUserNotInChannel=Non. Hors de question que je vienne alors que toi t'y es m\u00eame pas. Et puis tu m'\u00e9nerves de toutes fa\u00e7ons. playerJoinConnectDenied=M-mais... je ne peux pas rejoindre ce salon... ;_; playerJoinSpeakDenied=*Ne peut pas parler dans ce salon vocal*\nHmmm, hmmmm, hmm hmm \! \:anger\: -playerNotInChannel=Tu n'es pas dans un canal vocal, idiot. +playerNotInChannel=Tu n'es pas dans un salon vocal, baka playerLeftChannel=Ouais c'est \u00e7a, je quitte ton salon pourri et ses gens pourris. shutdownUpdating=STOP \! J'me mets \u00e0 jour, sinon je pourrai pas te supporter plus longtemps. shutdownRestarting=A-attends, juste une pause \! Une petite m-minute, s'il te pla\u00eet... je me relance... @@ -151,7 +156,7 @@ modFailUserHierarchy=Nan mais ho, tu te crois au-dessus de {0} ? \:anger\: modFailBotHierarchy=Je ne le ferais que si j''ai le r\u00f4le de {0}, sinon ne me le demande pas \! modBanlistFail=Impossible de r\u00e9cup\u00e9rer la liste des bannis. modBanFail=Je n'ai pas r\u00e9ussi \u00e0 le bannir, je... Je suis d\u00e9sol\u00e9. -modUnbanFail=Je n'ai pas r\u00e9ussi \u00e0 le d\u00e9bannir, pard... pard... Pardonne moi. +modUnbanFail=Je n'ai pas r\u00e9ussi \u00e0 le d\u00e9bannir, pard... pard... pardonne-moi \:< modUnbanFailNotBanned=L''utilisateur {0} n''est pas banni de cette guilde. modKeepMessages=Ajoutez {0} pour ne supprimer aucun message. modActionTargetDmKicked=Hey toi \! Tu viens de te faire exclure par {1} du serveur {0}. J''suis contente de plus te voir tu sais \:3 @@ -166,32 +171,32 @@ softbanFailSelf=Tu ne peux pas t'exclure temporairement toi-m\u00eame, idiot -_- softbanFailOwner=Vous ne pouvez exclure temporairement l'administrateur du serveur. C'est con... softbanFailMyself=Je ne peux pas m'exclure temporairement, idiot \! hardbanSuccess=L''utilisateur {0} a bien \u00e9t\u00e9 banni. -hardbanFailSelf=Tu ne peux pas te bannir toi m\u00eame. Au moins tu peux rester ici \! +hardbanFailSelf=Tu ne peux pas te bannir toi m\u00eame. Dommage, tu m'agaces. hardbanFailOwner=Vous ne pouvez pas bannir le propri\u00e9taire du serveur. C'est con... -hardbanFailMyself=Je ne vais pas m'exclure moi m\u00eame \! Idiot \! +hardbanFailMyself=Je vais pas me bannir moi-m\u00eame \! Idiot \! unbanSuccess={0} a bien \u00e9t\u00e9 d\u00e9-banni, f\u00e9licitation, je suppose ? -getidSuccess=L''ID de cette guilde est {0}. L''ID de ce channel est {1}. +getidSuccess=L''identifiant de ce serveur est {0}, et celui de ce salon est {1}. Ajoute un - devant et on obtient ta stat de charisme. statsParagraph=\ Je fonctionne depuis {0} jours, {1} heures, {2} minutes et {3} secondes. J''ai ex\u00e9cut\u00e9 {4} commandes cette session. Tu me pousses vraiment \u00e0 bout... statsRate={0}C''est un taux de {1} commande par heure. Je suis rapide hein? HEIN? -catgirlFail=Je n''ai pas r\u00e9ussi \u00e0 extraire l''image depuis {0}. Elle \u00e9tait moche de toute fa\u00e7on. -catgirlFailConn=Impossible de se connecter \u00e0 {0} +catgirlFail=Je n''ai pas r\u00e9ussi \u00e0 extraire l''image depuis {0}. Elle \u00e9tait moche de toutes fa\u00e7ons. +catgirlFailConn=J''arrive pas \u00e0 me connecter \u00e0 {0} hugBot=Un c\u00e2lin...? J-je... m-merci... >////< hugSuccess=C\u00e2line {0}. Mais juste pour cette fois... patBot=N-Ne touche pas ma t\u00eate si soudainement, imb\u00e9cile... patSuccess=Caresse {0}. Mais juste pour cette fois... rollSuccess=Cet idiot de {0} roule sur le sol... Mais je n''ai pas envie de le rejoindre hein\! facedeskSuccess={0} Tape sa t\u00eate sur le bureau. -langInvalidCode=C''est quoi \u00e7a? "{0}" ne veut rien dire, abruti\! -langSuccess=Bien\! Je parlerais en {0} maintenant. -langInfo=Je prend en charge plusieurs langues parl\u00e9es par les utilisateurs que vous pouvez s\u00e9lectionner avec cette commande. Les Administrateurs de ce serveur peuvent s\u00e9lectionner une langue avec ';;lang ' Voici la liste des langues prisent en charge\: -langDisclaimer=Ma traduction n'est pas pas totalement exacte ou compl\u00e8te... Je suis d\u00e9sol\u00e9, vraiment. Tu peux toujours les compl\u00e9ter ici, je suis pas ton larbin \: . +langInvalidCode=C''est quoi \u00e7a? \u00ab {0} \u00bb ne veut rien dire, abruti \! +langSuccess=Bien \! Je parlerai en {0} maintenant. +langInfo=J'peux parler plein de langues tu sais \! Les admins du serveur peuvent choisir avec la commande `;;lang `. Et comme je suis sympa, j'te donne la liste des langues disponibles \: +langDisclaimer=Ma traduction n'est ni exacte ni compl\u00e8te... C'est la faute de mes traducteurs, ils sont pas terribles. Mais si tu te sens de faire mieux, tu peux contribuer sur . loadSingleTrack=**{0}** a \u00e9t\u00e9 ajout\u00e9 \u00e0 la file d''attente. loadSingleTrackFirst=**{0}** a \u00e9t\u00e9 ajout\u00e9 au tout d\u00e9but de la file d''attente, hihi. loadSingleTrackAndPlay=Je vais te jouer **{0}** rien que pour toi. -invite=Lien d''invitation divin pour **{0}** \: +invite=Lien d''invitation pour **{0}** \: ratelimitedCommandsUser=Houl\u00e0 ON SE CALME, tu vas trop vite, ralentis >< -ratelimitedCommandsGuild=On se calme, tu vas trop vite, ralentis ><. -ratelimitedSkipCommand=Tu ne peux pas passer plusieurs musique en m\u00eame temps avec cette commande, dommage +ratelimitedCommandsGuild=On se calme, tu vas trop vite, ralentis >< +ratelimitedSkipCommand=Tu ne peux pas passer plusieurs musique en m\u00eame temps avec cette commande, dommage... pour toi \:3 ratelimitedGuildSlowLoadingPlaylist=Le serveur va exploser si tu rajoute des musiques \u00e0 lire, donc on va attendre un moment avant. unblacklisted={0} a \u00e9t\u00e9 retir\u00e9 de la liste noire. serverinfoTitle=Tout ce que je sais \u00e0 propos de {0} \: @@ -200,7 +205,7 @@ serverinfoTotalUsers=Nombre total d'utilisateurs \: serverinfoRoles=R\u00f4les \: serverinfoText=Salons textuels \: serverinfoVoice=Salons vocaux \: -serverinfoGuildID=Identifiant de la guilde \: +serverinfoGuildID=Identifiant du serveur \: serverinfoCreationDate=Date de cr\u00e9ation \: serverinfoOwner=Propri\u00e9taire \: serverinfoVLv=Niveau de v\u00e9rification\u00a0\: @@ -211,12 +216,12 @@ userinfoNick=Pseudo \: userinfoKnownServer=Serveurs connus \: userinfoJoinDate=Date d'inscription\: userinfoCreationTime=Date de cr\u00e9ation \: -userinfoBlacklisted=La liste noire du mal absolue \: +userinfoBlacklisted=La liste noire du mal absolu \: skipDeniedTooManyTracks=Vous ne pouvez pas passer le morceau de quelqu'un d'autre si vous n'\u00eates pas DJ. Envisagez d'utiliser la commande de Voteskip. eventUsersLeftVC=Tous les utilisateurs ont quitt\u00e9 le canal vocal. Le lecteur a \u00e9t\u00e9 mis en pause. eventAutoResumed=Pr\u00e9sence d'utilisateur d\u00e9tect\u00e9e, reprise automatique du lecteur. commandsFun=Fun -commandsMemes=M\u00e8mes +commandsMemes=Memes commandsUtility=Utilitaire commandsModeration=Mod\u00e9ration commandsMaintenance=Maintenance @@ -225,7 +230,7 @@ commandsMoreHelp=Dis moi {0} si tu veux plus d''info sur une certaine commande. commandsModulesHint=Vous pouvez activer et d\u00e9sactiver des modules suppl\u00e9mentaires avec {0} helpUnknownCommand=J'connais pas cette commande. helpDocsLocation=La documentation peut \u00eatre trouv\u00e9e ici \: -helpBotInvite=Vous voulez ajouter FredBoat \u00e0 votre serveur ? Si vous avez la permission de g\u00e9rer cette guilde, vous pouvez inviter FredBoat \: +helpBotInvite=Toi aussi tu veux m'ajouter sur ton serveur ? Bah si t'as les bonnes permissions, tu peux ici \: helpHangoutInvite=Vous avez besoin d'aide ou avez des id\u00e9es pour FredBoat ? Ou vous voulez juste passer le temps ? Rejoignez la communaut\u00e9 FredBoat \! helpNoDmCommands=Vous ne pouvez pas envoyer de commandes via la messagerie priv\u00e9e. helpCredits=Cr\u00e9\u00e9 par Fre_d et ses contributeurs en open-source @@ -244,12 +249,14 @@ helpJoinCommand=Fait venir le bot dans votre canal vocal actuel. helpLeaveCommand=Fait partir le bot de votre canal vocal actuel. helpPauseCommand=Met le lecteur en pause. helpPlayCommand=Joue de la musique \u00e0 partir d''un l''URL donn\u00e9e ou d''une recherche par noms. Pour une liste compl\u00e8te des sources, veuillez visiter {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Joue de la musique \u00e0 partir d''un l''URL donn\u00e9e ou d''une recherche par noms, et l''ajoute au d\u00e9but de la file d''attente. Pour une liste compl\u00e8te des sources, veuillez visiter {0} helpPlaySplitCommand=**D\u00e9coupe** une vid\u00e9o Youtube pour la manger en plusieurs fois, selon la ~~recette~~ description. >\:3c -helpRepeatCommand=Propose diff\u00e9rents modes de r\u00e9p\u00e9titions. +helpRepeatCommand=Change parmi les diff\u00e9rents modes de r\u00e9r\u00e9r\u00e9r\u00e9r\u00e9r\u00e9r\u00e9p\u00e9tition. helpReshuffleCommand=Remet en mode al\u00e9atoire la file d'attente actuelle. helpSelectCommand=S\u00e9lectionne et lance l'une des pistes propos\u00e9es apr\u00e8s une recherche. helpShuffleCommand=Bascule en mode al\u00e9atoire pour la file d\u2019attente actuelle. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Passe la musique actuelle, la n-i\u00e8me dans la queue, toutes les musiques de n \u00e0 m, ou toutes les musiques de l'utilisateur mentionn\u00e9. S'il vous pla\u00eet utilisez cette commande avec mod\u00e9ration. helpStopCommand=Arr\u00eate le lecteur et efface la liste de lecture. Ceci est r\u00e9serv\u00e9 aux mod\u00e9rateurs ayant l\u2019autorisation de g\u00e9rer les messages. helpUnpauseCommand=Relance le lecteur. @@ -263,7 +270,7 @@ helpForwardCommand=Avance la piste d'un temps donn\u00e9 par la commande. Exempl helpRestartCommand=Rejoue depuis le d\u00e9but la piste en cours de lecture. helpRewindCommand=Rembobine la piste d'un temps donn\u00e9 par la commande. Exemple \: helpSeekCommand=R\u00e8gle la position dans la piste par un temps donn\u00e9 par la commande. Exemple \: -helpAvatarCommand=Affiche l\u2019avatar d\u2019un utilisateur dans le chat. +helpAvatarCommand=Affiche l'avatar d'un utilisateur. helpBrainfuckCommand=Ex\u00e9cute du code Brainfuck. Nan, n'essaie pas, t'as pas assez de cervelle pour \u00e7a. Regarde, tu vas rien comprendre \: helpWeatherCommand=Affiche la m\u00e9t\u00e9o actuelle par emplacement. helpClearCommand=Supprime tous les messages de ce bot dans les 50 derniers messages de ce salon. @@ -289,7 +296,7 @@ helpMathOperationSqrt=\u00c9crit la racine carr\u00e9e de n. helpMathOperationPow=\u00c9crit le r\u00e9sultat de num1 puissance num2. destroyDenied=Vous devez avoir la permission de g\u00e9rer les messages pour r\u00e9initialiser le lecteur. destroyHelp=R\u00e9initialise le lecteur et efface la liste de lecture. Ceci est r\u00e9serv\u00e9 aux mod\u00e9rateurs ayant la permission de g\u00e9rer les messages. -destroySucc=J'ai \u00e9t\u00e9 r\u00e9initialis\u00e9, et ma file d'attente aussi \! Je suis toute propre maintenant. +destroySucc=J'ai \u00e9t\u00e9 r\u00e9initialis\u00e9e, et ma file d'attente aussi \! Je suis toute propre maintenant. listPageNum=Page **{0}** sur **{1}**. permsListTitle=Utilisateurs et r\u00f4les avec les permissions suivantes {0} permsAdded=J''ai ajout\u00e9 de `{0}` \u00e0 `{1}`. @@ -297,7 +304,7 @@ permsRemoved=Suppression de `{0}` dans `{1}`. permsFailSelfDemotion=Tu ne peux pas retirer \u00e7a car cela te retirerait une permission d'admin \! permsAlreadyAdded={0} d\u00e9j\u00e0 ajout\u00e9 \u00e0 {1} permsNotAdded={0} n''est pas dans {1}, c''est b\u00eate \u00af\\_(\u30c4)_/\u00af -fuzzyMultiple=J'ai trouv\u00e9 un tas de trucs sympa \! Y en a un qui t'int\u00e9resse ? +fuzzyMultiple=J'ai trouv\u00e9 un tas de trucs sympa \! Yen a un qui t'int\u00e9resse ? fuzzyNothingFound=Je n''ai rien trouv\u00e9 pour `{0}` \u00af\\_(\u30c4)_/\u00af cmdPermsTooLow=Vous n''avez pas l''autorisation d''ex\u00e9cuter cette commande \! Cette commande requiert la permission `{0}` mais vous avez seulement la permission `{1}`. playersLimited=FredBoat est actuellement \u00e0 plein r\u00e9gime \! Le bot est actuellement r\u00e9gl\u00e9 pour lire seulement jusqu''\u00e0 `{0}` streams, faute de quoi nous risquerions une d\u00e9connexion de Discord \u00e0 cause de la surcharge du r\u00e9seau.\nSi vous voulez nous aider \u00e0 augmenter la limite ou si vous souhaitez utiliser notre bot non-surcharg\u00e9, soutenez notre travail sur Patreon \: {1}\n\nD\u00e9sol\u00e9s pour le d\u00e9rangement \! Vous devriez essayer plus tard. Ce message n''appara\u00eet habituellement qu''aux heures de pointe. @@ -329,7 +336,7 @@ moduleUtility=Utilitaire moduleFun=Fun moduleLocked=Le module {0} est verrouill\u00e9 et ne peut pas \u00eatre activ\u00e9/d\u00e9sactiv\u00e9. moduleCantParse=Aucun module. Affichez une liste des modules disponibles avec {0} -moduleStatus=Status du module +moduleStatus=\u00c9tat du module moduleDisable=Le module {0} est d\u00e9sactiv\u00e9. moduleEnable=Le module {0} est activ\u00e9. moduleShowCommands=Dites {0} pour voir les commandes de ce module. @@ -337,6 +344,5 @@ modulesCommands=Dites {0} pour afficher les commandes d''un module, ou {1} pour modulesEnabledInGuild=J'ai accept\u00e9 ces fichus modules pour c'te guilde \: modulesHowTo=Pour me donner plus de boulot ou m''en retirer, m\u00eame si je sais que tu vas m''en donner, c''est {0} parseNotAUser=Votre entr\u00e9e {0} n''a renvoy\u00e9 \u00e0 un utilisateur Discord. -parseNotAMember={0} n''est pas sur ce serveur, et tant mieux \! \=v\= +parseNotAMember={0} est pas sur ce serveur, et tant mieux \! \=v\= parseSnowflakeIdHelp=T''as un probl\u00e8me avec l''ID d''un utilisateur, d''un channel ou aut''chose? Bah r''gardes la doc de Discord sur ce lien {0} - diff --git a/FredBoat/src/main/resources/lang/ga_IE.properties b/FredBoat/src/main/resources/lang/ga_IE.properties index 408788bdb..51c9ef1e1 100644 --- a/FredBoat/src/main/resources/lang/ga_IE.properties +++ b/FredBoat/src/main/resources/lang/ga_IE.properties @@ -7,6 +7,8 @@ playSearching=Ag lorg `{q}` ar YouTube... playYoutubeSearchError=Bh\u00ed bot\u00fan ann sa gcuardach YouTube. Nach fearr nasc d\u00edreach chuig an bhfoinse a tabhairt?\n```\n;;play ``` playSearchNoResults=N\u00ed bhfuair m\u00e9 aon rud ag lorg `{q}` playSelectVideo=**Roghnaigh traic leis an gcommand\: `{0}play 1-5`** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Ag dul i {0} joinErrorAlreadyJoining=T\u00e1 bot\u00fan ann. N\u00ed raibh m\u00e9 in ann dul i {0} mar t\u00e1 m\u00e9 ag iarraidh nasc a dh\u00e9anamh leis an gcain\u00e9al sin. Bain triail as ar\u00eds m\u00e1s \u00e9 do thoil \u00e9. pauseAlreadyPaused=T\u00e1 an seinnteoir ar sos cheana. @@ -21,6 +23,9 @@ shuffleOn=Seinnfidh an seinnteoir traic f\u00e1nach anois. shuffleOff=N\u00ed bheidh an seinnteoir ag seinm traiceanna f\u00e1nach n\u00edos m\u00f3. reshufflePlaylist=T\u00e1 ord an chi\u00fa suaite. reshufflePlayerNotShuffling=Caithfidh t\u00fa ligean don seinnteoir traiceanna a shuaitheadh ar dt\u00fas. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=N\u00edl faic sa gci\u00fa\! skipOutOfBounds=N\u00ed f\u00e9idir traic a {0} a bhaineadh nuair nach bhfuil ach {1} traic ann. skipNumberTooLow=Caithfidh t\u00fa uimhir n\u00edos m\u00f3 n\u00e1 0 a chur. @@ -244,12 +249,14 @@ helpJoinCommand=Make the bot join your current voice channel. helpLeaveCommand=Make the bot leave the current voice channel. helpPauseCommand=Cuir sos ar an seinnteoir. helpPlayCommand=Play music from the given URL or search for a track. For a full list of sources please visit {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Split a YouTube video into a tracklist provided in it's description. helpRepeatCommand=Toggle between repeat modes. helpReshuffleCommand=Reshuffle the current queue. helpSelectCommand=Select one of the offered tracks after a search to play. helpShuffleCommand=Toggle shuffle mode for the current queue. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Skip the current song, the n'th song in the queue, all songs from n to m, or all songs from mentioned users. Please use in moderation. helpStopCommand=Stop the player and clear the playlist. Reserved for moderators with Manage Messages permission. helpUnpauseCommand=Unpause the player. @@ -339,4 +346,3 @@ modulesHowTo=Say {0} to enable/disable modules. parseNotAUser=Your input {0} did not yield a Discord user. parseNotAMember=User {0} is not a member of this guild. parseSnowflakeIdHelp=Having trouble getting the id of a user/message/channel/something else? Check out the Discord docs at {0} - diff --git a/FredBoat/src/main/resources/lang/he_IL.properties b/FredBoat/src/main/resources/lang/he_IL.properties index cb0efb5fe..dd9f3aec4 100644 --- a/FredBoat/src/main/resources/lang/he_IL.properties +++ b/FredBoat/src/main/resources/lang/he_IL.properties @@ -7,6 +7,8 @@ playSearching=\u05de\u05d7\u05e4\u05e9 `{q}` \u05d1\u05d9\u05d5\u05d8\u05d9\u05d playYoutubeSearchError=\u05d0\u05d9\u05e8\u05e2\u05d4 \u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05e2\u05ea \u05d4\u05d7\u05d9\u05e4\u05d5\u05e9 \u05d1\u05d9\u05d5\u05d8\u05d9\u05d5\u05d1. \u05e9\u05e7\u05d5\u05dc \u05e9\u05d9\u05de\u05d5\u05e9 \u05d1\u05e7\u05d9\u05e9\u05d5\u05e8 \u05d9\u05e9\u05d9\u05e8\u05d5\u05ea \u05d0\u05dc \u05d0\u05de\u05e6\u05e2\u05d9 \u05e9\u05de\u05e2 \u05d1\u05de\u05e7\u05d5\u05dd.\n```\n;;play <\u05e7\u05d9\u05e9\u05d5\u05e8>``` playSearchNoResults=\u05dc\u05d0 \u05e0\u05de\u05e6\u05d0\u05d5 \u05ea\u05d5\u05e6\u05d0\u05d5\u05ea \u05e2\u05d1\u05d5\u05e8 `{q}` playSelectVideo=\u05d0\u05e0\u05d0 \u05d1\u05d7\u05e8 \u05e9\u05d9\u05e8 \u05e2\u05dd \u05d4\u05e4\u05e7\u05d5\u05d3\u05d4 {0}play n +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=\u05de\u05e6\u05d8\u05e8\u05e3 \u05d0\u05dc {0} joinErrorAlreadyJoining=\u05d0\u05d9\u05e8\u05e2\u05d4 \u05e9\u05d2\u05d9\u05d0\u05d4. \u05dc\u05d0 \u05d9\u05db\u05d5\u05dc\u05ea\u05d9 \u05dc\u05d4\u05e6\u05d8\u05e8\u05e3 \u05d0\u05dc {0} \u05de\u05db\u05d9\u05d5\u05d5\u05df \u05e9\u05d0\u05e0\u05d9 \u05db\u05d1\u05e8 \u05de\u05e0\u05e1\u05d4 \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8 \u05dc\u05e2\u05e8\u05d5\u05e5 \u05d6\u05d4. \u05d0\u05e0\u05d0 \u05e0\u05e1\u05d4 \u05e9\u05e0\u05d9\u05ea. pauseAlreadyPaused=\u05d4\u05e0\u05d2\u05df \u05db\u05d1\u05e8 \u05d1\u05d4\u05e9\u05d4\u05d9\u05d4. @@ -21,6 +23,9 @@ shuffleOn=\u05d4\u05e0\u05d2\u05df \u05e2\u05db\u05e9\u05d9\u05d5 \u05de\u05e2\u shuffleOff=\u05d4\u05e0\u05d2\u05df \u05d0\u05d9\u05e0\u05d5 \u05de\u05e2\u05d5\u05e8\u05d1\u05d1. reshufflePlaylist=\u05d4\u05ea\u05d5\u05e8 \u05d4\u05ea\u05e2\u05e8\u05d1\u05d1. reshufflePlayerNotShuffling=\u05d0\u05ea\u05d4 \u05d7\u05d9\u05d9\u05d1 \u05dc\u05d4\u05e4\u05e2\u05d9\u05dc \u05d0\u05ea \u05e8\u05e9\u05d9\u05de\u05ea \u05d4\u05d4\u05e9\u05de\u05e2\u05d4 \u05d1\u05e1\u05d3\u05e8 \u05e8\u05e0\u05d3\u05d5\u05de\u05dc\u05d9. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=\u05e8\u05e9\u05d9\u05de\u05ea \u05d4\u05e9\u05d9\u05e8\u05d9\u05dd \u05e8\u05d9\u05e7\u05d4\! skipOutOfBounds=\u05d0\u05d9\u05df \u05d0\u05e4\u05e9\u05e8\u05d5\u05ea \u05dc\u05d4\u05e1\u05d9\u05e8 \u05d0\u05ea \u05e9\u05d9\u05e8 \u05de\u05e1\u05e4\u05e8 {0} \u05db\u05d0\u05e9\u05e8 \u05d9\u05e9 \u05e8\u05e7 {1} \u05e9\u05d9\u05e8\u05d9\u05dd. skipNumberTooLow=\u05d4\u05de\u05e1\u05e4\u05e8 \u05d4\u05e0\u05ea\u05d5\u05df \u05d7\u05d9\u05d9\u05d1 \u05dc\u05d4\u05d9\u05d5\u05ea \u05d2\u05d3\u05d5\u05dc \u05de-0. @@ -244,12 +249,14 @@ helpJoinCommand=\u05d2\u05d5\u05e8\u05dd \u05dc\u05d1\u05d5\u05d8 \u05dc\u05d4\u helpLeaveCommand=\u05d2\u05d5\u05e8\u05dd \u05dc\u05d1\u05d5\u05d8 \u05dc\u05e2\u05d6\u05d5\u05d1 \u05d0\u05ea \u05e2\u05e8\u05d5\u05e5 \u05d4\u05e7\u05d5\u05dc \u05d4\u05e0\u05d5\u05db\u05d7\u05d9. helpPauseCommand=\u05e2\u05d5\u05e6\u05e8 \u05d0\u05ea \u05d4\u05e0\u05d2\u05df. helpPlayCommand=\u05e0\u05d2\u05df \u05de\u05d5\u05d6\u05d9\u05e7\u05d4 \u05de\u05d4\u05e7\u05d9\u05e9\u05d5\u05e8 \u05d4\u05e0\u05ea\u05d5\u05df \u05d0\u05d5 \u05d7\u05e4\u05e9 \u05e9\u05d9\u05e8. \u05d1\u05e9\u05d1\u05d9\u05dc \u05e8\u05e9\u05d9\u05de\u05d4 \u05e9\u05dc \u05de\u05e7\u05d5\u05e8\u05d5\u05ea \u05d1\u05d1\u05e7\u05e9\u05d4 \u05d1\u05e7\u05e8 \u05d1{0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Split a YouTube video into a tracklist provided in it's description. helpRepeatCommand=\u05d4\u05d7\u05dc\u05e3 \u05d1\u05d9\u05df \u05de\u05e6\u05d1\u05d9 \u05d7\u05d6\u05e8\u05d5\u05ea. helpReshuffleCommand=\u05de\u05d0\u05e8\u05d2\u05df \u05de\u05d7\u05d3\u05e9 \u05d0\u05ea \u05d4\u05e8\u05e9\u05d9\u05de\u05d4 \u05d4\u05e0\u05d5\u05db\u05d7\u05d9\u05ea. helpSelectCommand=Select one of the offered tracks after a search to play. helpShuffleCommand=\u05d4\u05e4\u05e2\u05dc\\\u05d1\u05d8\u05dc \u05de\u05e6\u05d1 \u05e2\u05e8\u05d1\u05d5\u05d1 \u05dc\u05e8\u05e9\u05d9\u05de\u05d4 \u05d4\u05e0\u05d5\u05db\u05d7\u05d9\u05ea. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Skip the current song, the n'th song in the queue, all songs from n to m, or all songs from mentioned users. Please use in moderation. helpStopCommand=Stop the player and clear the playlist. Reserved for moderators with Manage Messages permission. helpUnpauseCommand=\u05de\u05de\u05e9\u05d9\u05da \u05d0\u05ea \u05de\u05d4\u05dc\u05da \u05d4\u05e0\u05d2\u05df. @@ -339,4 +346,3 @@ modulesHowTo=Say {0} to enable/disable modules. parseNotAUser=Your input {0} did not yield a Discord user. parseNotAMember=User {0} is not a member of this guild. parseSnowflakeIdHelp=Having trouble getting the id of a user/message/channel/something else? Check out the Discord docs at {0} - diff --git a/FredBoat/src/main/resources/lang/hr_HR.properties b/FredBoat/src/main/resources/lang/hr_HR.properties index 3eb40f5cd..c32a40352 100644 --- a/FredBoat/src/main/resources/lang/hr_HR.properties +++ b/FredBoat/src/main/resources/lang/hr_HR.properties @@ -7,6 +7,8 @@ playSearching=Tra\u017eim YouTube za `{q}`... playYoutubeSearchError=Dogodila se gre\u0161ka prilikom pretra\u017eivanja YouTube-a. Razmislite o stavljanju direktne poveznice na izvor zvuka\n```\n;;play ``` playSearchNoResults=Nismo na\u0161li nikakvu pjesmu pod nazivom `{q}` playSelectVideo=**Molimo odaberite pjesmu sa `{0}play n` command\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Dolazim u va\u0161 kanal za par sekundi {0} joinErrorAlreadyJoining=Pojavila se pogre\u0161ka. Ne mogu se pridru\u017eiti {0} jer se ve\u0107 poku\u0161avam povezati na taj kanal. Molimo poku\u0161ajte ponovo. pauseAlreadyPaused=[DJ]GaMiNg Discord-a je ve\u0107 pauziran. @@ -21,6 +23,9 @@ shuffleOn=[DJ]GaMiNg Discord-a je sad pomije\u0161ao pjesme. shuffleOff=Svira\u010d vi\u0161e nije promije\u0161ao pjesme. reshufflePlaylist=Red \u010dekanja ponovno promije\u0161an. reshufflePlayerNotShuffling=Prvo morate uklju\u010diti shuffle na\u010din. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=Red \u010dekanja je prazan\! skipOutOfBounds=Nemogu\u0107e je ukloniti pjesmu {0} po redu kad ima {1} pjesma. skipNumberTooLow=Dani broj bi trebao biti ve\u0107i od 0. @@ -244,12 +249,14 @@ helpJoinCommand=Neka bot pridru\u017ei va\u0161em trenutnom glasovnom kanalu. helpLeaveCommand=Napravite bot da napusti trenutni glasovni kanal. helpPauseCommand=Pauziraj svira\u010d. helpPlayCommand=Reproducirajte glazbu s navedenog URL-a ili potra\u017eite zapis. Za potpuni popis izvora posjetite {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Podijelite YouTube videozapis u popis pjesama naveden u svom opisu. helpRepeatCommand=Prebacivanje izme\u0111u na\u010dina ponavljanja. helpReshuffleCommand=Preusmjeri trenuta\u010dni red. helpSelectCommand=Odaberite jednu od ponu\u0111enih pjesama nakon pretra\u017eivanja za reprodukciju. helpShuffleCommand=Prebacivanje na slu\u010dajni na\u010din rada za trenuta\u010dni red. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Presko\u010dite trenutnu pjesmu, n'th pjesmu u redu, sve pjesme od n do m ili sve pjesme spomenutih korisnika. Molimo koristite umjereno. helpStopCommand=Zaustavite ure\u0111aj i izbri\u0161ite popis za reprodukciju. Rezervirano za moderatore s dozvolom za upravljanje porukama. helpUnpauseCommand=Odpauziraj svira\u010d. @@ -339,4 +346,3 @@ modulesHowTo=Recite {0} da biste omogu\u0107ili / onemogu\u0107ili module. parseNotAUser=Ulazni {0} nisu doneli nesloga korisnika. parseNotAMember=Korisnik {0} nije clan ovog chata. parseSnowflakeIdHelp=Imate problem za dobivanje id-a od korisnika poruke kanala ili neceg drugog posjetite Discord docs na {0} - diff --git a/FredBoat/src/main/resources/lang/hu_HU.properties b/FredBoat/src/main/resources/lang/hu_HU.properties index b411cba8d..17de6ac74 100644 --- a/FredBoat/src/main/resources/lang/hu_HU.properties +++ b/FredBoat/src/main/resources/lang/hu_HU.properties @@ -7,6 +7,8 @@ playSearching=Keres\u00e9s a YouTube-on erre `{q}`... playYoutubeSearchError=Hiba t\u00f6rt\u00e9nt a YouTube-on keres\u00e9s k\u00f6zben. Fontold meg ink\u00e1bb a k\u00f6zvetlen link haszn\u00e1lat\u00e1t a hang forr\u00e1s\u00e1hoz.\n```\n;;play ``` playSearchNoResults=Nincsen tal\u00e1lat erre `{q}` playSelectVideo=**K\u00e9rlek v\u00e1lasz zen\u00e9t a `{0}play 1-5` paranccsal\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Csatlakoz\u00e1s {0} szob\u00e1ba joinErrorAlreadyJoining=Egy hiba t\u00f6rt\u00e9nt. Nem tudok csatlakozni {0} szob\u00e1ba, mert m\u00e1r pr\u00f3b\u00e1lok csatlakozni oda. K\u00e9rlek pr\u00f3b\u00e1ld \u00fajra k\u00e9s\u0151bb. pauseAlreadyPaused=A lej\u00e1tsz\u00f3 m\u00e1r sz\u00fcneteltetve van. @@ -21,6 +23,9 @@ shuffleOn=A lej\u00e1tsz\u00e1si lista megkeverve. shuffleOff=A lej\u00e1tsz\u00e1si lista mostant\u00f3l nincs megkeverve. reshufflePlaylist=Lista \u00fajrakeverve. reshufflePlayerNotShuffling=El\u0151bb enged\u00e9lyezned kell a kevert m\u00f3dot. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=A lista \u00fcres\! skipOutOfBounds=Nem tudom elt\u00e1vol\u00edtani a {0}. sz\u00e1mot, mert csak {1} sz\u00e1m van. skipNumberTooLow=A megadott sz\u00e1mnak nagyobbnak kell lennie 0-n\u00e1l. @@ -62,7 +67,7 @@ npDescription=Le\u00edr\u00e1s npLoadedSoundcloud=[{0}/{1}]\n\nSoundcloud-r\u00f3l bet\u00f6ltve npLoadedBandcamp={0}\n\nBandcamp-r\u0151l bet\u00f6ltve npLoadedTwitch=Twitch-r\u0151l bet\u00f6ltve -npRequestedBy=Requested by {0} +npRequestedBy=Hozz\u00e1adta\: {0} permissionMissingBot=A k\u00f6vetkez\u0151 jogosults\u00e1gra van sz\u00fcks\u00e9gem a m\u0171velet v\u00e9grehajt\u00e1s\u00e1hoz\: permissionMissingInvoker=A k\u00f6vetkez\u0151 jogosults\u00e1gra van sz\u00fcks\u00e9gem a m\u0171velet v\u00e9grehajt\u00e1s\u00e1hoz\: permissionEmbedLinks=Be\u00e1gyazott Linkek @@ -92,11 +97,11 @@ loadPlaylistTooMany={0} zene hozz\u00e1adva. T\u00fal sok zen\u00e9t tal\u00e1lt loadErrorCommon=Hiba t\u00f6rt\u00e9nt az inform\u00e1ci\u00f3 bet\u00f6lt\u00e9sekor innen\:`{0}`\:\n{1} loadErrorSusp=Gyan\u00fas hiba `{0}` bet\u00f6lt\u00e9se k\u00f6zben. loadQueueTrackLimit=Nem adhatsz hozz\u00e1 t\u00f6bb, mint {0} sz\u00e1mot a list\u00e1hoz\! Ez a vissza\u00e9l\u00e9sek elker\u00fcl\u00e9se \u00e9rdek\u00e9ben van. -loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. -loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. -loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. -loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. -loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! +loadPlaylistDisabled=Ezen a szerveren letiltott\u00e1k a lej\u00e1tsz\u00e1si list\u00e1k hozz\u00e1ad\u00e1s\u00e1t. K\u00e9rlek egyes\u00e9vel add hozz\u00e1 a zen\u00e9ket\! +loadMaxTracksExceeded=Ezen a szerveren nem lehets\u00e9ges {0} sz\u00e1mn\u00e1l t\u00f6bbet v\u00e1r\u00f3list\u00e1ra helyezni. +loadMaxUserTracksExceeded=Ezen a szerveren nem lehets\u00e9ges fejenk\u00e9nt {0} sz\u00e1mn\u00e1l t\u00f6bbet v\u00e1r\u00f3list\u00e1ra helyezni. +loadMaxTrackLengthExceeded=Ezen a szerrveren nem j\u00e1tszhatsz hosszabb sz\u00e1mokat mint {0}. Pr\u00f3b\u00e1lj valami r\u00f6videbbet\! +loadPlaylistGeneralError=Nem siker\u00fclt hozz\u00e1adni {0} sz\u00e1mot, mert a szerver be\u00e1ll\u00edt\u00e1sai miatt. loadAnnouncePlaylist=\u00c9ppen most t\u00f6lt\u00f6m be a(z) **{0}** lej\u00e1tsz\u00e1si list\u00e1t `{1}` zenesz\u00e1mmal. Ez eltarthat egy kis ideig, k\u00e9rlek l\u00e9gy t\u00fcrelmes. playerUserNotInChannel=El\u0151bb csatlakozz egy hang csatorn\u00e1hoz. playerJoinConnectDenied=Nem csatlakozhatok ahhoz a hang csatorn\u00e1hoz. @@ -108,7 +113,7 @@ shutdownRestarting=FredBoat\u266a\u266a \u00fajraindul. Ez csak egy percig tart shutdownIndef=FredBoat\u266a\u266a le\u00e1ll. Amint a bot visszaj\u00f6n, a jelenlegi lej\u00e1tsz\u00e1si list\u00e1t \u00fajra fogja t\u00f6lteni. shutdownPersistenceFail=Hiba t\u00f6rt\u00e9nt a f\u00e1jl ment\u00e9sekor\: {0} reloadSuccess=Lej\u00e1tsz\u00e1si lista \u00fajrat\u00f6lt\u00e9se. `{0}` zen\u00e9t tal\u00e1ltam. -trackAnnounce=ghhgg +trackAnnounce=Now playing **{0}**. Requested by\: **{1}**. cmdAccessDenied=Nincs enged\u00e9lyed haszn\u00e1lni ezt a parancsot\! malRevealAnime={0}\: A keres\u00e9s tal\u00e1lt egy anime-t.\n malTitle={0}**C\u00edm\: **{1}\n @@ -244,12 +249,14 @@ helpJoinCommand=A bot bel\u00e9p a jelenlegi hang csatorn\u00e1dba. helpLeaveCommand=A bot elhagyja a jelenlegi hang csatorn\u00e1t. helpPauseCommand=Lej\u00e1tsz\u00f3 sz\u00fcneteltet\u00e9se. helpPlayCommand=Zene lej\u00e1tsz\u00e1sa a megadott linken, vagy sz\u00e1m keres\u00e9se. A forr\u00e1sok teljes list\u00e1j\u00e1\u00e9rt l\u00e1togass el ide\: {0} -helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} +helpReplayCommand=Replay the most recent track from history. +helpPlayNextCommand=Zene lej\u00e1tsz\u00e1sa a megadott URL-r\u0151l vagy keres\u00e9s c\u00edm alapj\u00e1n. A zenesz\u00e1mok a v\u00e1r\u00f3sor elej\u00e9re ker\u00fclnek. A forr\u00e1sok teljes list\u00e1j\u00e1\u00e9rt l\u00e1togass ide\: {0} helpPlaySplitCommand=Egy YouTube vide\u00f3 feloszt\u00e1sa sz\u00e1mokra a le\u00edr\u00e1sa alapj\u00e1n. helpRepeatCommand=Ism\u00e9tl\u00e9s m\u00f3dok k\u00f6z\u00f6tti v\u00e1lt\u00e1s. helpReshuffleCommand=A jelenlegi lej\u00e1tsz\u00e1si lista \u00fajrakever\u00e9se. helpSelectCommand=V\u00e1laszthatsz a felaj\u00e1nlott sz\u00e1mok k\u00f6z\u00fcl keres\u00e9s ut\u00e1n. helpShuffleCommand=Jelenlegi lista kever\u00e9s be- vagy kikapcsol\u00e1sa. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=A jelenlegi zene \u00e1tugr\u00e1sa, n-edik zene \u00e1tugr\u00e1sa, minden zene \u00e1tugr\u00e1sa n-t\u0151l m-ig, vagy minden zene \u00e1tugr\u00e1sa az eml\u00edtett felhaszn\u00e1l\u00f3t\u00f3l. K\u00e9rlek haszn\u00e1ld moder\u00e1ltan. helpStopCommand=Meg\u00e1ll\u00edtja a lej\u00e1tsz\u00f3t \u00e9s t\u00f6rli a lej\u00e1tsz\u00e1si list\u00e1t. fenntartva moder\u00e1toroknak \u00fczenet kezel\u00e9s jogosults\u00e1ggal. helpUnpauseCommand=Lej\u00e1tsz\u00e1s folytat\u00e1sa. @@ -278,7 +285,7 @@ helpUserInfoCommand=Inform\u00e1ci\u00f3k megjelen\u00edt\u00e9se r\u00f3lad vag helpPerms=Tagok \u00e9s szerverrangok hozz\u00e1ad\u00e1sa {0} ranghoz. helpPrefixCommand=Be\u00e1ll\u00edtja az el\u0151tagot ezen a szerveren. helpVoteSkip=Szavaz\u00e1s a jelenlegi zene \u00e1tugr\u00e1s\u00e1r\u00f3l. Legal\u00e1bb a szob\u00e1ban l\u00e9v\u0151 felhaszn\u00e1l\u00f3k 50%-\u00e1nak szavaznia kell. -helpUnvoteSkip=Remove your vote to skip the current song. +helpUnvoteSkip=Zenesz\u00e1m \u00e1tugr\u00e1s\u00e1r\u00f3l val\u00f3 szavazatod elt\u00e1vol\u00edt\u00e1sa. helpMathOperationAdd=Ki\u00edrja sz\u00e1m1 \u00e9s sz\u00e1m2 \u00f6sszeg\u00e9t. helpMathOperationSub=Ki\u00edrja sz\u00e1m1 \u00e9s sz\u00e1m2 k\u00fcl\u00f6nbs\u00e9g\u00e9t. helpMathOperationMult=Ki\u00edrja sz\u00e1m1 \u00e9s sz\u00e1m2 szorzat\u00e1t. @@ -307,8 +314,8 @@ skipUserMultiple={1} \u00e1ltal hozz\u00e1adott {0} sz\u00e1m \u00e1tugorva. skipUsersMultiple={1} felhaszn\u00e1l\u00f3 \u00e1ltal hozz\u00e1adott {0} sz\u00e1m \u00e1tugorva. skipUserNoTracks=Az eml\u00edtett felhaszn\u00e1l\u00f3k \u00e1ltal nincs hozz\u00e1adva zenesz\u00e1m a v\u00e1r\u00f3list\u00e1n. voteSkipAdded=A szavazatod hozz\u00e1adva\! -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! +voteSkipRemoved=A szavazatod elt\u00e1vol\u00edtva\! +voteSkipNotFound=Nem szavazt\u00e1l a zenesz\u00e1m \u00e1tugr\u00e1s\u00e1r\u00f3l\! voteSkipAlreadyVoted=M\u00e1r szavazt\u00e1l a zenesz\u00e1m \u00e1tugr\u00e1s\u00e1r\u00f3l\! voteSkipSkipping={0} szavazott az \u00e1tugr\u00e1sr\u00f3l. {1} \u00e1tugr\u00e1sa. voteSkipNotEnough={0} szavazott az \u00e1tugr\u00e1sr\u00f3l. Legal\u00e1bb {1} sz\u00fcks\u00e9ges. @@ -339,4 +346,3 @@ modulesHowTo={0} paranccsal enged\u00e9lyezhetsz vagy letilthatsz modulokat. parseNotAUser={0} nem egy Discord felhaszn\u00e1l\u00f3. parseNotAMember=Felhaszn\u00e1l\u00f3 {0} nem tagja ennek a szervernek. parseSnowflakeIdHelp=Probl\u00e9m\u00e1d van egy felhaszn\u00e1l\u00f3/\u00fczenet/szoba/m\u00e1s Fejleszt\u0151i k\u00f3dj\u00e1nak megszerz\u00e9s\u00e9vel? N\u00e9zd \u00e1t a Discord dokument\u00e1ci\u00f3j\u00e1t itt\: {0} - diff --git a/FredBoat/src/main/resources/lang/id_ID.properties b/FredBoat/src/main/resources/lang/id_ID.properties index dcef73e4e..8e58146e9 100644 --- a/FredBoat/src/main/resources/lang/id_ID.properties +++ b/FredBoat/src/main/resources/lang/id_ID.properties @@ -7,6 +7,8 @@ playSearching=Mencari link youtube untuk `{q}`... playYoutubeSearchError=Terjadi kesalahan dalam pencarian. Silahkan gunakan link playSearchNoResults=Tidak ada hasil untuk {q} playSelectVideo=**Pilih trek dengan perintah `{0}play 1-5`** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Tergabung {0} joinErrorAlreadyJoining=Terjadi kesalahan. tidak dapat tersambung {0} karena telah bergabung dengan channel tersebut. Silahkan coba lagi. pauseAlreadyPaused=Pemutar telah dihentikan sementara. @@ -21,6 +23,9 @@ shuffleOn=Pemutar Sekarang Dalam Mode Acak. shuffleOff=Pemutar tidak lagi menggunakan mode acak. reshufflePlaylist=Antrian Diacak ulang. reshufflePlayerNotShuffling=Kamu harus mengaktifkan mode acak terlebih dahulu. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=Antrian lagu kosong\! skipOutOfBounds=Tidak dapat menghapus track nomor {0} ketika hanya ada {1} track. skipNumberTooLow=Nomor yang diberikan harus lebih besar dari 0. @@ -244,12 +249,14 @@ helpJoinCommand=Membuat bot bergabung kedalam voice channel yang Anda masuki. helpLeaveCommand=Membuat bot meninggalkan voice channel yang telah masuki. helpPauseCommand=Menjeda pemutar musik. helpPlayCommand=Mainkan musik dari URL yang diberikan atau cari trek. Untuk daftar lengkap sumber silakan kunjungi {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Membagi YouTube video ke dalam daftar lagu yang disediakan dalam deskripsi tersebut. helpRepeatCommand=Beralih antara mode berulang. helpReshuffleCommand=Mengacak ulang antrian lagu saat ini. helpSelectCommand=Pilih salah satu trek yang ditawarkan setelah pencarian untuk memainkannya. helpShuffleCommand=Beralih mode acak untuk antrian lagu saat ini. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Lewati lagu saat ini, lagu n'th dalam antrian, semua lagu dari n ke m, atau semua lagu dari pengguna yang disebutkan di atas. Mohon gunakan secukupnya. helpStopCommand=Menghentikan pemutar musik dan membersihkan daftar lagu. Diperuntukkan bagi moderator dengan izin mengelola pesan. helpUnpauseCommand=Menjalankan kembali pemutar musik. @@ -339,4 +346,3 @@ modulesHowTo=Say {0} to enable/disable modules. parseNotAUser=Your input {0} did not yield a Discord user. parseNotAMember=User {0} is not a member of this guild. parseSnowflakeIdHelp=Having trouble getting the id of a user/message/channel/something else? Check out the Discord docs at {0} - diff --git a/FredBoat/src/main/resources/lang/it_IT.properties b/FredBoat/src/main/resources/lang/it_IT.properties index 16b34e5be..8e131096e 100644 --- a/FredBoat/src/main/resources/lang/it_IT.properties +++ b/FredBoat/src/main/resources/lang/it_IT.properties @@ -7,6 +7,8 @@ playSearching=Cercando `{q}` su YouTube... playYoutubeSearchError=Si \u00e8 verificato un errore durante la ricerca su YouTube. Prova ad usare direttamente l'url.\n```\n;;play ``` playSearchNoResults=Nessun risultato per `{q}` playSelectVideo=* * Si prega di selezionare una traccia con il comando ''{0}play n''\: * * +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Entrando in {0} joinErrorAlreadyJoining=Si \u00e8 verificato un errore. Non mi \u00e8 stato possibile unirmi a {0} poich\u00e9 sto gi\u00e0 provando ad effettuare la connessione. Per favore, prova di nuovo. pauseAlreadyPaused=Il player \u00e8 gi\u00e0 in pausa. @@ -21,6 +23,9 @@ shuffleOn=Il player \u00e8 ora in modalit\u00e0 casuale. shuffleOff=Il player non \u00e8 pi\u00f9 in modalit\u00e0 casuale. reshufflePlaylist=Lista di brani riorganizzata. reshufflePlayerNotShuffling=Devi prima attivare la modalit\u00e0 shuffle. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=La coda di riproduzione \u00e8 vuota\! skipOutOfBounds=Non \u00e8 possibile rimuovere la traccia numero {0} se ne sono presenti solamente {1}. skipNumberTooLow=Il numero deve essere maggiore di 0. @@ -62,7 +67,7 @@ npDescription=Descrizione npLoadedSoundcloud=[{0}/{1}]\n\nCaricato da SoundCloud npLoadedBandcamp={0}\n\nCaricato da Bandcamp npLoadedTwitch=Caricato da Twitch -npRequestedBy=Requested by {0} +npRequestedBy=Richiesto da {0} permissionMissingBot=Ho bisogno della seguente autorizzazione per eseguire tale azione\: permissionMissingInvoker=Hai bisogno del seguente permesso per eseguire tale azione\: permissionEmbedLinks=Incorpora i collegamenti @@ -92,11 +97,11 @@ loadPlaylistTooMany=Aggiunta/e {0} traccia/e. Sono state trovate troppe tracce p loadErrorCommon=Si \u00e8 verificato un errore durante il caricamento delle info relative a `{0}`\:\n{1} loadErrorSusp=Errore sospetto durante il caricamento delle informazioni relative a `{0}`. loadQueueTrackLimit=Non \u00e8 possibile aggiungere brani a una coda con pi\u00f9 di {0} brani\! Ci\u00f2 \u00e8 per prevenire l''abuso. -loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. -loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. -loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. -loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. -loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! +loadPlaylistDisabled=Questo server ha disabilitato la coda delle playlist. Per favore, metti in coda ogni traccia individualmente. +loadMaxTracksExceeded=Questo server non consente di mettere in coda pi\u00f9 di {0} tracce. +loadMaxUserTracksExceeded=Questo server non ti permette di avere pi\u00f9 di {0} tracce nella coda. +loadMaxTrackLengthExceeded=Questo server non ti permette di riprodurre tracce pi\u00f9 lunghe di {0}. Per favore, prova con qualcosa di pi\u00f9 corto. +loadPlaylistGeneralError={0} tracce non sono state aggiungte perch\u00e8 questo server applica restrizioni per la coda\! loadAnnouncePlaylist=Sto per caricare la playlist ** {0} ** con fino a `{1}` brani. Questo potrebbe richiedere un po ''di tempo, per favore essere paziente.\n \nContesto | Contesto di richiest. playerUserNotInChannel=Devi prima unirti ad un canale vocale. playerJoinConnectDenied=Non ho i permessi necessari per unirmi a quel canale vocale. @@ -244,12 +249,14 @@ helpJoinCommand=Far si che il bot si unisce al vostro canale corrente. helpLeaveCommand=Fai lasciare il bot lasciare il canale vocale corrente. helpPauseCommand=Sospendi il lettore. helpPlayCommand=Riprodurre musica dal determinato URL o cercare una traccia. Per un elenco completo delle fonti si prega di visitare {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Riproduci musica dall''URL indicato o cerca una traccia. Le tracce vengono aggiunte alla cima della coda di riproduzione. Per una lista completa delle fonti si prega di visitare {0} helpPlaySplitCommand=Distribuisci un video di YouTube in una lista di brani fornita nella descrizione. helpRepeatCommand=Passare tra le modalit\u00e0 di ripetizione. helpReshuffleCommand=Rimescola la lista corrente. helpSelectCommand=Selezionare una delle tracce offerte dopo una ricerca da riprodurre. helpShuffleCommand=Attiva la modalit\u00e0 shuffle per la coda corrente. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Per saltare delle canzoni, la canzone numero n, tutte le canzoni da n a m. o tutte le canzoni da utenti specifici. Da usare con moderazione. helpStopCommand=Arrestare il lettore e cancellare la playlist. Riservato ai moderatori con permesso di gestire i messaggi. helpUnpauseCommand=Riattivare il bot. @@ -339,4 +346,3 @@ modulesHowTo=Dire {0} per attivare/disattivare i moduli. parseNotAUser=Il tuo input {0} non ha fatto cedere un utente di Discord. parseNotAMember=L''utente {0} non \u00e8 membro di questo server. parseSnowflakeIdHelp=Hai difficolt\u00e0 ad avere l\u2019id di un utente/messaggio/canale/qualcos\u2019altro? Guarda i documenti di Discord su {0} - diff --git a/FredBoat/src/main/resources/lang/ja_JP.properties b/FredBoat/src/main/resources/lang/ja_JP.properties index 3f57ba285..c05292070 100644 --- a/FredBoat/src/main/resources/lang/ja_JP.properties +++ b/FredBoat/src/main/resources/lang/ja_JP.properties @@ -7,6 +7,8 @@ playSearching=`{q}`\u3092\u63a2\u3057\u3066\u3044\u307e\u3059\u3002 playYoutubeSearchError=YouTube\u3092\u691c\u7d22\u3059\u308b\u3068\u304d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u4ee3\u308f\u308a\u306b\u30aa\u30fc\u30c7\u30a3\u30aa \u30bd\u30fc\u30b9\u306b\u76f4\u63a5\u30ea\u30f3\u30af\u3057\u3066\u304f\u3060\u3055\u3044\u3002```\n ;;play ``` playSearchNoResults=`{q}`\u306e\u691c\u7d22\u7d50\u679c\u304c\u3042\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002 playSelectVideo=**`{0}play 1-5`\u30b3\u30de\u30f3\u30c9\u3092\u4f7f\u7528\u3057\u3066\u518d\u751f\u3059\u308b\u66f2\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining={0} \u306b\u53c2\u52a0\u3057\u3066\u3044\u307e\u3059 joinErrorAlreadyJoining=\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002Fredboat\u306f\u3059\u3067\u306b\u305d\u306e\u30c1\u30e3\u30cd\u30eb\u306b\u63a5\u7d9a\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u308b\u306e\u3067\u3001{0} \u306b\u306f\u53c2\u52a0\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u3082\u3046\u4e00\u5ea6\u3084\u308a\u76f4\u3057\u3066\u304f\u3060\u3055\u3044\u3002 pauseAlreadyPaused=\u30d7\u30ec\u30fc\u30e4\u30fc\u306f\u65e2\u306b\u4e00\u6642\u505c\u6b62\u3057\u3066\u3044\u307e\u3059\u3002 @@ -21,6 +23,9 @@ shuffleOn=\u3053\u306e\u30d7\u30ec\u30a4\u30e4\u30fc\u306f\u30b7\u30e3\u30c3\u30 shuffleOff=\u30d7\u30ec\u30a4\u30e4\u30fc\u306f\u30b7\u30e3\u30c3\u30d5\u30eb\u3055\u308c\u306a\u304f\u306a\u308a\u307e\u3057\u305f\u3002 reshufflePlaylist=\u30ad\u30e5\u30fc\u3092\u3082\u3046\u4e00\u5ea6\u30b7\u30e3\u30c3\u30d5\u30eb\u3057\u307e\u3059\u3002 reshufflePlayerNotShuffling=\u307e\u305a\u3001\u30b7\u30e3\u30c3\u30d5\u30eb \u30e2\u30fc\u30c9\u3092\u30aa\u30f3\u306b\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002 +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=\u30ad\u30e5\u30fc\u306b\u4f55\u3082\u3042\u308a\u307e\u305b\u3093\uff01 skipOutOfBounds={1} \u30c8\u30e9\u30c3\u30af\u304c\u304c\u3042\u308b\u3068\u304d\u306f\u3001\u30c8\u30e9\u30c3\u30af\u756a\u53f7 {0} \u3092\u524a\u9664\u3067\u304d\u307e\u305b\u3093\u3002 skipNumberTooLow=\u4e0e\u3048\u3089\u308c\u305f\u756a\u53f7\u306f0\u3088\u308a\u5927\u304d\u3044\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\uff01 @@ -244,12 +249,14 @@ helpJoinCommand=Bot\u3092\u3001\u3042\u306a\u305f\u304c\u73fe\u5728\u63a5\u7d9a\ helpLeaveCommand=Bot\u3092\u3001\u73fe\u5728\u63a5\u7d9a\u3057\u3066\u3044\u308b\u30dc\u30a4\u30b9\u30c1\u30e3\u30f3\u30cd\u30eb\u304b\u3089\u9000\u51fa\u3055\u305b\u307e\u3059\u3002 helpPauseCommand=\u30d7\u30ec\u30fc\u30e4\u30fc\u3092\u4e00\u6642\u505c\u6b62\u3002 helpPlayCommand=\u6307\u5b9a\u3057\u305fURL\u304b\u3089\u97f3\u697d\u3092\u518d\u751f\u3059\u308b\u304b\u3001\u30c8\u30e9\u30c3\u30af\u3092\u691c\u7d22\u3057\u307e\u3059\u3002\u30bd\u30fc\u30b9\u306e\u5b8c\u5168\u306a\u30ea\u30b9\u30c8\u306b\u3064\u3044\u3066\u306f\u3001{0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=\u7279\u5b9a\u306e URL \u307e\u305f\u306f\u30c8\u30e9\u30c3\u30af\u306e\u691c\u7d22\u304b\u3089\u97f3\u697d\u3092\u518d\u751f\u3057\u307e\u3059\u3002\u30c8\u30e9\u30c3\u30af\u306f\u3001\u30ad\u30e5\u30fc\u306e\u5148\u982d\u306b\u8ffd\u52a0\u3055\u308c\u307e\u3059\u3002\u30bd\u30fc\u30b9\u306e\u5b8c\u5168\u306a\u30ea\u30b9\u30c8\u306f\u3001 {0} \u3092\u53c2\u7167\u3057\u3066\u304f\u3060\u3055\u3044\u3002 helpPlaySplitCommand=YouTube\u306e\u52d5\u753b\u3092\u305d\u306e\u8aac\u660e\u306e\u30c8\u30e9\u30c3\u30af\u30ea\u30b9\u30c8\u306b\u5206\u5272\u3057\u307e\u3059\u3002 helpRepeatCommand=\u30ea\u30d4\u30fc\u30c8\u30e2\u30fc\u30c9\u3092\u5207\u308a\u66ff\u3048\u307e\u3059\u3002 helpReshuffleCommand=\u73fe\u5728\u306e\u30ad\u30e5\u30fc\u3092\u30b7\u30e3\u30c3\u30d5\u30eb\u3057\u306a\u304a\u3057\u307e\u3059\u3002 helpSelectCommand=\u691c\u7d22\u5f8c\u306b\u63d0\u4f9b\u3055\u308c\u305f\u30c8\u30e9\u30c3\u30af\u306e1\u3064\u3092\u9078\u629e\u3057\u3066\u518d\u751f\u3057\u307e\u3059\u3002 helpShuffleCommand=\u73fe\u5728\u306e\u30ad\u30e5\u30fc\u306e\u30b7\u30e3\u30c3\u30d5\u30eb\u30e2\u30fc\u30c9\u3092\u5207\u308a\u66ff\u3048\u3002 +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=\u73fe\u5728\u306e\u66f2\u3001\u30ad\u30e5\u30fc\u306en\u756a\u76ee\u306e\u66f2\u3001n\u304b\u3089m\u307e\u3067\u306e\u3059\u3079\u3066\u306e\u66f2\u3001\u307e\u305f\u306f\u524d\u8ff0\u306e\u30e6\u30fc\u30b6\u30fc\u306e\u3059\u3079\u3066\u306e\u66f2\u3092\u30b9\u30ad\u30c3\u30d7\u3057\u307e\u3059\u3002\u9069\u5ea6\u306b\u3054\u4f7f\u7528\u304f\u3060\u3055\u3044\u3002 helpStopCommand=\u30d7\u30ec\u30fc\u30e4\u30fc\u3092\u505c\u6b62\u3057\u3001\u30d7\u30ec\u30a4\u30ea\u30b9\u30c8\u3092\u30af\u30ea\u30a2\u3057\u307e\u3059\u3002\u30e1\u30c3\u30bb\u30fc\u30b8\u7ba1\u7406\u6a29\u9650\u3092\u6301\u3064\u30e2\u30c7\u30ec\u30fc\u30bf\u30fc\u306e\u305f\u3081\u306b\u4e88\u7d04\u3055\u308c\u3066\u3044\u307e\u3059\u3002 helpUnpauseCommand=\u30d7\u30ec\u30fc\u30e4\u30fc\u3092\u518d\u958b\u3002 @@ -339,4 +346,3 @@ modulesHowTo={0} \u3068\u9001\u4fe1\u3059\u308b\u3053\u3068\u3067\u30e2\u30b8\u3 parseNotAUser=\u3042\u306a\u305f\u306e\u5165\u529b {0} \u306b\u306f\u4e0d\u548c\u30e6\u30fc\u30b6\u30fc\u304c\u51fa\u306a\u304b\u3063\u305f\u3002 parseNotAMember=\u30e6\u30fc\u30b6\u30fc {0} \u306f\u3001\u3053\u306e\u30ae\u30eb\u30c9\u306e\u30e1\u30f3\u30d0\u30fc\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002 parseSnowflakeIdHelp=\u30c8\u30e9\u30d6\u30eb\u3001\u30e6\u30fc\u30b6\u30fc/\u30e1\u30c3\u30bb\u30fc\u30b8/\u30c1\u30e3\u30f3\u30cd\u30eb/\u4f55\u304b\u4ed6\u306e id?{0} \u306e\u4e0d\u548c\u306e\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u3092\u30c1\u30a7\u30c3\u30af\u3057\u3066\u304f\u3060\u3055\u3044\u3002 - diff --git a/FredBoat/src/main/resources/lang/ko_KR.properties b/FredBoat/src/main/resources/lang/ko_KR.properties index 400e863c8..c2037d677 100644 --- a/FredBoat/src/main/resources/lang/ko_KR.properties +++ b/FredBoat/src/main/resources/lang/ko_KR.properties @@ -7,6 +7,8 @@ playSearching=\uc720\ud29c\ube0c\uc5d0\uc11c `{q}` \uc744 \uac80\uc0c9\ud558\uac playYoutubeSearchError=\uc720\ud29c\ube0c \uace1 \uac80\uc0c9 \uc911 \uc5d0\ub7ec\uac00 \ubc1c\uc0dd\ud558\uc600\uc2b5\ub2c8\ub2e4. \uc720\ud29c\ube0c \uc8fc\uc18c \uc785\ub825\uc744 \ud1b5\ud574\uc11c\ub3c4 \uace1 \ucd94\uac00\uac00 \uac00\ub2a5\ud569\ub2c8\ub2e4. \n```;;play ``` playSearchNoResults=`{q}` \uc5d0 \ub300\ud55c \uac80\uc0c9\uacb0\uacfc\uac00 \uc5c6\uc2b5\ub2c8\ub2e4. playSelectVideo=**`{0}play n`\uba85\ub839\uc5b4\ub97c \uc0ac\uc6a9\ud558\uc5ec \ud2b8\ub799\uc744 \uc120\ud0dd\ud574 \uc8fc\uc2dc\uae38 \ubc14\ub78d\ub2c8\ub2e4** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining={0} \ucc44\ub110\uc5d0 \uc811\uc18d\ud569\ub2c8\ub2e4. joinErrorAlreadyJoining=\uc774\ubbf8 {0} \ucc44\ub110\uc5d0 \uc811\uc18d\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4. \uc7a0\uc2dc \ud6c4 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694. pauseAlreadyPaused=\uc774\ubbf8 \uc77c\uc2dc\uc815\uc9c0 \ub41c \uc0c1\ud0dc\uc785\ub2c8\ub2e4. @@ -21,6 +23,9 @@ shuffleOn=\uc154\ud50c(\ub79c\ub364) \uae30\ub2a5\uc774 \ub3d9\uc791\ub418\uc5c8 shuffleOff=\uc154\ud50c(\ub79c\ub364) \uae30\ub2a5\uc774 \ud574\uc81c\ub418\uc5c8\uc2b5\ub2c8\ub2e4. reshufflePlaylist=\ub300\uae30 \ubaa9\ub85d \ub2e4\uc2dc \uc11e\uc784. reshufflePlayerNotShuffling=\ub2f9\uc2e0\uc740 \uba3c\uc800 \uc154\ud50c \ubaa8\ub4dc\ub97c \ucf1c\uc57c \ud569\ub2c8\ub2e4. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=\ud604\uc7ac \ud50c\ub808\uc774\uc5b4\uac00 \uc7ac\uc0dd\ud560\uc218 \uc788\ub294 \uace1\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. skipOutOfBounds={0} \ubc88 \ud2b8\ub799\uc744 \uc2a4\ud0b5\ud560\uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ud604\uc7ac \uc7ac\uc0dd \ud050\uc5d0 {1} \ud2b8\ub799 \ubc16\uc5d0 \uc5c6\uc2b5\ub2c8\ub2e4. skipNumberTooLow=\uc2a4\ud0b5 \uc120\ud0dd \ubc88\ud638\ub294 0 \ubcf4\ub2e4\ub294 \ucee4\uc57c\ud569\ub2c8\ub2e4. @@ -92,10 +97,10 @@ loadPlaylistTooMany={0} \uac1c\uc758 \uc74c\uc545\uc744 \ucd94\uac00\ud588\uc2b5 loadErrorCommon=`{0}`\uc744(\ub97c) \uc9c4\ud589\ud558\ub294 \uc911 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4\:\n{1} loadErrorSusp=`{0}`\uc5d0\uc11c \uc624\ub958\uac00 \uc758\uc2ec\ub429\ub2c8\ub2e4. loadQueueTrackLimit=\ud2b8\ub799\uc744 {0}\uac1c \uc774\uc0c1\uc758 \ud2b8\ub799\uc774 \uc788\ub294 \ub300\uae30 \uc5f4\uc5d0 \ucd94\uac00\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4\! \uc774\uac83\uc740 \ub0a8\uc6a9 \ubc29\uc9c0\ud558\uae30 \uc704\ud568\uc785\ub2c8\ub2e4. -loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadPlaylistDisabled=\uc774 \uc11c\ubc84\ub294 \uc7ac\uc0dd\ubaa9\ub85d\uc744 \ub123\ub294 \uac83\uc774 \ube44\ud65c\uc131\ud654\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4.\n\ud55c \uace1\uc529 \ub123\uc5b4\uc8fc\uc138\uc694. loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. -loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadMaxTrackLengthExceeded=\uc774 \uc11c\ubc84\ub294 {0}\ubcf4\ub2e4 \uae34 \ub178\ub798\uc758 \uc7ac\uc0dd\uc744 \ud5c8\uc6a9\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4. \ub354 \uc9e7\uc740 \uac83\uc73c\ub85c \uc2dc\ub3c4\ud574 \ubcf4\uc138\uc694. loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! loadAnnouncePlaylist=\uc7ac\uc0dd \ubaa9\ub85d **{0}** \uc5d0 \ub300\ud55c \ucd5c\ub300 "{1}" \ud2b8\ub799\uc744 \ub85c\ub4dc\ud558\ub294 \uc911\uc785\ub2c8\ub2e4. \uc7a0\uc2dc\ub9cc \uae30\ub2e4\ub824 \uc8fc\uc2ed\uc2dc\uc624. playerUserNotInChannel=\ub2f9\uc2e0\uc740 \uc6b0\uc120 \uc74c\uc131 \ucc44\ub110\uc5d0 \ub4e4\uc5b4\uc640\uc57c \ud569\ub2c8\ub2e4. @@ -244,12 +249,14 @@ helpJoinCommand=\ubd07\uc744 \ub2f9\uc2e0\uc774 \ud604\uc7ac \ub4e4\uc5b4\uac00 helpLeaveCommand=\ubd07\uc744 \ud604\uc7ac \uc74c\uc131 \ucc44\ub110\uc5d0\uc11c \ub5a0\ub098\uac8c \ud558\uc138\uc694. helpPauseCommand=\ud50c\ub808\uc774\uc5b4\ub97c \uc77c\uc2dc \uc911\uc9c0 \ud569\ub2c8\ub2e4. helpPlayCommand=\uc9c0\uc815\ub41c URL\uc5d0\uc11c \uc74c\uc545\uc744 \uc7ac\uc0dd\ud558\uac70\ub098 \ud2b8\ub799\uc744 \uac80\uc0c9\ud569\ub2c8\ub2e4. \uc804\uccb4 \uc18c\uc2a4 \ubaa9\ub85d\uc744 \ubcf4\ub824\uba74{0} \uc744(\ub97c) \ubc29\ubb38\ud558\uc2ed\uc2dc\uc624. +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=\uc720\ud29c\ube0c \uc601\uc0c1\uc744 \uc124\uba85\uc5d0 \uc81c\uacf5\ub41c \ud2b8\ub799\ubaa9\ub85d\uc73c\ub85c \ub098\ub204\uc138\uc694. helpRepeatCommand=\ubc18\ubcf5 \ubaa8\ub4dc \uc5d0\uc11c \uc804\ud658\ud568 helpReshuffleCommand=\ud604\uc7ac \ub300\uae30 \ubaa9\ub85d\uc744 \ub2e4\uc2dc \uc154\ud50c \ud558\uc138\uc694. helpSelectCommand=\uac80\uc0c9 \ud6c4 \uc81c\uacf5\ub41c \ud2b8\ub799\uc911\uc5d0\uc11c \uc120\ud0dd\uc744 \ud558\uc5ec \uc7ac\uc0dd\ud558\uc138\uc694. helpShuffleCommand=\ud604\uc7ac \ub300\uae30\ubaa9\ub85d\uc744 \uc704\ud55c \uc154\ud50c \ubaa8\ub4dc\ub97c \ud0b5\ub2c8\ub2e4. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=\ud604\uc7ac \uace1, N\ubc88\uc9f8 \uace1, \ubaa8\ub4e0 \ub178\ub798, \ub610\ub294 \uc5b8\uae09\ub41c \uc0ac\uc6a9\uc790\uc758 \ubaa8\ub4e0 \ub178\ub798\ub97c \uac74\ub108\ub701\ub2c8\ub2e4. \uc801\ub2f9\ud788 \uc0ac\uc6a9\ud574 \uc8fc\uc138\uc694. helpStopCommand=\ud50c\ub808\uc774\uc5b4\ub97c \uc911\uc9c0\ud558\uace0 \ud50c\ub808\uc774\ub9ac\uc2a4\ud2b8\ub97c \uae54\ub054\ud558\uac8c \uc0ad\uc81c\ud569\ub2c8\ub2e4. \uba54\uc2dc\uc9c0 \uad00\ub9ac \uad8c\ud55c\uc774 \uc788\ub294 \uad00\ub9ac\uc790\ub4e4\ud55c\ud14c \uad8c\ud55c\uc774 \uc788\uc2b5\ub2c8\ub2e4. helpUnpauseCommand=\ud50c\ub808\uc774\uc5b4\ub97c \ub2e4\uc2dc \uc7ac\uc0dd \ud569\ub2c8\ub2e4. @@ -339,4 +346,3 @@ modulesHowTo={0} \ub97c \uc785\ub825\ud574 \ubaa8\ub4c8\uc744 \ud65c\uc131\ud654 parseNotAUser=\ub2f9\uc2e0\uc758 \uc785\ub825\ub41c {0} \ud32c \ub4e4\uc740 \uac08\ub4f1 \uc0ac\uc6a9\uc790\uc758 \uc815\ubcf4\ub97c \uc0dd\uc131 \ud558\uc9c0 \uc54a\uc558\ub2e4. parseNotAMember=\uc0ac\uc6a9\uc790 {0} \uc774 \uae38\ub4dc\uc758 \uc77c\uc6d0\uc774 \uc544\ub2c8\ub2e4. parseSnowflakeIdHelp=Id\ub294 \uc0ac\uc6a9\uc790/\uba54\uc2dc\uc9c0/\ucc44\ub110/\ubb54\uac00 \ub2e4\ub978\uc758 \uc9c0\uc5d0 \ubb38\uc81c\uac00? {0} \ubd88 \ud654 \ubb38\uc11c \ud655\uc778 - diff --git a/FredBoat/src/main/resources/lang/ms_MY.properties b/FredBoat/src/main/resources/lang/ms_MY.properties index 829d6badd..7209ba28c 100644 --- a/FredBoat/src/main/resources/lang/ms_MY.properties +++ b/FredBoat/src/main/resources/lang/ms_MY.properties @@ -7,6 +7,8 @@ playSearching=Mencari `{q}` di YouTube... playYoutubeSearchError=Terdapat ralat ketika mencari di YouTube. Sila pertimbangkan untuk menampal pautan terus ke sumber audio.\n```\n;;play ``` playSearchNoResults=Tiada keputusan untuk `{q}` playSelectVideo=**Sila pilih lagu dengan perintah `{0}play 1-5`\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Sedang masuk ke {0} joinErrorAlreadyJoining=Telah berlakunya ralat. Saya tidak boleh masuk ke {0} kerana saya memang sedang menyambung ke saluran itu. Sila cuba lagi. pauseAlreadyPaused=Pemain telah dijedakan. @@ -21,6 +23,9 @@ shuffleOn=Pemain akan dikocok sekarang. shuffleOff=Pemain sudah tidak dikocok. reshufflePlaylist=Senarai menunggu telah dikocok semula. reshufflePlayerNotShuffling=Anda mesti menghidupkan mod kocok dahulu. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=Senarai menunggu masih kosong\! skipOutOfBounds=Tidak boleh mengeluarkan lagu nombor {0} apabila hanya ada {1} lagu. skipNumberTooLow=Nombor yang diberi mestilah lebih daripada 0. @@ -244,12 +249,14 @@ helpJoinCommand=Masukkan bot ke dalam saluran suara anda sekarang. helpLeaveCommand=Keluarkan bot daripada saluran suara anda sekarang. helpPauseCommand=Jedakan pemain. helpPlayCommand=Mainkan muzik dari URL yang diberikan atau cari lagu. Untuk senarai sumber yang penuh, sila layari {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Pecahkan video YouTube menjadi senarai lagu yang disediakan dalam keterangannya. helpRepeatCommand=Togol antara mod-mod ulangan. helpReshuffleCommand=Kocok semula senarai menunggu semasa. helpSelectCommand=Pilih salah satu lagu yang ditawarkan untuk dimainkan selepas pencarian lagu. helpShuffleCommand=Togol mod kocokan untuk senarai menunggu semasa. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Langkau lagu semasa, lagu yang ke-n dalam senarai menunggu, semua lagu dari n ke m, atau semua lagu daripada pengguna yang dinyatakan. Sila guna perintah ini dengan berhati-hati. helpStopCommand=Hentikan pemain dan kosongkan senarai main. Dikhaskan kepada moderator dengan kebenaran Mengurus Mesej. helpUnpauseCommand=Sambung semula pemain. @@ -339,4 +346,3 @@ modulesHowTo=Sebut {0} untuk bolehkan/lumpuhkan modul. parseNotAUser=Input anda {0} tidak padan dengan sebarang pengguna Discord. parseNotAMember=Pengguna {0} bukannya ahli pelayan ini. parseSnowflakeIdHelp=Ada masalah mendapatkan id pengguna/mesej/saluran/benda lain? Periksalah dokumen Discord di {0} - diff --git a/FredBoat/src/main/resources/lang/nl_NL.properties b/FredBoat/src/main/resources/lang/nl_NL.properties index 91f9c21c4..7f68830c2 100644 --- a/FredBoat/src/main/resources/lang/nl_NL.properties +++ b/FredBoat/src/main/resources/lang/nl_NL.properties @@ -7,6 +7,8 @@ playSearching=Op YouTube aan het zoeken naar `{q}`... playYoutubeSearchError=Er is een fout opgetreden tijdens het doorzoeken van YouTube. Overweeg om direct te linken naar een audiobron.\n```\n;;play ``` playSearchNoResults=Geen zoekresultaten voor `{q}` playSelectVideo=**Selecteer alstublieft een nummer met het `{0}play n` commando\\\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Aan het verbinden met {0} joinErrorAlreadyJoining=Er is een fout opgetreden. Kon niet aansluiten bij {0}. Er wordt al geprobeerd om met dit kanaal te verbinden. Probeer het opnieuw. pauseAlreadyPaused=De speler is al gepauzeerd. @@ -21,6 +23,9 @@ shuffleOn=De nummers worden vanaf nu in willekeurige volgorde afgespeeld. shuffleOff=De nummers worden vanaf nu niet meer in willekeurige volgorde afgespeeld. reshufflePlaylist=De nummers in de wachtrij zijn in een willekeurige volgorde gezet.\n\n\n\n reshufflePlayerNotShuffling=U moet eerst de shuffle-modus inschakelen. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=De wachtrij is leeg\! skipOutOfBounds=Kan nummer {0} niet verwijderen wanneer er slechts {1} nummer(s) aanwezig is/zijn. skipNumberTooLow=Het gegeven getal moet hoger dan 0 zijn. @@ -62,7 +67,7 @@ npDescription=Beschrijving npLoadedSoundcloud=[{0}/{1}] \n\nGeladen vanuit Soundcloud npLoadedBandcamp={0}\n\nGeladen vanuit Bandcamp npLoadedTwitch=Geladen vanuit Twitch -npRequestedBy=Requested by {0} +npRequestedBy=Aangevraagd door {0} permissionMissingBot=Ik heb de volgende toestemming nodig om die opdracht uit te voeren\: permissionMissingInvoker=Je hebt de volgende toestemming nodig om die opdracht uit te voeren\: permissionEmbedLinks=Voeg links toe @@ -92,11 +97,11 @@ loadPlaylistTooMany={0} nummers toegevoegd. Te veel nummers gevonden om weer te loadErrorCommon=Er is een fout opgetreden bij het laden van info voor `{0}`\: {1} loadErrorSusp=Verdachte fout bij het laden van info voor "{0}". loadQueueTrackLimit=U kunt geen nummers toevoegen aan een nummerreeks met meer dan {0} nummers\! Dit is om misbruik te voorkomen. -loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. -loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. -loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. -loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. -loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! +loadPlaylistDisabled=Deze server heeft afspeellijsten uitgeschakeld. Voeg elk nummer afzonderlijk toe aan de queue. +loadMaxTracksExceeded=Deze server staat niet toe dat er meer dan {0} nummers in de queue staan. +loadMaxUserTracksExceeded=Deze server staat je niet toe om meer dan {0} nummers in de wachtrij te hebben. +loadMaxTrackLengthExceeded=Deze server staat je niet toe om nummers langer te spelen dan {0}. Probeer iets korter. +loadPlaylistGeneralError={0} nummers zijn niet toegevoegd omdat deze server wachtrijbeperkingen heeft\! loadAnnouncePlaylist=Zodadelijk wordt de afspeellijst **{0} ** met tot ''{1}'' nummers geladen. Dit kan een tijdje duren, wees geduldig. playerUserNotInChannel=Je moet je eerst bij een spraakkanaal aansluiten. playerJoinConnectDenied=Ik heb geen toestemming om met dat spraakkanaal te verbinden. @@ -244,12 +249,14 @@ helpJoinCommand=Voeg de bot aan je huidige stemkanaal toe. helpLeaveCommand=Laat de bot je huidige stemkanaal verlaten. helpPauseCommand=Pauzeer de Bot. helpPlayCommand=Speel muziek van de gegeven URL of zoek naar een nummer. Bezoek alsjeblieft {0} voor een volledige lijst van bronnen +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Speel muziek van de opgegeven URL of zoek naar een track. Tracks worden toegevoegd aan de top van de wachtrij. Voor een volledige lijst van bronnen bezoek {0} helpPlaySplitCommand=Verdeel een YouTube video in een nummerreeks voorzien in zijn descriptie. helpRepeatCommand=Schakel tussen herhaal modi. helpReshuffleCommand=Schuifel de huidige nummerreeks opnieuw. helpSelectCommand=Selecteer een van de voorgestelde nummers na het zoeken om af te spelen. helpShuffleCommand=Verander de Shuffle-modus voor de huidige afspeellijst. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Sla het huidige nummer over, het n'th nummer in de wachtrij, alle liedjes van n tot m, of alle liedjes van de genoemde gebruikers over. Gelieft te gebruiken in moderatie. helpStopCommand=Stop de Bot en maak de afspeellijst leeg. alleen voor moderators met de Manage Messages permissie. helpUnpauseCommand=Hervat de Bot. @@ -339,4 +346,3 @@ modulesHowTo=Zeg {0} om modules aan of uit te zetten. parseNotAUser=Uw input {0} bracht geen Discord gebruiker op. parseNotAMember=Gebruiker {0} is geen lid van deze guild. parseSnowflakeIdHelp=Moeite om het id van een gebruiker/bericht/kanaal of van iets anders te verkrijgen? Bekijk de Discord docs op {0} - diff --git a/FredBoat/src/main/resources/lang/no_NO.properties b/FredBoat/src/main/resources/lang/no_NO.properties index a47ec82ad..99c26153b 100644 --- a/FredBoat/src/main/resources/lang/no_NO.properties +++ b/FredBoat/src/main/resources/lang/no_NO.properties @@ -7,6 +7,8 @@ playSearching=S\u00f8ker p\u00e5 YouTube etter\: `{q}`... playYoutubeSearchError=Det oppstod en feil n\u00e5r du s\u00f8kte p\u00e5 YouTube. Vurder \u00e5 koble direkte til lydkilder i stedet.\n```\n;;spill ``` playSearchNoResults=Ingen resultater for ''{q}'' playSelectVideo=** Vennligst velg et spor med kommandoen `{0} play n`\: ** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Bli med {0} joinErrorAlreadyJoining=En feil oppstod. Kunne ikke bli med {0} fordi jeg allerede pr\u00f8ver \u00e5 koble til den kanalen. V\u00e6r s\u00e5 snill, pr\u00f8v p\u00e5 nytt. pauseAlreadyPaused=Spilleren er allerede pauset. @@ -21,6 +23,9 @@ shuffleOn=Spilleren er n\u00e5 blandet. shuffleOff=Spilleren er ikke lenger blandet. reshufflePlaylist=K\u00f8 stokkes. reshufflePlayerNotShuffling=Du m\u00e5 f\u00f8rst aktivere shuffle-modus. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=K\u00f8en er tom\! skipOutOfBounds=Kan ikke fjerne spor nummer {0} n\u00e5r det er bare {1} spor. skipNumberTooLow=Tallet m\u00e5 v\u00e6re st\u00f8rre enn 0. @@ -244,12 +249,14 @@ helpJoinCommand=Make the bot join your current voice channel. helpLeaveCommand=Gj\u00f8r botten forlate gjeldende talekanal. helpPauseCommand=Pause spilleren. helpPlayCommand=Spille musikk fra gitt URL eller S\u00f8k etter et spor. For en fullstendig liste over kilder bes\u00f8k {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Del en YouTube-video i en sporliste som er angitt i beskrivelsen. helpRepeatCommand=Veksle mellom gjenta. helpReshuffleCommand=Endre gjeldende k\u00f8. helpSelectCommand=Velg ett av de tilbudte sporene etter s\u00f8k spille. helpShuffleCommand=Bytt shuffle-modus for den n\u00e5v\u00e6rende k\u00f8en. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Hopp over sangen, n'th sangen i k\u00f8en, alle sanger fra n m eller alle sanger fra nevnte brukere. Bruk i moderasjon. helpStopCommand=Stopp spilleren og fjern spillelisten. Reservert for moderatorer med Administrer meldinger tillatelse. helpUnpauseCommand=Starte p\u00e5 nytt spilleren. @@ -339,4 +346,3 @@ modulesHowTo=Say {0} to enable/disable modules. parseNotAUser=Your input {0} did not yield a Discord user. parseNotAMember=User {0} is not a member of this guild. parseSnowflakeIdHelp=Having trouble getting the id of a user/message/channel/something else? Check out the Discord docs at {0} - diff --git a/FredBoat/src/main/resources/lang/pl_PL.properties b/FredBoat/src/main/resources/lang/pl_PL.properties index 7a4d2c435..4517f67b1 100644 --- a/FredBoat/src/main/resources/lang/pl_PL.properties +++ b/FredBoat/src/main/resources/lang/pl_PL.properties @@ -7,6 +7,8 @@ playSearching=Przeszukiwanie Youtube dla wynik\u00f3w `{q}`... playYoutubeSearchError=Wyst\u0105pi\u0142 b\u0142\u0105d podczas wyszukiwania na Youtube. Rozwa\u017c opcj\u0119 bezpo\u015bredniego po\u0142\u0105czenia ze \u017ar\u00f3d\u0142em audio.\n```\n;;play ``` playSearchNoResults=Brak wynik\u00f3w wyszukiwania dla `{q}` playSelectVideo=** Prosz\u0119 wybra\u0107 utw\u00f3r z poleceniem `{0}play n`\: ** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Do\u0142\u0105czanie {0} joinErrorAlreadyJoining=Wyst\u0105pi\u0142 b\u0142\u0105d. Nie mog\u0119 do\u0142\u0105czy\u0107 do {0}, poniewa\u017c aktualnie pr\u00f3buj\u0119 si\u0119 ju\u017c po\u0142\u0105czy\u0107 z tym kana\u0142em. Prosz\u0119 spr\u00f3bowa\u0107 ponownie. pauseAlreadyPaused=Bot jest ju\u017c zatrzymany. @@ -21,6 +23,9 @@ shuffleOn=Odtwarzacz jest aktualnie tasowany. shuffleOff=Odtwarzacz nie jest ju\u017c tasowany. reshufflePlaylist=Kolejka potasowana. reshufflePlayerNotShuffling=Nale\u017cy najpierw w\u0142\u0105czy\u0107 tryb odtwarzania losowego. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=Kolejka odtwarzania jest pusta\! skipOutOfBounds=Nie mo\u017cna usun\u0105\u0107 utworu numer {0} gdy s\u0105 tylko {1} utwory. skipNumberTooLow=Podana liczba musi by\u0107 wi\u0119ksza od 0. @@ -62,7 +67,7 @@ npDescription=Opis npLoadedSoundcloud=[{0}/{1}]\n\nZa\u0142adowano z Soundcloud''a npLoadedBandcamp={0}\n\nZa\u0142adowano z Bandcamp''a npLoadedTwitch=Za\u0142adowano z Twitch'a -npRequestedBy=Requested by {0} +npRequestedBy=Zam\u00f3wione przez {0} permissionMissingBot=Potrzebuj\u0119 nast\u0119puj\u0105cych uprawnie\u0144 do wykonania tej akcji\: permissionMissingInvoker=Musisz mie\u0107 nast\u0119puj\u0105ce uprawnienia do wykonania tej akcji\: permissionEmbedLinks=Osadzone \u0142\u0105cza @@ -93,10 +98,10 @@ loadErrorCommon=Wyst\u0105pi\u0142 b\u0142\u0105d podczas \u0142adowania informa loadErrorSusp=Podejrzany b\u0142\u0105d podczas \u0142adowania informacji dla `{0}`. loadQueueTrackLimit=Nie mo\u017cesz doda\u0107 utwor\u00f3w do kolejki je\u017celi ma wi\u0119cej ni\u017c {0} utwor\u00f3w\! Ma to zapobiec Nadu\u017cyciom. loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. -loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. -loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. -loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. -loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! +loadMaxTracksExceeded=Ten serwer nie pozwala na kolejkowanie wi\u0119cej ni\u017c {0} piosenek. +loadMaxUserTracksExceeded=Ten serwer nie pozwala na kolejki kt\u00f3re maj\u0105 wi\u0119cej ni\u017c {0} piosenek. +loadMaxTrackLengthExceeded=Ten serwer nie pozwala aby odgrywa\u0107 piosenki d\u0142u\u017csze ni\u017c {0}. Spr\u00f3buj co\u015b kr\u00f3tszego. +loadPlaylistGeneralError={0} piosenki nie zosta\u0142y dodane poniewa\u017c ten serwer wymusza ograniczenia piosenek\! loadAnnouncePlaylist=Zaczynam \u0142adowa\u0107 Playlist\u0119 **{0}** z `{1}`utworami. To mo\u017ce potrwa\u0107 troch\u0119, prosz\u0119 o cierpliwo\u015b\u0107. playerUserNotInChannel=Najpierw musisz do\u0142\u0105czy\u0107 do kana\u0142u g\u0142osowego. playerJoinConnectDenied=Nie mam pozwolenia by po\u0142\u0105czy\u0107 si\u0119 z tym kana\u0142em g\u0142osowym. @@ -244,12 +249,14 @@ helpJoinCommand=Zapro\u015b bota do twojego bie\u017c\u0105cego kana\u0142u g\u0 helpLeaveCommand=Wyrzu\u0107 bota z bie\u017c\u0105cego kana\u0142u g\u0142osowego. helpPauseCommand=Wstrzymaj odtwarzacz. helpPlayCommand=Graj muzyk\u0119 z podanego URL albo szukaj utworu. Aby uzyska\u0107 ca\u0142\u0105 list\u0119 \u017ar\u00f3de\u0142 wejd\u017a tu http\://docs.frederikam.com/ -helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} +helpReplayCommand=Replay the most recent track from history. +helpPlayNextCommand=Odtwarzanie muzyki z danego adresu URL lub wyszukaj utw\u00f3r. Utwory s\u0105 dodawane do g\u00f3ry kolejki. Aby uzyska\u0107 pe\u0142n\u0105 list\u0119 \u017ar\u00f3de\u0142 odwied\u017a {0} helpPlaySplitCommand=Podziel film z YouTuba w track list\u0119 opatrzony w jego opis. helpRepeatCommand=Prze\u0142\u0105czono pomi\u0119dzy trybami powtarzalno\u015bci. helpReshuffleCommand=Mieszanie obecnej kolejki. helpSelectCommand=Wybierz jeden z oferowanych utwor\u00f3w po wyszukiwaniu aby odtwarzacz zacz\u0105\u0142 gra\u0107. helpShuffleCommand=Prze\u0142\u0105cz tryb mieszania dla bie\u017c\u0105cej kolejce. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Pomi\u0144 bie\u017c\u0105c\u0105 piosenk\u0119, n-t\u0105 piosenk\u0119 w kolejce, wszystkie utwory od n do m lub wszystkie utwory od wspomnianych u\u017cytkownik\u00f3w. U\u017cywaj z umiarem. helpStopCommand=Zatrzymaj Odtwarzacz i wyczy\u015b\u0107 ca\u0142\u0105 playlist\u0119. Zastrze\u017cony tylko dla moderator\u00f3w z uprawnieniami o zarz\u0105dzanie wiadomo\u015bci. helpUnpauseCommand=Wzn\u00f3w Odtwarzacz. @@ -278,7 +285,7 @@ helpUserInfoCommand=Wy\u015bwietlanie informacji o sobie lub u\u017cytkowniku zn helpPerms=Pozwala gracz\u0105 kt\u00f3rzy s\u0105 w bia\u0142ej li\u015bcie oraz rangach z {0} rang. helpPrefixCommand=Ustaw prefiks dla tego serwera. helpVoteSkip=G\u0142osuj, aby pomin\u0105\u0107 bie\u017c\u0105c\u0105 piosenk\u0119. Do g\u0142osowania potrzeba 50% wszystkich u\u017cytkownik\u00f3w czatu g\u0142osowego. -helpUnvoteSkip=Remove your vote to skip the current song. +helpUnvoteSkip=Usu\u0144 sw\u00f3j g\u0142os, aby pomin\u0105\u0107 bie\u017c\u0105cy utw\u00f3r. helpMathOperationAdd=Wydrukuj sum\u0119 liczb1 i num2. helpMathOperationSub=Print the difference of subtracting num2 from num1. helpMathOperationMult=Wydrukuj produkt num1 * num2. @@ -307,8 +314,8 @@ skipUserMultiple=Pomini\u0119to {0} utwor\u00f3w dodanych przez {1}. skipUsersMultiple=Pomini\u0119to {0} utwor\u00f3w dodanych przez {1} u\u017cytkownik\u00f3w. skipUserNoTracks=\u017baden z wymienionych u\u017cytkownik\u00f3w nie ma \u017cadnych \u015bcie\u017cek w kolejce. voteSkipAdded=Tw\u00f3j g\u0142os zosta\u0142 oddany\! -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! +voteSkipRemoved=Tw\u00f3j g\u0142os zosta\u0142 usuni\u0119ty\! +voteSkipNotFound=Odda\u0142e\u015b/a\u015b ju\u017c g\u0142os aby pomin\u0105\u0107 ten utw\u00f3r\! voteSkipAlreadyVoted=Odda\u0142e\u015b/a\u015b ju\u017c g\u0142os aby pomin\u0105\u0107 ten utw\u00f3r\! voteSkipSkipping={0} g\u0142osowa\u0142o, aby pomin\u0105\u0107. Pomijanie \u015bcie\u017cki {1}. voteSkipNotEnough={0} g\u0142osowa\u0142, aby pomin\u0105\u0107. Najmniej {1} jest potrzebne. @@ -339,4 +346,3 @@ modulesHowTo=Powiedz {0}, aby w\u0142\u0105czy\u0107 / wy\u0142\u0105czy\u0107 m parseNotAUser=Twoje wej\u015bcie {0} nie znalaz\u0142o u\u017cytkownika Discord. parseNotAMember=U\u017cytkownik {0} nie jest cz\u0142onkiem tej gildii. parseSnowflakeIdHelp=Mamy problem z odnalezieniem identyfikatora u\u017cytkownika/wiadomo\u015bci/kana\u0142u/czego\u015b innego? Sprawd\u017a Discord docs na {0} - diff --git a/FredBoat/src/main/resources/lang/pt_BR.properties b/FredBoat/src/main/resources/lang/pt_BR.properties index f0497eca2..e9a4f63b2 100644 --- a/FredBoat/src/main/resources/lang/pt_BR.properties +++ b/FredBoat/src/main/resources/lang/pt_BR.properties @@ -7,6 +7,8 @@ playSearching=Procurando no YouTube por ''{q}''... playYoutubeSearchError=Ocorreu um erro ao pesquisar no YouTube. Considere usar o link direto da fonte de \u00e1udio em vez disso.\n```\n;;play ``` playSearchNoResults=Nenhum resultado para ''{q}'' playSelectVideo=**Por favor, selecione uma faixa com o comando `{0}play n`** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Entrando em {0} joinErrorAlreadyJoining=Ocorreu um erro. N\u00e3o foi poss\u00edvel entrar em {0} pois j\u00e1 estou tentando conectar a tal canal. Por favor, tente novamente. pauseAlreadyPaused=O reprodutor j\u00e1 est\u00e1 pausado. @@ -21,6 +23,9 @@ shuffleOn=O reprodutor agora est\u00e1 em modo aleat\u00f3rio. shuffleOff=O reprodutor n\u00e3o est\u00e1 mais em modo aleat\u00f3rio. reshufflePlaylist=Fila re-embaralhada. reshufflePlayerNotShuffling=Voc\u00ea deve primeiro ativar o modo aleat\u00f3rio. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=A fila est\u00e1 vazia\! skipOutOfBounds=N\u00e3o \u00e9 poss\u00edvel remover a faixa n\u00famero {0} quando h\u00e1 apenas {1} faixas. skipNumberTooLow=O n\u00famero dado deve ser maior que 0. @@ -62,7 +67,7 @@ npDescription=Descri\u00e7\u00e3o npLoadedSoundcloud=[{0}/{1}] \n\nCarregado do Soundcloud npLoadedBandcamp={0} \n\nCarregado do Bandcamp npLoadedTwitch=Carregado do Twitch -npRequestedBy=Requested by {0} +npRequestedBy=Solicitado por {0} permissionMissingBot=Eu preciso de permiss\u00e3o para executar essa a\u00e7\u00e3o a seguir\: permissionMissingInvoker=Eu preciso de permiss\u00e3o para executar essa a\u00e7\u00e3o a seguir\: permissionEmbedLinks=Inserir Links @@ -92,11 +97,11 @@ loadPlaylistTooMany=Adicionadas {0} faixas. Encontradas muitas faixas para exibi loadErrorCommon=Erro encontrado carregando informa\u00e7\u00e3o para `{0}`\:\n{1} loadErrorSusp=Erro suspeito ao carregar a informa\u00e7\u00e3o para ''{0}''. loadQueueTrackLimit=Voc\u00ea n\u00e3o pode adicionar faixas para uma fila com mais de {0} faixas\! Isso \u00e9 para evitar abusos. -loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. -loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. -loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. -loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. -loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! +loadPlaylistDisabled=Este servidor desativou as playlists da fila. Por favor, coloque cada faixa individualmente. +loadMaxTracksExceeded=Este servidor n\u00e3o permite enfileiramento mais do que {0} faixas. +loadMaxUserTracksExceeded=Esse servidor n\u00e3o permite que voc\u00ea tenha mais de {0} faixas na fila. +loadMaxTrackLengthExceeded=Esse servidor n\u00e3o permite que voc\u00ea reproduza faixas mais longas que {0}. Por favor tente algo mais curto. +loadPlaylistGeneralError={0} faixas n\u00e3o foram adicionados porque este servidor imp\u00f5e restri\u00e7\u00f5es para a fila\! loadAnnouncePlaylist=Prestes a carregar a lista de reprodu\u00e7\u00e3o * *{0} * * com at\u00e9 faixas de ''{1}''. Isto pode demorar um pouco, por favor, seja paciente. playerUserNotInChannel=Primeiro voc\u00ea deve se juntar a um canal. playerJoinConnectDenied=N\u00e3o estou autorizado a conectar-me a esse canal. @@ -244,12 +249,14 @@ helpJoinCommand=Fazer o bot entrar no seu canal de voz atual. helpLeaveCommand=Fazer o bot deixar o canal de voz atual. helpPauseCommand=Pausa o reprodutor. helpPlayCommand=Reproduza a m\u00fasica a partir do URL fornecido ou procure por uma faixa. Para a lista completa dos recursos por favor visite {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Toca m\u00fasica de determinado URL ou busca por uma faixa. Para obter uma lista completa de fontes, visite {0} helpPlaySplitCommand=Divide um v\u00eddeo do YouTube em uma tracklist fornecida na sua descri\u00e7\u00e3o. helpRepeatCommand=Alternar entre modos de repeti\u00e7\u00e3o. helpReshuffleCommand=Reembaralhar a fila atual. helpSelectCommand=Selecione uma das faixas oferecidas depois de uma busca para reproduzir. helpShuffleCommand=Alternar modo de embaralhamento da fila atual. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Pule a m\u00fasica atual, a n\u00aa m\u00fasica na fila, todas as m\u00fasicas de n \u00e0 m, ou todas as m\u00fasicas de usu\u00e1rios mencionados. Por favor use com modera\u00e7\u00e3o. helpStopCommand=Para o reprodutor e limpa a lista de reprodu\u00e7\u00e3o. Reservado para moderadores com permiss\u00e3o de Gerenciar Mensagens. helpUnpauseCommand=Retoma o reprodutor. @@ -339,4 +346,3 @@ modulesHowTo=Diga {0} para habilitar/desabilitar m\u00f3dulos. parseNotAUser={0} n\u00e3o deu um usu\u00e1rio do Discord. parseNotAMember={0} n\u00e3o \u00e9 um membro desse servidor. parseSnowflakeIdHelp=Tendo problemas para obter a id de um usu\u00e1rio/mensagem/canal/algo mais? Confira os docs do Discord em {0} - diff --git a/FredBoat/src/main/resources/lang/pt_PT.properties b/FredBoat/src/main/resources/lang/pt_PT.properties index dd9fb49f2..9c1b4a649 100644 --- a/FredBoat/src/main/resources/lang/pt_PT.properties +++ b/FredBoat/src/main/resources/lang/pt_PT.properties @@ -7,6 +7,8 @@ playSearching=Procurando no YouTube por ''{q}''... playYoutubeSearchError=Ocorreu um erro aquando da pesquisa no YouTube. Considere usar o link directo das fontes de \u00e1udio em vez disso.\n```\n;;play ``` playSearchNoResults=Nenhum resultado para ''{q}'' playSelectVideo=**Por favor, selecione uma faixa com o comando `{0} play 1-5`** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=A entrar em {0} joinErrorAlreadyJoining=Ocorreu um erro. N\u00e3o consigo entrar em {0} porque j\u00e1 estou a tentar conectar a esse canal. Por favor, tente novamente. pauseAlreadyPaused=O player j\u00e1 est\u00e1 pausado. @@ -21,6 +23,9 @@ shuffleOn=O reprodutor est\u00e1 agora embaralhado. shuffleOff=O reprodutor j\u00e1 n\u00e3o est\u00e1 embaralhado. reshufflePlaylist=Fila baralhada de novo. reshufflePlayerNotShuffling=Voc\u00ea deve primeiro ativar o modo aleat\u00f3rio. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=A fila est\u00e1 vazia\! skipOutOfBounds=N\u00e3o \u00e9 poss\u00edvel remover a faixa n\u00famero {0} quando h\u00e1 apenas {1} faixas. skipNumberTooLow=O n\u00famero dado deve ser maior que 0. @@ -244,12 +249,14 @@ helpJoinCommand=Fa\u00e7a o bot entrar no seu canal de voz atual. helpLeaveCommand=Fa\u00e7a o bot sair do canal de voz atual. helpPauseCommand=Pause o reprodutor. helpPlayCommand=Toque m\u00fasica pelo URL ou pesquisa pela faixa. Para uma lista cheia de fontes visite {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Reproduzir m\u00fasica do URL dado ou procurar uma faixa. As faixas s\u00e3o adicionadas ao topo da fila. Para uma lista completa de fontes, por favor visite {0} helpPlaySplitCommand=Dividir um v\u00eddeo do YouTube em uma lista de reprodu\u00e7\u00e3o fornecido na sua descri\u00e7\u00e3o. helpRepeatCommand=Alternar entre modos de repeti\u00e7\u00e3o. helpReshuffleCommand=Reorganizar a fila atual. helpSelectCommand=Selecione uma das faixas oferecidas depois de uma busca para reproduzir. helpShuffleCommand=Alternar modo de reorganiza\u00e7\u00e3o da fila atual. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Ignore a m\u00fasica atual, a can\u00e7\u00e3o de n'th na fila ou todas as m\u00fasicas de n a m. Por favor, use com modera\u00e7\u00e3o. helpStopCommand=Pare o reprodutor e limpe a lista de reprodu\u00e7\u00e3o. Reservado para moderadores com permiss\u00e3o de gerir mensagens. helpUnpauseCommand=Retomar o reprodutor. @@ -339,4 +346,3 @@ modulesHowTo=Utiliza {0} para ativar/desativar m\u00f3dulos. parseNotAUser=Sua entrada {0} n\u00e3o produziu um usu\u00e1rio do Discord. parseNotAMember=Usu\u00e1rio {0} n\u00e3o \u00e9 um membro desta guilda. parseSnowflakeIdHelp=Tendo dificuldade em obter o id de um usu\u00e1rio/mensagem/canal/outra coisa? Confira os documentos do Discord em {0} - diff --git a/FredBoat/src/main/resources/lang/ro_RO.properties b/FredBoat/src/main/resources/lang/ro_RO.properties index be41e2809..a717606f0 100644 --- a/FredBoat/src/main/resources/lang/ro_RO.properties +++ b/FredBoat/src/main/resources/lang/ro_RO.properties @@ -7,6 +7,8 @@ playSearching=Caut YouTube pentru `{q}`... playYoutubeSearchError=Am \u00eent\u00e2mpinat o eroare c\u0103ut\u00e2nd pe YouTube. Lua\u021bi \u00een considerare utilizarea unui link c\u0103tre melodia dorit\u0103. ```\n;;play ``` playSearchNoResults=Nici un rezultat pentru `{q}` playSelectVideo=**V\u0103 rug\u0103m s\u0103 selecta\u021bi o pies\u0103 cu comanda `{0}play 1-5`\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=M\u0103 conectez la {0} joinErrorAlreadyJoining=A ap\u0103rut o eroare. Nu am putut s\u0103 m\u0103 conectez la {0} pentru c\u0103 am \u00eencercat deja s\u0103 m\u0103 conectez la acel canal. V\u0103 rug\u0103m s\u0103 \u00eencerca\u0163i din nou. pauseAlreadyPaused=Player-ul este deja pauzat. @@ -21,6 +23,9 @@ shuffleOn=Player-ul este \u00een modul amestecare. shuffleOff=Player-ul nu mai este \u00een modul amestecare. reshufflePlaylist=Lista de redare a fost reamestecat\u0103. reshufflePlayerNotShuffling=Mai \u00eent\u00e2i trebuie s\u0103 activa\u021bi modul de amestecare. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=Lista este goal\u0103\! skipOutOfBounds=Melodia {0} nu poate fi \u0219tears\u0103 c\u00e2nd exist\u0103 numai {1} melodii. skipNumberTooLow=Num\u0103rul dat trebuie s\u0103 fie mai mare de 0. @@ -62,7 +67,7 @@ npDescription=Descriere npLoadedSoundcloud=[{0}/{1}]\n\n \u00cenc\u0103rcat de pe Soundcloud npLoadedBandcamp={0}\n\n\u00cenc\u0103rcat de pe Bandcamp npLoadedTwitch=\u00cenc\u0103rcat de pe Twitch -npRequestedBy=Requested by {0} +npRequestedBy=Solicitat de {0} permissionMissingBot=Am nevoie de urm\u0103toarea permisiune pentru a efectua aceast\u0103 ac\u0163iune\: permissionMissingInvoker=Ai nevoie de urm\u0103toarea permisiune pentru a putea efectua aceast\u0103 ac\u021biune\: permissionEmbedLinks=Embed Links @@ -92,11 +97,11 @@ loadPlaylistTooMany=S-au ad\u0103ugat {0} piese. S-au g\u0103sit prea multe pies loadErrorCommon=A ap\u0103rut o eroare la \u00eenc\u0103rcarea informa\u021biei pentru `{0}`\:\n{1} loadErrorSusp=A ap\u0103rut o eroarea suspicioas\u0103 c\u00e2nd \u00eenc\u0103rcam informa\u021bia pentru `{0}`. loadQueueTrackLimit=Nu pute\u021bi ad\u0103uga piese la o list\u0103 de redare cu mai mult de {0} piese\! Acest lucru este pentru a preveni abuzurile. -loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. -loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. -loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. -loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. -loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! +loadPlaylistDisabled=Acest server a dezactivat listele de redare \u00een a\u0219teptare. V\u0103 rog s\u0103 pune\u021bi fiecare pies\u0103 individual. +loadMaxTracksExceeded=Acest server nu permite redarea \u00een a\u0219teptare a mai mult de {0} piese. +loadMaxUserTracksExceeded=Acest server nu v\u0103 permite s\u0103 ave\u021bi mai mult de {0} piese \u00een list\u0103. +loadMaxTrackLengthExceeded=Acest server nu v\u0103 permite s\u0103 reda\u021bi piese mai lungi dec\u00e2t {0}. \u00cencerca\u021bi ceva mai scurt. +loadPlaylistGeneralError={0} piese nu au fost ad\u0103ugate deoarece acest server impune restric\u021bii de list\u0103\! loadAnnouncePlaylist=Voi \u00eencepe s\u0103 \u00eencarc lista de redare **{0}** cu p\u00e2n\u0103 la `{1}` piese. Acest lucru ar putea s\u0103 dureze mai mult timp, v\u0103 rog s\u0103 fi\u021bi r\u0103bd\u0103tori. playerUserNotInChannel=Mai \u00eent\u00e2i trebuie s\u0103 intri \u00eentr-un canal de voce. playerJoinConnectDenied=Nu-mi este permis s\u0103 m\u0103 conectez la acel canal de voce. @@ -244,12 +249,14 @@ helpJoinCommand=Face ca bot-ul s\u0103 intre \u00een canalul t\u0103u curent de helpLeaveCommand=Face ca bot-ul s\u0103 plece din canalul t\u0103u curent de voce. helpPauseCommand=Pauzeaz\u0103 playerul. helpPlayCommand=Red\u0103 muzic\u0103 de la URL-ul dat sau caut\u0103 pentru o pies\u0103. Pentru a list\u0103 cu toatele sursele v\u0103 rug\u0103m s\u0103 vizita\u021bi {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Reda\u021bi muzica de la URL-ul dat sau c\u0103uta\u021bi o pies\u0103. Piesele sunt ad\u0103ugate in topul listei. Pentru o list\u0103 complet\u0103 de surse, vizita\u021bi {0} helpPlaySplitCommand=Divide un video de pe YouTube \u00een o list\u0103 de piese dac\u0103 acea list\u0103 este dat\u0103 \u00een descriere. helpRepeatCommand=Comut\u0103 \u00eentre modurile de repetare. helpReshuffleCommand=Amestec\u0103 din nou lista de redare curent\u0103. helpSelectCommand=Selecta\u021bi una dintre piesele oferite dup\u0103 o c\u0103utare pentru a o reda. helpShuffleCommand=Comut\u0103 module de amestecare pentru lista de redare curent\u0103. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Sare piesa curent\u0103, piesa n din lista de redare, toate piesele de la n la m, sau toate piesele de la utilizatorul men\u021bionat. V\u0103 rug\u0103m s\u0103 folosi\u021bi comanda \u00een modera\u021bie. helpStopCommand=Opre\u0219te playerul \u0219i gole\u0219te lista de redare, Rezervat\u0103 pentru moderatori cu permisiunea Manage Messages. helpUnpauseCommand=Anuleaz\u0103 pauzarea playerului. @@ -339,4 +346,3 @@ modulesHowTo=Spune {0} pentru a activa/dezactiva un modul. parseNotAUser=Inputul t\u0103u, {0}, nu a dat na\u0219tere unui utilizator de Discord. parseNotAMember=Utilizatorul {0} nu este un membru al acestui server. parseSnowflakeIdHelp=Ai probleme in ob\u021binerea id-ului unui utilizator/mesaj/canal/sau altceva? Da o privire documenta\u021biei oferite de Discord la {0} - diff --git a/FredBoat/src/main/resources/lang/ru_RU.properties b/FredBoat/src/main/resources/lang/ru_RU.properties index c795f80af..126863c15 100644 --- a/FredBoat/src/main/resources/lang/ru_RU.properties +++ b/FredBoat/src/main/resources/lang/ru_RU.properties @@ -7,6 +7,8 @@ playSearching=\u0418\u0449\u0443 \u043d\u0430 YouTube `{q}`... playYoutubeSearchError=\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043f\u043e\u0438\u0441\u043a\u0435 YouTube. \u0412\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u043f\u0440\u044f\u043c\u0443\u044e \u0441\u0441\u044b\u043b\u043a\u0443 \u043d\u0430 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a. ``` ;; play ``` playSearchNoResults=\u041d\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u0434\u043b\u044f `{q}` playSelectVideo=** \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0434\u043e\u0440\u043e\u0436\u043a\u0443 \u0441 \u043a\u043e\u043c\u0430\u043d\u0434\u043e\u0439 \u00ab{0}play n\u00bb\: ** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=\u041f\u0440\u0438\u0441\u043e\u0435\u0434\u0438\u043d\u044f\u044e\u0441\u044c \u043a {0} joinErrorAlreadyJoining=\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430. \u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u0440\u0438\u0441\u043e\u0435\u0434\u0438\u043d\u0438\u0442\u044c\u0441\u044f \u043a {0} \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u044f \u0443\u0436\u0435 \u043f\u044b\u0442\u0430\u044e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u044d\u0442\u043e\u043c\u0443 \u043a\u0430\u043d\u0430\u043b\u0443. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437. pauseAlreadyPaused=\u0412\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0443\u0436\u0435 \u043f\u0440\u0438\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e. @@ -21,6 +23,9 @@ shuffleOn=\u0412\u043a\u043b\u044e\u0447\u0435\u043d \u0440\u0435\u0436\u0438\u0 shuffleOff=\u0420\u0435\u0436\u0438\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u0448\u0438\u0432\u0430\u043d\u0438\u044f \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d. reshufflePlaylist=\u041e\u0447\u0435\u0440\u0435\u0434\u044c \u0431\u044b\u043b\u0430 \u043f\u0435\u0440\u0435\u043c\u0435\u0448\u0430\u043d\u0430. reshufflePlayerNotShuffling=\u0412\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0440\u0435\u0436\u0438\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u0448\u0438\u0432\u0430\u043d\u0438\u044f. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=\u041e\u0447\u0435\u0440\u0435\u0434\u044c \u043f\u0443\u0441\u0442\u0430\! skipOutOfBounds=\u041d\u0435 \u0443\u0434\u0430\u0435\u0442\u0441\u044f \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0442\u0440\u0435\u043a \u043d\u043e\u043c\u0435\u0440 {0}, \u043a\u043e\u0433\u0434\u0430 \u0435\u0441\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e {1} \u0442\u0440\u0435\u043a\u043e\u0432. skipNumberTooLow=\u0414\u0430\u043d\u043d\u044b\u0439 \u043d\u043e\u043c\u0435\u0440 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 0. @@ -62,7 +67,7 @@ npDescription=\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 npLoadedSoundcloud=[{0}/{1}]\n\n\u0417\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043e \u0438\u0437 Soundcloud npLoadedBandcamp={0}\n\n\u0417\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043e \u0438\u0437 Bandcamp npLoadedTwitch=\u0417\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043e \u0438\u0437 Twitch -npRequestedBy=Requested by {0} +npRequestedBy=\u0417\u0430\u043f\u0440\u043e\u0448\u0435\u043d\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c {0} permissionMissingBot=\u041c\u043d\u0435 \u043d\u0443\u0436\u043d\u044b \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f\: permissionMissingInvoker=\u0412\u0430\u043c \u043d\u0443\u0436\u043d\u044b \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f\: permissionEmbedLinks=\u0412\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0435 \u0441\u0441\u044b\u043b\u043a\u0438 @@ -92,11 +97,11 @@ loadPlaylistTooMany=\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e {0} \ loadErrorCommon=\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e `{0}`\: {1} loadErrorSusp=\u041f\u043e\u0434\u043e\u0437\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e `{0}`. loadQueueTrackLimit=\u0412\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0442\u0440\u0435\u043a\u0438 \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u0431\u043e\u043b\u0435\u0435 \u0447\u0435\u043c {0} \u0442\u0440\u0435\u043a\u043e\u0432. \u0417\u043b\u043e\u0443\u043f\u043e\u0442\u0440\u0435\u0431\u043b\u044f\u0442\u044c \u043d\u0435 \u0441\u0442\u043e\u0438\u0442. -loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. -loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. -loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. -loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. -loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! +loadPlaylistDisabled=\u041d\u0430 \u044d\u0442\u043e\u043c \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0430 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u043b\u0435\u0439\u043b\u0438\u0441\u0442\u043e\u0432. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u043a\u0430\u0436\u0434\u044b\u0439 \u0442\u0440\u0435\u043a \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u043f\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438. +loadMaxTracksExceeded=\u042d\u0442\u043e\u0442 \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0431\u043e\u043b\u044c\u0448\u0435 \u0447\u0435\u043c {0} \u0442\u0440\u0435\u043a\u043e\u0432. +loadMaxUserTracksExceeded=\u042d\u0442\u043e\u0442 \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432\u0430\u043c \u0431\u043e\u043b\u044c\u0448\u0435 \u0447\u0435\u043c {0} \u0442\u0440\u0435\u043a\u043e\u0432 \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044c. +loadMaxTrackLengthExceeded=\u042d\u0442\u043e\u0442 \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u044c \u0442\u0440\u0435\u043a\u0438 \u0434\u043e\u043b\u044c\u0448\u0435 \u0447\u0435\u043c {0}. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0447\u0442\u043e-\u0442\u043e \u043a\u043e\u0440\u043e\u0447\u0435. +loadPlaylistGeneralError={0} \u0442\u0440\u0435\u043a\u043e\u0432 \u0431\u044b\u043b\u0438 \u043d\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u044b \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u044d\u0442\u043e\u0442 \u0441\u0435\u0440\u0432\u0435\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435\! loadAnnouncePlaylist=\u0417\u0430\u0433\u0440\u0443\u0436\u0430\u044e \u043f\u043b\u0435\u0439\u043b\u0438\u0441\u0442 **{0}** \u0441 {1} \u0442\u0440\u0435\u043a\u0430\u043c\u0438. \u042d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043d\u044f\u0442\u044c \u043a\u0430\u043a\u043e\u0435-\u0442\u043e \u0432\u0440\u0435\u043c\u044f, \u043f\u043e\u0434\u043e\u0436\u0434\u0438\u0442\u0435. playerUserNotInChannel=\u0421\u043f\u0435\u0440\u0432\u0430 \u0412\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0437\u0430\u0439\u0442\u0438 \u043d\u0430 \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0439 \u043a\u0430\u043d\u0430\u043b. playerJoinConnectDenied=\u041c\u043d\u0435 \u0437\u0430\u043f\u0440\u0435\u0449\u0435\u043d\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0442\u044c\u0441\u044f \u043a \u044d\u0442\u043e\u043c\u0443 \u043a\u0430\u043d\u0430\u043b\u0443. @@ -108,7 +113,7 @@ shutdownRestarting=FredBoat \u266a\u266a \u0442\u0440\u0435\u0431\u0443\u0435\u0 shutdownIndef=FredBoat\u266a\u266a \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d. \u041a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u0431\u043e\u0442 \u0432\u0435\u0440\u043d\u0435\u0442\u0441\u044f, \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u043f\u043b\u0435\u0439\u043b\u0438\u0441\u0442 \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u0441\u044f. shutdownPersistenceFail=\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0438 \u0444\u0430\u0439\u043b\u0430\: {0} reloadSuccess=\u041f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442\u0441\u044f \u043f\u043b\u0435\u0439\u043b\u0438\u0441\u0442. \u00ab{0}\u00bb \u0442\u0440\u0435\u043a\u043e\u0432 \u043d\u0430\u0439\u0434\u0435\u043d\u043e. -trackAnnounce=\u0422\u0435\u043f\u0435\u0440\u044c \u0438\u0433\u0440\u0430\u044e ** {0} **. \u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\: ** {1} **. +trackAnnounce=\u0421\u0435\u0439\u0447\u0430\u0441 \u0438\u0433\u0440\u0430\u0435\u0442 **{0}**. \u041f\u043e\u0441\u0442\u0430\u0432\u0438\u043b\: **{1}**. cmdAccessDenied=\u0412\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u0443 \u043a\u043e\u043c\u0430\u043d\u0434\u0443\! malRevealAnime={0}\: \u043f\u043e\u0438\u0441\u043a \u043f\u043e\u043a\u0430\u0437\u0430\u043b \u0430\u043d\u0438\u043ce.\n malTitle={0} ** \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\: **{1}\n @@ -244,12 +249,14 @@ helpJoinCommand=\u041f\u0440\u0438\u0441\u043e\u0435\u0434\u0438\u043d\u0438\u04 helpLeaveCommand=\u041f\u043e\u043a\u0438\u043d\u0443\u0442\u044c \u0431\u043e\u0442\u0443 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0439 \u043a\u0430\u043d\u0430\u043b. helpPauseCommand=\u041f\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043f\u043b\u0435\u0435\u0440 \u043d\u0430 \u043f\u0430\u0443\u0437\u0443. helpPlayCommand=\u041f\u0440\u043e\u0438\u0433\u0440\u044b\u0432\u0430\u0442\u044c \u043c\u0443\u0437\u044b\u043a\u0443 \u0438\u0437 \u0437\u0430\u0434\u0430\u043d\u043d\u043e\u0433\u043e URL \u0438\u043b\u0438 \u0438\u0441\u043a\u0430\u0442\u044c \u0442\u0440\u0435\u043a. \u0414\u043b\u044f \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430 \u043f\u043e\u043b\u043d\u043e\u0433\u043e \u0441\u043f\u0438\u0441\u043a\u0430 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u0432, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u0441\u0435\u0442\u0438\u0442\u0435 {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=\u0412\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u044c \u043c\u0443\u0437\u044b\u043a\u0443 \u0441 \u0434\u0430\u043d\u043d\u043e\u0433\u043e URL-\u0430\u0434\u0440\u0435\u0441\u0430 \u0438\u043b\u0438 \u043f\u043e\u0438\u0441\u043a \u0434\u043b\u044f \u0442\u0440\u0435\u043a\u0430. \u0422\u0440\u0435\u043a\u0438 \u0431\u0443\u0434\u0443\u0442 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u044b \u0432 \u043d\u0430\u0447\u0430\u043b\u043e \u043e\u0447\u0435\u0440\u0435\u0434\u0438. \u0414\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u043d\u043e\u0433\u043e \u0441\u043f\u0438\u0441\u043a\u0430 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u0432, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u0441\u0435\u0442\u0438\u0442\u0435 {0} helpPlaySplitCommand=\u0420\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u0432\u0438\u0434\u0435\u043e \u0441 YouTube \u043d\u0430 \u0442\u0440\u0435\u043a\u043b\u0438\u0441\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u043a\u0430\u0437\u0430\u043d \u0432 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0438. helpRepeatCommand=\u041f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043c\u0435\u0436\u0434\u0443 \u0440\u0435\u0436\u0438\u043c\u0430\u043c\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u0430. helpReshuffleCommand=\u041f\u0435\u0440\u0435\u043c\u0435\u0448\u0430\u0442\u044c \u0442\u0435\u043a\u0443\u0449\u0443\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c. helpSelectCommand=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0434\u0438\u043d \u0438\u0437 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0445 \u0442\u0440\u0435\u043a\u043e\u0432 \u043f\u043e\u0441\u043b\u0435 \u043f\u043e\u0438\u0441\u043a\u0430 \u0434\u043b\u044f \u0432\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u044f. helpShuffleCommand=\u041f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0440\u0435\u0436\u0438\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u0448\u0438\u0432\u0430\u043d\u0438\u044f \u0434\u043b\u044f \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u043e\u0447\u0435\u0440\u0435\u0434\u0438. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=\u041f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0442\u0435\u043a\u0443\u0449\u0443\u044e \u043f\u0435\u0441\u043d\u044e, \u0438\u043b\u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u044b\u0439 \u043d\u043e\u043c\u0435\u0440 \u043f\u0435\u0441\u043d\u0438 \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u0438, \u0438\u043b\u0438 \u0432\u0441\u0435 \u043f\u0435\u0441\u043d\u0438 \u0441 _ \u043f\u043e _. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0443\u043c\u0435\u0440\u0435\u043d\u043d\u043e. helpStopCommand=\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043f\u043b\u0435\u0435\u0440 \u0438 \u043e\u0447\u0438\u0441\u0442\u0438\u0442\u044c \u043f\u043b\u0435\u0439\u043b\u0438\u0441\u0442. \u0422\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u043c\u043e\u0434\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u0432 \u0441 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0435\u043c "\u0423\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438". helpUnpauseCommand=\u0421\u043d\u044f\u0442\u044c \u043f\u043b\u0435\u0435\u0440 \u0441 \u043f\u0430\u0443\u0437\u044b. @@ -339,4 +346,3 @@ modulesHowTo=\u041d\u0430\u043f\u0438\u0448\u0438 {0} \u0434\u043b\u044f \u0432\ parseNotAUser=\u0412\u0430\u0448 \u0432\u0445\u043e\u0434 {0} \u043d\u0435 \u0434\u0430\u043b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e Discord. parseNotAMember=\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c {0} \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0447\u043b\u0435\u043d\u043e\u043c \u044d\u0442\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430. parseSnowflakeIdHelp=\u041d\u0435 \u0443\u0434\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f / \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 / \u043a\u0430\u043d\u0430\u043b / \u0447\u0442\u043e-\u0442\u043e \u0435\u0449\u0435? \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b Discord \u0432 {0} - diff --git a/FredBoat/src/main/resources/lang/sk_SK.properties b/FredBoat/src/main/resources/lang/sk_SK.properties index ddfc39baa..19115e462 100644 --- a/FredBoat/src/main/resources/lang/sk_SK.properties +++ b/FredBoat/src/main/resources/lang/sk_SK.properties @@ -7,6 +7,8 @@ playSearching=H\u013ead\u00e1m YouTube pre `{q}`... playYoutubeSearchError=Stala sa chyba pri h\u013eadan\u00ed YouTubeu. Sk\u00faste da\u0165 priamu url na skladbu namiesto v\u00fdrazov. \n```;;play ``` playSearchNoResults=\u017diadne v\u00fdsledky pre `{q}` playSelectVideo=**Pros\u00edm vyberte si jednu z skladieb s pr\u00edkazom`{0}play 1-5`** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Prip\u00e1jam sa na{0} joinErrorAlreadyJoining=Stala sa chyba. Nem\u00f4\u017eem sa pripoji\u0165 na{0} preto\u017ee sa u\u017e sna\u017e\u00edm prip\u00e1ja\u0165 na tento chat. Pros\u00edm sk\u00faste to e\u0161te raz. pauseAlreadyPaused=Prehr\u00e1va\u010d u\u017e hr\u00e1. @@ -21,6 +23,9 @@ shuffleOn=Prehr\u00e1va\u010d je teraz zamie\u0161an\u00fd. shuffleOff=Prehr\u00e1va\u010d u\u017e nie je zamie\u0161an\u00fd. reshufflePlaylist=Zoznam zamie\u0161an\u00fd. reshufflePlayerNotShuffling=Najsk\u00f4r mus\u00edte zapn\u00fa\u0165 mie\u0161ajuci m\u00f3d. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=Zoznam je pr\u00e1zdny\! skipOutOfBounds=Nem\u00f4\u017eete odstr\u00e1ni\u0165 stopu \u010d\u00edslo {0} ke\u010f je tu iba {1} st\u00f4p. skipNumberTooLow=Dan\u00e9 \u010d\u00edslo mus\u00ed by\u0165 vy\u0161\u0161ie ako 0. @@ -244,12 +249,14 @@ helpJoinCommand=Pripoj\u00ed bota do aktu\u00e1lneho hlasov\u00e9ho kan\u00e1lu. helpLeaveCommand=Odpoj\u00ed bota z aktu\u00e1lneho hlasov\u00e9ho kan\u00e1lu. helpPauseCommand=Pozastav\u00ed prehr\u00e1va\u010d. helpPlayCommand=Play music from the given URL or search for a track. For a full list of sources please visit {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Split a YouTube video into a tracklist provided in it's description. helpRepeatCommand=Toggle between repeat modes. helpReshuffleCommand=Reshuffle the current queue. helpSelectCommand=Select one of the offered tracks after a search to play. helpShuffleCommand=Toggle shuffle mode for the current queue. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Skip the current song, the n'th song in the queue, all songs from n to m, or all songs from mentioned users. Please use in moderation. helpStopCommand=Stop the player and clear the playlist. Reserved for moderators with Manage Messages permission. helpUnpauseCommand=Unpause the player. @@ -339,4 +346,3 @@ modulesHowTo=Nap\u00ed\u0161 {0} pre povolenie/zablokovanie modulov. parseNotAUser=Tvoj vstup {0} u\u017e\u00edvate\u013e discordu neposkytol. parseNotAMember=Pou\u017e\u00edvate\u013e {0} nie je \u010dlenom tohto spolku. parseSnowflakeIdHelp=M\u00e1\u0161 probl\u00e9my so z\u00edskan\u00edm Id pou\u017e\u00edvate\u013ea/spr\u00e1vy/nie\u010doho in\u00e9ho? Pozri si Discord dokument\u00e1ciu na {0} - diff --git a/FredBoat/src/main/resources/lang/sq_AL.properties b/FredBoat/src/main/resources/lang/sq_AL.properties new file mode 100644 index 000000000..93e17e0dd --- /dev/null +++ b/FredBoat/src/main/resources/lang/sq_AL.properties @@ -0,0 +1,348 @@ +#X-Generator: crowdin.com +playQueueEmpty=The player is not currently playing anything. Use the following syntax to add a song\:\n;;play +playAlreadyPlaying=The player is already playing. +playVCEmpty=There are no users in the voice chat. +playWillNowPlay=The player will now play. +playSearching=Searching YouTube for `{q}`... +playYoutubeSearchError=An error occurred when searching YouTube. Consider linking directly to audio sources instead.\n```\n;;play ``` +playSearchNoResults=No results for `{q}` +playSelectVideo=**Please select a track with the `{0}play 1-5` command\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. +joinJoining=Joining {0} +joinErrorAlreadyJoining=An error occurred. Couldn''t join {0} because I am already trying to connect to that channel. Please try again. +pauseAlreadyPaused=The player is already paused. +pauseSuccess=The player is now paused. You can unpause it with `{0}unpause`. +repeatOnSingle=The player will now repeat the current track. +repeatOnAll=The player will now repeat the queue. +repeatOff=The player is no longer on repeat. +selectSuccess=Song **\#{0}** has been selected\: **{1}** ({2}) +selectInterval=Must be a number 1-{0}. +selectSelectionNotGiven=You must first be given a selection to choose from. +shuffleOn=The player is now shuffled. +shuffleOff=The player is no longer shuffled. +reshufflePlaylist=Queue reshuffled. +reshufflePlayerNotShuffling=You must first turn on shuffle mode. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. +skipEmpty=The queue is empty\! +skipOutOfBounds=Can''t remove track number {0} when there are only {1} tracks. +skipNumberTooLow=Given number must be greater than 0. +skipSuccess=Skipped track \#{0}\: **{1}** +skipRangeInvalid=Specified track range is invalid. +skipRangeSuccess=Tracks \#{0} to \#{1} have been removed. +skipTrackNotFound=Couldn't find track to skip. +stopAlreadyEmpty=The queue was already empty. +stopEmptyOne=The queue has been emptied, `1` track has been removed. +stopEmptySeveral=The queue has been emptied, `{0}` tracks have been removed. +stopAccessDenied=In order to prevent abuse, this command is only available to those who can manage messages. +unpauseQueueEmpty=The queue is empty. +unpausePlayerNotPaused=The player is not paused. +unpauseNoUsers=There are no users in the voice chat. +unpauseSuccess=The player is now unpaused. +volumeApology=Sorry\! The volume command is not available for use on the public Fredboat. Adjusting the audio volume heavily increases CPU load which is too expensive a feature to be supported on a wildly popular music bot. You can turn the volume down by right clicking (or tapping for mobile users) on it in the voice channel and using the slider. If you'd like to have a volume command and some extra awesome features, check out how to become a Patron and gain access to an exclusive bot here\: +volumeSyntax=Use `;;volume <0-150>`. {0}% is the default.\nThe player is currently at **{1}%**. +volumeSuccess=Changed volume from **{0}%** to **{1}%**. +exportEmpty=Nothing to export, the queue is empty. +exportPlaylistResulted=Exported playlist\: {0}\nYou can provide this URL to play the current playlist later. +exportPlaylistFail=Failed to upload playlist to paste services. +listShowShuffled=Showing shuffled playlist. +listShowRepeatSingle=Repeating current track. +listShowRepeatAll=Repeating current queue. +listShowHistory=Showing tracks in history. +listAddedBy=**{0}** added by **{1}** `[{2}]` +listStreamsOnlySingle=There is **{0}** live {1} in the queue. +listStreamsOnlyMultiple=There are **{0}** live {1} in the queue. +listStreamsOrTracksSingle=There is **{0}** {1} with a remaining length of **[{2}]**{3} in the queue. +listStreamsOrTracksMultiple=There are **{0}** {1} with a remaining length of **[{2}]**{3} in the queue. +streamSingular=stream +streamPlural=streams +listAsWellAsLiveStreams=, as well as **{0}** live {1} +trackSingular=track +trackPlural=tracks +npNotPlaying=Not currently playing anything. +npNotInHistory=Currently no tracks in history. +npDescription=Description +npLoadedSoundcloud=[{0}/{1}]\n\nLoaded from Soundcloud +npLoadedBandcamp={0}\n\nLoaded from Bandcamp +npLoadedTwitch=Loaded from Twitch +npRequestedBy=Requested by {0} +permissionMissingBot=I need the following permission to perform that action\: +permissionMissingInvoker=You need the following permission to perform that action\: +permissionEmbedLinks=Embed Links +rating=Rating +listeners=Listeners +year=Year +album=Album +artist=Artist +circle=Circle +npLoadedFromHTTP={0}\n\nLoaded from {1} +npLoadedDefault={0}\n\nLoaded from {1} +noneYet=None yet +npRatingRange={0}/5 from {1} vote(s) +fwdSuccess=Forwarding **{0}** by {1}. +restartSuccess=**{0}** has been restarted. +queueEmpty=The queue is empty. +rewSuccess=Rewinding **{0}** by {1}. +seekSuccess=Seeking **{0}** to {1}. +seekDeniedLiveTrack=You can't seek a live track. +loadPlaySplitListFail=That link leads to a playlist, not a track. Try `;;play` instead. +loadListSuccess=Found and added `{0}` songs from playlist **{1}**. +loadNoMatches=No audio could be found for `{0}`. +loadSplitNotYouTube=This is not a YouTube track. Only YouTube tracks are supported with the `;;split` command. Try using `;;play` instead. +loadSplitNotResolves=Couldn't resolve that video's tracklist. Try using `;;play` instead. +loadFollowingTracksAdded=The following tracks were added\: +loadPlaylistTooMany=Added {0} tracks. Found too many tracks to display. +loadErrorCommon=Error occurred when loading info for `{0}`\:\n{1} +loadErrorSusp=Suspicious error when loading info for `{0}`. +loadQueueTrackLimit=You can''t add tracks to a queue with more than {0} tracks\! This is to prevent abuse. +loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. +loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. +loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. +loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. +loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! +loadAnnouncePlaylist=About to load playlist **{0}** with up to `{1}` tracks. This may take a while, please be patient. +playerUserNotInChannel=You must join a voice channel first. +playerJoinConnectDenied=I am not permitted to connect to that voice channel. +playerJoinSpeakDenied=I am not permitted to play music in that voice channel. +playerNotInChannel=Not currently in a channel. +playerLeftChannel=Left channel {0}. +shutdownUpdating=FredBoat\u266a\u266a is updating. This should only take a minute and will reload the current playlist. +shutdownRestarting=FredBoat\u266a\u266a is restarting. This should only take a minute and will reload the current playlist. +shutdownIndef=FredBoat\u266a\u266a is shutting down. Once the bot comes back the current playlist will reload. +shutdownPersistenceFail=Error occurred when saving persistence file\: {0} +reloadSuccess=Reloading playlist. `{0}` tracks found. +trackAnnounce=Now playing **{0}**. Requested by\: **{1}**. +cmdAccessDenied=You are not allowed to use that command\! +malRevealAnime={0}\: Search revealed an anime.\n +malTitle={0}**Title\: **{1}\n +malEnglishTitle={0}**English\: **{1}\n +malSynonyms={0}**Synonyms\: **{1}\n +malEpisodes={0}**Episodes\: **{1}\n +malScore={0}**Score\: **{1}\n +malType={0}**Type\: **{1}\n +malStatus={0}**Status\: **{1}\n +malStartDate={0}**Start date\: **{1}\n +malEndDate={0}**End date\: **{1} +malSynopsis={0}**Synopsis\: **"{1}"\n +malUserReveal={0}\: Search revealed a user.\n +malNoResults={0}\: No results. +malUserName={0}**Name\: **{1}\n +malUrl={0}**URL\: **{1}\n +luaError=\ A Lua error occured \:anger\:\n```{0}``` +luaErrorOutputTooBig=\ Output buffer is too large \:anger\: Discord only allows 2000 characters per message, got {0} +luaTimeout=\ Function timed out \:anger\: allowed computation time is {0} seconds. +helpSuccess=Documentation has been sent to your direct messages\! +helpDmFailed=Could not send documentation to your DMs. Please check that you don't have them disabled\! +helpCommandsPromotion=Say {0} to learn what this bot can do\! +musicCommandsPromotion=Say {0} to see a complete list of music commands. +fuzzyNoResults=No such users +brainfuckCycleLimit=Program exceeded the maximum cycle count of {0} +brainfuckDataPointerOutOfBounds=Data pointer out of bounds\: {0} +brainfuckInputOOB=Input out of bounds at position\: {0} +brainfuckNoOutput=\ There was no output +weatherLocationNotFound=Unable to find location, please check your input {0}. +weatherError=Error retrieving weather for {0} +avatarSuccess=\ found it\n{0} +configNoArgs=Configuration for **{0}**\:``` +configSetTo=is now set to `{0}`. +configUnknownKey={0}\: Unknown key. +configMustBeBoolean={0}\: Value must be true or false. +modReason=Reason +modAuditLogMessage=Action issued by {0} against {1} +modAudioLogNoReason=No reason provided. +modFailUserHierarchy=You do not have a higher role than {0}. +modFailBotHierarchy=I need to have a higher role than {0}. +modBanlistFail=Failed to fetch the ban list. +modBanFail=Failed to ban {0} +modUnbanFail=Failed to unban {0} +modUnbanFailNotBanned=User {0} is not banned in this guild. +modKeepMessages=Add {0} to not delete any messages. +modActionTargetDmKicked=Ahoy\! You have been kicked from the guild {0} by user {1}. +modActionTargetDmBanned=Ahoy\! You have been banned from the guild {0} by user {1}. +kickSuccess=User {0} has been kicked. +kickFail=Failed to kick {0} +kickFailSelf=You can't kick yourself. +kickFailOwner=You can't kick the server owner. +kickFailMyself=I can't kick myself. +softbanSuccess=User {0} has been softbanned. +softbanFailSelf=You can't softban yourself. +softbanFailOwner=You can't softban the server owner. +softbanFailMyself=I can't softban myself. +hardbanSuccess=User {0} has been banned. +hardbanFailSelf=You can't ban yourself. +hardbanFailOwner=You can't ban the server owner. +hardbanFailMyself=I can't ban myself. +unbanSuccess=User {0} has been unbanned. +getidSuccess=The id of this guild is {0}. The id of this text channel is {1}. +statsParagraph=\ This bot has been running for {0} days, {1} hours, {2} minutes and {3} seconds.\nThis bot has executed {4} commands this session. +statsRate={0}That''s a rate of {1} commands per hour +catgirlFail=Failed to extract image from {0} +catgirlFailConn=Failed to connect to {0} +hugBot=Thanks for the hugs \:blush\: +hugSuccess=Hugs {0}. +patBot=Thanks for the pats \:blush\: +patSuccess=Pats {0}. +rollSuccess={0} rolls around on the floor. +facedeskSuccess={0} facedesks. +langInvalidCode=The language code {0} doesn''t exist or is unsupported. +langSuccess=Switched to speaking {0}. +langInfo=FredBoat supports several user-contributed languages that you can select with this command. Admins on this server can select a language with `;;lang ` Here is the full list of supported languages\: +langDisclaimer=Translations may not be 100% accurate or complete. Missing translations may be contributed at . +loadSingleTrack=**{0}** has been added to the queue. +loadSingleTrackFirst=**{0}** has been added to the top of the queue. +loadSingleTrackAndPlay=**{0}** will now play. +invite=Invite link for **{0}**\: +ratelimitedCommandsUser=You are sending commands too fast\! Please slow down. +ratelimitedCommandsGuild=This guild is sending commands too fast\! Please slow down. +ratelimitedSkipCommand=You can skip more than one song by using this command\: {0} +ratelimitedGuildSlowLoadingPlaylist=This server is not allowed to add more playlists at this moment. Please don't spam long playlists. +unblacklisted=Removed {0} from the blacklist. +serverinfoTitle=Info about {0}\: +serverinfoOnlineUsers=Online Users\: +serverinfoTotalUsers=Total Users\: +serverinfoRoles=Roles\: +serverinfoText=Text Channels\: +serverinfoVoice=Voice Channels\: +serverinfoGuildID=Guild ID\: +serverinfoCreationDate=Creation Date\: +serverinfoOwner=Owner\: +serverinfoVLv=Verification Level\: +userinfoTitle=Information about {0}\: +userinfoUsername=Username\: +userinfoId=ID\: +userinfoNick=Nickname\: +userinfoKnownServer=Known Servers\: +userinfoJoinDate=Join Date\: +userinfoCreationTime=Creation Date\: +userinfoBlacklisted=Blacklisted\: +skipDeniedTooManyTracks=You can't skip someone else's tracks if you are not a DJ.\nConsider using the Voteskip command. +eventUsersLeftVC=All users have left the voice channel. The player has been paused. +eventAutoResumed=User presence detected, automatically resuming the player. +commandsFun=Fun +commandsMemes=Memes +commandsUtility=Utility +commandsModeration=Moderation +commandsMaintenance=Maintenance +commandsBotOwner=Bot owner +commandsMoreHelp=Say {0} to get more information on a specific command. +commandsModulesHint=You can enable and disable additional modules with {0} +helpUnknownCommand=Unknown command. +helpDocsLocation=Documentation can be found at\: +helpBotInvite=Want to add FredBoat to your server? If you have Manage Server permissions for your guild, you can invite FredBoat\: +helpHangoutInvite=Need help or have any ideas for FredBoat? Perhaps you just want to hang out? Join the FredBoat community\! +helpNoDmCommands=You cannot send FredBoat commands through DMs. +helpCredits=Created by Fre_d and open source contributors +helpSent=Documentation has been sent to your DMs\! +helpProperUsage=Proper usage\: +helpCommandOwnerRestricted=This command is restricted to the owner of the bot. +helpConfigCommand=Show the config of this guild or adjust settings. +helpLanguageCommand=Show available languages or set a language for this guild. +helpModules=Show, enable or disable command modules for this guild. +helpHardbanCommand=Ban a user and delete their messages from the last day. +helpKickCommand=Kick a user from this guild. +helpSoftbanCommand=Softban a user by kicking them and deleting their messages from the last day. +helpUnbanCommand=Unban a previously banned user. +helpMusicCommandsHeader=FredBoat Music Commands +helpJoinCommand=Make the bot join your current voice channel. +helpLeaveCommand=Make the bot leave the current voice channel. +helpPauseCommand=Pause the player. +helpPlayCommand=Play music from the given URL or search for a track. For a full list of sources please visit {0} +helpReplayCommand=Replay the most recent track from history. +helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} +helpPlaySplitCommand=Split a YouTube video into a tracklist provided in it's description. +helpRepeatCommand=Toggle between repeat modes. +helpReshuffleCommand=Reshuffle the current queue. +helpSelectCommand=Select one of the offered tracks after a search to play. +helpShuffleCommand=Toggle shuffle mode for the current queue. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. +helpSkipCommand=Skip the current song, the n'th song in the queue, all songs from n to m, or all songs from mentioned users. Please use in moderation. +helpStopCommand=Stop the player and clear the playlist. Reserved for moderators with Manage Messages permission. +helpUnpauseCommand=Unpause the player. +helpVolumeCommand=Changes the volume. Values are 0-150 and 100 is the default. The volume command is deprecated on the public bot. +helpExportCommand=Export the current queue to a wastebin link, can be later used as a playlist. +helpGensokyoRadioCommand=Show the current song played on gensokyoradio.net +helpListCommand=Display a list of the current songs in the playlist. +helpHistoryCommand=Display a list of the songs in playlist history. +helpNowplayingCommand=Display the currently playing song. +helpForwardCommand=Forward the track by a given amount of time. Example\: +helpRestartCommand=Restart the currently playing track. +helpRewindCommand=Rewind the track by a given amount of time. Example\: +helpSeekCommand=Set the position of the track to the given time. Example\: +helpAvatarCommand=Display the avatar of a user. +helpBrainfuckCommand=Executes Brainfuck code. Example\: +helpWeatherCommand=Display current weather by location. +helpClearCommand=Delete all messages by this bot in the last 50 messages of this channel. +helpCommandsCommand=Show available commands. +helpHelpCommand=Receive help for this bot or help for any command. +helpInviteCommand=Post invite link for this bot. +helpMALCommand=Search MyAnimeList and display an anime or profile of a user. +helpMusicHelpCommand=Show music commands and their usage. +helpSayCommand=Make the bot echo something. +helpServerInfoCommand=Display some stats about this guild. +helpUserInfoCommand=Display information about yourself or a user known to the bot. +helpPerms=Allows whitelisting members and roles for the {0} rank. +helpPrefixCommand=Set the prefix for this guild. +helpVoteSkip=Vote to skip the current song. Needs 50% of all users in the voice chat to vote. +helpUnvoteSkip=Remove your vote to skip the current song. +helpMathOperationAdd=Print the sum of num1 and num2. +helpMathOperationSub=Print the difference of subtracting num2 from num1. +helpMathOperationMult=Print the product of num1*num2. +helpMathOperationDiv=Print the quotient of dividing num1 by num2. +helpMathOperationMod=Print the remainder of dividing num1 by num2. +helpMathOperationPerc=Print the percentage represented by num1 in num2. +helpMathOperationSqrt=Print the square root of num. +helpMathOperationPow=Print the result of num1^num2. +destroyDenied=You must have the manage messages permission to reset the player. +destroyHelp=Reset the player and clear the playlist. Reserved for moderators with Manage Messages permission. +destroySucc=Reset the player and cleared the queue. +listPageNum=Page **{0}** of **{1}**. +permsListTitle=Users and roles with the {0} permissions +permsAdded=Added `{0}` to `{1}`. +permsRemoved=Removed `{0}` from `{1}`. +permsFailSelfDemotion=You cannot remove this as it would render you without admin permissions\! +permsAlreadyAdded={0} already added to {1} +permsNotAdded={0} is not in {1} +fuzzyMultiple=Multiple items were found. Did you mean any of these? +fuzzyNothingFound=Nothing found for `{0}`. +cmdPermsTooLow=You don''t have permission to run this command\! This command requires `{0}` but you only have `{1}`. +playersLimited=FredBoat is currently at maximum capacity\! The bot is currently fixed to only play up to `{0}` streams, otherwise we would risk disconnecting from Discord under the network load.\nIf you want to help us increase the limit or you want to use our non-overcrowded bot, please support our work on Patreon\:\n{1}\n\nSorry for the inconvenience\! You might want to try again later. This message usually only appears at peak time. +tryLater=Please try again later. +skipUserSingle=Skipped {0} added by {1}. +skipUserMultiple=Skipped {0} tracks added by {1}. +skipUsersMultiple=Skipped {0} tracks added by {1} users. +skipUserNoTracks=None of the mentioned users have any tracks queued. +voteSkipAdded=Your vote has been added\! +voteSkipRemoved=Your vote has been removed\! +voteSkipNotFound=You have not voted to skip this track\! +voteSkipAlreadyVoted=You already voted to skip this track\! +voteSkipSkipping={0} have voted to skip. Skipping track {1}. +voteSkipNotEnough={0} have voted to skip. At least {1} needed. +voteSkipEmbedNoVotes=No votes to skip this track yet. +voteSkipEmbedVoters={0} out of {1} have voted to skip the current track +mathOperationResult=The result is +mathOperationDivisionByZeroError=I cannot divide by zero. +mathOperationInfinity=The number is too big to be displayed\! +prefix=Prefix +prefixGuild=The prefix for this guild is {0} +prefixShowAgain=You can show the prefix anytime again by mentioning me. +moduleAdmin=Administration +moduleInfo=Information +moduleConfig=Configuration +moduleMusic=Music +moduleModeration=Moderation +moduleUtility=Utility +moduleFun=Fun +moduleLocked=The {0} module is locked and cannot be enabled/disabled. +moduleCantParse=No such module. Show a list of available modules with {0} +moduleStatus=Module Status +moduleDisable=Disabled module {0}. +moduleEnable=Enabled module {0}. +moduleShowCommands=Say {0} to see the commands of this module. +modulesCommands=Say {0} to show commands for a module, or {1} to show all commands. +modulesEnabledInGuild=Enabled modules for this guild\: +modulesHowTo=Say {0} to enable/disable modules. +parseNotAUser=Your input {0} did not yield a Discord user. +parseNotAMember=User {0} is not a member of this guild. +parseSnowflakeIdHelp=Having trouble getting the id of a user/message/channel/something else? Check out the Discord docs at {0} diff --git a/FredBoat/src/main/resources/lang/sr_SP.properties b/FredBoat/src/main/resources/lang/sr_SP.properties index 71b0182b7..fda3be37f 100644 --- a/FredBoat/src/main/resources/lang/sr_SP.properties +++ b/FredBoat/src/main/resources/lang/sr_SP.properties @@ -7,6 +7,8 @@ playSearching=\u0422\u0440\u0430\u0436\u0438\u043c YouTube \u0437\u0430 \u00b4{q playYoutubeSearchError=\u0414\u043e\u0433\u043e\u0434\u0438\u043b\u0430 \u0441\u0435 \u0433\u0440\u0435\u0448\u043a\u0430 \u043f\u0440\u0438\u043b\u0438\u043a\u043e\u043c \u043f\u0440\u0435\u0442\u0440\u0430\u0436\u0438\u0432\u0430\u045a\u0430 YouTube-\u0430. \u0420\u0430\u0437\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u043e \u0441\u0442\u0430\u0432\u0459\u0430\u045a\u0443 \u0434\u0438\u0440\u0435\u043a\u0442\u043d\u043e\u0433 \u043b\u0438\u043d\u043a\u0430 \u043d\u0430 \u0438\u0437\u0432\u043e\u0440 \u0437\u0432\u0443\u043a\u0430.\n```\n;;play ``` playSearchNoResults=\u041d\u0435\u043c\u0430 \u0440\u0435\u0437\u0443\u043b\u0442\u0430\u0442\u0430 \u0437\u0430 `{q}` playSelectVideo=**\u041c\u043e\u043b\u0438\u043c\u043e \u0438\u0437\u0430\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0440\u0430\u043a\u0443 \u0441 `{0}play 1-5` \u043a\u043e\u043c\u0430\u043d\u0434\u043e\u043c\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=\u041f\u0440\u0438\u0434\u0440\u0443\u0436\u0438\u0432\u0430\u045a\u0435 {0} joinErrorAlreadyJoining=\u0414\u0435\u0441\u0438\u043b\u0430 \u0441\u0435 \u0433\u0440\u0435\u0448\u043a\u0430. \u041d\u0435 \u043c\u043e\u0433\u0443 \u0441\u0435 \u043f\u0440\u0438\u0434\u0440\u0443\u0436\u0438\u0442\u0438 {0} \u0458\u0435\u0440 \u0432\u0435\u045b \u043f\u043e\u043a\u0443\u0448\u0430\u0432\u0430\u043c \u0434\u0430 \u043f\u0440\u0438\u0441\u0442\u0443\u043f\u0438\u043c \u0442\u043e\u043c\u0435 \u043a\u0430\u043d\u0430\u043b\u0443. \u041c\u043e\u043b\u0438\u043c\u043e, \u043f\u043e\u043a\u0443\u0448\u0430\u0458\u0442\u0435 \u043f\u043e\u043d\u043e\u0432\u043e. pauseAlreadyPaused=\u041f\u043b\u0435\u0458\u0435\u0440 \u0458\u0435 \u0432\u0435\u045b \u043f\u0430\u0443\u0437\u0438\u0440\u0430\u043d. @@ -21,6 +23,9 @@ shuffleOn=\u041f\u043b\u0435\u0458\u0435\u0440 \u0458\u0435 \u0441\u0430\u0434\u shuffleOff=\u041f\u043b\u0435\u0458\u0435\u0440 \u0432\u0438\u0448\u0435 \u043d\u0435 \u043c\u0435\u0448\u0430 \u043f\u0435\u0441\u043c\u0435. reshufflePlaylist=\u0420\u0435\u0434 \u0447\u0435\u043a\u0430\u045a\u0430 \u043f\u043e\u043d\u043e\u0432\u043e \u043f\u0440\u043e\u043c\u0435\u0448\u0430\u043d. reshufflePlayerNotShuffling=\u041f\u0440\u0432\u043e \u043c\u043e\u0440\u0430\u0442\u0435 \u0443\u043a\u0459\u0443\u0447\u0438\u0442\u0438 \u043c\u043e\u0434 \u043c\u0435\u0448\u0430\u045a\u0430 \u043f\u0435\u0441\u0430\u043c\u0430. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=\u0420\u0435\u0434 \u0447\u0435\u043a\u0430\u045a\u0430 \u0458\u0435 \u043f\u0440\u0430\u0437\u0430\u043d\! skipOutOfBounds=\u041d\u0435\u043c\u043e\u0433\u0443\u045b\u0435 \u0458\u0435 \u0443\u043a\u043b\u043e\u043d\u0438\u0442\u0438 \u0442\u0440\u0430\u043a\u0443 \u0431\u0440\u043e\u0458 {0} \u0458\u0435\u0440 \u043f\u043e\u0441\u0442\u043e\u0458\u0438 \u0441\u0430\u043c\u043e {1} \u0442\u0440\u0430\u043a\u0430. skipNumberTooLow=\u0414\u0430\u0442 \u0431\u0440\u043e\u0458 \u043c\u043e\u0440\u0430 \u0431\u0438\u0442\u0438 \u0432\u0435\u045b\u0438 \u043e\u0434 0. @@ -62,7 +67,7 @@ npDescription=\u041e\u043f\u0438\u0441 npLoadedSoundcloud=[{0}/{1}]\n\n\u0423\u0447\u0438\u0442\u0430\u043d\u043e \u0438\u0437 SoundCloud-\u0430 npLoadedBandcamp={0}\n\n\u0423\u0447\u0438\u0442\u0430\u043d\u043e \u0441\u0430 Bandcamp-\u0430 npLoadedTwitch=\u0423\u0447\u0438\u0442\u0430\u043d\u043e \u0438\u0437 Twitch-\u0430 -npRequestedBy=Requested by {0} +npRequestedBy=\u0417\u0430\u0442\u0440\u0430\u0436\u0435\u043d\u043e \u043e\u0434 {0} permissionMissingBot=\u041f\u043e\u0442\u0440\u0435\u0431\u043d\u0435 \u0441\u0443 \u043c\u0438 \u0441\u0435\u043b\u0435\u0434\u0435\u045b\u0435 \u0434\u043e\u0437\u0432\u043e\u043b\u0435 \u0437\u0430 \u0438\u0437\u0432\u043e\u0452\u0435\u043d\u0435 \u0442\u0435 \u0430\u043a\u0446\u0438\u0458\u0435\: permissionMissingInvoker=\u041c\u043e\u0440\u0430\u0442\u0435 \u0438\u043c\u0430\u0442\u0438 \u0441\u043b\u0435\u0434\u0435\u045b\u0443 \u0434\u043e\u0437\u0432\u043e\u043b\u0443 \u0434\u0430 \u0431\u0438\u0441\u0442\u0435 \u0438\u0437\u0432\u0435\u043b\u0438 \u0442\u0443 \u0440\u0430\u0434\u045a\u0443\: permissionEmbedLinks=\u0423\u0433\u0440\u0430\u0452\u0435\u043d\u0435 \u0432\u0435\u0437\u0435 @@ -244,12 +249,14 @@ helpJoinCommand=\u0423\u0447\u0438\u043d\u0438\u0442\u0435 \u0434\u0430 \u0441\u helpLeaveCommand=\u0423\u0447\u0438\u043d\u0438\u0442\u0435 \u0434\u0430 \u0431\u043e\u0442 \u043d\u0430\u043f\u0443\u0441\u0442\u0438 \u0432\u0430\u0448 \u0442\u0440\u0435\u043d\u0443\u0442\u043d\u0438 \u0433\u043b\u0430\u0441\u043e\u0432\u043d\u0438 \u043a\u0430\u043d\u0430\u043b. helpPauseCommand=\u041f\u0430\u0443\u0437\u0438\u0440\u0430\u0458\u0442\u0435 \u043f\u043b\u0435\u0458\u0435\u0440. helpPlayCommand=\u041f\u0443\u0441\u0442\u0438\u0442\u0435 \u043c\u0443\u0437\u0438\u043a\u0443 \u0441\u0430 \u043d\u0430\u0432\u0435\u0434\u0435\u043d\u043e\u0433 URL-\u0430 \u0438\u043b\u0438 \u043f\u043e\u0442\u0440\u0430\u0436\u0438\u0442\u0435 \u0437\u0430 \u0442\u0440\u0430\u043a\u043e\u043c. \u0417\u0430 \u043f\u043e\u0442\u043f\u0443\u043d\u0438 \u043f\u043e\u043f\u0438\u0441 \u0438\u0437\u0432\u043e\u0440\u0430, \u043c\u043e\u043b\u0438\u043c\u043e \u043f\u043e\u0441\u0435\u0442\u0438\u0442\u0435 {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=\u041f\u0443\u0441\u0442\u0438\u0442\u0435 \u043c\u0443\u0437\u0438\u043a\u0443 \u0438\u0437 \u0434\u0430\u0442\u043e\u0433 URL-\u0430 \u0438\u043b\u0438 \u043f\u0440\u0435\u0442\u0440\u0430\u0436\u0438\u0442\u0435 \u0442\u0440\u0430\u043a\u0443. \u0422\u0440\u0430\u043a\u0435 \u0441\u0443 \u0434\u043e\u0434\u0430\u0442\u0435 \u043d\u0430 \u0432\u0440\u0445 \u043b\u0438\u0441\u0442\u0435 \u0447\u0435\u043a\u0430\u045a\u0430. \u0417\u0430 \u0446\u0435\u043b\u0443 \u043b\u0438\u0441\u0442\u0443 \u0438\u0437\u0432\u043e\u0440\u0430, \u043c\u043e\u043b\u0438\u043c\u043e \u043f\u043e\u0441\u0435\u0442\u0438\u0442\u0435 {0} helpPlaySplitCommand=\u0420\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u0435 YouTube \u0432\u0438\u0434\u0435\u043e \u0437\u0430\u043f\u0438\u0441 \u0443 \u043f\u043e\u043f\u0438\u0441 \u043f\u0435\u0441\u0430\u043c\u0430 \u043d\u0430\u0432\u0435\u0434\u0435\u043d\u043e\u0433 \u0443 \u0441\u0432\u043e\u0458\u043e\u043c \u043e\u043f\u0438\u0441\u0443. helpRepeatCommand=\u041f\u0440\u0435\u0431\u0430\u0446\u0438\u0432\u0430\u045a\u0435 \u0438\u0437\u043c\u0435\u0452\u0443 \u043d\u0430\u0447\u0438\u043d\u0430 \u043f\u043e\u043d\u0430\u0432\u0459\u0430\u045a\u0430. helpReshuffleCommand=\u041f\u043e\u043d\u043e\u0432\u043d\u043e \u043c\u0435\u0448\u0430\u045a\u0435 \u0442\u0440\u0435\u043d\u0443\u0442\u043d\u043e\u0433 \u0440\u0435\u0434\u0430. helpSelectCommand=\u041e\u0434\u0430\u0431\u0435\u0440\u0438\u0442\u0435 \u0458\u0435\u0434\u043d\u0443 \u043e\u0434 \u043f\u043e\u043d\u0443\u0452\u0435\u043d\u0438\u0445 \u0442\u0440\u0430\u043a\u0430 \u043d\u0430\u043a\u043e\u043d \u043f\u0440\u0435\u0442\u0440\u0430\u0436\u0438\u0432\u0430\u045a\u0430 \u0437\u0430 \u0440\u0435\u043f\u0440\u043e\u0434\u0443\u043a\u0446\u0438\u0458\u0443. helpShuffleCommand=\u041f\u0440\u0435\u0431\u0430\u0446\u0438\u0432\u0430\u045a\u0435 \u043d\u0430 \u043c\u043e\u0434 \u043c\u0435\u0448\u0430\u045a\u0430 \u0442\u0440\u0430\u043a\u0430 \u0437\u0430 \u0442\u0440\u0435\u043d\u0443\u0442\u043d\u0438 \u0440\u0435\u0434. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=\u041f\u0440\u0435\u0441\u043a\u043e\u0447\u0438\u0442\u0435 \u0442\u0440\u0435\u043d\u0443\u0442\u043d\u0443 \u043f\u0435\u0441\u043c\u0443, \u0431\u0438\u043b\u043e \u043a\u043e\u0458\u0443 \u043f\u0435\u0441\u043c\u0443 \u0443 \u0440\u0435\u0434\u0443, \u0441\u0432\u0435 \u043f\u0435\u0441\u043c\u0435 \u043e\u0434 n \u0434\u043e m, \u0438\u043b\u0438 \u0441\u0432\u0435 \u043f\u0435\u0441\u043c\u0435 \u043f\u043e\u043c\u0435\u043d\u0443\u0442\u0438\u0445 \u043a\u043e\u0440\u0438\u0441\u043d\u0438\u043a\u0430. \u041c\u043e\u043b\u0438\u043c\u043e \u043a\u043e\u0440\u0438\u0441\u0442\u0438\u0442\u0435 \u0443\u043c\u0435\u0440\u0435\u043d\u043e. helpStopCommand=\u0417\u0430\u0443\u0441\u0442\u0430\u0432\u0438\u0442\u0435 \u043f\u043b\u0435\u0458\u0435\u0440 \u0438 \u043e\u0447\u0438\u0441\u0442\u0438\u0442\u0435 \u043f\u043b\u0435\u0458\u043b\u0438\u0441\u0442\u0443. \u0420\u0435\u0437\u0435\u0440\u0432\u0438\u0441\u0430\u043d\u043e \u0437\u0430 \u043c\u043e\u0434\u0435\u0440\u0430\u0442\u043e\u0440\u0435 \u0441\u0430 \u0434\u043e\u0437\u0432\u043e\u043b\u043e\u043c \u0437\u0430 \u0443\u043f\u0440\u0430\u0432\u0459\u0430\u045a\u0435 \u043f\u043e\u0440\u0443\u043a\u0430. helpUnpauseCommand=\u041f\u0443\u0441\u0442\u0438\u0442\u0435 \u043f\u043b\u0435\u0458\u0435\u0440. @@ -339,4 +346,3 @@ modulesHowTo=\u041d\u0430\u043f\u0438\u0448\u0438\u0442\u0435 {0} \u0434\u0430 \ parseNotAUser=\u0412\u0430\u0448 \u0443\u043d\u043e\u0441 {0} \u043d\u0438\u0458\u0435 \u043f\u0440\u0438\u0441\u0442\u0443\u043f\u0438\u043e \u0414\u0438\u0441\u043a\u043e\u0440\u0434 \u043a\u043e\u0440\u0438\u0441\u043d\u0438\u043a\u0443. parseNotAMember=\u041a\u043e\u0440\u0438\u0441\u043d\u0438\u043a {0} \u043d\u0438\u0458\u0435 \u0447\u043b\u0430\u043d \u043e\u0432\u043e\u0433 \u0443\u0434\u0440\u0443\u0436\u0435\u045a\u0430. parseSnowflakeIdHelp=\u0418\u043c\u0430\u0442\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c \u0434\u0430 \u0434\u043e\u0431\u0438\u0458\u0435\u0442\u0435 \u0438\u0434 \u043e\u0434 \u043a\u043e\u0440\u0438\u0441\u043d\u0438\u043a\u0430/\u043f\u043e\u0440\u0443\u043a\u0435/\u043a\u0430\u043d\u0430\u043b\u0430/\u043d\u0435\u0447\u0435\u0433\u0430 \u0434\u0440\u0443\u0433\u043e\u0433? \u041f\u043e\u0441\u0435\u0442\u0438\u0442\u0435 \u0414\u0438\u0441\u043a\u043e\u0440\u0434 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0435 \u043d\u0430 {0} - diff --git a/FredBoat/src/main/resources/lang/sv_SE.properties b/FredBoat/src/main/resources/lang/sv_SE.properties index 27d164b43..56b3116f3 100644 --- a/FredBoat/src/main/resources/lang/sv_SE.properties +++ b/FredBoat/src/main/resources/lang/sv_SE.properties @@ -7,6 +7,8 @@ playSearching=S\u00f6ker p\u00e5 YouTube efter ''{q}''... playYoutubeSearchError=Ett fel uppstod vid s\u00f6kning p\u00e5 YouTube. \u00d6verv\u00e4g att l\u00e4nka direkt till ljudk\u00e4llan ist\u00e4llet.\n```\n;;play``` playSearchNoResults=Inga resultat f\u00f6r `{q}` playSelectVideo=**V\u00e4nligen anv\u00e4nd ett sp\u00e5r med kommandot `{0}play n`\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Ansluter till {0} joinErrorAlreadyJoining=Ett fel intr\u00e4ffade. Kunde inte ansluta till {0} eftersom jag redan f\u00f6rs\u00f6ker ansluta till den kanalen. pauseAlreadyPaused=Spelaren \u00e4r redan pausad. @@ -21,6 +23,9 @@ shuffleOn=Spelaren \u00e4r nu blandad. shuffleOff=Spelaren \u00e4r inte l\u00e4ngre blandad. reshufflePlaylist=Blandade \u00e5ter k\u00f6n. reshufflePlayerNotShuffling=Du m\u00e5ste s\u00e4tta p\u00e5 shuffle-l\u00e4get f\u00f6rst. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=K\u00f6n \u00e4r tom\! skipOutOfBounds=Kan inte ta bort l\u00e5t nummer {0} n\u00e4r det bara finns {1} l\u00e5tar. skipNumberTooLow=Det angivna numret m\u00e5ste vara st\u00f6rre \u00e4n 0. @@ -244,12 +249,14 @@ helpJoinCommand=F\u00e5 botten att ansluta till din nuvarande r\u00f6stkanal. helpLeaveCommand=F\u00e5 botten att l\u00e4mna den nuvarande r\u00f6stkanalen. helpPauseCommand=Pausa spelaren. helpPlayCommand=Spela upp musik fr\u00e5n den angivna URLen eller s\u00f6k efter ett sp\u00e5r. F\u00f6r en full lista av k\u00e4llor v\u00e4nligen bes\u00f6k {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Spela musik fr\u00e5n den utgivna URL eller s\u00f6k efter en l\u00e5t. L\u00e5tar \u00e4r tillagda i toppen av k\u00f6n. F\u00f6r en full lista av k\u00e4llor v\u00e4nligen bes\u00f6k {0} helpPlaySplitCommand=Dela upp en YouTube-video i en l\u00e5tlista angiven i beskrivningen. helpRepeatCommand=V\u00e4xla mellan upprepningsl\u00e4gen. helpReshuffleCommand=Blandar om den aktuella k\u00f6n. helpSelectCommand=V\u00e4lj en av de erbjudna sp\u00e5ren efter en s\u00f6kning f\u00f6r att spela den. helpShuffleCommand=P\u00e5/av f\u00f6r shuffle-l\u00e4get f\u00f6r den aktuella k\u00f6n. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Skippar den nuvarande l\u00e5ten, den n\: te l\u00e5ten i k\u00f6n, alla l\u00e5tar fr\u00e5n n till m, eller alla l\u00e5tar tillagda av specifika anv\u00e4ndare. Var v\u00e4nlig anv\u00e4nd med m\u00e5tta. helpStopCommand=Stoppa spelaren och rensa spellistan. Reserverade f\u00f6r moderatorer med befogenheten "hantera meddelanden". helpUnpauseCommand=\u00c5teraktivera spelaren. @@ -339,4 +346,3 @@ modulesHowTo=S\u00e4g {0} f\u00f6r att aktivera/avaktivera moduler. parseNotAUser=Din inmatning {0} avkastade inte en os\u00e4mja anv\u00e4ndare. parseNotAMember=Anv\u00e4ndare {0} \u00e4r inte en medlem i detta gille. parseSnowflakeIdHelp=Har du problem att f\u00e5 id f\u00f6r en anv\u00e4ndare/meddelande/kanal/n\u00e5got annat? Kolla in Discord dokumenten p\u00e5 {0} - diff --git a/FredBoat/src/main/resources/lang/th_TH.properties b/FredBoat/src/main/resources/lang/th_TH.properties index 4d7c2246c..5c6cb0873 100644 --- a/FredBoat/src/main/resources/lang/th_TH.properties +++ b/FredBoat/src/main/resources/lang/th_TH.properties @@ -7,6 +7,8 @@ playSearching=\u0e01\u0e33\u0e25\u0e31\u0e07\u0e04\u0e49\u0e19\u0e2b\u0e32 `{q}` playYoutubeSearchError=\u0e21\u0e35\u0e1b\u0e31\u0e0d\u0e2b\u0e32\u0e23\u0e30\u0e2b\u0e27\u0e48\u0e32\u0e07\u0e01\u0e32\u0e23\u0e04\u0e49\u0e19\u0e2b\u0e32\u0e43\u0e19 Youtube \u0e25\u0e2d\u0e07\u0e40\u0e1e\u0e34\u0e48\u0e21\u0e40\u0e1e\u0e25\u0e07\u0e42\u0e14\u0e22\u0e01\u0e32\u0e23\u0e43\u0e0a\u0e49 \u0e25\u0e34\u0e49\u0e07\u0e01\u0e4c \u0e41\u0e17\u0e19\u0e14\u0e49\u0e27\u0e22\u0e04\u0e33\u0e2a\u0e31\u0e48\u0e07\n;;play <\u0e43\u0e2a\u0e48\u0e25\u0e34\u0e49\u0e07\u0e15\u0e23\u0e07\u0e19\u0e35\u0e49\!> playSearchNoResults=\u0e2b\u0e32 `{q}` \u0e44\u0e21\u0e48\u0e40\u0e08\u0e2d\u0e2d\u0e30\! playSelectVideo=**\u0e40\u0e25\u0e37\u0e2d\u0e01\u0e40\u0e1e\u0e25\u0e07\u0e14\u0e49\u0e27\u0e22\u0e01\u0e32\u0e23\u0e43\u0e0a\u0e49\u0e04\u0e33\u0e2a\u0e31\u0e48\u0e07 `{0}play n` **\nn\u0e04\u0e37\u0e2d\u0e15\u0e31\u0e27\u0e40\u0e25\u0e02\u0e19\u0e30\u0e08\u0e49\u0e30\! +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=\u0e01\u0e33\u0e25\u0e31\u0e07\u0e40\u0e02\u0e49\u0e32\u0e2b\u0e49\u0e2d\u0e07 {0} \u0e08\u0e49\u0e32 joinErrorAlreadyJoining=\u0e21\u0e35\u0e1b\u0e31\u0e0d\u0e2b\u0e32\u0e19\u0e34\u0e14\u0e2b\u0e19\u0e48\u0e2d\u0e22 \u0e40\u0e02\u0e49\u0e32\u0e2b\u0e49\u0e2d\u0e07 {0} \u0e44\u0e21\u0e48\u0e44\u0e14\u0e49\u0e40\u0e1e\u0e23\u0e32\u0e30\u0e01\u0e33\u0e25\u0e31\u0e07\u0e1e\u0e22\u0e32\u0e22\u0e32\u0e21\u0e40\u0e02\u0e49\u0e32\u0e2b\u0e49\u0e2d\u0e07\u0e19\u0e31\u0e49\u0e19\u0e2d\u0e22\u0e48\u0e39\u0e40\u0e40\u0e25\u0e49\u0e27 \u0e25\u0e2d\u0e07\u0e2a\u0e31\u0e48\u0e07\u0e2d\u0e35\u0e01\u0e17\u0e35\u0e19\u0e36\u0e07\u0e19\u0e30 pauseAlreadyPaused=\u0e1a\u0e2d\u0e15\u0e01\u0e33\u0e25\u0e31\u0e07\u0e2b\u0e22\u0e38\u0e14\u0e40\u0e1b\u0e34\u0e14\u0e40\u0e1e\u0e25\u0e07\u0e0a\u0e31\u0e48\u0e27\u0e04\u0e23\u0e32\u0e27\u0e2d\u0e22\u0e48\u0e39 @@ -21,6 +23,9 @@ shuffleOn=\u0e1a\u0e2d\u0e15\u0e40\u0e1b\u0e34\u0e14 \u0e42\u0e2b\u0e21\u0e14\u0 shuffleOff=\u0e1a\u0e2d\u0e15\u0e1b\u0e34\u0e14 \u0e42\u0e2b\u0e21\u0e14\u0e2a\u0e31\u0e1a\u0e40\u0e1b\u0e25\u0e35\u0e48\u0e22\u0e19 [shuffle] \u0e40\u0e40\u0e25\u0e49\u0e27 reshufflePlaylist=\u0e2a\u0e31\u0e1a\u0e40\u0e1b\u0e25\u0e35\u0e48\u0e22\u0e19\u0e40\u0e40\u0e17\u0e23\u0e47\u0e04\u0e43\u0e19\u0e04\u0e34\u0e27\u0e43\u0e2b\u0e21\u0e48\u0e40\u0e40\u0e25\u0e49\u0e27 reshufflePlayerNotShuffling=\u0e15\u0e49\u0e2d\u0e07\u0e40\u0e1b\u0e34\u0e14 \u0e42\u0e2b\u0e21\u0e14\u0e2a\u0e31\u0e1a\u0e40\u0e1b\u0e25\u0e35\u0e48\u0e22\u0e19[shuffle] \u0e01\u0e48\u0e2d\u0e19\u0e19\u0e30 +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=\u0e44\u0e21\u0e48\u0e21\u0e35\u0e40\u0e1e\u0e25\u0e07\u0e43\u0e19\u0e04\u0e34\u0e27\u0e43\u0e2b\u0e49\u0e40\u0e25\u0e48\u0e19\u0e40\u0e25\u0e22\! skipOutOfBounds=\u0e40\u0e2d\u0e32\u0e40\u0e40\u0e17\u0e23\u0e47\u0e04\u0e17\u0e35\u0e48 {0} \u0e2d\u0e2d\u0e01\u0e43\u0e2b\u0e49\u0e44\u0e21\u0e48\u0e44\u0e14\u0e49\u0e40\u0e1e\u0e23\u0e32\u0e30\u0e21\u0e31\u0e19\u0e21\u0e35\u0e40\u0e40\u0e04\u0e48 {1} \u0e40\u0e40\u0e17\u0e23\u0e47\u0e04\u0e43\u0e19\u0e04\u0e34\u0e27 skipNumberTooLow=\u0e15\u0e49\u0e2d\u0e07\u0e40\u0e1b\u0e47\u0e19\u0e40\u0e25\u0e02\u0e17\u0e35\u0e48\u0e21\u0e32\u0e01\u0e01\u0e27\u0e48\u0e32 0 \u0e40\u0e17\u0e48\u0e32\u0e19\u0e31\u0e49\u0e19\u0e19\u0e30 @@ -244,12 +249,14 @@ helpJoinCommand=\u0e17\u0e33\u0e43\u0e2b\u0e49\u0e1a\u0e2d\u0e17\u0e40\u0e02\u0e helpLeaveCommand=\u0e17\u0e33\u0e43\u0e2b\u0e49\u0e1a\u0e2d\u0e17\u0e2d\u0e2d\u0e01\u0e08\u0e32\u0e01\u0e0a\u0e48\u0e2d\u0e07\u0e2a\u0e31\u0e0d\u0e0d\u0e32\u0e13\u0e40\u0e2a\u0e35\u0e22\u0e07 helpPauseCommand=\u0e2b\u0e22\u0e38\u0e14\u0e40\u0e25\u0e48\u0e19 helpPlayCommand=\u0e40\u0e25\u0e48\u0e19\u0e40\u0e1e\u0e25\u0e07\u0e08\u0e32\u0e01 URL \u0e17\u0e35\u0e48\u0e01\u0e33\u0e2b\u0e19\u0e14\u0e2b\u0e23\u0e37\u0e2d\u0e04\u0e49\u0e19\u0e2b\u0e32\u0e08\u0e32\u0e01\u0e04\u0e33\u0e2a\u0e31\u0e48\u0e07\u0e19\u0e35\u0e49 \u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a\u0e40\u0e27\u0e47\u0e1a\u0e44\u0e0b\u0e15\u0e4c\u0e17\u0e35\u0e48\u0e23\u0e2d\u0e07\u0e23\u0e31\u0e1a \u0e42\u0e1b\u0e23\u0e14\u0e14\u0e39\u0e17\u0e35\u0e48 {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=\u0e40\u0e25\u0e48\u0e19\u0e40\u0e1e\u0e25\u0e07\u0e08\u0e32\u0e01 URL \u0e17\u0e35\u0e48\u0e01\u0e33\u0e2b\u0e19\u0e14\u0e2b\u0e23\u0e37\u0e2d\u0e04\u0e49\u0e19\u0e2b\u0e32\u0e01\u0e32\u0e23\u0e15\u0e34\u0e14\u0e15\u0e32\u0e21 \u0e21\u0e35\u0e40\u0e1e\u0e34\u0e48\u0e21\u0e40\u0e1e\u0e25\u0e07\u0e02\u0e2d\u0e07\u0e04\u0e34\u0e27 \u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a\u0e23\u0e32\u0e22\u0e0a\u0e37\u0e48\u0e2d\u0e02\u0e2d\u0e07\u0e41\u0e2b\u0e25\u0e48\u0e07\u0e02\u0e49\u0e2d\u0e21\u0e39\u0e25\u0e42\u0e1b\u0e23\u0e14\u0e40\u0e22\u0e35\u0e48\u0e22\u0e21\u0e0a\u0e21 {0} helpPlaySplitCommand=\u0e41\u0e1a\u0e48\u0e07\u0e27\u0e34\u0e14\u0e35\u0e42\u0e2d YouTube tracklist \u0e17\u0e35\u0e48\u0e43\u0e2b\u0e49\u0e44\u0e27\u0e49\u0e43\u0e19\u0e04\u0e33\u0e2d\u0e18\u0e34\u0e1a\u0e32\u0e22\u0e02\u0e2d\u0e07\u0e21\u0e31\u0e19 helpRepeatCommand=\u0e2a\u0e25\u0e31\u0e1a\u0e23\u0e30\u0e2b\u0e27\u0e48\u0e32\u0e07\u0e42\u0e2b\u0e21\u0e14\u0e0b\u0e49\u0e33 helpReshuffleCommand=\u0e1e\u0e34\u0e08\u0e32\u0e23\u0e13\u0e32\u0e04\u0e34\u0e27\u0e1b\u0e31\u0e08\u0e08\u0e38\u0e1a\u0e31\u0e19 helpSelectCommand=\u0e40\u0e25\u0e37\u0e2d\u0e01\u0e41\u0e17\u0e23\u0e47\u0e04\u0e17\u0e35\u0e48\u0e19\u0e33\u0e40\u0e2a\u0e19\u0e2d\u0e2d\u0e22\u0e48\u0e32\u0e07\u0e43\u0e14\u0e2d\u0e22\u0e48\u0e32\u0e07\u0e2b\u0e19\u0e36\u0e48\u0e07\u0e2b\u0e25\u0e31\u0e07\u0e08\u0e32\u0e01\u0e01\u0e32\u0e23\u0e04\u0e49\u0e19\u0e2b\u0e32\u0e01\u0e32\u0e23\u0e40\u0e25\u0e48\u0e19 helpShuffleCommand=\u0e2a\u0e25\u0e31\u0e1a\u0e42\u0e2b\u0e21\u0e14 shuffle \u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a\u0e04\u0e34\u0e27\u0e1b\u0e31\u0e08\u0e08\u0e38\u0e1a\u0e31\u0e19 +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=\u0e02\u0e49\u0e32\u0e21\u0e40\u0e1e\u0e25\u0e07\u0e1b\u0e31\u0e08\u0e08\u0e38\u0e1a\u0e31\u0e19, \u0e40\u0e1e\u0e25\u0e07\u0e25\u0e33\u0e14\u0e31\u0e1a\u0e17\u0e35\u0e48 n \u0e43\u0e19\u0e04\u0e34\u0e27, \u0e40\u0e1e\u0e25\u0e07\u0e25\u0e33\u0e14\u0e31\u0e1a\u0e17\u0e35\u0e48 n \u0e16\u0e36\u0e07 m \u0e2b\u0e23\u0e37\u0e2d\u0e40\u0e1e\u0e25\u0e07\u0e17\u0e31\u0e49\u0e07\u0e2b\u0e21\u0e14\u0e02\u0e2d\u0e07\u0e1c\u0e39\u0e49\u0e43\u0e0a\u0e49\u0e17\u0e35\u0e48\u0e41\u0e17\u0e47\u0e01 \u0e01\u0e23\u0e38\u0e13\u0e32\u0e43\u0e0a\u0e49\u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e01\u0e32\u0e23\u0e1a\u0e34\u0e23\u0e2b\u0e32\u0e23\u0e08\u0e31\u0e14\u0e01\u0e32\u0e23\u0e04\u0e34\u0e27\u0e40\u0e17\u0e48\u0e32\u0e19\u0e31\u0e49\u0e19 helpStopCommand=\u0e2b\u0e22\u0e38\u0e14\u0e40\u0e25\u0e48\u0e19 \u0e41\u0e25\u0e30\u0e22\u0e01\u0e40\u0e25\u0e34\u0e01\u0e40\u0e25\u0e37\u0e2d\u0e01\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\u0e17\u0e35\u0e48\u0e40\u0e25\u0e48\u0e19 \u0e2a\u0e07\u0e27\u0e19\u0e44\u0e27\u0e49\u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a\u0e1c\u0e39\u0e49\u0e17\u0e35\u0e48\u0e21\u0e35\u0e2a\u0e34\u0e17\u0e18\u0e34\u0e4c\u0e08\u0e31\u0e14\u0e01\u0e32\u0e23\u0e02\u0e49\u0e2d\u0e04\u0e27\u0e32\u0e21 helpUnpauseCommand=\u0e40\u0e25\u0e34\u0e01\u0e1e\u0e31\u0e01\u0e40\u0e04\u0e23\u0e37\u0e48\u0e2d\u0e07\u0e40\u0e25\u0e48\u0e19 @@ -339,4 +346,3 @@ modulesHowTo=\u0e1e\u0e34\u0e21\u0e1e\u0e4c {0} \u0e40\u0e1e\u0e37\u0e48\u0e2d\u parseNotAUser={0} \u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a\u0e01\u0e32\u0e23\u0e1b\u0e49\u0e2d\u0e19\u0e04\u0e48\u0e32\u0e02\u0e2d\u0e07\u0e04\u0e38\u0e13\u0e44\u0e14\u0e49\u0e1c\u0e25\u0e15\u0e2d\u0e1a\u0e41\u0e17\u0e19\u0e1c\u0e39\u0e49\u0e1a\u0e32\u0e14\u0e2b\u0e21\u0e32\u0e07 parseNotAMember=\u0e1c\u0e39\u0e49\u0e43\u0e0a\u0e49 {0} \u0e44\u0e21\u0e48\u0e44\u0e14\u0e49\u0e40\u0e1b\u0e47\u0e19\u0e2a\u0e21\u0e32\u0e0a\u0e34\u0e01\u0e02\u0e2d\u0e07\u0e01\u0e34\u0e25\u0e14\u0e4c\u0e19\u0e35\u0e49 parseSnowflakeIdHelp=\u0e21\u0e35\u0e1b\u0e31\u0e0d\u0e2b\u0e32\u0e43\u0e19\u0e01\u0e32\u0e23 id \u0e02\u0e2d\u0e07\u0e15\u0e31\u0e27\u0e1c\u0e39\u0e49\u0e43\u0e0a\u0e49/\u0e02\u0e49\u0e2d\u0e04\u0e27\u0e32\u0e21/\u0e0a\u0e48\u0e2d\u0e07/\u0e2a\u0e34\u0e48\u0e07\u0e2d\u0e37\u0e48\u0e19 \u0e15\u0e23\u0e27\u0e08\u0e2a\u0e2d\u0e1a\u0e40\u0e2d\u0e01\u0e2a\u0e32\u0e23\u0e04\u0e27\u0e32\u0e21\u0e1a\u0e32\u0e14\u0e2b\u0e21\u0e32\u0e07\u0e17\u0e35\u0e48 {0} - diff --git a/FredBoat/src/main/resources/lang/tr_TR.properties b/FredBoat/src/main/resources/lang/tr_TR.properties index 935c621bd..6628a780d 100644 --- a/FredBoat/src/main/resources/lang/tr_TR.properties +++ b/FredBoat/src/main/resources/lang/tr_TR.properties @@ -7,6 +7,8 @@ playSearching=Youtube''da ''{q}'' arat\u0131l\u0131yor... playYoutubeSearchError=YouTube'da arama yap\u0131l\u0131rken bir hata olu\u015ftu. Direk olarak ses kayna\u011f\u0131na ba\u011flant\u0131 kurmay\u0131 ye\u011fleyin.\n```\n;;play ``` playSearchNoResults=''{q}'' i\u00e7in hi\u00e7 sonu\u00e7 yok playSelectVideo=**L\u00fctfen `{0}play 1-5` komutu ile bir m\u00fczik se\u00e7in\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining={0}''a kat\u0131l\u0131n\u0131yor joinErrorAlreadyJoining=Bir hata olu\u015ftu. {0}''a kat\u0131l\u0131namad\u0131 \u00e7\u00fcnk\u00fc zaten o kanala kat\u0131lmaya \u00e7al\u0131\u015f\u0131yorum. L\u00fctfen tekrar deneyin. pauseAlreadyPaused=\u015eark\u0131 zaten durduruldu. @@ -21,6 +23,9 @@ shuffleOn=\u015eark\u0131 kar\u0131\u015f\u0131k modda \u00e7al\u0131n\u0131yor. shuffleOff=\u015eark\u0131 art\u0131k kar\u0131\u015f\u0131k modda \u00e7al\u0131nm\u0131yor. reshufflePlaylist=S\u0131ra tekrardan kar\u0131\u015ft\u0131r\u0131ld\u0131. reshufflePlayerNotShuffling=\u00d6ncelikle Kar\u0131\u015f\u0131k \u00c7alma modunu a\u00e7man\u0131z gerekir. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=S\u0131ra bo\u015f\! skipOutOfBounds={0} numaral\u0131 par\u00e7ay\u0131, yaln\u0131zca {1} par\u00e7a varken, kald\u0131ramay\u0131z. skipNumberTooLow=Verilen say\u0131 s\u0131f\u0131rdan (0) b\u00fcy\u00fck olmal\u0131d\u0131r. @@ -92,11 +97,11 @@ loadPlaylistTooMany={0} par\u00e7a eklendi. G\u00f6sterilebilece\u011finden \u00 loadErrorCommon=`{0}` i\u00e7in bilgi y\u00fcklenirken bir hata olu\u015ftu\:\n{1} loadErrorSusp=`{0}` i\u00e7in bilgi y\u00fcklenirken \u015f\u00fcpheli bir hata olu\u015ftu. loadQueueTrackLimit={0} par\u00e7adan fazla kuyru\u011fa par\u00e7a ekleyemezsiniz\! Bu, k\u00f6t\u00fcye kullan\u0131m\u0131 \u00f6nlemek i\u00e7in mevcut. -loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. -loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. -loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. -loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. -loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! +loadPlaylistDisabled=Bu sunucu \u00e7alma listelerini s\u0131raya koymay\u0131 devre d\u0131\u015f\u0131 b\u0131rakt\u0131. L\u00fctfen her par\u00e7ay\u0131 ayr\u0131 ayr\u0131 s\u0131raya al\u0131n. +loadMaxTracksExceeded=Bu sunucu {0} par\u00e7adan daha fazla kuyru\u011fa al\u0131nmas\u0131na izin vermiyor. +loadMaxUserTracksExceeded=Bu sunucu, s\u0131rada {0} par\u00e7adan daha fazlas\u0131na sahip olman\u0131za izin vermiyor. +loadMaxTrackLengthExceeded=Bu sunucu {0}''dan daha uzun par\u00e7alar\u0131 \u00e7alman\u0131za izin vermiyor. L\u00fctfen daha k\u0131sa bir \u015fey deneyin. +loadPlaylistGeneralError={0} izleri eklenmedi \u00e7\u00fcnk\u00fc bu sunucu s\u0131ra k\u0131s\u0131tlamalar\u0131n\u0131 zorlad\u0131\! loadAnnouncePlaylist=Oynatma listesi **{0}**, `{1}`ye kadar par\u00e7a, y\u00fcklenecek. Bu biraz zaman alabilir, l\u00fctfen sab\u0131rl\u0131 olun. playerUserNotInChannel=\u00d6nce sesli bir kanala kat\u0131lman\u0131z gerekmektedir. playerJoinConnectDenied=Bu sesli kanala ba\u011flanabilmek i\u00e7in yeterli yetkiye sahip de\u011filim. @@ -244,12 +249,14 @@ helpJoinCommand=Botun sesli sohbet kanal\u0131n\u0131za kat\u0131lmas\u0131n\u01 helpLeaveCommand=Bot sesli sohbet kanal\u0131n\u0131zdan ayr\u0131lmas\u0131n\u0131 sa\u011flar. helpPauseCommand=Oynat\u0131c\u0131y\u0131 duraklat\u0131r. helpPlayCommand=Belirtilen adres veya arama i\u00e7in bir m\u00fczik \u00e7alar. Kaynaklar\u0131n\u0131n tam listesi i\u00e7in l\u00fctfen {0} adresini ziyaret ediniz +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Belirtilen URL''den m\u00fczik \u00e7al\u0131n veya par\u00e7a aray\u0131n. Eklenen par\u00e7alar listenin \u00fcst\u00fcne eklenir. Kaynaklar\u0131m tam listesi i\u00e7in {0} ziyaret edin helpPlaySplitCommand=Bir YouTube videosunu a\u00e7\u0131klamas\u0131ndaki \u015fark\u0131 listesine g\u00f6re ay\u0131r\u0131r. helpRepeatCommand=Tekrarlama modunu de\u011fi\u015ftirir. helpReshuffleCommand=\u00c7alma listesi s\u0131ras\u0131n\u0131 kar\u0131\u015ft\u0131r\u0131r. helpSelectCommand=\u015eark\u0131 i\u00e7ini, arama yap\u0131ld\u0131ktan sonra \u00e7alacak olan\u0131 se\u00e7mek i\u00e7in kullan\u0131l\u0131r. helpShuffleCommand=S\u0131radaki \u015fark\u0131lar\u0131n s\u0131ras\u0131n\u0131 kar\u0131\u015ft\u0131r\u0131r. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Belirtilen kullan\u0131c\u0131lar\u0131n ekledi\u011fi \u00e7alan \u015fark\u0131y\u0131, listedeki n. ve n ile m aras\u0131ndaki b\u00fct\u00fcn \u015fark\u0131lar\u0131 atlar. L\u00fctfen dikkatli kullan\u0131n. helpStopCommand=M\u00fczik \u00e7alar\u0131 durdur ve \u00e7alma listesini temizler. Sunucu y\u00f6netme yetkisine sahip moderat\u00f6rler taraf\u0131ndan kullan\u0131labilir. helpUnpauseCommand=Oynat\u0131c\u0131y\u0131 devam ettirir. @@ -339,4 +346,3 @@ modulesHowTo={0} yazarak mod\u00fclleri aktifle\u015ftirin/devre d\u0131\u015f\u parseNotAUser=Senin giri\u015f {0} bir anla\u015fmazl\u0131k Kullan\u0131c\u0131 vermemi\u015ftir. parseNotAMember=Kullan\u0131c\u0131 {0} bu sunucunun bir \u00fcyesi de\u011fil. parseSnowflakeIdHelp=Bir kullan\u0131c\u0131n\u0131n/mesaj\u0131n/kanal\u0131n veya ba\u015fka bir \u015feyin bilgisini almakta sorun mu ya\u015f\u0131yor sunuz? Discord d\u00f6k\u00fcmanlar\u0131n\u0131 kontrol edin {0} - diff --git a/FredBoat/src/main/resources/lang/uk_UA.properties b/FredBoat/src/main/resources/lang/uk_UA.properties index 4fb10c483..a8f20af65 100644 --- a/FredBoat/src/main/resources/lang/uk_UA.properties +++ b/FredBoat/src/main/resources/lang/uk_UA.properties @@ -1,12 +1,14 @@ #X-Generator: crowdin.com -playQueueEmpty=\u0413\u0440\u0430\u0432\u0435\u0446\u044c \u0432 \u0434\u0430\u043d\u0438\u0439 \u0447\u0430\u0441 \u043d\u0435 \u0433\u0440\u0430\u0454 \u043d\u0456\u0447\u043e\u0433\u043e. \u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0438\u0439 \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441, \u0449\u043e\u0431 \u0434\u043e\u0434\u0430\u0442\u0438 \u043f\u0456\u0441\u043d\u044e\:; \u0433\u0440\u0430\u0442\u0438 -playAlreadyPlaying=\u0413\u0440\u0430\u0432\u0435\u0446\u044c \u0432\u0436\u0435 \u0433\u0440\u0430\u0454. +playQueueEmpty=\u041f\u043b\u0435\u0454\u0440 \u0432 \u0434\u0430\u043d\u0438\u0439 \u0447\u0430\u0441 \u043d\u0435 \u0433\u0440\u0430\u0454 \u043d\u0456\u0447\u043e\u0433\u043e. \u0412\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0438\u0439 \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441, \u0449\u043e\u0431 \u0434\u043e\u0434\u0430\u0442\u0438 \u043f\u0456\u0441\u043d\u044e\:\n;;play +playAlreadyPlaying=\u041f\u043b\u0435\u0454\u0440 \u0432\u0436\u0435 \u0433\u0440\u0430\u0454. playVCEmpty=\u041d\u0435\u043c\u0430\u0454 \u0436\u043e\u0434\u043d\u043e\u0433\u043e \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430 \u0443 \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u043c\u0443 \u0447\u0430\u0442\u0456. playWillNowPlay=\u0413\u0440\u0430\u0432\u0435\u0446\u044c \u0431\u0443\u0434\u0435 \u0433\u0440\u0430\u0442\u0438 \u0437\u0430\u0440\u0430\u0437. playSearching=\u041f\u043e\u0448\u0443\u043a\u0443 YouTube ''{q}''... playYoutubeSearchError=\u0421\u0442\u0430\u043b\u0430\u0441\u044f \u043f\u043e\u043c\u0438\u043b\u043a\u0430 \u043f\u0456\u0434 \u0447\u0430\u0441 \u043f\u043e\u0448\u0443\u043a\u0443 YouTube. \u0420\u043e\u0437\u0433\u043b\u044f\u043d\u0435 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u044c \u0431\u0435\u0437\u043f\u043e\u0441\u0435\u0440\u0435\u0434\u043d\u044c\u043e \u0434\u043e \u0434\u0436\u0435\u0440\u0435\u043b\u0430 \u0430\u0443\u0434\u0456\u043e \u0437\u0430\u043c\u0456\u0441\u0442\u044c \u0446\u044c\u043e\u0433\u043e. ``` ;; \u0433\u0440\u0430\u0442\u0438 ' ' playSearchNoResults=\u041d\u0435\u043c\u0430 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0456\u0432 \u0434\u043b\u044f `{q}` playSelectVideo=* * \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0432\u0438\u0431\u0435\u0440\u0456\u0442\u044c \u0442\u0440\u0435\u043a \u0437\u0430 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u043e\u044e ''{0}play n'' \u043a\u043e\u043c\u0430\u043d\u0434\u0438\: * * +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=\u041f\u0440\u0438\u0454\u0434\u043d\u0430\u043d\u043d\u044f {0} joinErrorAlreadyJoining=\u0421\u0442\u0430\u043b\u0430\u0441\u044f \u043f\u043e\u043c\u0438\u043b\u043a\u0430. \u041d\u0435\u043c\u043e\u0436\u0443 \u043f\u0440\u0438\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f {0} \u0442\u043e\u043c\u0443 \u0449\u043e \u044f \u0432\u0436\u0435 \u043d\u0430\u043c\u0430\u0433\u0430\u044e\u0441\u044f \u043f\u0440\u0438\u0454\u0434\u043d\u0430\u0442\u0438\u0441\u044f \u0434\u043e \u0446\u044c\u043e\u0433\u043e \u043a\u0430\u043d\u0430\u043b\u0443. \u0411\u0443\u0434\u044c-\u043b\u0430\u0441\u043a\u0430 \u043f\u043e\u0432\u0442\u043e\u0440\u0438 \u0437\u043d\u043e\u0432\u0443. pauseAlreadyPaused=\u0413\u0440\u0430\u0432\u0446\u044f \u0432\u0436\u0435 \u043f\u0440\u0438\u043f\u0438\u043d\u0435\u043d\u043e. @@ -21,6 +23,9 @@ shuffleOn=\u041f\u043b\u0435\u0454\u0440 \u0437\u043d\u0430\u0445\u043e\u0434\u0 shuffleOff=\u041f\u043b\u0435\u0454\u0440 \u0431\u0456\u043b\u044c\u0448\u0435 \u043d\u0435 \u0437\u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0443 \u0440\u0435\u0436\u0438\u043c\u0456 \u043f\u0435\u0440\u0435\u043c\u0456\u0448\u0443\u0432\u0430\u043d\u043d\u044f. reshufflePlaylist=\u041f\u043b\u0435\u0439\u043b\u0456\u0441\u0442 \u0443\u0441\u043f\u0456\u0448\u043d\u043e \u043f\u0435\u0440\u0435\u0442\u0430\u0441\u043e\u0432\u0430\u043d\u0438\u0439. reshufflePlayerNotShuffling=\u0421\u043f\u043e\u0447\u0430\u0442\u043a\u0443 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0440\u0435\u0436\u0438\u043c \u043f\u0435\u0440\u0435\u043c\u0456\u0448\u0443\u0432\u0430\u043d\u043d\u044f. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=\u0427\u0435\u0440\u0433\u0430 \u043f\u043e\u0440\u043e\u0436\u043d\u044f\! skipOutOfBounds=\u041d\u0435 \u043c\u043e\u0436\u043b\u0438\u0432\u043e \u0432\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u0442\u0440\u0435\u043a \u043f\u0456\u0434 \u043d\u043e\u043c\u0435\u0440\u043e\u043c {0}, \u0442\u0430\u043a \u044f\u043a \u0456\u0441\u043d\u0443\u0454 \u0442\u0456\u043b\u044c\u043a\u0438 {1} \u0442\u0440\u0435\u043a\u0456\u0432. skipNumberTooLow=\u0414\u0430\u043d\u0435 \u0447\u0438\u0441\u043b\u043e \u043c\u0430\u0454 \u0431\u0443\u0442\u0438 \u0431\u0456\u043b\u044c\u0448\u0438\u043c \u0437\u0430 \u043d\u0443\u043b\u044c. @@ -53,34 +58,34 @@ listStreamsOrTracksSingle=\u0423 \u0447\u0435\u0440\u0437\u0456 **{0}** {1} \u04 listStreamsOrTracksMultiple=\u0423 \u0447\u0435\u0440\u0437\u0456 **{0}** {1} \u0456\u0437 \u0437\u0430\u0433\u0430\u043b\u044c\u043d\u043e\u044e \u0442\u0440\u0438\u0432\u0430\u043b\u0456\u0441\u0442\u044e **{2}** {3}. streamSingular=\u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0456\u044f streamPlural=\u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0456\u0457 -listAsWellAsLiveStreams=, \u0442\u0430\u043a \u0441\u0430\u043c\u043e \u044f\u043a \u0456 **{0}** \u043b\u0430\u0439\u0432-{1} +listAsWellAsLiveStreams=, \u0442\u0430\u043a \u0441\u0430\u043c\u043e \u044f\u043a \u0456 **{0}** \u0442\u0440\u0430\u043d\u0441\u043b\u044e\u0454\u0442\u044c\u0441\u044f {1} trackSingular=\u0422\u0440\u0435\u043a -trackPlural=\u043a\u043e\u043c\u043f\u043e\u0437\u0438\u0446\u0456\u0457 -npNotPlaying=\u041d\u0430 \u0434\u0430\u043d\u0438\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u043d\u0456\u0447\u043e\u0433\u043e \u043d\u0435 \u0433\u0440\u0430\u0454. -npNotInHistory=\u041d\u0430 \u0434\u0430\u043d\u0438\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u043d\u0435\u043c\u0430\u0454 \u043d\u044f\u043a\u0438\u0445 \u0442\u0440\u0435\u043a\u0456\u0432 \u0432 \u0456\u0441\u0442\u043e\u0440\u0456\u0457. +trackPlural=\u0442\u0440\u0435\u043a\u0438 +npNotPlaying=\u041d\u0430 \u0434\u0430\u043d\u0438\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u043d\u0435 \u0432\u0456\u0434\u0442\u0432\u043e\u0440\u044e\u0454\u0442\u044c\u0441\u044f \u043d\u0456\u0447\u043e\u0433\u043e. +npNotInHistory=\u041d\u0430 \u0434\u0430\u043d\u0438\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u043d\u0435\u043c\u0430\u0454 \u043d\u0456\u044f\u043a\u0438\u0445 \u0442\u0440\u0435\u043a\u0456\u0432 \u0432 \u0456\u0441\u0442\u043e\u0440\u0456\u0457. npDescription=\u041e\u043f\u0438\u0441 npLoadedSoundcloud=[{0}/{1}]\n\n\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043e \u0437 Soundcloud npLoadedBandcamp={0}\n\n\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043e \u0437 Bandcamp npLoadedTwitch=\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043e \u0437 Twitch -npRequestedBy=Requested by {0} +npRequestedBy=\u0417\u0430\u043f\u0438\u0442\u0430\u043d\u043e {0} permissionMissingBot=\u041c\u0435\u043d\u0456 \u043f\u043e\u0442\u0440\u0456\u0431\u043d\u0456 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0456 \u0434\u043e\u0437\u0432\u043e\u043b\u0438 \u0434\u043b\u044f \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u043d\u044f \u0446\u0456\u0454\u0457 \u0434\u0456\u0457\: permissionMissingInvoker=\u0412\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u0456\u0434\u043d\u0456 \u043d\u0430\u0441\u0442\u0443\u043f\u043d\u0456 \u0434\u043e\u0437\u0432\u043e\u043b\u0438 \u0434\u043b\u044f \u0432\u0438\u043a\u043e\u043d\u0430\u043d\u043d\u044f \u0446\u0456\u0454\u0457 \u0434\u0456\u0457\: permissionEmbedLinks=\u0412\u0431\u0443\u0434\u043e\u0432\u0430\u043d\u0456 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f rating=\u0420\u0435\u0439\u0442\u0438\u043d\u0433 listeners=\u0421\u043b\u0443\u0445\u0430\u0447\u0456\u0432 -year=\u0420\u0456\u0439 +year=\u0420\u0456\u043a album=\u0410\u043b\u044c\u0431\u043e\u043c -artist=\u0410\u0440\u0442\u0438\u0441\u0442 +artist=\u0412\u0438\u043a\u043e\u043d\u0430\u0432\u0435\u0446\u044c circle=\u041a\u043e\u043b\u043e npLoadedFromHTTP={0}\n\n\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043e \u0437 {1}\n -npLoadedDefault={0}\n\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0430\u043d\u043e \u0437 {1} +npLoadedDefault={0}\n\n\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0430\u043d\u043e \u0437 {1} noneYet=\u0429\u0435 \u043d\u0435\u043c\u0430\u0454 npRatingRange={0}/5 \u0437\u0433\u0456\u0434\u043d\u043e \u0437 {1} \u0433\u043e\u043b\u043e\u0441\u043e\u043c/\u0430\u043c\u0438 fwdSuccess=\u041f\u0435\u0440\u0435\u0445\u0456\u0434 \u0437 **{0}** \u0434\u043e {1}. -restartSuccess=**{0}** \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043e. +restartSuccess=**{0}** \u0431\u0443\u043b\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043e. queueEmpty=\u0427\u0435\u0440\u0433\u0430 \u043f\u0443\u0441\u0442\u0430. -rewSuccess=**{0}** \u043f\u0435\u0440\u0435\u043c\u043e\u0442\u0430\u043d\u043e \u0434\u043e {1}. -seekSuccess=\u0422\u0440\u0435\u043a **{0}** \u0442\u0435\u043f\u0435\u0440 \u043d\u0430 \u043f\u043e\u0437\u0438\u0446\u0456\u0457 {1}. +rewSuccess=\u041f\u0435\u0440\u0435\u043c\u043e\u0442\u0443\u0432\u0430\u043d\u043d\u044f **{0}** \u0434\u043e {1}. +seekSuccess=\u0412\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044f **{0}** \u043d\u0430 {1}. seekDeniedLiveTrack=\u0412\u0438 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0448\u0443\u043a\u0430\u0442\u0438 \u0436\u0438\u0432\u0438\u0439 \u0442\u0440\u0435\u043a. loadPlaySplitListFail=\u0426\u0435 \u043f\u043e\u0441\u0438\u043b\u0430\u043d\u043d\u044f \u043f\u0440\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u044c \u0434\u043e \u0441\u043f\u0438\u0441\u043a\u0443 \u0432\u0456\u0434\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f, \u0430 \u043d\u0435 \u0434\u043e \u0442\u0440\u0435\u043a\u0443. \u0421\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 `;; \u0433\u0440\u0430\u0442\u0438` \u0437\u0430\u043c\u0456\u0441\u0442\u044c \u0446\u044c\u043e\u0433\u043e. loadListSuccess=`{0}` \u043f\u0456\u0441\u0435\u043d\u044c \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e \u0456 \u0434\u043e\u0434\u0430\u043d\u043e \u0437 \u043f\u043b\u0435\u0439\u043b\u0456\u0441\u0442\u0443 **{1}**. @@ -92,11 +97,11 @@ loadPlaylistTooMany=\u0414\u043e\u0434\u0430\u043d\u043e {0} \u0442\u0440\u0435\ loadErrorCommon=\u0412\u0456\u0434\u0431\u0443\u043b\u0430\u0441\u044f \u043f\u043e\u043c\u0438\u043b\u043a\u0430 \u043f\u0440\u0438 \u0437\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u0456 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457 \u043f\u0440\u043e `{0}`\:\n{1} loadErrorSusp=\u0412\u0438\u043d\u0438\u043a\u043b\u0430 \u043f\u0456\u0434\u043e\u0437\u0440\u0456\u043b\u0430 \u043f\u043e\u043c\u0438\u043b\u043a\u0430 \u043f\u0440\u0438 \u0437\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u0456 \u0456\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u0457 \u043f\u0440\u043e `{0}`. loadQueueTrackLimit=\u0412\u0438 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0434\u043e\u0434\u0430\u0442\u0438 \u0442\u0440\u0435\u043a\u0438 \u0434\u043e \u0447\u0435\u0440\u0433\u0438, \u0443 \u044f\u043a\u0456\u0439 \u0431\u0456\u043b\u044c\u0448\u0435 \u0437\u0430 {0} \u0442\u0440\u0435\u043a\u0456\u0432\! \u0426\u0435 \u0434\u043b\u044f \u0437\u0430\u043f\u043e\u0431\u0456\u0433\u0430\u043d\u043d\u044f \u0437\u043b\u043e\u0432\u0436\u0438\u0432\u0430\u043d\u043d\u044e. -loadPlaylistDisabled=This server has disabled queueing up playlists. Please queue up each track individually. -loadMaxTracksExceeded=This server does not allow queueing more than {0} tracks. -loadMaxUserTracksExceeded=This server does not allow you to have more than {0} tracks in the queue. -loadMaxTrackLengthExceeded=This server does not allow you to play tracks longer than {0}. Please try something shorter. -loadPlaylistGeneralError={0} tracks were not added because this server enforces queue restrictions\! +loadPlaylistDisabled=\u041d\u0430 \u0446\u044c\u043e\u043c\u0443 \u0441\u0435\u0440\u0432\u0435\u0440\u0456 \u043d\u0435\u043c\u0430\u0454 \u043c\u043e\u0436\u043b\u0438\u0432\u043e\u0441\u0442\u0456 \u0434\u043e\u0434\u0430\u0432\u0430\u043d\u043d\u044f \u043f\u043b\u0435\u0439\u043b\u0438\u0441\u0442\u0456\u0432. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430 \u0434\u043e\u0434\u0430\u0439\u0442\u0435 \u043a\u043e\u0436\u0435\u043d \u0442\u0440\u0435\u043a \u0432 \u0447\u0435\u0440\u0433\u0443 \u043e\u043a\u0440\u0435\u043c\u043e. +loadMaxTracksExceeded=\u0426\u0435\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0435 \u0434\u043e\u0437\u0432\u043e\u043b\u044f\u0454 \u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0432 \u0447\u0435\u0440\u0433\u0443 \u0431\u0456\u043b\u044c\u0448\u0435 {0} \u0442\u0440\u0435\u043a\u0456\u0432. +loadMaxUserTracksExceeded=\u0426\u0435\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0435 \u0434\u043e\u0437\u0432\u043e\u043b\u044f\u0454 \u0432\u0430\u043c \u0441\u0442\u0430\u0432\u0438\u0442\u0438 \u0432 \u0447\u0435\u0440\u0433\u0443 \u0431\u0456\u043b\u044c\u0448\u0435 {0} \u0442\u0440\u0435\u043a\u0456\u0432. +loadMaxTrackLengthExceeded=\u0426\u0435\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0435 \u0434\u043e\u0437\u0432\u043e\u043b\u044f\u0454 \u0432\u0456\u0434\u0442\u0432\u043e\u0440\u044e\u0432\u0430\u0442\u0438 \u0442\u0440\u0435\u043a\u0438 \u0434\u043e\u0432\u0448\u0435, \u043d\u0456\u0436 {0}. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430 \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u043e\u0441\u044c \u043a\u043e\u0440\u043e\u0442\u0448\u0435. +loadPlaylistGeneralError={0} \u0442\u0440\u0435\u043a\u0456\u0432 \u043d\u0435 \u0431\u0443\u043b\u043e \u0434\u043e\u0434\u0430\u043d\u043e \u0447\u0435\u0440\u0435\u0437 \u0442\u0435, \u0449\u043e \u0446\u0435\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 \u043c\u0430\u0454 \u043e\u0431\u043c\u0435\u0436\u0435\u043d\u043d\u044f\! loadAnnouncePlaylist=\u0417\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0443\u044e \u043f\u043b\u0435\u0439\u043b\u0456\u0441\u0442 **{0}** \u0456\u0437 `{1}` \u0442\u0440\u0435\u043a\u0430\u043c\u0438. \u0426\u0435 \u043c\u043e\u0436\u0435 \u0437\u0430\u0439\u043d\u044f\u0442\u0438 \u043f\u0435\u0432\u043d\u0438\u0439 \u0447\u0430\u0441, \u0431\u0443\u0434\u044c\u0442\u0435 \u0442\u0435\u0440\u043f\u043b\u044f\u0447\u0438\u043c\u0438. playerUserNotInChannel=\u0421\u043f\u043e\u0447\u0430\u0442\u043a\u0443 \u0432\u0438 \u043f\u043e\u0432\u0438\u043d\u043d\u0456 \u0443\u0432\u0456\u0439\u0442\u0438 \u0434\u043e \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0433\u043e \u043a\u0430\u043d\u0430\u043b\u0443. playerJoinConnectDenied=\u0423 \u043d\u0435 \u043c\u0430\u044e \u043f\u0440\u0430\u0432 \u0437\u0430\u0445\u043e\u0434\u0438\u0442\u0438 \u0443 \u0446\u0435\u0439 \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u0438\u0439 \u043a\u0430\u043d\u0430\u043b. @@ -244,12 +249,14 @@ helpJoinCommand=\u041f\u0456\u0434'\u0454\u0434\u043d\u0430\u0442\u0438 \u0431\u helpLeaveCommand=\u0412\u0438\u043b\u0443\u0447\u0438\u0442\u0438 \u0431\u043e\u0442\u0430 \u0437 \u043f\u043e\u0442\u043e\u0447\u043d\u043e\u0433\u043e \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0433\u043e \u043a\u0430\u043d\u0430\u043b\u0443. helpPauseCommand=\u041f\u0440\u0438\u0437\u0443\u043f\u0438\u043d\u0438\u0442\u0438 \u043f\u043b\u0435\u0454\u0440. helpPlayCommand=\u041f\u0440\u043e\u0433\u0440\u0430\u0432\u0430\u0442\u0438 \u043c\u0443\u0437\u0438\u043a\u0443 \u0437 \u0437\u0430\u0434\u0430\u043d\u043e\u0433\u043e URL \u0430\u0431\u043e \u0448\u0443\u043a\u0430\u0442\u0438 \u0442\u0440\u0435\u043a. \u0414\u043b\u044f \u043f\u043e\u0432\u043d\u043e\u0433\u043e \u0441\u043f\u0438\u0441\u043a\u0443 \u0434\u0436\u0435\u0440\u0435\u043b, \u0432\u0456\u0434\u0432\u0456\u0434\u0430\u0439\u0442\u0435 {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=\u0413\u0440\u0430\u0442\u0438 \u043c\u0443\u0437\u0438\u043a\u0443 \u0437 \u0434\u0430\u043d\u043e\u0433\u043e URL \u0430\u0431\u043e \u0448\u0443\u043a\u0430\u0442\u0438 \u0442\u0440\u0435\u043a. \u0422\u0440\u0435\u043a\u0438 \u0434\u043e\u0434\u0430\u044e\u0442\u044c\u0441\u044f \u0434\u043e \u0432\u0435\u0440\u0448\u0438\u043d\u0438 \u0447\u0435\u0440\u0433\u0438. \u0414\u043b\u044f \u043f\u043e\u0432\u043d\u043e\u0433\u043e \u043f\u0435\u0440\u0435\u043b\u0456\u043a\u0443 \u0434\u0436\u0435\u0440\u0435\u043b, \u0431\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0456\u0442\u044c \u0434\u043e {0} helpPlaySplitCommand=\u0420\u043e\u0437\u0434\u0456\u043b\u0438\u0442\u0438 \u0432\u0456\u0434\u0435\u043e \u0437 YouTube \u043d\u0430 \u0442\u0440\u0435\u043a\u043b\u0456\u0441\u0442, \u0449\u043e \u0432\u043a\u0430\u0437\u0430\u043d\u043e \u0432 \u043e\u043f\u0438\u0441\u0456. helpRepeatCommand=\u041f\u0435\u0440\u0435\u043c\u0438\u043a\u0430\u043d\u043d\u044f \u043c\u0456\u0436 \u0440\u0435\u0436\u0438\u043c\u0430\u043c\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u0443. helpReshuffleCommand=\u041f\u0435\u0440\u0435\u043c\u0456\u0448\u0430\u0442\u0438 \u043f\u043e\u0442\u043e\u0447\u043d\u0443 \u0447\u0435\u0440\u0433\u0443. helpSelectCommand=\u041e\u0431\u0435\u0440\u0456\u0442\u044c \u043e\u0434\u0438\u043d \u0456\u0437 \u0437\u0430\u043f\u0440\u043e\u043f\u043e\u043d\u043e\u0432\u0430\u043d\u0438\u0445 \u0442\u0440\u0435\u043a\u0456\u0432, \u043f\u0456\u0441\u043b\u044f \u043f\u043e\u0448\u0443\u043a\u0443 \u0434\u043b\u044f \u0432\u0456\u0434\u0442\u0432\u043e\u0440\u0435\u043d\u043d\u044f. helpShuffleCommand=\u041f\u0435\u0440\u0435\u043c\u0438\u043a\u0430\u043d\u043d\u044f \u0432 \u043f\u043e\u0442\u043e\u0447\u043d\u0456\u0439 \u043f\u043e\u0441\u043b\u0456\u0434\u043e\u0432\u043d\u043e\u0441\u0442\u0456 \u0432\u0438\u043f\u0430\u0434\u043a\u043e\u0432\u0438\u043c \u0447\u0438\u043d\u043e\u043c. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=\u041f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u0438 \u0432\u0456\u0434\u0442\u0432\u043e\u0440\u0435\u043d\u0443 \u043f\u0456\u0441\u043d\u044e, \u043d\u043e\u043c\u0435\u0440 \u043f\u0456\u0441\u043d\u0456 \u0432 \u0447\u0435\u0440\u0437\u0456, \u0432\u0441\u0456 \u043f\u0456\u0441\u043d\u0456 \u0432 \u0447\u0435\u0440\u0437\u0456, \u0432\u0441\u0456 \u043f\u0456\u0441\u043d\u0456 \u0432\u0456\u0434 \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0456\u0432. \u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0439\u0442\u0435 \u0432 \u0443\u043f\u043e\u0432\u0456\u043b\u044c\u043d\u0435\u043d\u043d\u0456. helpStopCommand=\u0417\u0443\u043f\u0438\u043d\u0438\u0442\u0438 \u043f\u0440\u043e\u0433\u0440\u0430\u0432\u0430\u0447 \u0442\u0430 \u043e\u0447\u0438\u0441\u0442\u0438\u0442\u0438 \u043f\u043b\u0435\u0439\u043b\u0456\u0441\u0442. \u0422\u0456\u043b\u044c\u043a\u0438 \u0434\u043b\u044f \u043c\u043e\u0434\u0435\u0440\u0430\u0442\u043e\u0440\u0456\u0432 \u0456\u0437 \u043f\u0440\u0430\u0432\u0430\u043c\u0438 \u043a\u0435\u0440\u0443\u0432\u0430\u0442\u0438 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u043d\u044f\u043c\u0438. helpUnpauseCommand=\u0417\u043d\u044f\u0442\u0438 \u043f\u0440\u043e\u0433\u0440\u0430\u0432\u0430\u0447 \u0437 \u043f\u0430\u0443\u0437\u0438. @@ -278,7 +285,7 @@ helpUserInfoCommand=\u0412\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0430\u0442 helpPerms=\u0414\u043e\u0437\u0432\u043e\u043b\u044f\u0454 \u0447\u043b\u0435\u043d\u0430\u043c \u0431\u0456\u043b\u043e\u0433\u043e \u0441\u043f\u0438\u0441\u043a\u0443 \u0442\u0430 \u0440\u043e\u043b\u0435\u0439 \u0434\u043b\u044f \u0440\u0430\u043d\u0433\u0443 {0}. helpPrefixCommand=\u0412\u0441\u0442\u0430\u043d\u043e\u0432\u0456\u0442\u044c \u043f\u0440\u0435\u0444\u0456\u043a\u0441 \u0434\u043b\u044f \u0446\u0456\u0454\u0457 \u0433\u0456\u043b\u044c\u0434\u0456\u0457. helpVoteSkip=\u041f\u0440\u043e\u0433\u043e\u043b\u043e\u0441\u0443\u0439\u0442\u0435, \u0449\u043e\u0431 \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u0438 \u043f\u043e\u0442\u043e\u0447\u043d\u0443 \u043f\u0456\u0441\u043d\u044e. \u041f\u043e\u0442\u0440\u0435\u0431\u0443\u0454 50% \u0432\u0441\u0456\u0445 \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0456\u0432 \u0443 \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u043c\u0443 \u0447\u0430\u0442\u0456 \u0433\u043e\u043b\u043e\u0441\u0443\u0432\u0430\u0442\u0438. -helpUnvoteSkip=Remove your vote to skip the current song. +helpUnvoteSkip=\u0412\u0438\u0434\u0430\u043b\u0456\u0442\u044c \u0441\u0432\u0456\u0439 \u0433\u043e\u043b\u043e\u0441, \u0449\u043e\u0431 \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u0438 \u043f\u043e\u0442\u043e\u0447\u043d\u0443 \u043f\u0456\u0441\u043d\u044e. helpMathOperationAdd=\u0412\u0438\u0432\u0435\u0434\u0456\u0442\u044c \u0441\u0443\u043c\u0443 num1 \u0456 num2. helpMathOperationSub=\u041d\u0430\u0434\u0440\u0443\u043a\u0443\u0439\u0442\u0435 \u0440\u0456\u0437\u043d\u0438\u0446\u044e \u0432\u0456\u0434\u043d\u0456\u043c\u0430\u043d\u043d\u044f num2 \u0437 num1. helpMathOperationMult=\u041d\u0430\u0434\u0440\u0443\u043a\u0443\u0439\u0442\u0435 \u0442\u0432\u0456\u0440 num1 * num2. @@ -296,47 +303,46 @@ permsAdded=\u0414\u043e\u0434\u0430\u043d\u043e `{0}` \u0434\u043e `{1}`. permsRemoved=\u0412\u0438\u0434\u0430\u043b\u0435\u043d\u043e `{0}` \u0437 `{1}`. permsFailSelfDemotion=\u0412\u0438 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u0438\u0434\u0430\u043b\u0438\u0442\u0438 \u0446\u0435, \u043e\u0441\u043a\u0456\u043b\u044c\u043a\u0438 \u0446\u0435 \u043d\u0430\u0434\u0430\u0441\u0442\u044c \u0432\u0430\u043c \u0431\u0435\u0437 \u0434\u043e\u0437\u0432\u043e\u043b\u0456\u0432 \u0430\u0434\u043c\u0456\u043d\u0456\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430\! permsAlreadyAdded={0} \u0443\u0436\u0435 \u0434\u043e\u0434\u0430\u043d\u043e \u0434\u043e {1} -permsNotAdded={0} is not in {1} -fuzzyMultiple=Multiple items were found. Did you mean any of these? -fuzzyNothingFound=Nothing found for `{0}`. -cmdPermsTooLow=You don''t have permission to run this command\! This command requires `{0}` but you only have `{1}`. -playersLimited=FredBoat is currently at maximum capacity\! The bot is currently fixed to only play up to `{0}` streams, otherwise we would risk disconnecting from Discord under the network load.\nIf you want to help us increase the limit or you want to use our non-overcrowded bot, please support our work on Patreon\:\n{1}\n\nSorry for the inconvenience\! You might want to try again later. This message usually only appears at peak time. -tryLater=Please try again later. +permsNotAdded={0} \u043d\u0435 \u0454 \u0432 {1} +fuzzyMultiple=\u0417\u043d\u0430\u0439\u0434\u0435\u043d\u043e \u043a\u0456\u043b\u044c\u043a\u0430 \u0432\u0430\u0440\u0456\u0430\u043d\u0442\u0456\u0432. \u0412\u0438 \u043c\u0430\u043b\u0456 \u043d\u0430 \u0443\u0432\u0430\u0437\u0456 \u044f\u043a\u0438\u0439\u0441\u044c \u0456\u0437 \u043d\u0438\u0445? +fuzzyNothingFound=\u041f\u043e \u0437\u0430\u043f\u0438\u0442\u0443 {0} \u043d\u0456\u0447\u043e\u0433\u043e \u043d\u0435 \u0437\u043d\u0430\u0439\u0434\u0435\u043d\u043e. +cmdPermsTooLow=\u0412\u0438 \u043d\u0435 \u043c\u0430\u0454\u0442\u0435 \u0434\u043e\u0437\u0432\u043e\u043b\u0443 \u0434\u043b\u044f \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u0430\u043d\u043d\u044f \u0446\u0456\u0454\u0457 \u043a\u043e\u043c\u0430\u043d\u0434\u0438\! \u0426\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u0430 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0454 {0}, \u0430\u043b\u0435 \u0443 \u0432\u0430\u0441 \u0442\u0456\u043b\u044c\u043a\u0438 {1}. +playersLimited=FredBoat \u0437\u0430\u0440\u0430\u0437 \u043f\u0435\u0440\u0435\u043d\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u0438\u0439\! \u0411\u043e\u0442 \u043d\u0430 \u0434\u0430\u043d\u0438\u0439 \u0447\u0430\u0441 \u043d\u0430\u043b\u0430\u0448\u0442\u043e\u0432\u0430\u043d\u0438\u0439 \u0434\u043b\u044f \u043f\u0440\u043e\u0433\u0440\u0430\u0432\u0430\u043d\u043d\u044f \u0434\u043e `{0}` \u0442\u0440\u0435\u043a\u0456\u0432, \u0456\u043d\u0430\u043a\u0448\u0435 \u043c\u0438 \u0440\u0438\u0437\u0438\u043a\u0443\u0454\u043c\u043e \u0432\u0456\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u0438\u0441\u044f \u0432\u0456\u0434 Discord \u043f\u0456\u0434 \u0434\u0456\u0454\u044e \u043f\u0435\u0440\u0435\u043d\u0430\u0432\u0430\u043d\u0442\u0430\u0436\u0435\u043d\u043d\u044f \u043c\u0435\u0440\u0435\u0436\u0456.\n\u042f\u043a\u0449\u043e \u0432\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u0434\u043e\u043f\u043e\u043c\u043e\u0433\u0442\u0438 \u043d\u0430\u043c \u043f\u0456\u0434\u0432\u0438\u0449\u0438\u0442\u0438 \u043b\u0456\u043c\u0456\u0442 \u0430\u0431\u043e \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0432\u0430\u0442\u0438 \u043d\u0435 \u043f\u0435\u0440\u0435\u043f\u043e\u0432\u043d\u0435\u043d\u043e\u0433\u043e \u0431\u043e\u0442\u0430, \u0442\u043e\u0434\u0456 \u043f\u0456\u0434\u0442\u0440\u0438\u043c\u0430\u0439\u0442\u0435 \u043d\u0430\u0448\u0443 \u0440\u043e\u0431\u043e\u0442\u0443 \u043d\u0430 Patreon\:\n{1}\n\n\u041f\u0440\u043e\u0431\u0430\u0447\u0430\u0454\u043c\u043e\u0441\u044f \u0437\u0430 \u043d\u0435\u0437\u0440\u0443\u0447\u043d\u043e\u0441\u0442\u0456\! \u0421\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u0456\u0437\u043d\u0456\u0448\u0435 \u0442\u043e\u043c\u0443, \u0449\u043e \u0434\u0430\u043d\u0435 \u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u043d\u044f \u0437''\u044f\u0432\u043b\u044f\u0454\u0442\u044c\u0441\u044f \u043b\u0438\u0448\u0435 \u0432 \u0433\u043e\u0434\u0438\u043d\u0443 \u043f\u0456\u043a. +tryLater=\u0411\u0443\u0434\u044c \u043b\u0430\u0441\u043a\u0430, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437 \u043f\u0456\u0437\u043d\u0456\u0448\u0435. skipUserSingle=\u041f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u0435 {0} \u0434\u043e\u0434\u0430\u043d\u043e {1}. -skipUserMultiple=Skipped {0} tracks added by {1}. -skipUsersMultiple=Skipped {0} tracks added by {1} users. -skipUserNoTracks=None of the mentioned users have any tracks queued. -voteSkipAdded=Your vote has been added\! -voteSkipRemoved=Your vote has been removed\! -voteSkipNotFound=You have not voted to skip this track\! -voteSkipAlreadyVoted=You already voted to skip this track\! -voteSkipSkipping={0} have voted to skip. Skipping track {1}. -voteSkipNotEnough={0} have voted to skip. At least {1} needed. -voteSkipEmbedNoVotes=No votes to skip this track yet. -voteSkipEmbedVoters={0} out of {1} have voted to skip the current track -mathOperationResult=The result is -mathOperationDivisionByZeroError=I cannot divide by zero. -mathOperationInfinity=The number is too big to be displayed\! -prefix=Prefix -prefixGuild=The prefix for this guild is {0} -prefixShowAgain=You can show the prefix anytime again by mentioning me. -moduleAdmin=Administration -moduleInfo=Information -moduleConfig=Configuration -moduleMusic=Music -moduleModeration=Moderation -moduleUtility=Utility -moduleFun=Fun -moduleLocked=The {0} module is locked and cannot be enabled/disabled. -moduleCantParse=No such module. Show a list of available modules with {0} -moduleStatus=Module Status -moduleDisable=Disabled module {0}. -moduleEnable=Enabled module {0}. -moduleShowCommands=Say {0} to see the commands of this module. -modulesCommands=Say {0} to show commands for a module, or {1} to show all commands. -modulesEnabledInGuild=Enabled modules for this guild\: -modulesHowTo=Say {0} to enable/disable modules. -parseNotAUser=Your input {0} did not yield a Discord user. -parseNotAMember=User {0} is not a member of this guild. -parseSnowflakeIdHelp=Having trouble getting the id of a user/message/channel/something else? Check out the Discord docs at {0} - +skipUserMultiple=\u041f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u0456 {0} \u0442\u0440\u0435\u043a\u0456\u0432 \u0434\u043e\u0434\u0430\u043d\u043e {1}. +skipUsersMultiple=\u041f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u0456 {0} \u0442\u0440\u0435\u043a\u0456\u0432 \u0434\u043e\u0434\u0430\u043d\u043e {1} \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430\u043c\u0438. +skipUserNoTracks=\u041d\u0456\u0445\u0442\u043e \u0437\u0456 \u0437\u0433\u0430\u0434\u0430\u043d\u0438\u0445 \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0456\u0432 \u043d\u0435 \u043c\u0430\u0454 \u0436\u043e\u0434\u043d\u0438\u0445 \u0442\u0440\u0435\u043a\u0456\u0432 \u0443 \u0447\u0435\u0440\u0437\u0456. +voteSkipAdded=\u0412\u0430\u0448 \u0433\u043e\u043b\u043e\u0441 \u0434\u043e\u0434\u0430\u043d\u043e\! +voteSkipRemoved=\u0412\u0430\u0448 \u0433\u043e\u043b\u043e\u0441 \u0431\u0443\u043b\u043e \u0432\u0438\u0434\u0430\u043b\u0435\u043d\u043e\! +voteSkipNotFound=\u0412\u0438 \u043d\u0435 \u043f\u0440\u043e\u0433\u043e\u043b\u043e\u0441\u0443\u0432\u0430\u043b\u0438, \u0449\u043e\u0431 \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u0438 \u0446\u0435\u0439 \u0442\u0440\u0435\u043a\! +voteSkipAlreadyVoted=\u0412\u0438 \u0432\u0436\u0435 \u0433\u043e\u043b\u043e\u0441\u0443\u0432\u0430\u043b\u0438, \u0449\u043e\u0431 \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u0438 \u0446\u0435\u0439 \u0442\u0440\u0435\u043a\! +voteSkipSkipping={0} \u043f\u0440\u043e\u0433\u043e\u043b\u043e\u0441\u0443\u0432\u0430\u043b\u0438 \u0437\u0430 \u0442\u0435, \u0449\u043e\u0431 \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u0438 \u0442\u0440\u0435\u043a. \u041f\u0440\u043e\u043f\u0443\u0441\u043a \u0442\u0440\u0435\u043a\u0443 {1}. +voteSkipNotEnough={0} \u043f\u0440\u043e\u043b\u043e\u0441\u0443\u0432\u0430\u043b\u0438 \u0437\u0430 \u0442\u0435, \u0449\u043e\u0431 \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u0438 \u0442\u0440\u0435\u043a. \u041f\u043e\u0442\u0440\u0456\u0431\u043d\u043e \u043f\u0440\u0438\u043d\u0430\u0439\u043c\u043d\u0456 {1}, \u0449\u043e\u0431 \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u0438 \u0442\u0440\u0435\u043a. +voteSkipEmbedNoVotes=\u0429\u0435 \u043d\u0435\u043c\u0430\u0454 \u0433\u043e\u043b\u043e\u0441\u0456\u0432, \u0449\u043e\u0431 \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u0438 \u0442\u0440\u0435\u043a \u0437\u0430\u0440\u0430\u0437. +voteSkipEmbedVoters={0} \u0437 {1} \u043f\u0440\u043e\u0433\u043e\u043b\u043e\u0441\u0443\u0432\u0430\u043b\u0438 \u0437\u0430 \u0442\u0435, \u0449\u043e\u0431 \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u0438 \u0434\u0430\u043d\u0438\u0439 \u0442\u0440\u0435\u043a +mathOperationResult=\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0454 +mathOperationDivisionByZeroError=\u042f \u043d\u0435 \u043c\u043e\u0436\u0443 \u043f\u043e\u0434\u0456\u043b\u0438\u0442\u0438 \u043d\u0430 \u043d\u0443\u043b\u044c. +mathOperationInfinity=\u0427\u0438\u0441\u043b\u043e \u0454 \u043d\u0430\u0434\u0442\u043e \u0432\u0435\u043b\u0438\u043a\u0438\u043c \u0434\u043b\u044f \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f\! +prefix=\u041f\u0440\u0435\u0444\u0456\u043a\u0441 +prefixGuild=\u041f\u0440\u0435\u0444\u0456\u043a\u0441 \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0454 {0} +prefixShowAgain=\u0412\u0438 \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u0438 \u043f\u0440\u0435\u0444\u0456\u043a\u0441 \u0431\u0443\u0434\u044c-\u043a\u043e\u043b\u0438, \u0437\u0433\u0430\u0434\u0443\u044e\u0447\u0438 \u043c\u0435\u043d\u0435. +moduleAdmin=\u0410\u0434\u043c\u0456\u043d\u0456\u0441\u0442\u0440\u0430\u0446\u0456\u044f +moduleInfo=\u0406\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0456\u044f +moduleConfig=\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f +moduleMusic=\u041c\u0443\u0437\u0438\u043a\u0430 +moduleModeration=\u041c\u043e\u0434\u0435\u0440\u0430\u0446\u0456\u044f +moduleUtility=\u0423\u0442\u0438\u043b\u0456\u0442\u0430 +moduleFun=\u0420\u043e\u0437\u0432\u0430\u0433\u0438 +moduleLocked=\u041c\u043e\u0434\u0443\u043b\u044c {0} \u0437\u0430\u0431\u043b\u043e\u043a\u043e\u0432\u0430\u043d\u043e, \u0456 \u043d\u0435 \u043c\u043e\u0436\u0435 \u0431\u0443\u0442\u0438 \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0438\u043c/\u0432\u0438\u043c\u043a\u043d\u0435\u043d\u0438\u043c. +moduleCantParse=\u041d\u0435\u043c\u0430\u0454 \u0442\u0430\u043a\u043e\u0433\u043e \u043c\u043e\u0434\u0443\u043b\u044f. \u041f\u043e\u043a\u0430\u0437\u0430\u0442\u0438 \u0441\u043f\u0438\u0441\u043e\u043a \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0438\u0445 \u043c\u043e\u0434\u0443\u043b\u0456\u0432 \u0437 {0} +moduleStatus=\u0421\u0442\u0430\u0442\u0443\u0441 \u043c\u043e\u0434\u0443\u043b\u044f +moduleDisable=\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u043e \u043c\u043e\u0434\u0443\u043b\u044c {0}. +moduleEnable=\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e \u043c\u043e\u0434\u0443\u043b\u044c {0}. +moduleShowCommands=\u041d\u0430\u043f\u0438\u0448\u0456\u0442\u044c {0}, \u0449\u043e\u0431 \u043f\u043e\u0431\u0430\u0447\u0438\u0442\u0438 \u043a\u043e\u043c\u0430\u043d\u0434\u0438 \u0446\u044c\u043e\u0433\u043e \u043c\u043e\u0434\u0443\u043b\u044f. +modulesCommands=\u041d\u0430\u043f\u0438\u0448\u0456\u0442\u044c {0}, \u0449\u043e\u0431 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u0438 \u043a\u043e\u043c\u0430\u043d\u0434\u0438 \u0446\u044c\u043e\u0433\u043e \u043c\u043e\u0434\u0443\u043b\u044f, \u0430\u0431\u043e {1}, \u0449\u043e\u0431 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u0438 \u0432\u0441\u0456 \u043a\u043e\u043c\u0430\u043d\u0434\u0438. +modulesEnabledInGuild=\u0423\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u0456 \u043c\u043e\u0434\u0443\u043b\u0456 \u0434\u043b\u044f \u0446\u044c\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430\: +modulesHowTo=\u041d\u0430\u043f\u0438\u0448\u0456\u0442\u044c {0}, \u0449\u043e\u0431 \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438/\u0432\u0438\u043c\u043a\u043d\u0443\u0442\u0438 \u043c\u043e\u0434\u0443\u043b\u044c. +parseNotAUser=\u0412\u0430\u0448 \u0432\u0445\u0456\u0434 {0} \u043d\u0435 \u0434\u0430\u0432 \u043d\u0456\u0447\u043e\u0433\u043e \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0443 Discord. +parseNotAMember=\u041a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447 {0} \u043d\u0435 \u0454 \u0447\u043b\u0435\u043d\u043e\u043c \u0446\u044c\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0443. +parseSnowflakeIdHelp=\u041d\u0435 \u0432\u0434\u0430\u0454\u0442\u044c\u0441\u044f \u0434\u0456\u0437\u043d\u0430\u0442\u0438\u0441\u044f \u0456\u0434\u0435\u043d\u0442\u0438\u0444\u0456\u043a\u0430\u0442\u043e\u0440 \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430/\u043f\u043e\u0432\u0456\u0434\u043e\u043c\u043b\u0435\u043d\u043d\u044f/\u043a\u0430\u043d\u0430\u043b\u0443/\u0447\u0438 \u0449\u0435 \u0447\u043e\u0433\u043e\u0441\u044c? \u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438 Discord \u0432 {0} diff --git a/FredBoat/src/main/resources/lang/vi_VN.properties b/FredBoat/src/main/resources/lang/vi_VN.properties index 9745ce39a..46c69beb8 100644 --- a/FredBoat/src/main/resources/lang/vi_VN.properties +++ b/FredBoat/src/main/resources/lang/vi_VN.properties @@ -7,6 +7,8 @@ playSearching=\u0110ang t\u00ecm ki\u1ebfm YouTube v\u1ec1 ''{q}''... playYoutubeSearchError=C\u00f3 l\u1ed7i khi t\u00ecm ki\u1ebfm YouTube. H\u00e3y xem x\u00e9t li\u00ean k\u1ebft tr\u1ef1c ti\u1ebfp \u0111\u1ebfn ngu\u1ed3n b\u00e0i h\u00e1t \u0111\u1ec3 thay th\u1ebf.\n ``` \n;;play ``` playSearchNoResults=Kh\u00f4ng c\u00f3 k\u1ebft qu\u1ea3 cho `{q}` playSelectVideo=**H\u00e3y ch\u1ecdn m\u1ed9t b\u00e0i h\u00e1t b\u1eb1ng l\u1ec7nh `{0}play s\u1ed1` command\:** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=\u0110ang tham gia {0} joinErrorAlreadyJoining=\u0110\u00e3 h\u1ea3y xa l\u1ed7i. Kh\u00f4ng th\u1ec3 tham gia {0} v\u00ec t\u00f4i \u0111ang c\u1ed1 g\u1eafng k\u1ebft n\u1ed1i v\u1edbi k\u00eanh \u0111\u00f3. Xin vui l\u00f2ng th\u1eed l\u1ea1i. pauseAlreadyPaused=M\u00e1y nghe nh\u1ea1c \u0111\u00e3 d\u1eebng r\u1ed3i. @@ -21,6 +23,9 @@ shuffleOn=M\u00e1y nghe nh\u1ea1c \u0111\u00e3 tr\u1ed9n c\u00e1c b\u00e0i h\u00 shuffleOff=M\u00e1y nghe nh\u1ea1c \u0111\u00e3 d\u1eebng tr\u1ed9n b\u00e0i h\u00e1t. reshufflePlaylist=H\u00e0ng ch\u1edd \u0111\u00e3 \u0111\u01b0\u1ee3c x\u00e1o tr\u1ed9n. reshufflePlayerNotShuffling=\u0110\u1ea7u ti\u00ean b\u1ea1n ph\u1ea3i b\u1eadt ch\u1ebf \u0111\u1ed9 ng\u1eabu nhi\u00ean. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=H\u00e0ng \u0111\u1ee3i \u0111ang tr\u1ed1ng\! skipOutOfBounds=Kh\u00f4ng th\u1ec3 lo\u1ea1i b\u1ecf b\u00e0i h\u00e1t s\u1ed1 {0} khi ch\u1ec9 c\u00f2n {1} b\u00e0i h\u00e1t. skipNumberTooLow=S\u1ed1 \u0111\u00e3 cho ph\u1ea3i l\u1edbn h\u01a1n 0. @@ -244,12 +249,14 @@ helpJoinCommand=L\u00e0m cho bot tham gia k\u00eanh tho\u1ea1i hi\u1ec7n t\u1ea1 helpLeaveCommand=L\u00e0m cho bot r\u1eddi kh\u1ecfi k\u00eanh tho\u1ea1i hi\u1ec7n t\u1ea1i. helpPauseCommand=T\u1ea1m d\u1eebng m\u00e1y nghe nh\u1ea1c. helpPlayCommand=Ch\u01a1i nh\u1ea1c t\u1eeb URL \u0111\u00e3 cho ho\u1eb7c t\u00ecm ki\u1ebfm m\u1ed9t b\u00e0i h\u00e1t. \u0110\u1ec3 c\u00f3 danh s\u00e1ch \u0111\u1ea7y \u0111\u1ee7 ngu\u1ed3n vui l\u00f2ng truy c\u1eadp {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=T\u00e1ch m\u1ed9t video Youtube th\u00e0nh m\u1ed9t danh s\u00e1ch b\u00e0i h\u00e1t \u0111\u01b0\u1ee3c cho \u1edf ph\u1ea7n mi\u00eau t\u1ea3. helpRepeatCommand=Chuy\u1ec3n \u0111\u1ed5i gi\u1eefa c\u00e1c ch\u1ebf \u0111\u1ed9 l\u1eb7p l\u1ea1i. helpReshuffleCommand=X\u00e1o tr\u1ed9n l\u1ea1i h\u00e0ng ch\u1edd hi\u1ec7n t\u1ea1i. helpSelectCommand=Ch\u1ecdn m\u1ed9t trong c\u00e1c b\u00e0i h\u00e1t \u0111\u01b0\u1ee3c y\u00eau c\u1ea7u sau khi t\u00ecm ki\u1ebfm \u0111\u1ec3 ch\u01a1i. helpShuffleCommand=B\u1eadt t\u1eaft ch\u1ebf \u0111\u1ed9 ng\u1eabu nhi\u00ean cho h\u00e0ng ch\u1edd hi\u1ec7n t\u1ea1i. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=B\u1ecf qua b\u00e0i h\u00e1t hi\u1ec7n t\u1ea1i, b\u00e0i h\u00e1t n'th trong h\u00e0ng ch\u1edd ho\u1eb7c t\u1ea5t c\u1ea3 b\u00e0i h\u00e1t t\u1eeb n t\u1edbi m. Xin vui l\u00f2ng s\u1eed d\u1ee5ng trong tr\u00ecnh qu\u1ea3n l\u00ed. helpStopCommand=D\u1eebng m\u00e1y nghe nh\u1ea1c v\u00e0 d\u1ecdn danh s\u00e1ch ph\u00e1t. D\u00e0nh ri\u00eang cho ng\u01b0\u1eddi ki\u1ec3m duy\u1ec7t v\u1edbi quy\u1ec1n qu\u1ea3n l\u00fd tin nh\u1eafn. helpUnpauseCommand=H\u1ee7y t\u1ea1m d\u1eebng m\u00e1y nghe nh\u1ea1c. @@ -339,4 +346,3 @@ modulesHowTo=N\u00f3i {0} \u0111\u1ec3 k\u00edch ho\u1ea1t/v\u00f4 hi\u1ec7u h\u parseNotAUser=\u0110\u1ea7u v\u00e0o {0} kh\u00f4ng ph\u1ea3i l\u00e0 m\u1ed9t ng\u01b0\u1eddi d\u00f9ng Discord. parseNotAMember=Ng\u01b0\u1eddi d\u00f9ng {0} kh\u00f4ng ph\u1ea3i l\u00e0 m\u1ed9t th\u00e0nh vi\u00ean c\u1ee7a guild n\u00e0y. parseSnowflakeIdHelp=B\u1ea1n g\u1eb7p s\u1ef1 c\u1ed1 khi l\u1ea5y id c\u1ee7a m\u1ed9t ng\u01b0\u1eddi d\u00f9ng/tin nh\u1eafn/k\u00eanh/th\u1ee9 g\u00ec \u0111\u00f3 kh\u00e1c? H\u00e3y ki\u1ec3m tra t\u00e0i li\u1ec7u Discord t\u1ea1i {0} - diff --git a/FredBoat/src/main/resources/lang/yo_NG.properties b/FredBoat/src/main/resources/lang/yo_NG.properties index f00808073..aacf73525 100644 --- a/FredBoat/src/main/resources/lang/yo_NG.properties +++ b/FredBoat/src/main/resources/lang/yo_NG.properties @@ -7,6 +7,8 @@ playSearching=Wiwa YouTube fun `{q}` ... playYoutubeSearchError=A\u1e63i\u1e63e kan \u1e63\u1eb9l\u1eb9 nigbati o wa YouTube. Wo n sop\u1ecd m\u1ecd taara si aw\u1ecdn orisun ohun dipo. '```; play ```' playSearchNoResults=Ko si aw\u1ecdn esi fun ''{q}` playSelectVideo=** J\u1ecdw\u1ecd yan orin kan p\u1eb9lu a\u1e63\u1eb9 `{0} t\u1eb9 1-5`\: ** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=Aj\u1ecdp\u1ecd {0} joinErrorAlreadyJoining=An error occurred. Couldn''t join {0} because I am already trying to connect to that channel. Please try again. pauseAlreadyPaused=The player is already paused. @@ -21,6 +23,9 @@ shuffleOn=The player is now shuffled. shuffleOff=The player is no longer shuffled. reshufflePlaylist=Queue reshuffled. reshufflePlayerNotShuffling=You must first turn on shuffle mode. +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=The queue is empty\! skipOutOfBounds=Can''t remove track number {0} when there are only {1} tracks. skipNumberTooLow=Given number must be greater than 0. @@ -244,12 +249,14 @@ helpJoinCommand=Make the bot join your current voice channel. helpLeaveCommand=Make the bot leave the current voice channel. helpPauseCommand=Pause the player. helpPlayCommand=Play music from the given URL or search for a track. For a full list of sources please visit {0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=Split a YouTube video into a tracklist provided in it's description. helpRepeatCommand=Toggle between repeat modes. helpReshuffleCommand=Reshuffle the current queue. helpSelectCommand=Select one of the offered tracks after a search to play. helpShuffleCommand=Toggle shuffle mode for the current queue. +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=Skip the current song, the n'th song in the queue, all songs from n to m, or all songs from mentioned users. Please use in moderation. helpStopCommand=Stop the player and clear the playlist. Reserved for moderators with Manage Messages permission. helpUnpauseCommand=Unpause the player. @@ -339,4 +346,3 @@ modulesHowTo=Say {0} to enable/disable modules. parseNotAUser=Your input {0} did not yield a Discord user. parseNotAMember=User {0} is not a member of this guild. parseSnowflakeIdHelp=Having trouble getting the id of a user/message/channel/something else? Check out the Discord docs at {0} - diff --git a/FredBoat/src/main/resources/lang/zh_CN.properties b/FredBoat/src/main/resources/lang/zh_CN.properties index a58856b09..62ecf2b31 100644 --- a/FredBoat/src/main/resources/lang/zh_CN.properties +++ b/FredBoat/src/main/resources/lang/zh_CN.properties @@ -7,6 +7,8 @@ playSearching=\u6b63\u5728 YouTube \u4e2d\u641c\u7d22 `{q}`\u2026\u2026 playYoutubeSearchError=\u641c\u7d22 YouTube \u65f6\u53d1\u751f\u9519\u8bef\uff0c\u60a8\u4e5f\u53ef\u4ee5\u4f7f\u7528\u5982\u4e0b\u8bed\u6cd5\u76f4\u63a5\u901a\u8fc7\u94fe\u63a5\u6765\u6dfb\u52a0\u6b4c\u66f2\uff1a\n```\n;;play <\u94fe\u63a5>``` playSearchNoResults=\u672a\u627e\u5230 `{q}` \u76f8\u5173\u7ed3\u679c playSelectVideo=**\u8bf7\u4f7f\u7528 `{0}play n` \u9009\u62e9\u66f2\u76ee** +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=\u6b63\u5728\u52a0\u5165 {0} joinErrorAlreadyJoining=\u9519\u8bef\uff1a\u52a0\u5165 {0} \u5931\u8d25\uff0c\u8bf7\u91cd\u8bd5\u3002 pauseAlreadyPaused=\u5df2\u6682\u505c\u64ad\u653e\u3002 @@ -21,6 +23,9 @@ shuffleOn=\u300c\u968f\u673a\u64ad\u653e\u300d\u6a21\u5f0f\u5df2\u6253\u5f00\u30 shuffleOff=\u300c\u968f\u673a\u64ad\u653e\u300d\u6a21\u5f0f\u5df2\u5173\u95ed\u3002 reshufflePlaylist=\u961f\u5217\u6539\u7ec4\u3002 reshufflePlayerNotShuffling=\u5fc5\u987b\u5148\u6253\u5f00 "\u65e0\u5e8f\u64ad\u653e" \u6a21\u5f0f\u3002 +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=\u5f53\u524d\u64ad\u653e\u5217\u8868\u4e3a\u7a7a\u3002 skipOutOfBounds=\u65e0\u6cd5\u79fb\u9664\u7b2c {0} \u9996\u6b4c\uff0c\u56e0\u4e3a\u5f53\u524d\u64ad\u653e\u5217\u8868\u53ea\u6709 {1} \u9996\u6b4c\u66f2\u3002 skipNumberTooLow=\u60a8\u8f93\u5165\u7684\u6570\u5b57\u5fc5\u987b\u5927\u4e8e 0\u3002 @@ -244,12 +249,14 @@ helpJoinCommand=\u8ba9Bot\u52a0\u5165\u60a8\u5f53\u524d\u7684\u9891\u9053\u3002 helpLeaveCommand=\u8ba9 bot \u79bb\u5f00\u5f53\u524d\u7684\u8bdd\u97f3\u4fe1\u9053\u3002 helpPauseCommand=\u6682\u505c\u64ad\u653e helpPlayCommand=\u4ece\u7ed9\u5b9a\u7684 URL \u64ad\u653e\u97f3\u4e50\u6216\u641c\u7d22\u66f2\u76ee\u3002\u5982\u6b32\u83b7\u5f97\u5b8c\u6574\u7684\u8d44\u6599, \u8bf7\u8bbf\u95ee{0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=Play music from the given URL or search for a track. Tracks are added to the top of the queue. For a full list of sources please visit {0} helpPlaySplitCommand=\u5c06 YouTube \u89c6\u9891\u5206\u5272\u6210\u4e00\u4e2a\u66f2\u76ee\u63d0\u4f9b\u7684\u63cf\u8ff0\u3002 helpRepeatCommand=\u5728\u91cd\u590d\u6a21\u5f0f\u4e4b\u95f4\u5207\u6362\u3002 helpReshuffleCommand=\u91cd\u65b0\u968f\u673a\u6392\u5217\u5f53\u524d\u66f2\u5e8f helpSelectCommand=\u9009\u62e9\u4e00\u4e2a\u63d0\u4f9b\u7684\u66f2\u76ee\u540e, \u641c\u7d22\u64ad\u653e\u3002 helpShuffleCommand=\u5207\u6362\u5f53\u524d\u961f\u5217\u7684\u65e0\u5e8f\u64ad\u653e\u6a21\u5f0f\u3002 +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=\u8df3\u8fc7\u5f53\u524d\u7684\u6b4c\u66f2, \u5728\u961f\u5217\u4e2d\u7684 n \u9996\u6b4c\u66f2, \u6240\u6709\u6b4c\u66f2\u4ece n \u5230 m, \u6216\u6240\u6709\u6b4c\u66f2\u4ece\u88ab\u63d0\u53ca\u7684\u7528\u6237\u3002\u8bf7\u9002\u5ea6\u4f7f\u7528\u3002 helpStopCommand=\u505c\u6b62\u64ad\u653e\u5668\u5e76\u6e05\u9664\u64ad\u653e\u5217\u8868\u3002\u4fdd\u7559\u7ed9\u5177\u6709\u7ba1\u7406\u6d88\u606f\u6743\u9650\u7684\u7248\u4e3b\u3002 helpUnpauseCommand=\u7ee7\u7eed\u64ad\u653e @@ -339,4 +346,3 @@ modulesHowTo=\u8bf4{0} \u542f\u7528/\u7981\u7528\u6a21\u5757\u3002 parseNotAUser=\u60a8\u7684\u8f93\u5165 {0} \u65e0\u6cd5\u53d6\u5f97Discord\u7684\u4f7f\u7528\u8005\u3002 parseNotAMember=\u4f7f\u7528\u8005 {0} \u4e0d\u662f\u8fd9\u7684\u56e2\u4f53\u7684\u6210\u5458\u3002 parseSnowflakeIdHelp=\u627e\u4e0d\u5230\u4f7f\u7528\u8005/\u6d88\u606f/\u9891\u9053/\u5176\u4ed6\u5185\u5bb9\u7684 id \u5417\uff1f\u77a7\u77a7 {0} \u7684Discord\u6863\u6848\u5427 - diff --git a/FredBoat/src/main/resources/lang/zh_TW.properties b/FredBoat/src/main/resources/lang/zh_TW.properties index 47a1355dc..b6704e0d7 100644 --- a/FredBoat/src/main/resources/lang/zh_TW.properties +++ b/FredBoat/src/main/resources/lang/zh_TW.properties @@ -7,6 +7,8 @@ playSearching=\u6b63\u5728\u5f9e Youtube \u4e2d\u641c\u5c0b `{q}`... playYoutubeSearchError=\u5728 Youtube \u641c\u5c0b\u6642\u767c\u751f\u932f\u8aa4\uff0c\u8acb\u8003\u616e\u76f4\u63a5\u653e\u8a72\u97f3\u6a02\u7684\u7db2\u5740\u3002\n```\n;;play ``` playSearchNoResults=\u627e\u4e0d\u5230\u8207 `{q}` \u76f8\u7b26\u7684\u7d50\u679c playSelectVideo=\u8acb\u4f7f\u7528 `{0}play n` \u9078\u64c7\u4e00\u500b\u66f2\u76ee +replayWillNowReplay=Track {0} will now be replayed. +replayHistoryEmpty=There is no track in history to replay. joinJoining=FredBoat\u266a\u266a \u5df2\u52a0\u5165 {0} joinErrorAlreadyJoining=\u767c\u751f\u932f\u8aa4\u3002\u7121\u6cd5\u52a0\u5165 {0} \u56e0\u70ba\u6211\u5df2\u7d93\u52a0\u5165\u90a3\u500b\u983b\u9053\uff0c\u8acb\u7a0d\u5f8c\u5728\u8a66\u3002 pauseAlreadyPaused=\u64ad\u653e\u5668\u5df2\u7d93\u66ab\u505c\u64ad\u653e\u4e86 @@ -21,6 +23,9 @@ shuffleOn=\u64ad\u653e\u5668\u5c07\u96a8\u6a5f\u64ad\u653e\u672c\u64ad\u653e\u52 shuffleOff=\u64ad\u653e\u5668\u5c07\u4e0d\u518d\u96a8\u6a5f\u64ad\u653e\u672c\u64ad\u653e\u5217\u8868 reshufflePlaylist=\u5c07\u96a8\u6a5f\u64ad\u653e\u64ad\u653e\u5e8f\u5217 reshufflePlayerNotShuffling=\u60a8\u5fc5\u9808\u5148\u6253\u958b\u91cd\u8907\u64ad\u653e\u6a21\u5f0f +roundRobinOn=The player is now in Fair Queue (Round-Robin) mode.\nWhat is Fair Queue? Do {0} for more information. +roundRobinOff=The player is no longer in Fair Queue (Round-Robin) mode. +listShowRoundRobin=Showing fair queue. skipEmpty=\u64ad\u653e\u5e8f\u5217\u662f\u7a7a\u7684\! skipOutOfBounds=\u7121\u6cd5\u522a\u9664\u7b2c {0} \u9996\u6b4c\u56e0\u70ba\u5e8f\u5217\u4e2d\u53ea\u6709 {1} \u9996\u6b4c skipNumberTooLow=\u6240\u63d0\u4f9b\u7684\u6578\u5b57\u5fc5\u9808\u5927\u65bc0 @@ -62,7 +67,7 @@ npDescription=\u8a73\u60c5 npLoadedSoundcloud=[{0}/{1}]\n\n\u5df2\u5f9e Soundcloud \u4e2d\u8f09\u5165 npLoadedBandcamp={0} \n\n\u5df2\u5f9e Bandcamp \u4e2d\u8f09\u5165 npLoadedTwitch=\u5df2\u5f9e Twitch \u4e2d\u8f09\u5165 -npRequestedBy=Requested by {0} +npRequestedBy=\u9ede\u6b4c\u8005\uff1a{0} permissionMissingBot=\u6211\u9700\u8981\u4e0b\u5217\u6b0a\u9650\u4f86\u57f7\u884c\u6b64\u64cd\u4f5c\: permissionMissingInvoker=\u4f60\u9700\u8981\u4e0b\u5217\u6b0a\u9650\u4f86\u57f7\u884c\u6b64\u64cd\u4f5c\: permissionEmbedLinks=\u5167\u5d4c\u9023\u7d50 @@ -244,12 +249,14 @@ helpJoinCommand=\u8b93 Bot \u52a0\u5165\u60a8\u76ee\u524d\u7684\u8a9e\u97f3\u983 helpLeaveCommand=\u8b93 Bot \u96e2\u958b\u60a8\u76ee\u524d\u7684\u8a9e\u97f3\u983b\u9053 helpPauseCommand=\u6682\u505c\u64ad\u653e helpPlayCommand=\u7528\u9023\u7d50\u64ad\u653e\u97f3\u6a02\u6216\u5229\u7528\u95dc\u9375\u5b57\u641c\u5c0b\u66f2\u76ee\u3002\u5982\u6b32\u7372\u5f97\u5b8c\u6574\u7684\u8cc7\u6599, \u8acb\u8a2a\u554f{0} +helpReplayCommand=Replay the most recent track from history. helpPlayNextCommand=\u7528\u9023\u7d50\u64ad\u653e\u97f3\u6a02\u6216\u5229\u7528\u95dc\u9375\u5b57\u641c\u5c0b\u66f2\u76ee\uff0c\u4e26\u653e\u7f6e\u5728\u7b2c\u4e00\u9806\u4f4d\u64ad\u653e\u3002\u5982\u6b32\u7372\u5f97\u5b8c\u6574\u7684\u8cc7\u6599, \u8acb\u8a2a\u554f {0} helpPlaySplitCommand=\u5c07\u5f71\u7247\u6839\u64daYoutube\u5b8c\u6574\u8cc7\u8a0a\u4e2d\u7684\u6642\u9593\u9ede\u5206\u5272\u6210\u4e0d\u540c\u66f2\u76ee\u3002 helpRepeatCommand=\u5207\u63db\u91cd\u8907\u64ad\u653e\u6a21\u5f0f\u3002 helpReshuffleCommand=\u96a8\u6a5f\u6392\u5217\u64ad\u653e\u5e8f\u5217 helpSelectCommand=\u8acb\u5f9e\u641c\u5c0b\u7d50\u679c\u9078\u4e00\u500b\u66f2\u76ee\u4f86\u64ad\u653e helpShuffleCommand=\u5207\u63db\u76ee\u524d\u5e8f\u5217\u7684\u96a8\u6a5f\u64ad\u653e\u6a21\u5f0f\u3002 +helpRoundRobinCommand=Toggle \u201dFair Queue\u201c (Round Robin) mode.\n\#In this mode the queue will automatically be sorted fairly, so that music will be heard from as many people as possible. helpSkipCommand=\u8df3\u904e\u7576\u524d\u7684\u6b4c\u66f2, \u6216\u5728\u4f47\u5217\u4e2d\u7684 n \u9996\u6b4c\u66f2, \u6216\u662f\u6e05\u55ae\u4e0a\u7531 n \u5230 m\u7684\u6b4c\u66f2, \u6216\u67d0\u4f7f\u7528\u8005\u63d0\u4f9b\u7684\u6240\u6709\u6b4c\u66f2\u3002\u8acb\u659f\u914c\u4f7f\u7528\u3002 helpStopCommand=\u505c\u6b62\u64ad\u653e\u5668\u4e26\u6e05\u9664\u64ad\u653e\u6e05\u55ae\u3002\u53ea\u6709\u7ba1\u7406\u8005\u6b0a\u9650\u624d\u80fd\u4f7f\u7528\u3002 helpUnpauseCommand=\u7e7c\u7e8c\u64ad\u653e\u3002 @@ -339,4 +346,3 @@ modulesHowTo=\u7528 {0} \u555f\u7528/\u7981\u7528\u6a21\u7d44\u3002 parseNotAUser=\u60a8\u8f38\u5165\u7684 {0} \u627e\u4e0d\u5230\u7b26\u5408\u7684Discord\u4f7f\u7528\u8005\u3002 parseNotAMember=\u4f7f\u7528\u8005 {0} \u4e0d\u662f\u9019\u7684\u7fa4\u7d44\u7684\u6210\u54e1\u3002 parseSnowflakeIdHelp=\u627e\u4e0d\u5230\u4f7f\u7528\u8005/\u6d88\u606f/\u983b\u9053/\u5176\u4ed6\u5167\u5bb9\u7684 id \u55ce\uff1f\u77a7\u77a7 {0} \u7684Discord\u6a94\u6848\u5427 - From 0ac8f9f3d68a64ed525f809021f664cbbd3eb1ce Mon Sep 17 00:00:00 2001 From: "Frederik Ar. Mikkelsen" Date: Sat, 22 Jun 2019 16:25:15 +0200 Subject: [PATCH 168/172] Fix connect loop regression --- .../src/main/java/fredboat/audio/lavalink/SentinelLink.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt index b4daf24a2..4fdfeb28c 100644 --- a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt +++ b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt @@ -86,8 +86,8 @@ class SentinelLink(val lavalink: SentinelLavalink, guildId: String) : Link(laval queueAudioConnect(vc.toLong()) } else { log.warn("{}: Got WS close code $code twice within $MIN_RETRY_INTERVAL ms, disconnecting " + - " to prevent bouncing and getting stuck...", guild) - queueAudioDisconnect() + "to prevent bouncing and getting stuck...", guild) + disconnect() } } } From 2d50c37283deac7ebeb6b2b15019148252c187ed Mon Sep 17 00:00:00 2001 From: "Frederik Ar. Mikkelsen" Date: Thu, 1 Aug 2019 13:05:02 +0200 Subject: [PATCH 169/172] Update build.gradle --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ee8ca02fc..5fac26eb0 100644 --- a/build.gradle +++ b/build.gradle @@ -64,7 +64,7 @@ subprojects { kotlinVersion = kotlinVersion //audio deps - lavaplayerVersion = '1.3.9' + lavaplayerVersion = '1.3.19' lavalinkVersion = '9f6c9c50' //utility deps From 90639f062872f4bf2318e214df99b776c0d02dfc Mon Sep 17 00:00:00 2001 From: "Frederik Ar. Mikkelsen" Date: Sun, 4 Aug 2019 13:08:42 +0200 Subject: [PATCH 170/172] Fix compile error --- .../audio/source/HttpSourceManager.java | 165 ------------------ .../AudioPlayerManagerConfiguration.java | 10 +- 2 files changed, 5 insertions(+), 170 deletions(-) delete mode 100644 FredBoat/src/main/java/fredboat/audio/source/HttpSourceManager.java diff --git a/FredBoat/src/main/java/fredboat/audio/source/HttpSourceManager.java b/FredBoat/src/main/java/fredboat/audio/source/HttpSourceManager.java deleted file mode 100644 index 10688a4a0..000000000 --- a/FredBoat/src/main/java/fredboat/audio/source/HttpSourceManager.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Frederik Ar. Mikkelsen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ - -package fredboat.audio.source; - -import com.sedmelluq.discord.lavaplayer.container.MediaContainerDetection; -import com.sedmelluq.discord.lavaplayer.container.MediaContainerDetectionResult; -import com.sedmelluq.discord.lavaplayer.container.MediaContainerHints; -import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager; -import com.sedmelluq.discord.lavaplayer.source.http.HttpAudioSourceManager; -import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; -import com.sedmelluq.discord.lavaplayer.tools.io.HttpClientTools; -import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterface; -import com.sedmelluq.discord.lavaplayer.tools.io.PersistentHttpStream; -import com.sedmelluq.discord.lavaplayer.track.AudioItem; -import com.sedmelluq.discord.lavaplayer.track.AudioReference; -import org.apache.commons.io.IOUtils; -import org.apache.http.HttpStatus; - -import java.io.IOException; -import java.io.StringWriter; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity.*; -import static com.sedmelluq.discord.lavaplayer.tools.io.HttpClientTools.getHeaderValue; - -/** - * Audio source manager which implements finding audio files from HTTP addresses (either direct links or embedded in html). - * This completely replaces HttpAudioSourceManager as it already has its function - */ -public class HttpSourceManager extends HttpAudioSourceManager { - - private static final Pattern PLAYLIST_PATTERN = Pattern.compile("]*href=\"([^\"]*\\.(?:m3u|pls))\""); - private static final Pattern CHARSET_PATTERN = Pattern.compile("\\bcharset=([^\\s;]+)\\b"); - - private MediaContainerDetectionResult checkHtmlResponse(AudioReference reference, PersistentHttpStream stream, MediaContainerHints hints) { - StringWriter writer = new StringWriter(); - Matcher mimeMatcher = CHARSET_PATTERN.matcher(hints.mimeType); - String charset = mimeMatcher.find() ? mimeMatcher.group(1) : null; - try { - //reset position to start of stream to get full html content - stream.seek(0L); - if(charset != null) - IOUtils.copy(stream, writer, charset); - else - IOUtils.copy(stream, writer, StandardCharsets.UTF_8); - } catch(IOException ex) { - throw new FriendlyException("Could not read HTML body", SUSPICIOUS, ex); - } - String htmlBody = writer.toString(); - Matcher matcher = PLAYLIST_PATTERN.matcher(htmlBody); - if(matcher.find()) { - return detectContainer(resolve(reference, matcher.group(1)), true); - } - return null; - } - - private AudioReference resolve(AudioReference original, String resolve) { - if(resolve.startsWith("http")) { - return new AudioReference(resolve, original.title); - } - try { - URL resolved = new URL(new URL(original.identifier), resolve); - return new AudioReference(resolved.toString(), original.title); - } catch(MalformedURLException e) { - throw new FriendlyException("Error resolving relative url of playlist link", SUSPICIOUS, e); - } - } - - /* - Following code is from Sedmelluq's LavaPlayer project (https://github.com/sedmelluq/lavaplayer) - and therefore under the Apache 2.0 License. - A copy of the License file is provided in "ThirdPartyLicenses/APACHE2" - - Changes: - - Added ignoreHtml boolean parameter to detectContainer and detectContainerWithClient - Other changes are surrounded with comments for clarification. - */ - @Override - public AudioItem loadItem(DefaultAudioPlayerManager manager, AudioReference reference) { - AudioReference httpReference = getAsHttpReference(reference); - if (httpReference == null) { - return null; - } - - return handleLoadResult(detectContainer(httpReference, false)); - } - - private AudioReference getAsHttpReference(AudioReference reference) { - if (reference.identifier.startsWith("https://") || reference.identifier.startsWith("http://")) { - return reference; - } else if (reference.identifier.startsWith("icy://")) { - return new AudioReference("http://" + reference.identifier.substring(6), reference.title); - } - return null; - } - - private MediaContainerDetectionResult detectContainer(AudioReference reference, boolean ignoreHtml) { - MediaContainerDetectionResult result; - - try (HttpInterface httpInterface = getHttpInterface()) { - result = detectContainerWithClient(httpInterface, reference, ignoreHtml); - } catch (IOException e) { - throw new FriendlyException("Connecting to the URL failed.", SUSPICIOUS, e); - } - - return result; - } - - private MediaContainerDetectionResult detectContainerWithClient(HttpInterface httpInterface, AudioReference reference, boolean ignoreHtml) throws IOException { - try (PersistentHttpStream inputStream = new PersistentHttpStream(httpInterface, new URI(reference.identifier), Long.MAX_VALUE)) { - int statusCode = inputStream.checkStatusCode(); - String redirectUrl = HttpClientTools.getRedirectLocation(reference.identifier, inputStream.getCurrentResponse()); - - if (redirectUrl != null) { - return new MediaContainerDetectionResult(null, new AudioReference(redirectUrl, null)); - } else if (statusCode == HttpStatus.SC_NOT_FOUND) { - return null; - } else if (!HttpClientTools.isSuccessWithContent(statusCode)) { - throw new FriendlyException("That URL is not playable.", COMMON, new IllegalStateException("Status code " + statusCode)); - } - - MediaContainerHints hints = MediaContainerHints.from(getHeaderValue(inputStream.getCurrentResponse(), "Content-Type"), null); - - /* START CUSTOM CHANGES */ - MediaContainerDetectionResult detection = MediaContainerDetection.detectContainer(reference, inputStream, hints); - if(!ignoreHtml && !detection.isReference() && !detection.isContainerDetected() && hints.mimeType.startsWith("text/html")) { - return checkHtmlResponse(reference, inputStream, hints); - } - return detection; - /* END CUSTOM CHANGES */ - - } catch (URISyntaxException e) { - throw new FriendlyException("Not a valid URL.", COMMON, e); - } - } -} \ No newline at end of file diff --git a/FredBoat/src/main/java/fredboat/config/AudioPlayerManagerConfiguration.java b/FredBoat/src/main/java/fredboat/config/AudioPlayerManagerConfiguration.java index 281be05c8..00eec7522 100644 --- a/FredBoat/src/main/java/fredboat/config/AudioPlayerManagerConfiguration.java +++ b/FredBoat/src/main/java/fredboat/config/AudioPlayerManagerConfiguration.java @@ -30,12 +30,12 @@ import com.sedmelluq.discord.lavaplayer.source.AudioSourceManager; import com.sedmelluq.discord.lavaplayer.source.bandcamp.BandcampAudioSourceManager; import com.sedmelluq.discord.lavaplayer.source.beam.BeamAudioSourceManager; +import com.sedmelluq.discord.lavaplayer.source.http.HttpAudioSourceManager; import com.sedmelluq.discord.lavaplayer.source.local.LocalAudioSourceManager; import com.sedmelluq.discord.lavaplayer.source.soundcloud.SoundCloudAudioSourceManager; import com.sedmelluq.discord.lavaplayer.source.twitch.TwitchStreamAudioSourceManager; import com.sedmelluq.discord.lavaplayer.source.vimeo.VimeoAudioSourceManager; import com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioSourceManager; -import fredboat.audio.source.HttpSourceManager; import fredboat.audio.source.PlaylistImportSourceManager; import fredboat.audio.source.SpotifyPlaylistSourceManager; import fredboat.config.property.AppConfig; @@ -129,7 +129,7 @@ public ArrayList getConfiguredAudioSourceManagers(AudioSourc BeamAudioSourceManager beamAudioSourceManager, SpotifyPlaylistSourceManager spotifyPlaylistSourceManager, LocalAudioSourceManager localAudioSourceManager, - HttpSourceManager httpSourceManager) { + HttpAudioSourceManager httpAudioSourceManager) { ArrayList audioSourceManagers = new ArrayList<>(); if (audioSourcesConfig.isYouTubeEnabled()) { @@ -159,7 +159,7 @@ public ArrayList getConfiguredAudioSourceManagers(AudioSourc if (audioSourcesConfig.isHttpEnabled()) { //add new source managers above the HttpAudio one, because it will either eat your request or throw an exception //so you will never reach a source manager below it - audioSourceManagers.add(httpSourceManager); + audioSourceManagers.add(httpAudioSourceManager); } return audioSourceManagers; } @@ -250,7 +250,7 @@ public LocalAudioSourceManager localAudioSourceManager() { @Bean(destroyMethod = "") @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) - public HttpSourceManager httpSourceManager() { - return new HttpSourceManager(); + public HttpAudioSourceManager httpSourceManager() { + return new HttpAudioSourceManager(); } } From efbb34417481a9a0eeb6bf46d966ac31c4eeb845 Mon Sep 17 00:00:00 2001 From: "Frederik Ar. Mikkelsen" Date: Sun, 4 Aug 2019 13:11:05 +0200 Subject: [PATCH 171/172] Revert "Fix connect loop regression" This reverts commit 0ac8f9f3d68a64ed525f809021f664cbbd3eb1ce. --- .../src/main/java/fredboat/audio/lavalink/SentinelLink.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt index 4fdfeb28c..b4daf24a2 100644 --- a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt +++ b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt @@ -86,8 +86,8 @@ class SentinelLink(val lavalink: SentinelLavalink, guildId: String) : Link(laval queueAudioConnect(vc.toLong()) } else { log.warn("{}: Got WS close code $code twice within $MIN_RETRY_INTERVAL ms, disconnecting " + - "to prevent bouncing and getting stuck...", guild) - disconnect() + " to prevent bouncing and getting stuck...", guild) + queueAudioDisconnect() } } } From 9cb734339844b672e07efab4d47ada81705746cb Mon Sep 17 00:00:00 2001 From: "Frederik Ar. Mikkelsen" Date: Sun, 4 Aug 2019 13:23:17 +0200 Subject: [PATCH 172/172] Revert "Revert "Fix connect loop regression"" This reverts commit efbb34417481a9a0eeb6bf46d966ac31c4eeb845. --- .../src/main/java/fredboat/audio/lavalink/SentinelLink.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt index b4daf24a2..4fdfeb28c 100644 --- a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt +++ b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt @@ -86,8 +86,8 @@ class SentinelLink(val lavalink: SentinelLavalink, guildId: String) : Link(laval queueAudioConnect(vc.toLong()) } else { log.warn("{}: Got WS close code $code twice within $MIN_RETRY_INTERVAL ms, disconnecting " + - " to prevent bouncing and getting stuck...", guild) - queueAudioDisconnect() + "to prevent bouncing and getting stuck...", guild) + disconnect() } } }