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 diff --git a/FredBoat/build.gradle b/FredBoat/build.gradle index 3aa401ddd..e50bf070b 100644 --- a/FredBoat/build.gradle +++ b/FredBoat/build.gradle @@ -49,10 +49,13 @@ 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 - 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 @@ -72,6 +75,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:3.1.6.RELEASE' optional group: 'org.springframework.boot', name: 'spring-boot-configuration-processor', version: springBootVersion /* Explicitly set these, instead of getting outdated transitive ones */ diff --git a/FredBoat/src/main/java/fredboat/agent/GuildCacheInvalidationAgent.kt b/FredBoat/src/main/java/fredboat/agent/GuildCacheInvalidationAgent.kt index 58ebce762..b44820e4c 100644 --- a/FredBoat/src/main/java/fredboat/agent/GuildCacheInvalidationAgent.kt +++ b/FredBoat/src/main/java/fredboat/agent/GuildCacheInvalidationAgent.kt @@ -3,18 +3,23 @@ package fredboat.agent import com.fredboat.sentinel.entities.GuildUnsubscribeRequest import fredboat.audio.lavalink.SentinelLavalink 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 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 playerRepository: PlayerRepository, private val lavalink: SentinelLavalink ) : FredBoatAgent("cache-invalidator", 5, TimeUnit.MINUTES) { @@ -59,14 +64,19 @@ class GuildCacheInvalidationAgent( } fun invalidateGuild(guild: InternalGuild) { - try { - playerRegistry.destroyPlayer(guild) - lavalink.getExistingLink(guild)?.destroy() - } catch (e: Exception) { - log.error("Got exception when invaliding GuildPlayer and Link for {}", guild) + val mono = playerRegistry.getExisting(guild)?.let { + playerRepository.convertAndSave(it).timeout(Duration.ofSeconds(60)) + } ?: Mono.empty() + mono.subscribe { + try { + playerRegistry.destroyPlayer(guild) + lavalink.getExistingLink(guild)?.destroy() + } catch (e: Exception) { + log.error("Got exception when invaliding GuildPlayer and Link for {}", guild) + } + guild.sentinel.sendAndForget(guild.routingKey, GuildUnsubscribeRequest(guild.id)) + guildCache.cache.remove(guild.id) } - guild.sentinel.sendAndForget(guild.routingKey, GuildUnsubscribeRequest(guild.id)) - guildCache.cache.remove(guild.id) } } \ No newline at end of file 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/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/audio/lavalink/SentinelLavalink.kt b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt index 2e2e95ef0..0b59b810a 100644 --- a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt +++ b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLavalink.kt @@ -1,39 +1,77 @@ 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.db.mongo.ActivityMetricsController 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 +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, + val activityMetrics: ActivityMetricsController, + val userSessionHandler: UserSessionHandler, lavalinkConfig: LavalinkConfig ) : Lavalink( sentinel.selfUser.idString, appConfig.shardCount ) { + 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 { - @Suppress("LeakingThis") INSTANCE = this - lavalinkConfig.nodes.forEach { addNode(it.name, it.uri, it.password) } - @Suppress("LeakingThis") + setHoldEvents(true) + lavalinkConfig.nodes.forEach { + addNode(it.name, it.uri, it.password, resumeKey) + .setResuming(resumeKey, DEFAULT_RESUME_TIMEOUT) + } LavalinkCollector(this).register() } - override fun buildNewLink(guildId: String) = SentinelLink(this, guildId) + override fun buildNewLink(guildId: String): SentinelLink { + 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@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) + } fun getLink(guild: Guild) = getLink(guild.id.toString()) fun getExistingLink(guild: Guild) = getExistingLink(guild.idString) diff --git a/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt b/FredBoat/src/main/java/fredboat/audio/lavalink/SentinelLink.kt index 68bae2875..4fdfeb28c 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 @@ -59,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) @@ -75,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() } } } 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 46562ebbe..000000000 --- a/FredBoat/src/main/java/fredboat/audio/player/AbstractPlayer.kt +++ /dev/null @@ -1,286 +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.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 -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.LavalinkPlayer -import lavalink.client.player.event.AudioEventAdapterWrapped -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 -) : AudioEventAdapterWrapped() { - - val player: LavalinkPlayer = lavalink.getLink(guild.id.toString()).player - protected var context: 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 && context == null) { - audioTrackProvider.peek() - } else context - } - - //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 { - player.addListener(this) - } - - fun play() { - log.trace("play()") - - if (player.isPaused) { - player.isPaused = false - } - if (player.playingTrack == null) { - 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()") - - context = 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: AudioPlayer?, 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() { - val lastTrack = lastLoadedTrack - if (lastTrack == null) { - log.debug("No lastLoadedTrack in $this after track end") - return - } - if (historyQueue.size == MAX_HISTORY_SIZE) { - historyQueue.poll() - } - historyQueue.add(lastTrack) - } - - /** - * 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) - - context = 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: AudioPlayer?, track: AudioTrack, exception: FriendlyException?) { - log.error("Lavaplayer encountered an exception while playing {}", - track.identifier, exception) - } - - override fun onTrackStuck(player: AudioPlayer?, track: AudioTrack, thresholdMs: Long) { - log.error("Lavaplayer got stuck while playing {}", - track.identifier) - } - - fun seekTo(position: Long) { - if (context!!.track.isSeekable) { - player.seekTo(position) - } else { - throw MessagingException(context!!.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 03f399885..d6b7ac5db 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/GuildPlayer.kt @@ -25,87 +25,70 @@ 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 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.* +import fredboat.audio.queue.limiter.QueueLimitStatus +import fredboat.audio.queue.limiter.QueueLimiter +import fredboat.audio.queue.handlers.* 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.db.api.GuildSettingsRepository 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.InternalGuild import fredboat.sentinel.TextChannel -import fredboat.sentinel.VoiceChannel +import fredboat.util.TextUtils import fredboat.util.extension.escapeAndDefuse import fredboat.util.ratelimit.Ratelimiter import fredboat.util.rest.YoutubeAPI -import lavalink.client.io.Link.State.CONNECTED -import org.apache.commons.lang3.tuple.ImmutablePair -import org.apache.commons.lang3.tuple.Pair +import fredboat.ws.emptyPlayerInfo +import fredboat.ws.toPlayerInfo +import lavalink.client.player.IPlayer +import lavalink.client.player.LavalinkPlayer +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 -import kotlin.streams.toList +import javax.annotation.CheckReturnValue 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, - private val guildConfigService: GuildConfigService, + private val guildSettingsRepository: GuildSettingsRepository, ratelimiter: Ratelimiter, youtubeAPI: YoutubeAPI -) : AbstractPlayer(lavalink, SimpleTrackProvider(), guild) { +) : PlayerEventListenerAdapter() { - private val audioLoader: AudioLoader + val queueHandler: IQueueHandler = RepeatableQueueHandler(this) + private val audioLoader = AudioLoader(ratelimiter, queueHandler, 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 + + private var onPlayHook: Consumer? = null + private var onErrorHook: Consumer? = null + @Volatile + private var lastLoadedTrack: AudioTrackContext? = null + 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) context 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) context 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. * @@ -117,59 +100,99 @@ 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 + 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 isRoundRobin : 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() = audioTrackProvider is AbstractTrackProvider && audioTrackProvider.isShuffle - set(shuffle) = if (audioTrackProvider is AbstractTrackProvider) { - audioTrackProvider.isShuffle = shuffle - context?.isPriority = false + 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 { + if (guild.selfPresent) { + return guildSettingsRepository.fetch(guild.id).map { it.trackAnnounce } + } + + return Mono.just(false) + } + + val playingTrack: AudioTrackContext? + get() { + return if (player.playingTrack == null && internalContext == null) { + queueHandler.peek() + } else internalContext } - private val isTrackAnnounceEnabled: Boolean + //the unshuffled playlist + //Includes currently playing track, which comes first + val remainingTracks: List get() { - var enabled = false - try { - if (guild.selfPresent) { - enabled = guildConfigService.fetchGuildConfig(guild.id).isTrackAnnounce - } - } catch (ignored: Exception) { + log.trace("getRemainingTracks()") + val list = ArrayList() + val atc = playingTrack + if (atc != null) { + list.add(atc) } - return enabled + list.addAll(queueHandler.queue.toList()) + 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) + linkPostProcess() } 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 + + getTrackAnnounceStatus().subscribe { + if (it && !isPaused && repeatMode != RepeatMode.SINGLE) { + activeTextChannel?.send(atc.i18nFormat( + "trackAnnounce", + atc.effectiveTitle.escapeAndDefuse(), + atc.member.effectiveName.escapeAndDefuse() + ))?.subscribe() + } } } @@ -181,73 +204,7 @@ 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)) - } - - val link = lavalink.getLink(guild) - - if (link.state == CONNECTED && currentVoiceChannel?.members?.contains(guild.selfMember) == false) { - log.warn("Link is ${link.state} but we are not in its channel. Assuming our session expired...") - link.onDisconnected() - } - - try { - link.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, isPriority: Boolean = false) { + fun queueAsync(identifier: String, context: CommandContext, isPriority: Boolean = false) { val ic = IdentifierContext(identifier, context.textChannel, context.member) ic.isPriority = isPriority @@ -256,13 +213,13 @@ class GuildPlayer( audioLoader.loadAsync(ic) } - fun queue(ic: IdentifierContext) { + fun queueAsync(ic: IdentifierContext) { joinChannel(ic.member) 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) @@ -270,110 +227,54 @@ class GuildPlayer( joinChannel(member) } - if (isPriority) audioTrackProvider.addFirst(atc) else audioTrackProvider.add(atc) - play() + queueHandler.add(atc) + if (isPlaying) updateClients() } - //add a bunch of tracks to the track provider - fun loadAll(tracks: Collection) { - audioTrackProvider.addAll(tracks) + /** Add a bunch of tracks to the track provider */ + fun queueAll(tracks: Collection) { + queueHandler.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 + @CheckReturnValue + suspend fun queueLimited(atc: AudioTrackContext): QueueLimitStatus { + val status = queueLimiter.isQueueLimited(atc, this) - if (player.playingTrack != null) { - if (start_ <= 0) { - result.add(context!!) - 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 + // only queue if track was not limited + if (status.canQueue) { + queue(atc) } - result.addAll(audioTrackProvider.getTracksInRange(start_, end_)) - return result + return status } - /** Similar to [getTracksInRange], but only gets the trackIds */ - fun getTrackIdsInRange(start: Int, end: Int): List = getTracksInRange(start, end).stream() - .map { it.trackId } - .toList() + suspend fun queueLimited(tracks: List): List { + val states = queueLimiter.isQueueLimited(tracks, this) - fun getHumanUsersInVC(vc: VoiceChannel?): List { - vc ?: return emptyList() - return vc.members.stream() - .filter { !it.isBot } - .toList() + queueAll(states.filter { it.canQueue }.map { it.atc }) + return states } + override fun toString(): String { return "[GP:$guildId]" } fun reshuffle() { - if (audioTrackProvider is AbstractTrackProvider) { - audioTrackProvider.reshuffle() - context?.isPriority = false - } else { - throw UnsupportedOperationException("Can't reshuffle " + audioTrackProvider.javaClass) - } - } - - //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) + if (queueHandler is IShufflableQueueHandler) { + queueHandler.reshuffle() + internalContext?.isPriority = false + updateClients() } else { - context.replyWithName(pair.right) + throw UnsupportedOperationException("Can't reshuffle " + queueHandler.javaClass) } } - fun skipTracks(trackIds: Collection) { + fun skipTracks(trackIds: Collection) { var skipCurrentTrack = false - val toRemove = ArrayList() - val playing = if (player.playingTrack != null) context else null + val toRemove = ArrayList() + 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 @@ -383,23 +284,197 @@ class GuildPlayer( } } - audioTrackProvider.removeAllById(toRemove) - + if (toRemove.size > 0) queueHandler.removeById(toRemove) if (skipCurrentTrack) skip() } - override fun onTrackStart(player: AudioPlayer?, track: AudioTrack?) { + override fun onTrackStart(player: IPlayer?, track: AudioTrack?) { voteSkipCleanup() super.onTrackStart(player, track) } - override fun destroy() { - audioTrackProvider.clear() - super.destroy() + fun destroy() { + queueHandler.clear() + stop() + player.removeListener(this) + player.link.destroy() log.info("Player for $guildId was destroyed.") + lavalink.userSessionHandler.sendLazy(guildId) { emptyPlayerInfo } } 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) + } + updateClients() + } + + 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() + } + updateClients() + } + + fun pause() = setPause(true) + + /** + * Clear the tracklist and stop the current track + */ + fun stop() { + log.trace("stop()") + + queueHandler.clear() + stopTrack() + } + + /** + * Skip the current track + */ + fun skip() { + log.trace("skip()") + + queueHandler.onSkipped() + stopTrack() + } + + /** + * Stop the current track. + */ + fun stopTrack() { + log.trace("stopTrack()") + + internalContext = null + player.stopTrack() + } + + 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...")) + queueHandler.onSkipped() + 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 = queueHandler.take() + lastLoadedTrack = atc + atc?.let { playTrack(it) } + updateClients() + } + + private fun updateHistoryQueue() { + 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(lastTrack) + } + + /** + * 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) + updateClients() + } else { + throw MessagingException(internalContext!!.i18n("seekDeniedLiveTrack")) + } + } + + 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/audio/player/PlayerRegistry.kt b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt index 8f0434db7..f78c81bd3 100644 --- a/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt +++ b/FredBoat/src/main/java/fredboat/audio/player/PlayerRegistry.kt @@ -27,33 +27,55 @@ package fredboat.audio.player import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager import fredboat.audio.lavalink.SentinelLavalink -import fredboat.db.api.GuildConfigService +import fredboat.audio.queue.AudioTrackContext +import fredboat.audio.queue.SplitAudioTrackContext +import fredboat.config.property.AppConfig +import fredboat.db.api.GuildSettingsRepository +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 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 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.time.Duration import java.util.concurrent.ConcurrentHashMap import java.util.function.BiConsumer +import kotlin.concurrent.thread 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 guildSettingsRepository: GuildSettingsRepository, + 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 private val log: Logger = LoggerFactory.getLogger(PlayerRegistry::class.java) } 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 @@ -67,16 +89,24 @@ class PlayerRegistry(private val musicTextChannelProvider: MusicTextChannelProvi .toList() } - fun getOrCreate(guild: Guild): GuildPlayer { - return registry.computeIfAbsent( - guild.id) { - val p = GuildPlayer(lavalink, guild, musicTextChannelProvider, audioPlayerManager, guildConfigService, - ratelimiter, youtubeAPI) - p.volume = DEFAULT_VOLUME - p - } + init { + Runtime.getRuntime().addShutdownHook( + thread(start = false, name = "player-registry-shutdown") { beforeShutdown() } + ) + @Suppress("LeakingThis") + sentinelLavalink.playerRegistry = this + } + + 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): GuildPlayer = getOrCreate(guild).awaitFirst() + fun getExisting(guild: Guild): GuildPlayer? { return getExisting(guild.id) } @@ -117,4 +147,97 @@ class PlayerRegistry(private val musicTextChannelProvider: MusicTextChannelProvi .count() } } + + /** + * @return a [Mono] with a fully loaded [GuildPlayer] + */ + @Suppress("RedundantLambdaArrow") + 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 { + // 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 + val playerDeferred: Mono = Mono.fromSupplier { + GuildPlayer( + sentinelLavalink, + guild, + musicTextChannelProvider, + audioPlayerManager, + guildSettingsRepository, + ratelimiter, + youtubeAPI + ) + } + + 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() + } + + /** + * 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.isRoundRobin = mongo.roundRobin + player.repeatMode = RepeatMode.values()[mongo.repeat.toInt()] + + if (appConfig.distribution.volumeSupported()) { + player.volume = mongo.volume + } + + var 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 + } + } + + log.info("Restoring ${queue.size} loaded tracks for $guild") + + if (queue.isNotEmpty()) { + queue = queue.toMutableList() + val atc = queue.removeAt(0) + player.player.playTrack(atc.track, true) + player.internalContext = atc + // Optionally set current track position + if (mongo.position != null) atc.track.position = mongo.position + } + + val channel = mongo.textChannel?.let { guild.getTextChannel(it) } + if (channel != null) musicTextChannelProvider.setMusicChannel(channel) + + player.queueAll(queue) // no Priority on reload + } + + private fun beforeShutdown() { + 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/audio/player/guildPlayerUtils.kt b/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt new file mode 100644 index 000000000..d934c20c5 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/player/guildPlayerUtils.kt @@ -0,0 +1,231 @@ +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.handlers.IQueueHandler +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 = queueHandler.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 = queueHandler.totalDuration + + 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 = queueHandler.streamCount.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() = voiceChannel.getHumanUsersInVC() + +val GuildPlayer.isQueueEmpty: Boolean + get() { + log.trace("isQueueEmpty()") + + return player.playingTrack == null && queueHandler.isEmpty + } + +val GuildPlayer.trackCountInHistory: Int + get() = historyQueue.size + +val GuildPlayer.isHistoryQueueEmpty: Boolean + get() = historyQueue.isEmpty() + +fun GuildPlayer.getUserTrackCount(userId: Long): Int { + var trackCount = queueHandler.queue.filter { it.userId == userId }.size + if (player.playingTrack != null && internalContext?.userId == userId) trackCount++ + return trackCount +} + +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)) + } + + val link = lavalink.getLink(guild) + + if (link.state == ShardStatus.CONNECTED && guild.selfMember.voiceChannel?.members?.contains(guild.selfMember) == false) { + log.warn("Link is ${link.state} but we are not in its channel. Assuming our session expired...") + link.onDisconnected() + } + + try { + link.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((queueHandler as IQueueHandler).getInRange(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 VoiceChannel?.getHumanUsersInVC(): List { + this ?: return emptyList() + return this.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 && queueHandler.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/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 f4aaa5c56..5a51be130 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/AudioTrackContext.kt @@ -25,26 +25,48 @@ 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.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, val member: Member, priority: Boolean = false) : Comparable { - val added: Long = System.currentTimeMillis() - var rand: Int = 0 +open class AudioTrackContext( + val track: AudioTrack, + val member: Member, + priority: Boolean = false +) : 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 trackId: Long //used to identify this track even when the track gets cloned and the rand reranded + val added: Long = System.currentTimeMillis() - val userId: Long - get() = member.id + val guild: Guild + get() = member.guild + + val user: User + get() = member.user val guildId: Long get() = member.guild.id + val userId: Long + get() = member.id + open val effectiveDuration: Long get() = track.duration @@ -61,9 +83,16 @@ open class AudioTrackContext(val track: AudioTrack, val member: Member, priority 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 = ThreadLocalRandom.current().nextLong(java.lang.Long.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. @@ -81,6 +110,55 @@ open class AudioTrackContext(val track: AudioTrack, val member: Member, priority 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) } @@ -103,12 +181,7 @@ open class AudioTrackContext(val track: AudioTrack, val member: Member, priority 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 + companion object { + private val log = LoggerFactory.getLogger(AudioTrackContext::class.java) } - - } diff --git a/FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt b/FredBoat/src/main/java/fredboat/audio/queue/ITrackProvider.kt index 1c12306f1..fd2ca6585 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 { /** @@ -67,6 +69,11 @@ interface ITrackProvider { */ fun size(): Int + /** + * @return amount of tracks in the queue filtered by userId + */ + fun getCountByUser(userId: Long): Int + /** * @param track add a track to the queue */ @@ -77,20 +84,6 @@ interface ITrackProvider { */ 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) - /** * empty the queue */ @@ -112,7 +105,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 @@ -140,6 +133,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 aca8ad96d..0129fd42f 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.ConcurrentLinkedDeque @@ -133,7 +134,7 @@ class SimpleTrackProvider : AbstractTrackProvider() { } } - override fun removeAllById(trackIds: Collection) { + override fun removeAllById(trackIds: Collection) { queue.removeIf { audioTrackContext -> trackIds.contains(audioTrackContext.trackId) } shouldUpdateShuffledQueue = true } @@ -186,27 +187,23 @@ class SimpleTrackProvider : AbstractTrackProvider() { return queue.size } - override fun add(track: AudioTrackContext) { - shouldUpdateShuffledQueue = true - queue.add(track) + override fun getCountByUser(userId: Long): Int { + return queue.count { it.userId == userId } } - override fun addAll(tracks: Collection) { + override fun add(track: AudioTrackContext) { shouldUpdateShuffledQueue = true - queue.addAll(tracks) - } - override fun addFirst(track: AudioTrackContext) { - shouldUpdateShuffledQueue = true - track.rand = Integer.MIN_VALUE - queue.addFirst(track) + if (!track.isPriority) + queue.add(track) + else + queue.addFirst(track) } - override fun addAllFirst(tracks: Collection) { + override fun addAll(tracks: Collection) { shouldUpdateShuffledQueue = true - tracks.reversed().forEach { - it.rand = Integer.MIN_VALUE - queue.addFirst(it) } + + if (tracks.all { it.isPriority }) queue.addAllFirst(tracks) else queue.addAll(tracks) } override fun clear() { @@ -244,7 +241,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 @@ -253,3 +250,8 @@ class SimpleTrackProvider : AbstractTrackProvider() { return true } } + +fun ConcurrentLinkedDeque.addAllFirst(tracks: Collection) { + tracks.reversed().forEach { addFirst(it) } +} + diff --git a/FredBoat/src/main/java/fredboat/audio/queue/SplitAudioTrackContext.kt b/FredBoat/src/main/java/fredboat/audio/queue/SplitAudioTrackContext.kt index 870ffcdba..916d547db 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/SplitAudioTrackContext.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/SplitAudioTrackContext.kt @@ -34,9 +34,9 @@ class SplitAudioTrackContext( at: AudioTrack, member: Member, override val startPosition: Long, - private val endPosition: 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/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 { diff --git a/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt b/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt index 4e322425c..49d10415d 100644 --- a/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt +++ b/FredBoat/src/main/java/fredboat/audio/queue/audioLoading.kt @@ -32,6 +32,12 @@ 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.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.handlers.IQueueHandler import fredboat.audio.source.PlaylistImportSourceManager import fredboat.audio.source.PlaylistImporter import fredboat.audio.source.SpotifyPlaylistSourceManager @@ -41,16 +47,17 @@ 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 import java.util.regex.Pattern -class AudioLoader(private val ratelimiter: Ratelimiter, internal val trackProvider: ITrackProvider, - private val playerManager: AudioPlayerManager, internal val gplayer: GuildPlayer, +class AudioLoader(private val ratelimiter: Ratelimiter, internal val queueHandler: IQueueHandler, + private val playerManager: AudioPlayerManager, internal val player: GuildPlayer, internal val youtubeAPI: YoutubeAPI) { private val identifierQueue = ConcurrentLinkedQueue() @Volatile @@ -80,7 +87,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 @@ -175,10 +182,6 @@ class AudioLoader(private val ratelimiter: Ratelimiter, internal val trackProvid 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,25 +195,20 @@ private class ResultHandler(val loader: AudioLoader, val context: IdentifierCont if (context.isSplit) { loadSplit(at, context) } else { - - if (!context.isQuiet) { - context.reply(if (loader.gplayer.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) - if (context.isPriority) loader.trackProvider.addFirst(atc) else loader.trackProvider.add(atc) - - if (!loader.gplayer.isPaused) { - loader.gplayer.play() + GlobalScope.mono { loader.player.queueLimited(atc) }.subscribe { + if (it.canQueue) { + 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) + } } } } catch (th: Throwable) { @@ -229,14 +227,28 @@ 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)) } - if (context.isPriority) loader.trackProvider.addAllFirst(toAdd) else loader.trackProvider.addAll(toAdd) - context.reply(context.i18nFormat("loadListSuccess", ap.tracks.size, ap.name)) - if (!loader.gplayer.isPaused) { - loader.gplayer.play() + + GlobalScope.mono { loader.player.queueLimited(toAdd) }.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: 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()) { + loader.player.play() + } } } catch (th: Throwable) { loader.handleThrowable(context, th) @@ -312,25 +324,30 @@ 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) } - 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) }.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") + } + + //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()) + context.reply(mb.build()) + + if (it.any { status -> status.canQueue }) { + loader.player.play() + } + } } } diff --git a/FredBoat/src/main/java/fredboat/audio/queue/handlers/IQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/handlers/IQueueHandler.kt new file mode 100644 index 000000000..0c11ab875 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/queue/handlers/IQueueHandler.kt @@ -0,0 +1,63 @@ +package fredboat.audio.queue.handlers + +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) } +} + diff --git a/FredBoat/src/main/java/fredboat/audio/queue/handlers/IRepeatableQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/handlers/IRepeatableQueueHandler.kt new file mode 100644 index 000000000..dda49bf5d --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/queue/handlers/IRepeatableQueueHandler.kt @@ -0,0 +1,12 @@ +package fredboat.audio.queue.handlers + +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/handlers/IRoundRobinQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/handlers/IRoundRobinQueueHandler.kt new file mode 100644 index 000000000..e605457e5 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/queue/handlers/IRoundRobinQueueHandler.kt @@ -0,0 +1,11 @@ +package fredboat.audio.queue.handlers + +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/handlers/IShufflableQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/handlers/IShufflableQueueHandler.kt new file mode 100644 index 000000000..2c8387c66 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/queue/handlers/IShufflableQueueHandler.kt @@ -0,0 +1,13 @@ +package fredboat.audio.queue.handlers + +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/handlers/RepeatableQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/handlers/RepeatableQueueHandler.kt new file mode 100644 index 000000000..d2e41f7d5 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/queue/handlers/RepeatableQueueHandler.kt @@ -0,0 +1,38 @@ +package fredboat.audio.queue.handlers + +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 diff --git a/FredBoat/src/main/java/fredboat/audio/queue/handlers/RoundRobinQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/handlers/RoundRobinQueueHandler.kt new file mode 100644 index 000000000..9ae7fcb35 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/queue/handlers/RoundRobinQueueHandler.kt @@ -0,0 +1,129 @@ +package fredboat.audio.queue.handlers + +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 = false + 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 diff --git a/FredBoat/src/main/java/fredboat/audio/queue/handlers/ShufflableQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/handlers/ShufflableQueueHandler.kt new file mode 100644 index 000000000..86232fa10 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/queue/handlers/ShufflableQueueHandler.kt @@ -0,0 +1,95 @@ +package fredboat.audio.queue.handlers + +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 diff --git a/FredBoat/src/main/java/fredboat/audio/queue/handlers/SimpleQueueHandler.kt b/FredBoat/src/main/java/fredboat/audio/queue/handlers/SimpleQueueHandler.kt new file mode 100644 index 000000000..cae6901d3 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/queue/handlers/SimpleQueueHandler.kt @@ -0,0 +1,39 @@ +package fredboat.audio.queue.handlers + +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 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..288e8f54f --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimit.kt @@ -0,0 +1,26 @@ +package fredboat.audio.queue.limiter + +import fredboat.audio.player.GuildPlayer +import fredboat.audio.queue.AudioTrackContext +import fredboat.definitions.PermissionLevel +import fredboat.perms.PermsUtil +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 + 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..58895335f --- /dev/null +++ b/FredBoat/src/main/java/fredboat/audio/queue/limiter/QueueLimitStatus.kt @@ -0,0 +1,26 @@ +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 + +// 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..16c182161 --- /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.getUserTrackCount +import fredboat.audio.player.trackCount +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.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") } + })) + + 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/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/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/admin/EvalCommand.kt b/FredBoat/src/main/java/fredboat/command/admin/EvalCommand.kt index 779708b8d..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 @@ -96,12 +97,12 @@ 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) 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/config/ConfigCommand.kt b/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt index 373627e14..911748e50 100644 --- a/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/ConfigCommand.kt @@ -30,19 +30,77 @@ import fredboat.commandmeta.abs.Command import fredboat.commandmeta.abs.CommandContext import fredboat.commandmeta.abs.ICommandRestricted import fredboat.commandmeta.abs.IConfigCommand -import fredboat.db.transfer.GuildConfig +import fredboat.db.api.GuildSettingsRepository +import fredboat.db.transfer.GuildSettings 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.TextUtils import fredboat.util.localMessageBuilder +import org.apache.commons.lang3.StringUtils +import java.util.function.Predicate -class ConfigCommand(name: String, vararg aliases: String) : Command(name, *aliases), IConfigCommand, ICommandRestricted { +private typealias Validator = 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,19 +110,18 @@ class ConfigCommand(name: String, vararg aliases: String) : Command(name, *alias } private fun printConfig(context: CommandContext) { - val gc = Launcher.botController.guildConfigService.fetchGuildConfig(context.guild.id) + repo.fetch(context.guild.id).subscribe { + val mb = localMessageBuilder().append(context.i18nFormat("configNoArgs", context.guild.name)).append("\n") - 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 + for (config in configOptions) { + mb.append("${config.name} = ").append(config.getter(it)).append("\n") + } - context.reply(mb.build()) + context.reply(mb.append("```").build()) + } } private suspend fun setConfig(context: CommandContext) { - val invoker = context.member if (!PermsUtil.checkPermsWithFeedback(PermissionLevel.ADMIN, context)) { return } @@ -75,26 +132,21 @@ class ConfigCommand(name: String, vararg aliases: String) : Command(name, *alias } val key = context.args[0] - val `val` = context.args[1] - - 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`)) - } - context.replyWithName("`track_announce` " + context.i18nFormat("configSetTo", `val`)) - } else { - context.reply(context.i18nFormat("configMustBeBoolean", invoker.effectiveName.escapeAndDefuse())) - } - } 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())) - } - } else context.reply(context.i18nFormat("configUnknownKey", invoker.effectiveName.escapeAndDefuse())) + val value = context.args[1] + + val config = configOptions.firstOrNull { it.name == key } + if (config == null) { + context.replyWithName(context.i18n("configUnknownKey")) + return + } + + if (!config.validator.test(value)) { + context.replyWithName(context.i18n("configValueTypeInvalid")) + } + + config.update(repo, context, value).subscribe { + context.replyWithName("`$name` ${context.i18nFormat("configSetTo", value)}") + } } override fun help(context: Context): String { @@ -102,3 +154,18 @@ class ConfigCommand(name: String, vararg aliases: String) : Command(name, *alias return usage + context.i18n("helpConfigCommand") } } + +private data class ConfigOption( + val name: String, + val validator: Validator, + 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) } + + override fun toString(): String { + return name + } +} 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..77ad0da77 --- /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.api.GuildSettingsRepository +import fredboat.db.transfer.GuildSettings +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.fetch(context.guild.id) + .defaultIfEmpty(GuildSettings(context.guild.id)) + .awaitSingle() + + when(context.args.getOrNull(0)?.trim()?.toLowerCase()) { + "allow" -> settings.allowPublicPlayerInfo = true + "deny" -> settings.allowPublicPlayerInfo = false + else -> { + HelpCommand.sendFormattedCommandHelp(context) + return + } + } + 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." + 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/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 4884f0f31..0b0c6f1f7 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.GuildSettingsRepository 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: GuildSettingsRepository, name: String, vararg aliases: String ) : Command(name, *aliases), IConfigCommand { @@ -99,29 +100,32 @@ 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 settings = repo.fetch(context.guild.id).awaitSingle() + val gp = settings.permissions - val newList = gp.getFromEnum(permissionLevel).toMutableList() - newList.remove(mentionableToId(selected)) + if (!gp.getForEnum(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.getForEnum(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.setForEnum(permissionLevel, newList) + repo.update(settings).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 +136,28 @@ 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 settings = repo.fetch(context.guild.id).awaitSingle() + val gp = settings.permissions + if (gp.getForEnum(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.getForEnum(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.setForEnum(permissionLevel, newList) + repo.update(settings).subscribe() } suspend fun list(context: CommandContext) { val guild = context.guild val invoker = context.member - val gp = Launcher.botController.guildPermsService.fetchGuildPermissions(guild) + val settings = repo.fetch(context.guild.id).awaitSingle() - val mentionables = idsToMentionables(guild, gp.getFromEnum(permissionLevel)) + val mentionables = idsToMentionables(guild, settings.permissions.getForEnum(permissionLevel)) var roleMentions = "" var memberMentions = "" @@ -203,9 +206,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 +222,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/command/config/PrefixCommand.kt b/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt index 6f59552b9..199ae8ca2 100644 --- a/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/config/PrefixCommand.kt @@ -25,54 +25,40 @@ package fredboat.command.config -import com.google.common.cache.CacheBuilder -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 import fredboat.perms.PermsUtil import fredboat.sentinel.Guild -import fredboat.util.rest.CacheUtil -import io.prometheus.client.guava.cache.CacheMetricsCollector -import java.util.* -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, name: String, vararg aliases: String) : Command(name, *aliases), IConfigCommand { +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 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 - //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 - .build(CacheLoader.asyncReloading(CacheLoader.from> { - guildId -> Launcher.botController.prefixService.getPrefix(Prefix.GuildBotId( - guildId!!, - botId - )) - }, - Launcher.botController.executor))!! + lateinit var staticRepo: GuildSettingsRepository + val defaultPrefix: String get() = Launcher.botController.appConfig.prefix - fun giefPrefix(guildId: Long) = CacheUtil.getUncheckedUnwrapped(CUSTOM_PREFIXES, guildId) - .orElse(Launcher.botController.appConfig.prefix) + fun getPrefix(guildId: Long): Mono = staticRepo.fetch(guildId) + .map { it.prefix ?: defaultPrefix } + .defaultIfEmpty(defaultPrefix) + fun getPrefix(guild: Guild) = getPrefix(guild.id) - fun giefPrefix(guild: Guild) = giefPrefix(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 @@ -103,13 +89,10 @@ class PrefixCommand(cacheMetrics: CacheMetricsCollector, name: String, vararg al newPrefix = context.rawArgs } - Launcher.botController.prefixService.transformPrefix(context.guild, { - prefixEntity -> prefixEntity.setPrefix(newPrefix) - }) - - //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) + repo.fetch(context.guild.id) + .doOnSuccess { it.prefix = newPrefix } + .let { repo.update(it) } + .subscribe() showPrefix(context, giefPrefix(context.guild)) } diff --git a/FredBoat/src/main/java/fredboat/command/info/DebugCommand.kt b/FredBoat/src/main/java/fredboat/command/info/DebugCommand.kt index 0fda6cbcb..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 @@ -41,7 +44,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 +66,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])) } @@ -89,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/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/command/music/control/JoinCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/JoinCommand.kt index 79cfff908..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 @@ -39,7 +40,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/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/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/control/PauseCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/PauseCommand.kt new file mode 100644 index 000000000..e44e0196a --- /dev/null +++ b/FredBoat/src/main/java/fredboat/command/music/control/PauseCommand.kt @@ -0,0 +1,58 @@ +/* + * 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.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 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 54eacacf3..254922563 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 @@ -43,6 +41,7 @@ import fredboat.util.TextUtils import fredboat.util.extension.edit import fredboat.util.localMessageBuilder import fredboat.util.rest.TrackSearcher +import kotlinx.coroutines.reactive.awaitSingle import org.apache.commons.lang3.StringUtils import org.slf4j.LoggerFactory @@ -63,10 +62,10 @@ 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, isPriority) + player.queueAsync(atc, context, isPriority) } player.setPause(false) @@ -80,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 } @@ -95,8 +94,8 @@ 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) - player.queue(url, context, isPriority) + val player = Launcher.botController.playerRegistry.awaitPlayer(context.guild) + player.queueAsync(url, context, isPriority) player.setPause(false) context.deleteMessage() @@ -125,54 +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 -> - 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.tracks.isEmpty()) { + outMsg.edit( + context.textChannel, + context.i18n("playSearchNoResults").replace("{q}", query) + ).subscribe() + return + } - 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++ - } - - outMsg.edit(context.textChannel, builder.build()).subscribe() - videoSelectionCache.put(outMsg.messageId, context, selectable, isPriority) - } + //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/FredBoat/src/main/java/fredboat/command/music/control/PlaySplitCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/PlaySplitCommand.kt index d05debb6b..922550158 100644 --- a/FredBoat/src/main/java/fredboat/command/music/control/PlaySplitCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/control/PlaySplitCommand.kt @@ -55,8 +55,8 @@ 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) - player.queue(ic) + val player = playerRegistry.awaitPlayer(context.guild) + player.queueAsync(ic) player.setPause(false) context.deleteMessage() 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/ReplayCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/ReplayCommand.kt new file mode 100644 index 000000000..a30d97413 --- /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.queueAll(toQueue) + 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/command/music/control/RoundRobbinCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/RoundRobbinCommand.kt new file mode 100644 index 000000000..51e420ba2 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/command/music/control/RoundRobbinCommand.kt @@ -0,0 +1,29 @@ +package fredboat.command.music.control + +import fredboat.commandmeta.CommandInitializer +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) + context.i18nFormat("roundRobinOn", "`${context.prefix}${CommandInitializer.HELP_COMM_NAME} ${context.trigger}`") + else + context.i18n("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/command/music/control/SelectCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/SelectCommand.kt index f1758c7b7..600d70987 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] @@ -115,7 +115,8 @@ class SelectCommand(private val videoSelectionCache: VideoSelectionCache, name: } outputMsgBuilder.append(msg) - player.queue(AudioTrackContext(selectedTracks[i]!!, invoker, selection.isPriority), selection.isPriority) + player.queueLimited(AudioTrackContext(selectedTracks[i]!!, invoker, selection.isPriority)) + player.play() } videoSelectionCache.remove(invoker) 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/db/DatabaseNotReadyException.java b/FredBoat/src/main/java/fredboat/command/music/control/ShuffleCommand.kt similarity index 51% rename from FredBoat/src/main/java/fredboat/db/DatabaseNotReadyException.java rename to FredBoat/src/main/java/fredboat/command/music/control/ShuffleCommand.kt index 54803981e..ea3213909 100644 --- a/FredBoat/src/main/java/fredboat/db/DatabaseNotReadyException.java +++ b/FredBoat/src/main/java/fredboat/command/music/control/ShuffleCommand.kt @@ -1,8 +1,7 @@ /* - * * MIT License * - * Copyright (c) 2017-2018 Frederik Ar. Mikkelsen + * 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 @@ -21,33 +20,36 @@ * 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; +package fredboat.command.music.control -import fredboat.commandmeta.MessagingException; -import fredboat.feature.metrics.Metrics; +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 -public class DatabaseNotReadyException extends MessagingException { - private static final long serialVersionUID = -3320905078677229733L; +class ShuffleCommand(name: String, vararg aliases: String) : Command(name, *aliases), IMusicCommand, ICommandRestricted { - private static final String DEFAULT_MESSAGE = "The database is not available currently. Please try again in a moment."; + override val minimumPerms: PermissionLevel + get() = PermissionLevel.DJ - DatabaseNotReadyException(String str, Throwable cause) { - super(str, cause); - Metrics.databaseExceptionsCreated.inc(); - } - - DatabaseNotReadyException(String str) { - super(str); - Metrics.databaseExceptionsCreated.inc(); - } + override suspend fun invoke(context: CommandContext) { + val player = getBotController().playerRegistry.awaitPlayer(context.guild) + player.isShuffle = !player.isShuffle - public DatabaseNotReadyException(Throwable cause) { - this(DEFAULT_MESSAGE, cause); + if (player.isShuffle) { + context.reply(context.i18n("shuffleOn")) + } else { + context.reply(context.i18n("shuffleOff")) + } } - public DatabaseNotReadyException() { - this(DEFAULT_MESSAGE); + override fun help(context: Context): String { + return "{0}{1}\n#" + context.i18n("helpShuffleCommand") } } 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..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 @@ -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 { 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/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/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/command/music/control/VoteSkipCommand.kt b/FredBoat/src/main/java/fredboat/command/music/control/VoteSkipCommand.kt index 1d5d382a3..0a76fecfa 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/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 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"); - } -} 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..863ed9ba6 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/command/music/info/ListCommand.kt @@ -0,0 +1,162 @@ +/* + * 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.isRoundRobin) + mb.append(context.i18n("listShowRoundRobin")).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/info/NowplayingCommand.kt b/FredBoat/src/main/java/fredboat/command/music/info/NowplayingCommand.kt index abfe34738..850b52f72 100644 --- a/FredBoat/src/main/java/fredboat/command/music/info/NowplayingCommand.kt +++ b/FredBoat/src/main/java/fredboat/command/music/info/NowplayingCommand.kt @@ -51,6 +51,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) @@ -63,7 +65,7 @@ class NowplayingCommand(private val youtubeAPI: YoutubeAPI, name: String, vararg val at = atc!!.track val embed = when { - at is YoutubeAudioTrack && !FeatureFlags.DISABLE_NOWPLAYING_WITH_YTAPI.isActive -> + at is YoutubeAudioTrack && hasYtKeys && !FeatureFlags.DISABLE_NOWPLAYING_WITH_YTAPI.isActive -> getYoutubeEmbed(atc, player, at) at is SoundCloudAudioTrack -> getSoundcloudEmbed(atc, player, at) at is BandcampAudioTrack -> getBandcampResponse(atc, player, at) @@ -74,7 +76,7 @@ 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 + text = context.i18nFormat("npRequestedBy", atc.member.effectiveName) iconUrl = atc.member.info.awaitSingle().iconUrl } 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 + } +} 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/commandmeta/CommandContextParser.kt b/FredBoat/src/main/java/fredboat/commandmeta/CommandContextParser.kt index f6dc39684..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.giefPrefix(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 40c466c60..754a8b77a 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.api.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 */ @@ -127,14 +139,15 @@ class CommandInitializer(cacheMetrics: CacheMetricsCollector, weather: Weather, // 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")) + configModule.registerCommand(ModulesCommand("modules", guildSettingsRepository, "module", "mods")) + configModule.registerCommand(PrefixCommand(guildSettingsRepository, 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")) - configModule.registerCommand(PermissionsCommand(PermissionLevel.USER, "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 @@ -237,6 +250,7 @@ class CommandInitializer(cacheMetrics: CacheMetricsCollector, weather: Weather, 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)) @@ -246,6 +260,7 @@ class CommandInitializer(cacheMetrics: CacheMetricsCollector, weather: Weather, 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")) @@ -262,7 +277,7 @@ class CommandInitializer(cacheMetrics: CacheMetricsCollector, weather: Weather, /* 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/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( 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/AudioPlayerManagerConfiguration.java b/FredBoat/src/main/java/fredboat/config/AudioPlayerManagerConfiguration.java index 022accef2..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; @@ -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 { @@ -132,7 +129,7 @@ public ArrayList getConfiguredAudioSourceManagers(AudioSourc BeamAudioSourceManager beamAudioSourceManager, SpotifyPlaylistSourceManager spotifyPlaylistSourceManager, LocalAudioSourceManager localAudioSourceManager, - HttpSourceManager httpSourceManager) { + HttpAudioSourceManager httpAudioSourceManager) { ArrayList audioSourceManagers = new ArrayList<>(); if (audioSourcesConfig.isYouTubeEnabled()) { @@ -162,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; } @@ -253,7 +250,7 @@ public LocalAudioSourceManager localAudioSourceManager() { @Bean(destroyMethod = "") @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) - public HttpSourceManager httpSourceManager() { - return new HttpSourceManager(); + public HttpAudioSourceManager httpSourceManager() { + return new HttpAudioSourceManager(); } } diff --git a/FredBoat/src/main/java/fredboat/config/MetricsConfiguration.java b/FredBoat/src/main/java/fredboat/config/MetricsConfiguration.java index 70d240980..a3695d49f 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,13 @@ 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(); + } + + @Bean + 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/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); - } - } -} 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 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..afa243a19 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/config/RepositoriesConfiguration.kt @@ -0,0 +1,28 @@ +package fredboat.config + +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 + +@Configuration +class RepositoriesConfiguration(private val cacheMetrics: CacheMetricsCollector) { + + @Bean + fun guildSettingsRepository(repo: InternalGuildSettingsRepository): GuildSettingsRepository { + return GuildSettingsRepositoryImpl(repo, cacheMetrics, GuildSettingsRepository::class.java.simpleName) + } + + @Bean + fun blacklistRepository(repo: InternalBlacklistRepository): BlacklistRepository { + return BlacklistRepositoryImpl(repo, cacheMetrics, BlacklistRepository::class.java.simpleName) + } + + @Bean + fun searchResultRepository(repo: InternalSearchResultRepository): SearchResultRepository { + return SearchResultRepositoryImpl(repo, cacheMetrics, SearchResultRepository::class.java.simpleName) + } +} \ No newline at end of file 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..c0dae8870 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/config/WebSocketConfig.kt @@ -0,0 +1,35 @@ +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 java.util.* + + + +@Configuration +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/config/property/AppConfig.java b/FredBoat/src/main/java/fredboat/config/property/AppConfig.java index c6571b4da..e4e72d7a7 100644 --- a/FredBoat/src/main/java/fredboat/config/property/AppConfig.java +++ b/FredBoat/src/main/java/fredboat/config/property/AppConfig.java @@ -84,4 +84,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 b033aa438..857b7494f 100644 --- a/FredBoat/src/main/java/fredboat/config/property/AppConfigProperties.java +++ b/FredBoat/src/main/java/fredboat/config/property/AppConfigProperties.java @@ -53,6 +53,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; @@ -112,6 +113,11 @@ public int getShardCount() { return shardCount; } + @Override + public String getWebInfoBaseUrl() { + return webInfoBaseUrl; + } + public void setDevelopment(boolean development) { this.development = development; } @@ -156,4 +162,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/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/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 new file mode 100644 index 000000000..c9ea29aee --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/api/BaseRepository.kt @@ -0,0 +1,14 @@ +package fredboat.db.api + +import reactor.core.publisher.Mono + +interface BaseRepository { + + fun fetch(id: ID): Mono + + fun update(mono: Mono): Mono + + fun update(target: T): Mono + + fun remove(id: ID): Mono +} \ 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..f07d09570 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/api/BlacklistRepository.kt @@ -0,0 +1,5 @@ +package fredboat.db.api + +import fredboat.db.transfer.BlacklistEntity + +interface BlacklistRepository : BaseDefaultedRepository \ 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/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/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/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/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..181442d83 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/api/GuildSettingsRepository.kt @@ -0,0 +1,5 @@ +package fredboat.db.api + +import fredboat.db.transfer.GuildSettings + +interface GuildSettingsRepository : BaseDefaultedRepository \ 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/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/ActivityMetricsController.kt b/FredBoat/src/main/java/fredboat/db/mongo/ActivityMetricsController.kt new file mode 100644 index 000000000..3b01e063d --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/mongo/ActivityMetricsController.kt @@ -0,0 +1,95 @@ +package fredboat.db.mongo + +import fredboat.feature.togglz.FeatureFlags +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 (!FeatureFlags.INSTRUMENT_ACTIVE_USERS.isActive) return + 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() { + if (!FeatureFlags.INSTRUMENT_ACTIVE_USERS.isActive) return + + 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/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 new file mode 100644 index 000000000..78f2b1697 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/mongo/BaseRepositoryImpl.kt @@ -0,0 +1,45 @@ +package fredboat.db.mongo + +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, + cacheMetrics: CacheMetricsCollector, + cacheName: String +) : ReactiveCrudRepository by repo, BaseRepository { + + protected val cache: AsyncLoadingCache = Caffeine.newBuilder() + .expireAfterAccess(60, TimeUnit.SECONDS) + .expireAfterWrite(120, TimeUnit.SECONDS) + .recordStats() + .buildAsync { key, _ -> repo.findById(key).toFuture() } + + init { + cacheMetrics.addCache(cacheName, cache) + } + + override fun fetch(id: ID): Mono { + return cache[id].toMono() + } + + override fun update(mono: Mono): Mono { + return mono.flatMap { cache.synchronous().put(it.id, it); save(it) } + } + + override fun update(target: T): Mono { + return repo.save(target).doOnSuccess { cache.synchronous().put(target.id, target) } + } + + override fun remove(id: ID): Mono { + 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 new file mode 100644 index 000000000..bf539af0b --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/mongo/BlacklistRepositoryImpl.kt @@ -0,0 +1,16 @@ +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, + cacheMetrics: CacheMetricsCollector, + cacheName: String +) : BaseDefaultedRepositoryImpl(repo, cacheMetrics, cacheName), BlacklistRepository { + + 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 new file mode 100644 index 000000000..095527397 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/mongo/GuildSettingsRepositoryImpl.kt @@ -0,0 +1,18 @@ +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, + cacheMetrics: CacheMetricsCollector, + cacheName: String +) : BaseDefaultedRepositoryImpl(repo, cacheMetrics, cacheName), GuildSettingsRepository { + + override fun default(id: Long): GuildSettings { + return GuildSettings(id) + } + +} \ No newline at end of file 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..33b6481cc --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/mongo/SearchResultRepositoryImpl.kt @@ -0,0 +1,12 @@ +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, + cacheMetrics: CacheMetricsCollector, + cacheName: String +) : BaseRepositoryImpl(repo, cacheMetrics, cacheName), SearchResultRepository \ 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 new file mode 100644 index 000000000..f0363587f --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/mongo/player.kt @@ -0,0 +1,40 @@ +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 + +@Document(collection = "MongoPlayer") +class MongoPlayer( + @Id + 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, + val paused: Boolean, + val shuffled: Boolean, + val roundRobin: 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?, + /** Voice channel we were playing in if interrupted */ + val voiceChannel: Long?, + /** Channel to send event messages in */ + val textChannel: Long?, + val queue: List +) : MongoEntity + +class MongoTrack( + val id: ObjectId, + val blob: ByteArray, + 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 new file mode 100644 index 000000000..98a6f5a55 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/mongo/repositories.kt @@ -0,0 +1,71 @@ +package fredboat.db.mongo + +import fredboat.audio.player.GuildPlayer +import fredboat.audio.player.voiceChannel +import fredboat.audio.queue.SplitAudioTrackContext +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 +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 +interface InternalGuildSettingsRepository : ReactiveCrudRepository +interface InternalBlacklistRepository: ReactiveCrudRepository +interface InternalSearchResultRepository: ReactiveCrudRepository + +private val log: Logger = LoggerFactory.getLogger(PlayerRepository::class.java) + +fun PlayerRepository.convertAndSave(player: GuildPlayer): Mono { + return save(player.toMongo()) +} + +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, + isPlaying, + isPaused, + isShuffle, + isRoundRobin, + repeatMode.ordinal.toByte(), + 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) + } else { + MongoTrack(it.trackId, LavalinkUtil.toBinary(it.track), it.member.id, null, null, it.effectiveTitle) + } + } +) + +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/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/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/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/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/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/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/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/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/BlacklistEntity.kt b/FredBoat/src/main/java/fredboat/db/transfer/BlacklistEntity.kt new file mode 100644 index 000000000..ce0d13431 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/transfer/BlacklistEntity.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 BlacklistEntity( + @Id override val id: Long, + var level: Int = -1, + var hitCount: Int = 0, + var lastHitTime: Long = 0, + var blacklistTime: Long = 0 +) : MongoEntity { + + fun incLevel() { + level++ + } + + fun incHitCount() { + hitCount++ + } +} \ No newline at end of file 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/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/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/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/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/transfer/GuildSettings.kt b/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt new file mode 100644 index 000000000..40c0fd05c --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/transfer/GuildSettings.kt @@ -0,0 +1,28 @@ +package fredboat.db.transfer + +import fredboat.definitions.Module +import org.springframework.data.annotation.Id +import org.springframework.data.mongodb.core.mapping.Document + +@Document(collection = "GuildSettings") +data class GuildSettings( + @Id + override val id: Long, + var helloSent: Boolean = false, + 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) }, + var permissions: PermissionEntity = PermissionEntity(userList = listOf(id), djList = listOf(id)) +) : MongoEntity { + + 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/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/PermissionEntity.kt b/FredBoat/src/main/java/fredboat/db/transfer/PermissionEntity.kt new file mode 100644 index 000000000..f3a28be11 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/db/transfer/PermissionEntity.kt @@ -0,0 +1,27 @@ +package fredboat.db.transfer + +import fredboat.definitions.PermissionLevel + +data class PermissionEntity( + var adminList: List = emptyList(), + var djList: List = emptyList(), + var userList: List = emptyList() +) { + fun getForEnum(level: PermissionLevel): List { + return when (level) { + PermissionLevel.ADMIN -> adminList + PermissionLevel.DJ -> djList + PermissionLevel.USER -> userList + else -> throw IllegalArgumentException("Unexpected enum $level") + } + } + + fun setForEnum(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/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/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/AudioEventHandler.kt b/FredBoat/src/main/java/fredboat/event/AudioEventHandler.kt index bc6a13675..cb7fae7ef 100644 --- a/FredBoat/src/main/java/fredboat/event/AudioEventHandler.kt +++ b/FredBoat/src/main/java/fredboat/event/AudioEventHandler.kt @@ -3,8 +3,11 @@ 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.db.api.GuildSettingsRepository import fredboat.feature.I18n import fredboat.sentinel.Member import fredboat.sentinel.VoiceChannel @@ -15,13 +18,18 @@ class AudioEventHandler( private val appConfig: AppConfig, private val playerRegistry: PlayerRegistry, private val lavalink: SentinelLavalink, - private val guildConfigService: GuildConfigService + private val guildSettingsRepository: GuildSettingsRepository ) : SentinelEventHandler() { 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?.voiceChannel == member.voiceChannel) { + lavalink.activityMetrics.logListener(member) + } } override fun onVoiceLeave(channel: VoiceChannel, member: Member) { @@ -48,12 +56,12 @@ 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 } - if (player.getHumanUsersInVC(currentVc).isEmpty() && !player.isPaused) { + if (currentVc.getHumanUsersInVC().isEmpty() && !player.isPaused) { player.pause() player.activeTextChannel?.send(I18n.get(channelLeft.guild).getString("eventUsersLeftVC"))?.subscribe() } @@ -70,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()) { + 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/event/GuildEventHandler.kt b/FredBoat/src/main/java/fredboat/event/GuildEventHandler.kt index 3cc326144..a8a6c330f 100644 --- a/FredBoat/src/main/java/fredboat/event/GuildEventHandler.kt +++ b/FredBoat/src/main/java/fredboat/event/GuildEventHandler.kt @@ -1,8 +1,9 @@ package fredboat.event +import com.fredboat.sentinel.entities.SendMessageResponse 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,16 +14,11 @@ 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)) - .subscribe() + sendHelloOnJoin(guild).subscribe() } override fun onGuildLeave(guildId: Long, joinTime: Instant) { @@ -32,29 +28,31 @@ class GuildEventHandler( Metrics.guildLifespan.observe(lifespan.toDouble()) } - 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 - } + 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@flatMap Mono.empty() + } - 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 + 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 + } } } - } - //send actual hello message and persist on success - channel?.send(HelloCommand.getHello(guild)) - ?.doOnSuccess { guildDataService.transformGuildData(guild, { it.helloSent() }) } - ?.subscribe() + gs.helloSent = true + 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 +} 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/event/MusicPersistenceHandler.kt b/FredBoat/src/main/java/fredboat/event/MusicPersistenceHandler.kt deleted file mode 100644 index 4b41c744d..000000000 --- a/FredBoat/src/main/java/fredboat/event/MusicPersistenceHandler.kt +++ /dev/null @@ -1,324 +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.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.currentVoiceChannel - 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.getOrCreate(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/event/ShardLifecycleHandler.kt b/FredBoat/src/main/java/fredboat/event/ShardLifecycleHandler.kt index 9a60ed175..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) { @@ -121,9 +122,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() + } } } 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 ebca5c0d3..000000000 --- a/FredBoat/src/main/java/fredboat/feature/I18n.java +++ /dev/null @@ -1,130 +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.DatabaseNotReadyException; -import fredboat.definitions.Language; -import fredboat.sentinel.Guild; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -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().getGuildConfigService().fetchGuildConfig(guild).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; - } - } - - public static void set(@Nonnull Guild guild, @Nonnull String lang) throws LanguageNotSupportedException { - if (!LANGS.containsKey(lang)) - throw new LanguageNotSupportedException("Language not found"); - - getBotController().getGuildConfigService().transformGuildConfig(guild.getId(), config -> config.setLang(lang)); - } - - 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..70e7e398a --- /dev/null +++ b/FredBoat/src/main/java/fredboat/feature/I18n.kt @@ -0,0 +1,109 @@ +/* + * 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.* + +@Suppress("DeprecatedCallableAddReplaceWith") +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) + } + + @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) + } + + @Deprecated("Convert this to reactive at some point!") + 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) + 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 = ResourceBundle.getBundle("lang." + language.code, language.locale) + + val code: String + get() = language.code + + val nativeName: String + get() = language.nativeName + + val englishName: String + get() = language.englishName + + override fun toString(): String { + return "[$code $nativeName]" + } + } + + class LanguageNotSupportedException(message: String) : Exception(message) + +} diff --git a/FredBoat/src/main/java/fredboat/feature/metrics/Metrics.java b/FredBoat/src/main/java/fredboat/feature/metrics/Metrics.java index bde10b678..32891eddf 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 @@ -229,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") 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 } } diff --git a/FredBoat/src/main/java/fredboat/feature/togglz/FeatureFlags.java b/FredBoat/src/main/java/fredboat/feature/togglz/FeatureFlags.java index 32bf94a61..1a492cfc2 100644 --- a/FredBoat/src/main/java/fredboat/feature/togglz/FeatureFlags.java +++ b/FredBoat/src/main/java/fredboat/feature/togglz/FeatureFlags.java @@ -41,8 +41,11 @@ 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, + @Label("Disable the use of the Youtube API with ;;nowplaying") - DISABLE_NOWPLAYING_WITH_YTAPI + DISABLE_NOWPLAYING_WITH_YTAPI, ; diff --git a/FredBoat/src/main/java/fredboat/main/BotController.kt b/FredBoat/src/main/java/fredboat/main/BotController.kt index 96d348aa6..2f5d394b0 100644 --- a/FredBoat/src/main/java/fredboat/main/BotController.kt +++ b/FredBoat/src/main/java/fredboat/main/BotController.kt @@ -4,10 +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.api.* import fredboat.feature.metrics.BotMetrics import fredboat.feature.metrics.Metrics import fredboat.metrics.OkHttpEventMetrics @@ -30,10 +27,7 @@ class BotController(private val configProvider: ConfigPropertiesProvider, val botMetrics: BotMetrics, @param:Qualifier("loadAudioPlayerManager") val audioPlayerManager: AudioPlayerManager, val ratelimiter: Ratelimiter, - val guildConfigService: GuildConfigService, - val guildModulesService: GuildModulesService, - val guildPermsService: GuildPermsService, - val prefixService: PrefixService, + val guildSettingsRepository: GuildSettingsRepository, val sentinel: Sentinel, val sentinelCountingService: SentinelCountingService) { 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/main/java/fredboat/perms/PermsUtil.kt b/FredBoat/src/main/java/fredboat/perms/PermsUtil.kt index c5b9d58a7..2b8b6bdb9 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.guildSettingsRepository.fetch(member.guild.id).awaitSingle().permissions when { checkList(gp.adminList, member) -> PermissionLevel.ADMIN @@ -110,13 +110,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/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 ) 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 6448cd429..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 } @@ -97,30 +93,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 -> - GlobalScope.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 } @@ -128,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) diff --git a/FredBoat/src/main/java/fredboat/sentinel/wrapperEntities.kt b/FredBoat/src/main/java/fredboat/sentinel/wrapperEntities.kt index 00bc4956a..8cfea8af8 100644 --- a/FredBoat/src/main/java/fredboat/sentinel/wrapperEntities.kt +++ b/FredBoat/src/main/java/fredboat/sentinel/wrapperEntities.kt @@ -110,20 +110,35 @@ abstract class Guild(raw: RawGuild) : SentinelEntity { /** Has public members we want to hide */ class InternalGuild(raw: RawGuild) : Guild(raw) { - init { - update(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 - } - - /** 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 + 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") + } + fun update(raw: RawGuild) { if (id != raw.id) throw AmqpRejectAndDontRequeueException("Attempt to update $id with the data of ${raw.id}") @@ -137,6 +152,7 @@ class InternalGuild(raw: RawGuild) : Guild(raw) { val rawOwner = raw.owner _owner = if (rawOwner != null) members[rawOwner] else null + } fun handleMemberAdd(member: RawMember) { diff --git a/FredBoat/src/main/java/fredboat/util/TextUtils.java b/FredBoat/src/main/java/fredboat/util/TextUtils.java index a2460ae84..e4b731207 100644 --- a/FredBoat/src/main/java/fredboat/util/TextUtils.java +++ b/FredBoat/src/main/java/fredboat/util/TextUtils.java @@ -69,7 +69,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) 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 f74a5a864..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.BlacklistService; -import fredboat.db.transfer.BlacklistEntry; -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 BlacklistService blacklistService; //implementation as a RestRepo includes a cache - - - public Blacklist(BlacklistService blacklistService, Set userWhiteList, long rateLimitHitsBeforeBlacklist) { - this.blacklistService = blacklistService; - 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; - - BlacklistEntry blEntry = blacklistService.fetchBlacklistEntry(id); - 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()))) { - 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; - BlacklistEntry blEntry = blacklistService.fetchBlacklistEntry(id); - - //synchronize on the individual blacklist entries since we are about to change and save 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.getRateLimitReachedTimestamp() > 60 * 60 * 1000) { - blEntry.setRateLimitReached(0); - } - blEntry.incRateLimitReached(); - blEntry.setRateLimitReachedTimestamp(now); - if (blEntry.getRateLimitReached() >= 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 - - 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); - return blacklistingLength; - } - } - - /** - * completely resets a blacklist for an id - */ - public void liftBlacklist(long id) { - blacklistService.deleteBlacklistEntry(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 a266869ce..0fce5f985 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 @@ -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 2ea2ad425..b18274c53 100644 --- a/FredBoat/src/main/java/fredboat/util/ratelimit/Ratelimiter.java +++ b/FredBoat/src/main/java/fredboat/util/ratelimit/Ratelimiter.java @@ -32,12 +32,13 @@ 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; 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; @@ -63,18 +64,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; } @@ -140,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); } /** @@ -152,6 +154,7 @@ public void liftLimitAndBlacklist(long id) { ratelimit.liftLimit(id); } if (autoBlacklist != null) + autoBlacklist.liftBlacklist(id); } } 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 c4cc58b37..000000000 --- a/FredBoat/src/main/java/fredboat/util/rest/TrackSearcher.java +++ /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.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.SearchResultService; -import fredboat.db.transfer.SearchResult; -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.ExecutorService; -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 SearchResultService searchResultService; - private final AppConfig appConfig; - private final ExecutorService executor; - - public TrackSearcher(@Qualifier("searchAudioPlayerManager") AudioPlayerManager audioPlayerManager, - YoutubeAPI youtubeAPI, SearchResultService searchResultService, AppConfig appConfig, - ExecutorService executor) { - this.audioPlayerManager = audioPlayerManager; - this.youtubeAPI = youtubeAPI; - this.searchResultService = searchResultService; - this.appConfig = appConfig; - this.executor = executor; - } - - 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 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 - * @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 - executor.execute(() -> searchResultService - .mergeSearchResult(new SearchResult(audioPlayerManager, provider, query, lavaplayerResult))); - 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 - executor.execute(() -> searchResultService - .mergeSearchResult(new SearchResult(audioPlayerManager, provider, query, youtubeApiResult))); - 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 { - SearchResult.SearchResultId id = new SearchResult.SearchResultId(provider, searchTerm); - return searchResultService.getSearchResult(id, cacheMaxAge) - .map(searchResult -> searchResult.getSearchResult(audioPlayerManager)) - .orElse(null); - } 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 + } +} 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..929b03fb2 --- /dev/null +++ b/FredBoat/src/main/java/fredboat/ws/UserSession.kt @@ -0,0 +1,40 @@ +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 java.util.regex.Pattern + +class UserSession( + val session: WebSocketSession, + private val guildCache: GuildCache, + private val gson: Gson, + onSubscribe: () -> Unit +) : WebSocketSession by session { + + companion object { + private val expctedPath = Pattern.compile("/playerinfo/(\\d+)/?") + } + + @Volatile + private lateinit var sink: FluxSink + 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) + 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) + } +} \ 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..8e665923a --- /dev/null +++ b/FredBoat/src/main/java/fredboat/ws/UserSessionHandler.kt @@ -0,0 +1,91 @@ +package fredboat.ws + +import com.google.gson.Gson +import fredboat.db.api.GuildSettingsRepository +import fredboat.db.transfer.GuildSettings +import fredboat.sentinel.GuildCache +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Controller +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 +class UserSessionHandler( + val gson: Gson, + val guildCache: GuildCache, + val repository: GuildSettingsRepository +) : WebSocketHandler { + + companion object { + private val log: Logger = LoggerFactory.getLogger(UserSessionHandler::class.java) + } + + private val sessions = ConcurrentHashMap>() + + override fun handle(rawSession: WebSocketSession): Mono { + lateinit var interceptMono: Mono + val session = UserSession(rawSession, guildCache, gson) { interceptMono.subscribe() } + log.info("Established user connection for guild ${session.guildId}") + + interceptMono = repository.fetch(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 + } + + 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) + } + + return rawSession.send(session.sendStream) + .and(session.receive().doOnNext { handleMessage(session, it) }) + .doFinally { + afterConnectionClosed(session) + } + } + + 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) { + session.isOpen = false + 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) + } + + final fun sendLazy(guildId: Long, producer: () -> Any) { + val sessions = this[guildId] + if (sessions.isEmpty()) return + + val msg = gson.toJson(producer()) + sessions.forEach { + 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") + } + } + } + +} \ 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..b6a4d0d5d --- /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: Int?, + val queue: List +) + +data class TrackInfo( + val id: String, + val name: String, + val image: String?, + val duration: Int? +) + +val emptyPlayerInfo = PlayerInfo(false, false, false, RepeatMode.OFF.ordinal, null, emptyList()) + +fun GuildPlayer.toPlayerInfo() = PlayerInfo( + isPlaying, + isPaused, + isShuffle, + repeatMode.ordinal, + playingTrack?.getEffectivePosition(this)?.toInt(), + getTracksInRange(0, 10).map { it.toTrackInfo() } +) + +fun AudioTrackContext.toTrackInfo() = TrackInfo( + trackId.toHexString(), + effectiveTitle, + thumbnailUrl, + if (track.info.isStream) null else effectiveDuration.toInt() +) \ No newline at end of file diff --git a/FredBoat/src/main/resources/lang/af_ZA.properties b/FredBoat/src/main/resources/lang/af_ZA.properties index 13710bbf0..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. @@ -62,6 +67,7 @@ npDescription=Beskrywing npLoadedSoundcloud=[{0}/{1}] Gelaai van Soundcloud npLoadedBandcamp={0} gelaai van Bandcamp npLoadedTwitch=Gelaai van pyl +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 @@ -91,6 +97,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. @@ -238,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. @@ -333,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 5da695f8c..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,6 +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}\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 @@ -91,6 +97,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=\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. +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. @@ -238,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. @@ -333,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 35dccd008..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. @@ -62,6 +67,7 @@ npDescription=Descripci\u00f3n npLoadedSoundcloud=[{0}/{1}]\n\nLoaded from Soundcloud npLoadedBandcamp={0}\n\nLoaded from Bandcamp npLoadedTwitch=Loaded from Twitch +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 @@ -91,6 +97,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. @@ -238,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. @@ -333,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 24bbabd0f..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. @@ -62,6 +67,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=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 @@ -91,6 +97,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. @@ -238,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. @@ -333,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 bc515b890..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\! @@ -62,6 +67,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 @@ -91,6 +97,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. @@ -238,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. @@ -333,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 41376d272..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,6 +67,7 @@ npDescription=Descripci\u00f3 npLoadedSoundcloud=[{0}/{1}]\n\nCarregat de Soundcloud npLoadedBandcamp={0}\n\nCarregat de Bandcamp npLoadedTwitch=Carregat de Twitch +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 @@ -91,6 +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=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. @@ -143,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. @@ -238,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. @@ -333,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 43b5cef18..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. @@ -62,6 +67,7 @@ npDescription=Deskripsyon npLoadedSoundcloud={0}{1}\nBug-at gikan Soundcloud npLoadedBandcamp={0}\nBug-at gikan Bandcamp npLoadedTwitch=Bug-at gikan Twitch +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 @@ -91,6 +97,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. @@ -238,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. @@ -333,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 485e1dd44..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. @@ -62,6 +67,7 @@ npDescription=Popis npLoadedSoundcloud=[{0}/{1}]\n\nNa\u010dteno z Soundcloud npLoadedBandcamp={0}\n\nNa\u010dteno z Bandcamp npLoadedTwitch=Na\u010dteno z Twitch +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 @@ -91,6 +97,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. @@ -238,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. @@ -301,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. @@ -333,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 25845b232..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. @@ -62,6 +67,7 @@ npDescription=Disgrifiad npLoadedSoundcloud=[{0}/{1}] Lwytho o''r Soundcloud npLoadedBandcamp={0} lwytho o''r Bandcamp npLoadedTwitch=Llwytho o nerfusrwydd +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 @@ -91,6 +97,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. @@ -238,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. @@ -333,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 e794f0cdb..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. @@ -62,6 +67,7 @@ npDescription=Beskrivelse npLoadedSoundcloud=[{0}/{1}]\nIndl\u00e6st fra Soundcloud npLoadedBandcamp={0}\n\nIndl\u00e6st fra Bandcamp npLoadedTwitch=Indl\u00e6st fra Twitch +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 @@ -91,6 +97,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. @@ -238,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. @@ -333,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 a6724eb45..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,6 +67,7 @@ npDescription=Beschreibung npLoadedSoundcloud=[{0}/{1}]\n\nGeladen von Soundcloud npLoadedBandcamp={0} aus Bandcamp geladen npLoadedTwitch=Von Twitch geladen +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 @@ -91,6 +97,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=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. playerJoinConnectDenied=Mir ist es nicht gestattet, mich mit diesem Sprachkanal zu verbinden. @@ -216,7 +227,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\: @@ -238,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} -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=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. @@ -272,7 +285,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. @@ -301,8 +314,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. @@ -333,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 3819b98e3..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. @@ -62,6 +67,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=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 @@ -91,6 +97,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. @@ -102,7 +113,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 @@ -180,7 +191,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. @@ -238,17 +249,19 @@ 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} +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. 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. @@ -272,7 +285,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. @@ -301,8 +314,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. @@ -333,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 66eb84f1d..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. @@ -62,6 +67,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=Requested by {0} permissionMissingBot=I be lackin' this perm for that one\: permissionMissingInvoker=I be lackin' this perm for that one\: permissionEmbedLinks=Embed Links @@ -91,6 +97,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\: @@ -139,14 +150,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}. @@ -238,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. @@ -333,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 ac5f813c2..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. @@ -62,6 +67,7 @@ npDescription=Description npLoadedSoundcloud=[{0}/{1}]\n\nand it is from Soundcloud\! npLoadedBandcamp={0}\n\nLoaded from Bandcamp npLoadedTwitch=That was from Twitch +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 @@ -91,6 +97,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\! @@ -238,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. @@ -333,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/en_US.properties b/FredBoat/src/main/resources/lang/en_US.properties index 40871fa6f..96a181f8a 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=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. @@ -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. @@ -62,6 +67,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 @@ -91,6 +97,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. @@ -240,12 +251,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 ”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. diff --git a/FredBoat/src/main/resources/lang/es_ES.properties b/FredBoat/src/main/resources/lang/es_ES.properties index 592bd658a..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,6 +67,7 @@ npDescription=Descripci\u00f3n npLoadedSoundcloud=[{0}/{1}]\n\nCargado desde Soundcloud npLoadedBandcamp={0}\n\nCargado desde Bandcamp npLoadedTwitch=Cargado desde Twitch +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 @@ -91,6 +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=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. @@ -102,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 @@ -238,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. @@ -272,7 +285,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. @@ -301,8 +314,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. @@ -333,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 e79ee255f..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. @@ -62,6 +67,7 @@ npDescription=Kirjeldus npLoadedSoundcloud=[{0}/{1}] Laaditud Soundcloudi npLoadedBandcamp={0} Laaditud Bandicampist npLoadedTwitch=Laetud Twitchist +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 @@ -91,6 +97,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. @@ -180,7 +191,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. @@ -238,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. @@ -333,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 2a4a59be0..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. @@ -62,6 +67,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=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 @@ -91,6 +97,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. @@ -238,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. @@ -333,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 b9305ac83..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. @@ -62,6 +67,7 @@ npDescription=Kuvaus npLoadedSoundcloud=[{0}/{1}]\n\nLadattu Soundcloudista npLoadedBandcamp={0}\n\nLadattu Bandcampist\u00e4 npLoadedTwitch=Ladattu Twitchist\u00e4 +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 @@ -91,6 +97,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. @@ -238,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. @@ -333,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 d83eb344f..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. @@ -62,8 +67,9 @@ npDescription=Description npLoadedSoundcloud=[{0}/{1}]\n\nNakuha mula sa Soundcloud npLoadedBandcamp={0}\n\nNakuha mula sa Bandcamp npLoadedTwitch=Nakuha mula sa Twitch +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 @@ -91,6 +97,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. @@ -102,7 +113,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 @@ -139,31 +150,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 @@ -180,11 +191,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. @@ -238,17 +249,19 @@ 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} +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. 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. @@ -272,7 +285,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. @@ -301,8 +314,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. @@ -330,7 +343,6 @@ 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 07cef415c..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,6 +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=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 @@ -91,6 +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=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. @@ -180,7 +191,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. @@ -238,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=Aide\u00e0jouer\u00e0lacommandesuivante +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. @@ -272,7 +285,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. @@ -301,8 +314,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). @@ -333,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 83447d1d7..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. @@ -16,17 +18,20 @@ 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 \! 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 \! @@ -36,18 +41,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... 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* 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,6 +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=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 @@ -73,10 +79,10 @@ 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. +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}. @@ -87,24 +93,29 @@ 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=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=Vous n'\u00eates pas actuellement dans un canal vocal. +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... 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 @@ -122,9 +133,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. @@ -142,59 +153,59 @@ 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. Dommage, tu m'agaces. 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. -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} +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''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 toutes fa\u00e7ons. +catgirlFailConn=J''arrive pas \u00e0 me 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? \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 tout au d\u00e9but de la file d''attente. -loadSingleTrackAndPlay=**{0}** va \u00eatre lue. +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 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... 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=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 \: 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\: @@ -205,21 +216,21 @@ userinfoNick=Pseudo \: userinfoKnownServer=Serveurs connus \: userinfoJoinDate=Date d'inscription\: userinfoCreationTime=Date de cr\u00e9ation \: -userinfoBlacklisted="En liste noire \: +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=Amusement -commandsMemes=M\u00e8mes +commandsFun=Fun +commandsMemes=Memes 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 \: -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 @@ -237,13 +248,15 @@ 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} +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=Bascule entre les modes de r\u00e9p\u00e9tition. +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. @@ -257,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. +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. @@ -272,7 +285,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. @@ -283,33 +296,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\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}`. 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 \! 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. -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} @@ -323,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. @@ -333,4 +346,3 @@ modulesHowTo=Pour me donner plus de boulot ou m''en retirer, m\u00eame si je sai parseNotAUser=Votre entr\u00e9e {0} n''a renvoy\u00e9 \u00e0 un utilisateur Discord. 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 aae160ce1..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. @@ -62,6 +67,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=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 @@ -91,6 +97,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. @@ -238,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. @@ -333,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 30c6c4ff5..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. @@ -62,6 +67,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=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 @@ -91,6 +97,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. @@ -238,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. @@ -333,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 9367fb4cd..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. @@ -62,6 +67,7 @@ npDescription=Opis npLoadedSoundcloud=[{0}/{1}] \n\nU\u010ditano sa SoundClouda npLoadedBandcamp={0}\n\nU\u010ditano sa Bandcampa npLoadedTwitch=U\u010ditano sa Twitcha +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 @@ -91,6 +97,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. @@ -238,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. @@ -333,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 4cee3fa94..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,6 +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=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 @@ -91,6 +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=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. @@ -102,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 @@ -238,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. @@ -272,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. @@ -301,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. @@ -333,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 2586c0552..8e58146e9 100644 --- a/FredBoat/src/main/resources/lang/id_ID.properties +++ b/FredBoat/src/main/resources/lang/id_ID.properties @@ -6,7 +6,9 @@ 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`** +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. @@ -41,8 +46,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,18 +57,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 -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 @@ -80,7 +86,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}`. @@ -91,6 +97,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. @@ -149,7 +160,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. @@ -238,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. @@ -272,7 +285,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. @@ -301,8 +314,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}. @@ -314,7 +327,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 @@ -324,8 +337,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\: @@ -333,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 6952dfb6a..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. @@ -46,7 +51,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,6 +67,7 @@ npDescription=Descrizione npLoadedSoundcloud=[{0}/{1}]\n\nCaricato da SoundCloud npLoadedBandcamp={0}\n\nCaricato da Bandcamp npLoadedTwitch=Caricato da Twitch +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 @@ -91,6 +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=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. @@ -226,8 +237,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. @@ -238,19 +249,21 @@ 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. 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\: @@ -267,12 +280,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. @@ -288,7 +301,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? @@ -301,8 +314,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. @@ -333,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 cd15c5721..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 @@ -62,6 +67,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=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 @@ -91,6 +97,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 @@ -219,7 +230,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 @@ -238,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 @@ -272,11 +285,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 @@ -286,7 +299,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 @@ -294,15 +307,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 @@ -333,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 1cdf3e6f8..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. @@ -62,6 +67,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=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 @@ -91,6 +97,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=\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=\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. playerJoinConnectDenied=\uc74c\uc131 \ucc44\ub110\uc5d0 \ub4e4\uc5b4\uac08 \uad8c\ud55c\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. @@ -238,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. @@ -333,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 91c85c0e7..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. @@ -62,6 +67,7 @@ npDescription=Keterangan npLoadedSoundcloud=[{0}/{1}]\n\nDimuatkan dari Soundcloud npLoadedBandcamp={0}\n\nDimuatkan dari Bandcamp npLoadedTwitch=Dimuatkan dari Twitch +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 @@ -91,6 +97,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. @@ -238,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. @@ -333,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 59d778dbc..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,6 +67,7 @@ npDescription=Beschrijving npLoadedSoundcloud=[{0}/{1}] \n\nGeladen vanuit Soundcloud npLoadedBandcamp={0}\n\nGeladen vanuit Bandcamp npLoadedTwitch=Geladen vanuit Twitch +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 @@ -91,6 +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=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. @@ -180,7 +191,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. @@ -238,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 -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=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. @@ -272,7 +285,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. @@ -301,8 +314,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. @@ -333,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 30d3c3f40..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. @@ -62,6 +67,7 @@ npDescription=Beskrivelse npLoadedSoundcloud=[{0}/{1}] Lastet fra Soundcloud npLoadedBandcamp={0} lastet inn fra Bandcamp npLoadedTwitch=Lastet inn fra Twitch +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 @@ -91,6 +97,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. @@ -238,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. @@ -333,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 21de13eb0..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,6 +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=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 @@ -91,6 +97,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=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. @@ -180,7 +191,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. @@ -238,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. @@ -272,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. @@ -301,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. @@ -333,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 ae35cf7f9..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,9 +67,10 @@ npDescription=Descri\u00e7\u00e3o npLoadedSoundcloud=[{0}/{1}] \n\nCarregado do Soundcloud npLoadedBandcamp={0} \n\nCarregado do Bandcamp npLoadedTwitch=Carregado do Twitch +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 de Embeds +permissionEmbedLinks=Inserir Links rating=Notas listeners=Ouvintes year=Ano @@ -91,6 +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=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. @@ -102,7 +113,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 @@ -238,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} -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=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. @@ -272,7 +285,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. @@ -301,8 +314,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. @@ -333,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 f89c5b0c7..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. @@ -62,8 +67,9 @@ 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} 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 @@ -91,6 +97,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. @@ -102,7 +113,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 @@ -139,18 +150,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. @@ -159,11 +170,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 @@ -180,7 +191,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. @@ -229,26 +240,28 @@ 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} +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. 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. @@ -272,7 +285,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. @@ -301,8 +314,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. @@ -330,7 +343,6 @@ 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 a1782ed75..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,6 +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=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 @@ -91,6 +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=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. @@ -238,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. @@ -272,7 +285,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. @@ -301,8 +314,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}. @@ -333,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 843a2b4e4..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,6 +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=\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 @@ -91,6 +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=\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. @@ -102,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 @@ -238,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. @@ -272,7 +285,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. @@ -301,8 +314,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. @@ -333,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 faeaaf192..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. @@ -62,6 +67,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=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 @@ -91,6 +97,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. @@ -238,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. @@ -333,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 ba65f2362..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,6 +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=\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 @@ -91,6 +97,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. @@ -238,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. @@ -272,7 +285,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. @@ -301,8 +314,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. @@ -333,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 7d7667ca8..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. @@ -62,6 +67,7 @@ npDescription=Beskrivning npLoadedSoundcloud=[{0}/{1}]\n\nLaddas fr\u00e5n Soundcloud npLoadedBandcamp={0}\n\nLaddad fr\u00e5n Bandcamp npLoadedTwitch=Laddad fr\u00e5n Twitch +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 @@ -91,6 +97,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. @@ -102,7 +113,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 @@ -180,7 +191,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. @@ -238,17 +249,19 @@ 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} +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. 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. @@ -272,7 +285,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. @@ -301,8 +314,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. @@ -333,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 e09236dd4..5c6cb0873 100644 --- a/FredBoat/src/main/resources/lang/th_TH.properties +++ b/FredBoat/src/main/resources/lang/th_TH.properties @@ -1,12 +1,14 @@ #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 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 @@ -62,6 +67,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=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 @@ -91,6 +97,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 @@ -102,7 +113,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 @@ -180,7 +191,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. @@ -238,17 +249,19 @@ 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} +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 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 @@ -272,7 +285,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 @@ -301,8 +314,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 @@ -333,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 af0e3851f..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. @@ -62,6 +67,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=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 @@ -91,6 +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=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. @@ -180,7 +191,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. @@ -238,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. @@ -272,7 +285,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. @@ -301,8 +314,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. @@ -333,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 8a711752e..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,33 +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=\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}**. @@ -91,6 +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=\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. @@ -238,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. @@ -272,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. @@ -290,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 59e7fd72c..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. @@ -62,6 +67,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=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 @@ -91,6 +97,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. @@ -180,7 +191,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. @@ -238,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. @@ -333,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 d83dcf2c8..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. @@ -62,6 +67,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 @@ -91,6 +97,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. @@ -238,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. @@ -333,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 b31d079e9..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 @@ -62,6 +67,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=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 @@ -91,6 +97,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 @@ -238,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 @@ -333,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 679700f43..b6704e0d7 100644 --- a/FredBoat/src/main/resources/lang/zh_TW.properties +++ b/FredBoat/src/main/resources/lang/zh_TW.properties @@ -1,19 +1,21 @@ #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}`... 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 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 @@ -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,6 +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=\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 @@ -79,7 +85,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 @@ -91,6 +97,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 @@ -102,7 +113,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 @@ -146,10 +157,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 @@ -164,7 +175,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 @@ -184,7 +195,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} @@ -194,7 +205,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 @@ -219,31 +230,33 @@ 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} +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=\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 +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 @@ -251,7 +264,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 @@ -267,12 +280,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 @@ -301,8 +314,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} @@ -312,7 +325,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 @@ -328,9 +341,8 @@ 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 - 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..15514b065 --- /dev/null +++ b/FredBoat/src/test/java/fredboat/audio/PlayerRegistryTest.kt @@ -0,0 +1,38 @@ +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.reactive.awaitFirst +import kotlinx.coroutines.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 + 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 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..e024b21d1 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,24 @@ 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(url2, track?.track?.info?.uri) + + // Check that the first track is still playing + assertEquals(url, players.getOrCreate(guild).block()?.playingTrack?.track?.info?.uri) } } @@ -97,7 +103,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/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"); } } diff --git a/FredBoat/src/test/java/fredboat/perms/PermsUtilTest.kt b/FredBoat/src/test/java/fredboat/perms/PermsUtilTest.kt index be6231d42..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,34 +34,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 = { GuildSettings(it, permissions = PermissionEntity(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 = { GuildSettings(it, permissions = PermissionEntity(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 = { GuildSettings(it, permissions = PermissionEntity(userList = listOf(Raws.adminRole.id))) } assertEquals(PermissionLevel.USER, Raws.napster.level) } diff --git a/FredBoat/src/test/java/fredboat/testutil/config/MockConfig.kt b/FredBoat/src/test/java/fredboat/testutil/config/MockConfig.kt index 0e8255823..5073d4f82 100644 --- a/FredBoat/src/test/java/fredboat/testutil/config/MockConfig.kt +++ b/FredBoat/src/test/java/fredboat/testutil/config/MockConfig.kt @@ -112,6 +112,8 @@ class MockConfig : AppConfig, AudioSourcesConfig, Credentials, EventLoggerConfig override fun getWastebinPass() = "" + override fun getWebInfoBaseUrl() = "" + override fun getQuarterdeck() = object : BackendConfig.Quarterdeck { override fun getHost() = "http://localhost:4269" override fun getUser() = "test" diff --git a/FredBoat/src/test/java/fredboat/testutil/sentinel/testDsl.kt b/FredBoat/src/test/java/fredboat/testutil/sentinel/testDsl.kt index d8ea8c64e..e456342db 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. 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)) } -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) { diff --git a/FredBoat/src/test/java/fredboat/testutil/util/MockGuildPermsService.kt b/FredBoat/src/test/java/fredboat/testutil/util/MockGuildPermsService.kt index cb795c13c..add3dc6b1 100644 --- a/FredBoat/src/test/java/fredboat/testutil/util/MockGuildPermsService.kt +++ b/FredBoat/src/test/java/fredboat/testutil/util/MockGuildPermsService.kt @@ -1,24 +1,35 @@ package fredboat.testutil.util -import fredboat.db.api.GuildPermsService -import fredboat.db.transfer.GuildPermissions -import fredboat.sentinel.Guild +import fredboat.db.api.GuildSettingsRepository +import fredboat.db.transfer.GuildSettings 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 : GuildSettingsRepository { - final val default: (guild: Guild) -> GuildPermissions = { guild -> - GuildPermissions().apply { id = guild.id.toString() } + override fun default(id: Long): GuildSettings { + return GuildSettings(id) } + + final val default: (guild: Long) -> GuildSettings = { GuildSettings(it) } var factory = default - override fun fetchGuildPermissions(guild: Guild) = factory(guild) + override fun fetch(id: Long): Mono { + return Mono.just(factory(id)) + } + + override fun update(mono: Mono): Mono { + return mono.flatMap { Mono.just(factory(it.id)) } + } - override fun transformGuildPerms(guild: Guild, transformation: Function) - = transformation.apply(factory(guild)) + override fun update(target: GuildSettings): Mono { + return Mono.just(target) + } + override fun remove(id: Long): Mono { + return Mono.empty() + } } \ No newline at end of file diff --git a/FredBoat/src/test/java/fredboat/testutil/util/playerUtils.kt b/FredBoat/src/test/java/fredboat/testutil/util/playerUtils.kt index 76ade27fe..97e6148ab 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) @@ -36,6 +36,7 @@ fun Guild.queue( } player.queue(AudioTrackContext(track, member)) + player.play() return player } diff --git a/FredBoat/src/test/resources/docker-compose.yaml b/FredBoat/src/test/resources/docker-compose.yaml index 5e927f4b8..169b851b0 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: 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 722c70c79..5fac26eb0 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.20' @@ -59,12 +59,13 @@ subprojects { //@formatter:off sentinelVersion = 'd8680e86' - springBootVersion = "${springBootVersion}" - kotlinVersion = "${kotlinVersion}" + springBootVersion = springBootVersion + springWebSocketVersion = '5.0.8.RELEASE' + kotlinVersion = kotlinVersion //audio deps - lavaplayerVersion = '1.3.9' - lavalinkVersion = 'cab6c587' + lavaplayerVersion = '1.3.19' + lavalinkVersion = '9f6c9c50' //utility deps jsonOrgVersion = '20180130' @@ -76,9 +77,10 @@ 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" + amqpVersion = "2.1.4.RELEASE" jacksonKotlinVersion = "2.9.+" //logging / monitoring deps diff --git a/config/templates/docker-compose.yml b/config/templates/docker-compose.yml index c1d8d8eba..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 # ################################################################################ @@ -25,104 +24,71 @@ 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 + ## 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) + ## MongoDB ################################################################################ - 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 + mongo: + image: mongo:latest ports: - - 127.0.01:4269:4269 + - 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: - - ./quarterdeck.yml:/opt/Quarterdeck/quarterdeck.yaml - - ./quarterdeck_logs:/opt/Quarterdeck/logs + - ./mongo:/data/db" - # 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"}]}} + # 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 + ## 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,16 +105,16 @@ 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 +# 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/fredboat.yml b/config/templates/fredboat.yml index 29826e499..45e57e8d5 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,25 +89,13 @@ audio-sources: ### Essential credentials ################################################################ -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" - - 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.yml #discordBotToken: "" 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 debd1c6dd..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 # ################################################################################ @@ -25,105 +24,53 @@ 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 + ## 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: - # 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 - # 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 - 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 + ## FredBoat ################################################################################ bot: # for choosing between stable or dev, read the paragraph above in the Quarterdeck section @@ -136,7 +83,7 @@ services: labels: - "com.centurylinklabs.watchtower.enable=true" depends_on: - - quarterdeck + - mongo - sentinel - lavalink ports: @@ -158,9 +105,26 @@ 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 + 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 + ## 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,16 +141,16 @@ 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 +# 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