mirror of
https://github.com/UiO-Gaming/ShiteMusicBot.git
synced 2026-11-05 13:56:51 +01:00
Merge branch 'dev' into 'master'
Update master See merge request Ev-1/shite-music-bot!10
This commit is contained in:
commit
de19c11008
25 files changed
+947
-224
No files matched your search
@@ -8,6 +8,7 @@ import traceback
|
||||
from discord.ext import commands
|
||||
from cogs.utils.settings import Settings
|
||||
|
||||
from cogs.utils.localizer import Localizer
|
||||
|
||||
initial_extension = [
|
||||
'cogs.cogs',
|
||||
@@ -35,6 +36,7 @@ class Bot(commands.Bot):
|
||||
description=config["description"])
|
||||
|
||||
self.settings = Settings(**config)
|
||||
self.localizer = Localizer(self.settings.get_language_path(), "en_en")
|
||||
self.debug = debug
|
||||
|
||||
for extension in initial_extension:
|
||||
@@ -61,7 +63,7 @@ class Bot(commands.Bot):
|
||||
pass
|
||||
|
||||
elif isinstance(err, commands.NoPrivateMessage):
|
||||
await ctx.send("Denne kommandoen er ikke tilgjengelig i DMs")
|
||||
await ctx.send(self.localizer.format_string("{commands.not_available_dm}", setting.get_locale(ctx.guild.id)))
|
||||
|
||||
elif isinstance(err, commands.CheckFailure):
|
||||
pass
|
||||
@@ -84,6 +86,7 @@ class Bot(commands.Bot):
|
||||
print(f'Version: {discord.__version__}\n')
|
||||
|
||||
self.load_extension('cogs.music')
|
||||
self.load_extension('cogs.musicevents')
|
||||
|
||||
await self.change_presence(activity=discord.Game(type=0,
|
||||
name=config["playing"]),
|
||||
|
||||
@@ -17,6 +17,13 @@ class BotSettings:
|
||||
await ctx.invoke(self.bot.get_command('help'),
|
||||
ctx.command.qualified_name)
|
||||
|
||||
@commands.guild_only()
|
||||
@_set.command(name='serverlocale')
|
||||
async def _set_guild_locale(self, ctx, locale):
|
||||
self.settings.set_locale(ctx.guild.id, locale)
|
||||
locale = self.settings.get_locale(ctx.guild.id)
|
||||
await ctx.send(f'Locale set to {locale}')
|
||||
|
||||
@commands.guild_only()
|
||||
@_set.command(name='serverprefix')
|
||||
async def _set_guild_prefix(self, ctx, *prefixes):
|
||||
|
||||
@@ -2,6 +2,7 @@ import discord
|
||||
import os
|
||||
import json
|
||||
import codecs
|
||||
import traceback
|
||||
from discord.ext import commands
|
||||
from cogs.utils import checks
|
||||
|
||||
@@ -66,6 +67,7 @@ class Cogs:
|
||||
@checks.is_owner()
|
||||
async def _shutdown(self, ctx):
|
||||
"""Logs out and stops."""
|
||||
self.bot.lavalink.players.clear()
|
||||
await self.bot.logout()
|
||||
|
||||
|
||||
|
||||
+74
-17
@@ -3,16 +3,17 @@ import os
|
||||
import asyncio
|
||||
import time
|
||||
import random
|
||||
import platform
|
||||
|
||||
from discord.ext import commands
|
||||
from cogs.utils import checks
|
||||
from lavalink import __version__ as LavalinkVersion
|
||||
|
||||
class Misc:
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
@commands.command(name='ping', hidden=True)
|
||||
@checks.is_mod()
|
||||
async def _ping(self, ctx):
|
||||
start = time.perf_counter()
|
||||
message = await ctx.send('Ping...')
|
||||
@@ -40,27 +41,83 @@ class Misc:
|
||||
await ctx.send(guilds)
|
||||
|
||||
@commands.command()
|
||||
async def info(self, ctx, *, channel: str=None):
|
||||
async def musicinfo(self, ctx):
|
||||
"""
|
||||
Info om LovherkBot
|
||||
Info om musikkspilleren
|
||||
"""
|
||||
avatar = self.bot.user.avatar_url_as(format=None,
|
||||
static_format='png',
|
||||
size=1024)
|
||||
infotext = f'Shite Music Bot'
|
||||
embed = discord.Embed(color=0xD9C04D)
|
||||
embed.set_author(name=self.bot.user.name, icon_url=avatar)
|
||||
embed.set_thumbnail(url=avatar)
|
||||
embed.add_field(name="Hva?",
|
||||
value=infotext, inline=False)
|
||||
embed.set_footer(icon_url="https://i.imgur.com/dE6JaeT.gif",
|
||||
text="Laget av /r/Norge")
|
||||
embed = discord.Embed(title='{info.music.title}', color=ctx.me.color)
|
||||
lavalink = self.bot.lavalink
|
||||
|
||||
listeners = 0
|
||||
for guild, player in lavalink.players:
|
||||
listeners += len(player.listeners)
|
||||
|
||||
embed.add_field(name='{info.music.players}', value=f'{len(lavalink.players)}')
|
||||
embed.add_field(name='{info.music.listeners}', value=f'{listeners}')
|
||||
embed = self.bot.localizer.format_embed(embed, self.bot.settings.get_locale(ctx.guild.id))
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
|
||||
|
||||
@commands.command(name="reloadlocale")
|
||||
async def reload_locale(self, ctx):
|
||||
self.bot.localizer.index_localizations()
|
||||
self.bot.localizer.load_localizations()
|
||||
await ctx.send("Localizations reloaded.")
|
||||
|
||||
@commands.command()
|
||||
@checks.is_even()
|
||||
async def only_me(self, ctx):
|
||||
await ctx.send('Only you!')
|
||||
async def info(self, ctx):
|
||||
"""
|
||||
Info om Shite Music Bot
|
||||
"""
|
||||
membercount = []
|
||||
for guild in self.bot.guilds:
|
||||
for member in guild.members:
|
||||
if member.id in membercount:
|
||||
pass
|
||||
else:
|
||||
membercount.append(member.id)
|
||||
guilds = len(self.bot.guilds)
|
||||
members = len(membercount)
|
||||
now = time.time()
|
||||
diff = int(now - self.bot.uptime)
|
||||
days, remainder = divmod(diff, 24 * 60 * 60)
|
||||
hours, remainder = divmod(remainder, 60 * 60)
|
||||
minutes, seconds = divmod(remainder, 60)
|
||||
avatar = self.bot.user.avatar_url_as(format=None,
|
||||
static_format='png',
|
||||
size=1024)
|
||||
|
||||
uptimetext = f'{days}d {hours}t {minutes}m {seconds}s'
|
||||
embed = discord.Embed(color=ctx.me.color)
|
||||
embed.set_author(name=self.bot.user.name, icon_url=avatar)
|
||||
embed.set_thumbnail(url=avatar)
|
||||
embed.set_image(url='https://cdn.discordapp.com/attachments/298524946454282250/368118192251469835/vintage1turntable.png')
|
||||
embed.add_field(name="{info.bot.what}",
|
||||
value='{info.bot.infotext}', inline=False)
|
||||
embed.set_footer(icon_url="https://cdn.discordapp.com/icons/532176350019321917/92f43a1f67308a99a30c169db4b671dd.png?size=64",
|
||||
text="{info.bot.footer_text}")
|
||||
embed.add_field(name="{info.bot.how}",
|
||||
value='{info.bot.spectext}')
|
||||
embed.add_field(name="{info.bot.how_many}",
|
||||
value='{info.bot.stattext}')
|
||||
embed.add_field(name="{info.bot.how_long}",
|
||||
value=uptimetext)
|
||||
|
||||
embed = self.bot.localizer.format_embed(embed,
|
||||
self.bot.settings.get_locale(ctx.guild.id),
|
||||
_python_v=platform.python_version(),
|
||||
_discord_v=discord.__version__,
|
||||
_lavalink_v=LavalinkVersion,
|
||||
_guilds=guilds,
|
||||
_members=members
|
||||
)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.command()
|
||||
@checks.is_even()
|
||||
async def only_me(self, ctx):
|
||||
await ctx.send('Only you!')
|
||||
|
||||
|
||||
def setup(bot):
|
||||
|
||||
+203
-158
@@ -1,6 +1,5 @@
|
||||
"""
|
||||
This is an example cog that shows how you would make use of Lavalink.py.
|
||||
This example cog requires that you have python 3.6 or higher due to the f-strings.
|
||||
Music commands
|
||||
"""
|
||||
import math
|
||||
import re
|
||||
@@ -18,6 +17,8 @@ from typing import Optional
|
||||
from .utils.embedscroller import QueueScroller
|
||||
from lavasettings import *
|
||||
|
||||
from .utils.localizer import LocalizerWrapper
|
||||
|
||||
time_rx = re.compile('[0-9]+')
|
||||
url_rx = re.compile('https?:\\/\\/(?:www\\.)?.+')
|
||||
|
||||
@@ -31,19 +32,14 @@ class Music:
|
||||
bot.lavalink.add_node(host, port, password, region, 'default-node') # Host, Port, Password, Region, Name
|
||||
bot.add_listener(bot.lavalink.voice_update_handler, 'on_socket_response')
|
||||
|
||||
bot.lavalink.add_event_hook(self.track_hook)
|
||||
def getLocalizer(self, guild_id):
|
||||
lang = self.bot.settings.get_locale(guild_id)
|
||||
return LocalizerWrapper(self.bot.localizer, lang, "music.response")
|
||||
|
||||
def __unload(self):
|
||||
self.bot.lavalink._event_hooks.clear()
|
||||
|
||||
async def track_hook(self, event):
|
||||
if isinstance(event, lavalink.events.TrackEndEvent):
|
||||
pass # Send track ended message to channel.
|
||||
if isinstance(event, lavalink.events.QueueEndEvent):
|
||||
channel = self.bot.get_channel(event.player.fetch('channel'))
|
||||
await self.check_leave_voice(channel.guild)
|
||||
|
||||
async def __before_invoke(self, ctx):
|
||||
# TODO: rewrite this thing. Probably remove ensure_voice.
|
||||
|
||||
guild_check = ctx.guild is not None
|
||||
# This is essentially the same as `@commands.guild_only()`
|
||||
# except it saves us repeating ourselves (and also a few lines).
|
||||
@@ -59,11 +55,11 @@ class Music:
|
||||
ws = self.bot._connection._get_websocket(guild_id)
|
||||
await ws.voice_state(str(guild_id), channel_id)
|
||||
|
||||
@commands.command(aliases=['p'])
|
||||
async def play(self, ctx, *, query: str):
|
||||
@commands.command(name='play', aliases=['p','spill','s','spel'])
|
||||
async def _play(self, ctx, *, query: str):
|
||||
""" Searches and plays a song from a given query. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
|
||||
localizer = self.getLocalizer(ctx.guild.id)
|
||||
query = query.strip('<>')
|
||||
|
||||
if not url_rx.match(query):
|
||||
@@ -72,9 +68,9 @@ class Music:
|
||||
results = await player.node.get_tracks(query)
|
||||
|
||||
if not results or not results['tracks']:
|
||||
return await ctx.send('Nothing found!')
|
||||
return await ctx.send(localizer.format_str("{nothing_found}"))
|
||||
|
||||
embed = discord.Embed(color=0xEFD26C)
|
||||
embed = discord.Embed(color=ctx.me.color)
|
||||
|
||||
if results['loadType'] == 'PLAYLIST_LOADED':
|
||||
tracks = results['tracks']
|
||||
@@ -82,32 +78,35 @@ class Music:
|
||||
for track in tracks:
|
||||
player.add(requester=ctx.author.id, track=track)
|
||||
|
||||
embed.title = 'Playlist Enqueued!'
|
||||
embed.description = f'{results["playlistInfo"]["name"]} - {len(tracks)} tracks'
|
||||
embed.title = '{playlist_enqued}'
|
||||
embed.description = f'{results["playlistInfo"]["name"]} - {len(tracks)} {{tracks}}'
|
||||
embed = localizer.format_embed(embed)
|
||||
await ctx.send(embed=embed)
|
||||
else:
|
||||
track = results['tracks'][0]
|
||||
await self.enqueue(ctx, track, embed)
|
||||
embed = localizer.format_embed(embed)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
if not player.is_playing:
|
||||
await player.play()
|
||||
|
||||
@commands.command()
|
||||
@checks.DJ_or_alone()
|
||||
async def seek(self, ctx, *, time: str):
|
||||
@commands.command(name='seek', aliases=['spol'])
|
||||
@checks.DJ_or(alone=True)
|
||||
async def _seek(self, ctx, *, time: str):
|
||||
""" Seeks to a given position in a track. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
|
||||
localizer = self.getLocalizer(ctx.guild.id)
|
||||
|
||||
if not player.is_playing:
|
||||
return await ctx.send('Not playing.')
|
||||
return await ctx.send(localizer.format_str("{not_playing}"))
|
||||
|
||||
if ctx.author not in player.listeners:
|
||||
return await ctx.send('You have to be listening to the bot')
|
||||
return await ctx.send(localizer.format_str("{have_to_listen}"))
|
||||
|
||||
seconds = time_rx.search(time)
|
||||
if not seconds:
|
||||
return await ctx.send('You need to specify the amount of seconds to skip!')
|
||||
return await ctx.send(localizer.format_str("{seek.missing_amount}"))
|
||||
|
||||
seconds = int(seconds.group()) * 1000
|
||||
if time.startswith('-'):
|
||||
@@ -115,89 +114,104 @@ class Music:
|
||||
|
||||
track_time = player.position + seconds
|
||||
await player.seek(track_time)
|
||||
msg = localizer.format_str("{seek.track_moved}",_position=lavalink.utils.format_time(track_time))
|
||||
await ctx.send(msg)
|
||||
|
||||
await ctx.send(f'Moved track to **{lavalink.utils.format_time(track_time)}**')
|
||||
|
||||
@commands.command(aliases=['forceskip'])
|
||||
async def skip(self, ctx):
|
||||
@commands.command(name='skip', aliases=['hopp'])
|
||||
async def _skip(self, ctx):
|
||||
""" Skips the current track. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
localizer = self.getLocalizer(ctx.guild.id)
|
||||
|
||||
if not player.is_playing:
|
||||
return await ctx.send('Not playing.')
|
||||
return await ctx.send(localizer.format_str("{not_playing}"))
|
||||
|
||||
player.add_skipper(ctx.author)
|
||||
total = len(player.listeners)
|
||||
skips = len(player.skip_voters)
|
||||
if skips >= math.ceil(total/2) or player.current.requester == ctx.author.id:
|
||||
await player.skip()
|
||||
await ctx.send('Skipped.')
|
||||
await ctx.send(localizer.format_str("{skip.skipped}"))
|
||||
else:
|
||||
if skips != 0:
|
||||
await ctx.send(f'{skips} out of {math.ceil(total/2)} required haters have voted to skip.')
|
||||
msg = localizer.format_str("{skip.require_vote}", _skips=skips, _total=math.ceil(total/2))
|
||||
await ctx.send(msg)
|
||||
|
||||
@commands.command(name='skipto', aliases=['st','skip_to'])
|
||||
@checks.DJ_or_alone()
|
||||
async def skip_to(self, ctx, pos: int):
|
||||
@commands.command(name='skipto', aliases=['forceskip','hopptil','tvinghopp'])
|
||||
@checks.DJ_or(alone=True)
|
||||
async def _skip_to(self, ctx, pos: int=1):
|
||||
""" Plays the queue from a specific point. Disregards tracks before the pos. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
localizer = self.getLocalizer(ctx.guild.id)
|
||||
|
||||
if ctx.author not in player.listeners:
|
||||
return await ctx.send('You have to be listening to the bot')
|
||||
return await ctx.send(localizer.format_str("{have_to_listen}"))
|
||||
|
||||
if pos < 1:
|
||||
return await ctx.send('Invalid specified position.')
|
||||
return await ctx.send(localizer.format_str("{skip_to.invalid_pos}"))
|
||||
if len(player.queue) < pos:
|
||||
return await ctx.send('The position exceeds the queue\'s length.')
|
||||
return await ctx.send(localizer.format_str("{skip_to.exceeds_queue}"))
|
||||
await player.skip(pos - 1)
|
||||
await ctx.send(f'skipped to `{player.current.title}` at position `{pos}`')
|
||||
msg = localizer.format_str("{skip_to.skipped_to}", _title=player.current.title, _pos=pos)
|
||||
print(msg, player.current.title, pos)
|
||||
await ctx.send(msg)
|
||||
|
||||
@commands.command()
|
||||
@checks.DJ_or_alone()
|
||||
async def stop(self, ctx):
|
||||
@commands.command(name='stop', aliases=['stopp'])
|
||||
@checks.DJ_or(alone=True)
|
||||
async def _stop(self, ctx):
|
||||
""" Stops the player and clears its queue. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
localizer = self.getLocalizer(ctx.guild.id)
|
||||
|
||||
if ctx.author not in player.listeners:
|
||||
return await ctx.send('You have to be listening to the bot')
|
||||
return await ctx.send(localizer.format_str("{have_to_listen}"))
|
||||
|
||||
if not player.is_playing:
|
||||
return await ctx.send('Not playing.')
|
||||
return await ctx.send(localizer.format_str("{not_playing}"))
|
||||
|
||||
player.queue.clear()
|
||||
await player.stop()
|
||||
await ctx.send('⏹ | Stopped.')
|
||||
await ctx.send(localizer.format_str("{stop}"))
|
||||
|
||||
@commands.command(aliases=['np', 'n', 'playing'])
|
||||
async def now(self, ctx):
|
||||
@commands.command(name='now', aliases=['np','current', 'nå', 'nåspilles', 'gjeldende', 'spillernå','spelarno','nospelar'])
|
||||
async def _now(self, ctx):
|
||||
""" Shows some stats about the currently playing song. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
localizer = self.getLocalizer(ctx.guild.id)
|
||||
|
||||
if not player.current:
|
||||
return await ctx.send('Nothing playing.')
|
||||
return await ctx.send(localizer.format_str("{not_playing}"))
|
||||
|
||||
position = lavalink.utils.format_time(player.position)
|
||||
if player.current.stream:
|
||||
duration = '🔴 LIVE'
|
||||
duration = '{live}'
|
||||
else:
|
||||
duration = lavalink.utils.format_time(player.current.duration)
|
||||
song = f'**[{player.current.title}]({player.current.uri})**\n({position}/{duration})'
|
||||
|
||||
embed = discord.Embed(color=0xEFD26C,
|
||||
title='Now Playing', description=song)
|
||||
|
||||
member = ctx.guild.get_member(player.current.requester)
|
||||
embed = discord.Embed(color=ctx.me.color, description=song, title='{now}')
|
||||
thumbnail_url = await RoxUtils.ThumbNailer.identify(self,player.current.identifier, player.current.uri)
|
||||
if thumbnail_url:
|
||||
embed.set_thumbnail(url=thumbnail_url)
|
||||
|
||||
if member.nick:
|
||||
member_identifier = member.nick
|
||||
else:
|
||||
member_identifier = member.name
|
||||
embed.set_footer(text=f'{{requested_by}} {member_identifier}', icon_url=member.avatar_url)
|
||||
|
||||
embed = localizer.format_embed(embed)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.command(aliases=['q'])
|
||||
async def queue(self, ctx, user: discord.Member=None):
|
||||
@commands.command(name='queue', aliases=['q', 'kø'])
|
||||
async def _queue(self, ctx, user: discord.Member=None):
|
||||
""" Shows the player's queue. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
localizer = self.getLocalizer(ctx.guild.id)
|
||||
|
||||
if player.queue.empty:
|
||||
return await ctx.send('The queue is empty')
|
||||
return await ctx.send(localizer.format_str("{queue.empty}"))
|
||||
|
||||
if user is None:
|
||||
queue = player.global_queue()
|
||||
@@ -207,73 +221,80 @@ class Music:
|
||||
else:
|
||||
user_queue = player.user_queue(user.id, dual=True)
|
||||
if not user_queue:
|
||||
return await ctx.send(f'{user.name}\'s queue is empty')
|
||||
return await ctx.send(localizer.format_str("{queue.empty}", _user=user.name))
|
||||
|
||||
scroller = QueueScroller(ctx, user_queue, lines_per_page=10, user_name=user.name)
|
||||
await scroller.start_scrolling()
|
||||
|
||||
@commands.command(name='myqueue', aliases=['mq'])
|
||||
@commands.command(name='myqueue', aliases=['mq','minkø','mk'])
|
||||
async def _myqueue(self, ctx):
|
||||
""" Shows your queue. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
localizer = self.getLocalizer(ctx.guild.id)
|
||||
|
||||
user_queue = player.user_queue(ctx.author.id, dual=True)
|
||||
if not user_queue:
|
||||
return await ctx.send('Your queue is empty')
|
||||
return await ctx.send(localizer.format_str("{my_queue}"))
|
||||
|
||||
scroller = QueueScroller(ctx, user_queue, lines_per_page=10, user_name=ctx.author.name)
|
||||
await scroller.start_scrolling()
|
||||
|
||||
@commands.command(aliases=['resume'])
|
||||
@checks.is_DJ()
|
||||
async def pause(self, ctx):
|
||||
@commands.command(name='pause', aliases=['resume','gjenoppta'])
|
||||
@checks.DJ_or(alone=True)
|
||||
async def _pause(self, ctx):
|
||||
""" Pauses/Resumes the current track. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
localizer = self.getLocalizer(ctx.guild.id)
|
||||
|
||||
if not player.is_playing:
|
||||
return await ctx.send('Not playing.')
|
||||
return await ctx.send(localizer.format_str("{not_playing}"))
|
||||
|
||||
if player.paused:
|
||||
await player.set_pause(False)
|
||||
await ctx.send('⏯ | Resumed')
|
||||
await ctx.send(localizer.format_str("{resume.resumed}"))
|
||||
else:
|
||||
await player.set_pause(True)
|
||||
await ctx.send('⏯ | Paused')
|
||||
await ctx.send(localizer.format_str("{resume.paused}"))
|
||||
|
||||
@commands.command(name='shuffle')
|
||||
@commands.command(name='shuffle', aliases=['stokk'])
|
||||
async def _shuffle(self, ctx):
|
||||
""" Shuffles your queue. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
localizer = self.getLocalizer(ctx.guild.id)
|
||||
|
||||
user_queue = player.user_queue(ctx.author.id)
|
||||
|
||||
if not user_queue:
|
||||
return await ctx.send('Your queue is empty')
|
||||
return await ctx.send(localizer.format_str("{my_queue}"))
|
||||
|
||||
player.shuffle_user_queue(ctx.author.id)
|
||||
await ctx.send('Your queue has been shuffled')
|
||||
await ctx.send(localizer.format_str("{shuffle}"))
|
||||
|
||||
@commands.command(name='move', aliases=["m"])
|
||||
@commands.command(name='move', aliases=['m', 'flytt','f'])
|
||||
async def _move(self, ctx, from_pos: int, to_pos: int):
|
||||
""" Moves a song in your queue. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
localizer = self.getLocalizer(ctx.guild.id)
|
||||
|
||||
user_queue = player.user_queue(ctx.author.id)
|
||||
if not user_queue:
|
||||
return await ctx.send('Your queue is empty')
|
||||
return await ctx.send(localizer.format_str("{my_queue}"))
|
||||
|
||||
if not all(x in range(1,len(user_queue)+1) for x in [from_pos, to_pos]):
|
||||
return await ctx.send(f'Positions must be between 1 and {len(user_queue)}')
|
||||
if not all(x in range(1,len(user_queue) + 1) for x in [from_pos, to_pos]):
|
||||
return await ctx.send(localizer.format_str("{out_of_range}", _len=len(user_queue)))
|
||||
|
||||
moved = player.move_user_track(ctx.author.id, from_pos - 1, to_pos - 1)
|
||||
if moved is None:
|
||||
return await ctx.send('Check that the positions exist in your queue')
|
||||
return await ctx.send(localizer.format_str("{move.not_in_queue}"))
|
||||
|
||||
await ctx.send(f'{moved.title} moved from position {from_pos} to {to_pos} in your queue')
|
||||
msg = localizer.format_str("{move.moved_to}", _title=moved.title, _from=from_pos, _to=to_pos)
|
||||
await ctx.send(msg)
|
||||
|
||||
@commands.command()
|
||||
async def remove(self, ctx, pos: int):
|
||||
@commands.command(name='remove', aliases=['rem', 'fjern'])
|
||||
async def _remove(self, ctx, pos: int):
|
||||
""" Removes an item from the player's queue with the given pos. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
localizer = self.getLocalizer(ctx.guild.id)
|
||||
|
||||
if player.queue.empty:
|
||||
return
|
||||
@@ -283,28 +304,29 @@ class Music:
|
||||
return
|
||||
|
||||
if pos > len(user_queue) or pos < 1:
|
||||
return await ctx.send(f'Position has to be between 1 and {len(user_queue)}')
|
||||
return await ctx.send(localizer.format_str("{out_of_range}", _len=len(user_queue)))
|
||||
|
||||
removed = player.remove_user_track(ctx.author.id, pos - 1)
|
||||
|
||||
await ctx.send(f'**{removed.title}** removed.')
|
||||
await ctx.send(localizer.format_str("{remove}", _title=removed.title))
|
||||
|
||||
@commands.command(name="DJremove")
|
||||
@checks.is_DJ()
|
||||
@commands.command(name="DJremove", aliases=['DJfjern','djfjern'])
|
||||
@checks.DJ_or()
|
||||
async def _djremove(self, ctx, pos: int, user: discord.Member=None):
|
||||
""" Remove a song from either the global queue or a users queue"""
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
localizer = self.getLocalizer(ctx.guild.id)
|
||||
|
||||
if user is None:
|
||||
if player.queue.empty:
|
||||
return
|
||||
|
||||
if pos > len(player.queue) or pos < 1:
|
||||
return await ctx.send(f'Position has to be between 1 and {len(player.queue)}')
|
||||
return await ctx.send(localizer.format_str("{out_of_range}", _len=len(player.queue)))
|
||||
|
||||
removed = player.remove_global_track(pos - 1)
|
||||
requester = self.bot.get_user(removed.requester)
|
||||
await ctx.send(f'**{removed.title}** queued by {requester.name} removed.')
|
||||
await ctx.send(localizer.format_str("{dj_removed}", _title=removed.title, _user=requester.name))
|
||||
|
||||
else:
|
||||
if player.queue.empty:
|
||||
@@ -315,16 +337,17 @@ class Music:
|
||||
return
|
||||
|
||||
if pos > len(user_queue) or pos < 1:
|
||||
return await ctx.send(f'Position has to be between 1 and {len(user_queue)}')
|
||||
return await ctx.send(localizer.format_str("{out_of_range}", _len=len(user_queue)))
|
||||
|
||||
removed = player.remove_user_track(user.id, pos - 1)
|
||||
await ctx.send(f'**{removed.title}** queued by {user.name} removed.')
|
||||
await ctx.send(localizer.format_str("{dj_removed}", _title=removed.title, _user=requester.name))
|
||||
|
||||
@commands.command(name="removeuser")
|
||||
@checks.DJ_or_alone()
|
||||
@commands.command(name='removeuser', aliases=['fjernbruker','fjernbrukar'])
|
||||
@checks.DJ_or(alone=True)
|
||||
async def _user_queue_remove(self, ctx, user: discord.Member):
|
||||
""" Remove a song from either the global queue or a users queue"""
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
localizer = self.getLocalizer(ctx.guild.id)
|
||||
|
||||
if player.queue.empty:
|
||||
return
|
||||
@@ -334,22 +357,27 @@ class Music:
|
||||
return
|
||||
|
||||
player.remove_user_queue(user.id)
|
||||
embed = discord.Embed(color=0x36393F)
|
||||
embed.description = f'Removed all songs queued by <@{user.id}>.'
|
||||
|
||||
embed = discord.Embed(color=ctx.me.color)
|
||||
embed.description = "{dj_remove_user}"
|
||||
embed = localizer.format_embed(embed, _id=user.id)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.command()
|
||||
async def search(self, ctx, *, query):
|
||||
@commands.command(name='search', aliases=['søk','finn'])
|
||||
async def _search(self, ctx, *, query):
|
||||
""" Lists the first 10 search results from a given query. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
localizer = self.getLocalizer(ctx.guild.id)
|
||||
|
||||
if not query.startswith('ytsearch:') and not query.startswith('scsearch:'):
|
||||
query = 'ytsearch:' + query
|
||||
|
||||
results = await player.node.get_tracks(query)
|
||||
|
||||
embed = discord.Embed(description='Nothing found', color=0x36393F)
|
||||
embed = discord.Embed(description='{nothing_found}', color=0x36393F)
|
||||
if not results or not results['tracks']:
|
||||
embed = localizer.format_embed(embed)
|
||||
return await ctx.send(embed=embed)
|
||||
|
||||
tracks = results['tracks']
|
||||
@@ -389,7 +417,8 @@ class Music:
|
||||
return True
|
||||
return False
|
||||
|
||||
embed.description = 'searching'
|
||||
embed.description = '{search}'
|
||||
embed = localizer.format_embed(embed)
|
||||
result_msg = await ctx.send(embed=embed)
|
||||
|
||||
for emoji, index in choices[:result_count]:
|
||||
@@ -397,7 +426,11 @@ class Music:
|
||||
await result_msg.add_reaction('❌')
|
||||
|
||||
embed.description = search_results
|
||||
embed.title = 'Results'
|
||||
|
||||
embed.title = '{results}'
|
||||
embed.color = ctx.me.color
|
||||
embed = localizer.format_embed(embed)
|
||||
|
||||
await result_msg.edit(embed=embed)
|
||||
|
||||
try:
|
||||
@@ -407,7 +440,8 @@ class Music:
|
||||
except asyncio.TimeoutError:
|
||||
await result_msg.clear_reactions()
|
||||
embed.title = ''
|
||||
embed.description='Timer Expired'
|
||||
embed.description='{time_expired}'
|
||||
embed = localizer.format_embed(embed)
|
||||
await result_msg.edit(embed=embed)
|
||||
await asyncio.sleep(5)
|
||||
await result_msg.delete()
|
||||
@@ -418,88 +452,120 @@ class Music:
|
||||
await result_msg.clear_reactions()
|
||||
track = tracks[choice - 1]
|
||||
await self.enqueue(ctx, track, embed)
|
||||
embed = localizer.format_embed(embed)
|
||||
await result_msg.edit(embed=embed)
|
||||
if not player.is_playing:
|
||||
await player.play()
|
||||
|
||||
@commands.command(aliases=['dc'])
|
||||
@checks.DJ_or_alone()
|
||||
async def disconnect(self, ctx):
|
||||
@commands.command(name='disconnect', aliases=['dc','kf','koblefra','koblefrå'])
|
||||
@checks.DJ_or(alone=True)
|
||||
async def _disconnect(self, ctx):
|
||||
""" Disconnects the player from the voice channel and clears its queue. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
localizer = self.getLocalizer(ctx.guild.id)
|
||||
|
||||
if not player.is_connected:
|
||||
return await ctx.send('Not connected.')
|
||||
return await ctx.send(localizer.format_str("{not_connected}"))
|
||||
|
||||
if not ctx.author.voice or (player.is_connected and ctx.author.voice.channel.id != int(player.channel_id)):
|
||||
return await ctx.send('You\'re not in my voicechannel!')
|
||||
return await ctx.send(localizer.format_str("{disconnect.not_in_voice}"))
|
||||
|
||||
player.queue.clear()
|
||||
await player.stop()
|
||||
await self.connect_to(ctx.guild.id, None)
|
||||
embed = discord.Embed(description='Disconnected', color=0x36393F)
|
||||
embed = discord.Embed(description='{disconnect.disconnected}', color=ctx.me.color)
|
||||
embed = localizer.format_embed(embed)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.command(aliases=['vol'])
|
||||
@checks.DJ_or_current()
|
||||
async def volume(self, ctx, volume: int = None):
|
||||
@commands.command(name='volume', aliases=['vol', 'volum', 'lydstyrke'])
|
||||
@checks.DJ_or(alone=True, current=True)
|
||||
async def _volume(self, ctx, volume: int = None):
|
||||
""" Changes the player's volume. Must be between 0 and 1000. Error Handling for that is done by Lavalink. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
|
||||
# todo: limit to 50%-125% if user has current song.
|
||||
localizer = self.getLocalizer(ctx.guild.id)
|
||||
|
||||
if not volume:
|
||||
return await ctx.send(f'🔈 | {player.volume}%')
|
||||
|
||||
if int(player.current.requester) == ctx.author.id:
|
||||
if not 50 <= volume <= 125:
|
||||
return await ctx.send(localizer.format_str("{volume.out_of_range}"))
|
||||
|
||||
await player.set_volume(volume)
|
||||
embed = discord.Embed(color=0x36393F)
|
||||
embed.description = f'Volume set to {player.volume}'
|
||||
embed = discord.Embed(color=ctx.me.color)
|
||||
embed.description = "{volume.set_to}"
|
||||
embed = localizer.format_embed(embed, _volume=player.volume)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.command(aliases=['normal','nl'])
|
||||
@checks.DJ_or_alone()
|
||||
async def normalize(self, ctx):
|
||||
@commands.command(name='nomalize', aliases=['normal','nl','normaliser'])
|
||||
@checks.DJ_or(alone=True)
|
||||
async def _normalize(self, ctx):
|
||||
""" Reset the equalizer and """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
localizer = self.getLocalizer(ctx.guild.id)
|
||||
|
||||
await player.set_volume(100)
|
||||
await player.bassboost(False)
|
||||
embed = discord.Embed(color=0x36393F)
|
||||
embed.description = 'Volume and equalizer reset'
|
||||
|
||||
embed = discord.Embed(color=ctx.me.color)
|
||||
embed.description = '{volume.reset}'
|
||||
embed = localizer.format_embed(embed)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.command(name='boost', aliases=['boo'])
|
||||
@checks.DJ_or_alone()
|
||||
@commands.command(name='boost', aliases=['boo','bassforsterker','bassforsterkar'])
|
||||
@checks.DJ_or(alone=True)
|
||||
async def _boost(self, ctx, boost: bool=None):
|
||||
""" Set the equalizer to bass boost the music """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
|
||||
localizer = self.getLocalizer(ctx.guild.id)
|
||||
|
||||
if boost is not None:
|
||||
await player.bassboost(boost)
|
||||
|
||||
embed = discord.Embed(color=0x36393F)
|
||||
embed = discord.Embed(color=ctx.me.color)
|
||||
|
||||
if player.boosted:
|
||||
embed.description = 'Bass boost is on'
|
||||
await ctx.send(embed=embed)
|
||||
embed.description = '{boost.on}'
|
||||
else:
|
||||
embed.description = 'Bass boost is off'
|
||||
await ctx.send(embed=embed)
|
||||
embed.description = '{boost.off}'
|
||||
|
||||
embed = localizer.format_embed(embed)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@commands.command()
|
||||
@checks.DJ_or_alone()
|
||||
async def scrub(self, ctx):
|
||||
@commands.command(name='history', aliases=['h','hist','historie'])
|
||||
async def _history(self, ctx):
|
||||
""" Show the last 10 songs played """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
localizer = self.getLocalizer(ctx.guild.id)
|
||||
history = player.get_history()
|
||||
track = history[0]
|
||||
description = localizer.format_str("{history.current}", _title=track.title, _uri=track.uri,_id=track.requester) + '\n\n'
|
||||
description += localizer.format_str("{history.previous}", _len=len(history)-1) + '\n'
|
||||
thumb_url = await RoxUtils.ThumbNailer.identify(self,
|
||||
track.identifier,
|
||||
track.uri)
|
||||
for index, track in enumerate(history[1:], start=1):
|
||||
description += localizer.format_str("{history.track}",_index=-index, _title=track.title, _uri=track.uri, _id=track.requester) + '\n'
|
||||
|
||||
embed = discord.Embed(title=localizer.format_str('{history.title}'), color=ctx.me.color, description=description)
|
||||
|
||||
if thumb_url:
|
||||
embed.set_thumbnail(url=thumb_url)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
|
||||
@commands.command(name='scrub', aliases=['kontrol','konsoll'])
|
||||
@checks.DJ_or(alone=True)
|
||||
async def _scrub(self, ctx):
|
||||
""" Lists the first 10 search results from a given query. """
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
localizer = self.getLocalizer(ctx.guild.id)
|
||||
|
||||
controls = '''
|
||||
**Controls**
|
||||
Play/pause: \N{BLACK RIGHT-POINTING TRIANGLE} \N{DOUBLE VERTICAL BAR}
|
||||
skip 15 seconds forward/backward: \N{BLACK LEFT-POINTING DOUBLE TRIANGLE} \N{BLACK RIGHT-POINTING DOUBLE TRIANGLE}
|
||||
Skip to beginning/end of song: \N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR} \N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}
|
||||
controls = '{scrub.controls}'
|
||||
embed = discord.Embed(description='{nothing_playing}', color=ctx.me.color)
|
||||
embed = localizer.format_embed(embed)
|
||||
|
||||
'''
|
||||
embed = discord.Embed(description='Nothing playing', color=0x36393F)
|
||||
if player.current is None:
|
||||
return await ctx.send(embed=embed)
|
||||
|
||||
@@ -531,13 +597,15 @@ class Music:
|
||||
return True
|
||||
return False
|
||||
|
||||
embed.description = 'Adding controls'
|
||||
embed.description = '{scrub.add}'
|
||||
embed = localizer.format_embed(embed)
|
||||
msg = await ctx.send(embed=embed)
|
||||
|
||||
for (emoji, _, _) in scrubber:
|
||||
await msg.add_reaction(emoji)
|
||||
|
||||
embed.description = controls
|
||||
embed = localizer.format_embed(embed)
|
||||
await msg.edit(embed=embed)
|
||||
scrubbing = True
|
||||
while scrubbing:
|
||||
@@ -586,33 +654,10 @@ class Music:
|
||||
player.store('channel', ctx.channel.id)
|
||||
await self.connect_to(ctx.guild.id, str(ctx.author.voice.channel.id))
|
||||
|
||||
# Get listeners
|
||||
voice_channel = ctx.author.voice.channel
|
||||
while not player.is_connected:
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
for member in voice_channel.members:
|
||||
if not member.bot:
|
||||
player.update_listeners(member, member.voice)
|
||||
|
||||
else:
|
||||
if int(player.channel_id) != ctx.author.voice.channel.id:
|
||||
raise commands.CommandInvokeError('You need to be in my voicechannel.')
|
||||
|
||||
async def on_voice_state_update(self, member, before, after):
|
||||
if not member.bot:
|
||||
player = self.bot.lavalink.players.get(member.guild.id)
|
||||
if player is not None:
|
||||
player.update_listeners(member, after)
|
||||
await self.check_leave_voice(member.guild)
|
||||
|
||||
async def check_leave_voice(self, guild):
|
||||
player = self.bot.lavalink.players.get(guild.id)
|
||||
if len(player.listeners) == 0 and player.is_connected:
|
||||
if player.queue.empty and player.current is None:
|
||||
await player.stop()
|
||||
await self.connect_to(guild.id, None)
|
||||
|
||||
def max_track_length(self, guild):
|
||||
player = self.bot.lavalink.players.get(guild.id)
|
||||
if len(player.listeners):
|
||||
@@ -626,6 +671,7 @@ class Music:
|
||||
|
||||
player = self.bot.lavalink.players.get(ctx.guild.id)
|
||||
track, pos_global, pos_local = player.add(requester=ctx.author.id, track=track)
|
||||
localizer = self.getLocalizer(ctx.guild.id)
|
||||
|
||||
if player.current is not None:
|
||||
queue_duration = 0
|
||||
@@ -636,10 +682,10 @@ class Music:
|
||||
|
||||
until_play = queue_duration + player.current.duration - player.position
|
||||
until_play = lavalink.utils.format_time(until_play)
|
||||
embed.add_field(name="Position", value=f"`{pos_local + 1}({pos_global + 1})`", inline=True)
|
||||
embed.add_field(name="Playing in", value=f"`{until_play} (estimated)`", inline=True)
|
||||
embed.add_field(name="{enqueue.position}", value=f"`{pos_local + 1}({pos_global + 1})`", inline=True)
|
||||
embed.add_field(name="{enqueue.playing_in}", value=f"`{until_play} ({{enqueue.estimated}})`", inline=True)
|
||||
|
||||
embed.title = 'Song enqueued'
|
||||
embed.title = '{enqueue.enqueued}'
|
||||
thumb_url = await RoxUtils.ThumbNailer.identify(self,
|
||||
track.identifier,
|
||||
track.uri)
|
||||
@@ -650,6 +696,5 @@ class Music:
|
||||
duration = lavalink.utils.format_time(int(track.duration))
|
||||
embed.description = f'[{track.title}]({track.uri})\n**{duration}**'
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(Music(bot))
|
||||
@@ -0,0 +1,81 @@
|
||||
"""
|
||||
A cog to separate events from regular music commands
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
import discord
|
||||
import lavalink
|
||||
from discord.ext import commands
|
||||
import time
|
||||
|
||||
from .utils.mixplayer import MixPlayer
|
||||
from lavalink.events import *
|
||||
|
||||
from lavasettings import *
|
||||
|
||||
|
||||
class MusicEvents:
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
# TODO: maybe only load when Music loads
|
||||
if not hasattr(bot, 'lavalink'): # This ensures the client isn't overwritten during cog reloads.
|
||||
bot.lavalink = lavalink.Client(bot.user.id, player=MixPlayer)
|
||||
bot.lavalink.add_node(host, port, password, region, 'default-node') # Host, Port, Password, Region, Name
|
||||
bot.add_listener(bot.lavalink.voice_update_handler, 'on_socket_response')
|
||||
|
||||
bot.lavalink.add_event_hook(self.track_hook)
|
||||
|
||||
def __unload(self):
|
||||
self.bot.lavalink._event_hooks.clear()
|
||||
|
||||
async def track_hook(self, event):
|
||||
if isinstance(event, TrackEndEvent):
|
||||
pass # Send track ended message to channel.
|
||||
if isinstance(event, TrackStartEvent):
|
||||
pass
|
||||
if isinstance(event, QueueEndEvent):
|
||||
channel = self.bot.get_channel(event.player.fetch('channel'))
|
||||
await self.check_leave_voice(channel.guild)
|
||||
if isinstance(event, PlayerUpdateEvent):
|
||||
pass
|
||||
if isinstance(event, NodeDisconnectedEvent):
|
||||
pass
|
||||
if isinstance(event, NodeConnectedEvent):
|
||||
pass
|
||||
if isinstance(event, NodeChangedEvent):
|
||||
pass
|
||||
|
||||
async def connect_to(self, guild_id: int, channel_id: str):
|
||||
""" Connects to the given voicechannel ID. A channel_id of `None` means disconnect. """
|
||||
ws = self.bot._connection._get_websocket(guild_id)
|
||||
await ws.voice_state(str(guild_id), channel_id)
|
||||
|
||||
async def on_voice_state_update(self, member, before, after):
|
||||
""" Updates listeners when the bot or a user changes voice state """
|
||||
if member.id == self.bot.user.id and after.channel is not None:
|
||||
voice_channel = after.channel
|
||||
player = self.bot.lavalink.players.get(member.guild.id)
|
||||
player.clear_listeners()
|
||||
for member in voice_channel.members:
|
||||
if not member.bot:
|
||||
player.update_listeners(member, member.voice)
|
||||
|
||||
if not member.bot:
|
||||
player = self.bot.lavalink.players.get(member.guild.id)
|
||||
if player is not None:
|
||||
player.update_listeners(member, after)
|
||||
await self.check_leave_voice(member.guild)
|
||||
|
||||
async def check_leave_voice(self, guild):
|
||||
""" Checks if the bot should leave the voice channel """
|
||||
# TODO, disconnect timer?
|
||||
player = self.bot.lavalink.players.get(guild.id)
|
||||
if len(player.listeners) == 0 and player.is_connected:
|
||||
if player.queue.empty and player.current is None:
|
||||
await player.stop()
|
||||
await self.connect_to(guild.id, None)
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(MusicEvents(bot))
|
||||
+7
-24
@@ -37,7 +37,7 @@ def is_even():
|
||||
def is_owner():
|
||||
def predicate(ctx):
|
||||
is_owner = (ctx.message.author.id == 120970603556503552 or
|
||||
ctx.message.author.id == 142212883512557569)
|
||||
ctx.message.author.id == 142212883512557569 or ctx.message.author.id == 212635519706726410)
|
||||
return is_owner
|
||||
return commands.check(predicate)
|
||||
|
||||
@@ -55,35 +55,18 @@ def is_mod():
|
||||
return commands.check(pred)
|
||||
|
||||
|
||||
def is_DJ():
|
||||
async def predicate(ctx):
|
||||
is_dj = has_role(ctx, 'DJ') or has_role(ctx, 'dj') or has_role(ctx, 'Dj')
|
||||
is_admin = await check_guild_permissions(ctx, {'administrator': True})
|
||||
return is_dj or is_admin
|
||||
return commands.check(predicate)
|
||||
|
||||
|
||||
def DJ_or_current():
|
||||
def DJ_or(alone: bool=False, current: bool=False):
|
||||
async def predicate(ctx):
|
||||
try:
|
||||
player = ctx.bot.lavalink.players.get(ctx.guild.id)
|
||||
requester = player.current.requester
|
||||
is_alone = (ctx.author in player.listeners and len(player.listeners) == 1) and alone
|
||||
|
||||
requester = (player.current.requester == ctx.author.id) and current
|
||||
|
||||
is_dj = has_role(ctx, 'DJ') or has_role(ctx, 'dj') or has_role(ctx, 'Dj')
|
||||
is_admin = await check_guild_permissions(ctx, {'administrator': True})
|
||||
return is_dj or is_admin or requester == ctx.author.id
|
||||
except AttributeError:
|
||||
return False
|
||||
return commands.check(predicate)
|
||||
|
||||
|
||||
def DJ_or_alone():
|
||||
async def predicate(ctx):
|
||||
try:
|
||||
player = ctx.bot.lavalink.players.get(ctx.guild.id)
|
||||
alone = ctx.author in player.listeners and len(player.listeners) == 1
|
||||
is_dj = has_role(ctx, 'DJ') or has_role(ctx, 'dj') or has_role(ctx, 'Dj')
|
||||
is_admin = await check_guild_permissions(ctx, {'administrator': True})
|
||||
return is_dj or is_admin or alone
|
||||
return is_dj or is_admin or is_alone or requester
|
||||
except AttributeError:
|
||||
return False
|
||||
return commands.check(predicate)
|
||||
@@ -0,0 +1,17 @@
|
||||
import collections
|
||||
|
||||
class SafeDict(dict):
|
||||
def __missing__(self, key):
|
||||
return '{' + key + '}'
|
||||
|
||||
|
||||
# from https://stackoverflow.com/questions/6027558/flatten-nested-python-dictionaries-compressing-keys
|
||||
def flatten(d, parent_key='', sep='/'):
|
||||
items = []
|
||||
for k, v in d.items():
|
||||
new_key = parent_key + sep + k if parent_key else k
|
||||
if isinstance(v, collections.MutableMapping):
|
||||
items.extend(flatten(v, new_key, sep=sep).items())
|
||||
else:
|
||||
items.append((new_key, v))
|
||||
return dict(items)
|
||||
@@ -0,0 +1,167 @@
|
||||
from glob import glob
|
||||
from os import path
|
||||
|
||||
import re
|
||||
|
||||
from cogs.utils.dict_utils import flatten, SafeDict
|
||||
|
||||
import json
|
||||
|
||||
import copy
|
||||
|
||||
from discord import Embed
|
||||
|
||||
"""
|
||||
Localizer for bot
|
||||
"""
|
||||
|
||||
kmatch = re.compile('({(?!_)([^{}]+)})')
|
||||
|
||||
class Localizer:
|
||||
def __init__(self, localization_folder, default_lang):
|
||||
self.localization_folder = path.realpath(localization_folder)
|
||||
self.default_lang = default_lang
|
||||
# look for localization folders
|
||||
self.index_localizations()
|
||||
# load localizations
|
||||
self.load_localizations()
|
||||
|
||||
# indexes localization folder
|
||||
def index_localizations(self):
|
||||
self.localization_table = {}
|
||||
for folder in glob(path.join(self.localization_folder, "*/")):
|
||||
folder_base = path.basename(path.dirname(folder))
|
||||
self.localization_table[folder_base] = False
|
||||
|
||||
|
||||
# loads all localizations
|
||||
def load_localizations(self):
|
||||
for lang in self.localization_table.keys():
|
||||
self.localization_table[lang] = False
|
||||
self._load_localization(lang)
|
||||
|
||||
self.all_localizations = flatten(self.localization_table)
|
||||
for lang, d in self.localization_table.items():
|
||||
self.localization_table[lang] = Localizer._parse_localization_dictionary(self.localization_table[lang], self.all_localizations)
|
||||
|
||||
# internal function for loading a localization
|
||||
def _load_localization(self, lang):
|
||||
localization = self.localization_table.get(lang)
|
||||
if localization is None:
|
||||
raise Exception(f'Localization for {lang} does not exist')
|
||||
elif localization is False:
|
||||
self.localization_table[lang] = {}
|
||||
l_table = self.localization_table[lang]
|
||||
for file in glob(path.join(self.localization_folder, lang, "*.json")):
|
||||
file_base = path.basename(file).split(".")[0]
|
||||
with open(file, "r", encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
l_table[file_base] = data
|
||||
|
||||
for file in glob(path.join(self.localization_folder, lang, "*.txt")):
|
||||
file_base = path.basename(file).split(".")[0]
|
||||
with open(file, "r", encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
l_table[file_base] = content
|
||||
|
||||
|
||||
l_table = flatten(l_table)
|
||||
# parsing a few times to resolve all values
|
||||
for i in range(0, 5):
|
||||
l_table = Localizer._parse_localization_dictionary(l_table, l_table)
|
||||
|
||||
self.localization_table[lang] = Localizer._parse_localization_dictionary(l_table, l_table)
|
||||
|
||||
# parses and interpolates translation dictionary
|
||||
@staticmethod
|
||||
def _parse_localization_dictionary(d, lookup, prefix=None):
|
||||
n_dict = {}
|
||||
for k, v in d.items():
|
||||
if type(v) is str:
|
||||
n_dict[k] = Localizer._parse_localization_string(v, lookup, prefix)
|
||||
else:
|
||||
n_dict[k] = v
|
||||
return n_dict
|
||||
|
||||
@staticmethod
|
||||
def _replace_keys(value, prefix=None):
|
||||
for outer, inner in kmatch.findall(value):
|
||||
nstr = inner
|
||||
if prefix is not None:
|
||||
nstr = f'{prefix}.{inner}'
|
||||
nstr = f'{{{nstr}}}'
|
||||
nstr = nstr.replace(".", "/")
|
||||
value = value.replace(outer, nstr)
|
||||
return value
|
||||
# parses and interpolates strings
|
||||
@staticmethod
|
||||
def _parse_localization_string(value, d, prefix=None):
|
||||
d = SafeDict(d)
|
||||
value = Localizer._replace_keys(value, prefix)
|
||||
return value.format_map(d)
|
||||
|
||||
# returns true if localization is currently loaded
|
||||
def isLoaded(self, lang):
|
||||
return self.localization_table.get(lang, False)
|
||||
|
||||
def getAvaliableLocalizations(self):
|
||||
return self.localization_table.keys()
|
||||
|
||||
# returns translation string from a key
|
||||
def get(self, key, lang=None):
|
||||
lang = lang or self.default_lang
|
||||
if not self.isLoaded(lang):
|
||||
self._load_localization(lang)
|
||||
|
||||
return self.localization_table.get(lang, {}).get(key.replace(".", "/"))
|
||||
|
||||
# inserts translations into a string
|
||||
def format_str(self, s, lang=None, prefix=None, **kvpairs):
|
||||
lang = lang or self.default_lang
|
||||
if not self.isLoaded(lang):
|
||||
self._load_localization(lang)
|
||||
|
||||
ns = Localizer._parse_localization_string(s, self.localization_table.get(lang, {}), prefix)
|
||||
ns = Localizer._parse_localization_string(ns, self.all_localizations, prefix)
|
||||
return ns.format_map(SafeDict(kvpairs))
|
||||
|
||||
# inserts translations into a values of a dictionary
|
||||
def format_dict(self, d, lang=None, prefix=None, **kvpairs):
|
||||
lang = lang or self.default_lang
|
||||
if not self.isLoaded(lang):
|
||||
self._load_localization(lang)
|
||||
|
||||
nd = copy.deepcopy(d)
|
||||
cursorQueue = [nd]
|
||||
while cursorQueue:
|
||||
cursor = cursorQueue.pop()
|
||||
for k, v in (cursor.items() if type(cursor) == dict else enumerate(cursor)):
|
||||
if type(v) == str:
|
||||
# insert translations based on lang
|
||||
cursor[k] = self.format_str(v, lang, prefix, **kvpairs)
|
||||
elif type(v) == dict or type(v) == list:
|
||||
cursorQueue.append(v)
|
||||
|
||||
return nd
|
||||
|
||||
# inserts translations into a values of a embed
|
||||
def format_embed(self, embed, lang=None, prefix=None, **kvpairs):
|
||||
raw = embed.to_dict()
|
||||
return Embed.from_data(self.format_dict(raw, lang, prefix, **kvpairs))
|
||||
|
||||
|
||||
class LocalizerWrapper:
|
||||
def __init__(self, localizer, lang=None, prefix=None):
|
||||
self.localizer = localizer
|
||||
self.lang = lang
|
||||
self.prefix = prefix
|
||||
|
||||
def format_str(self, s, **kvpairs):
|
||||
return self.localizer.format_str(s, self.lang, self.prefix, **kvpairs)
|
||||
|
||||
def format_dict(self, d, **kvpairs):
|
||||
return self.localizer.format_dict(d, self.lang, self.prefix, **kvpairs)
|
||||
|
||||
def format_embed(self, embed, **kvpairs):
|
||||
return self.localizer.format_embed(embed, self.lang, self.prefix, **kvpairs)
|
||||
+47
-22
@@ -1,7 +1,7 @@
|
||||
import time
|
||||
import lavalink
|
||||
|
||||
from collections import OrderedDict
|
||||
from collections import OrderedDict, deque
|
||||
from itertools import cycle, islice, chain
|
||||
from random import shuffle
|
||||
|
||||
@@ -57,6 +57,9 @@ class MixPlayer(DefaultPlayer):
|
||||
def global_queue(self):
|
||||
return self.queue.get_queue()
|
||||
|
||||
def get_history(self):
|
||||
return self.queue.history
|
||||
|
||||
def queue_duration(self, include_current: bool=True):
|
||||
duration = 0
|
||||
for track in self.queue:
|
||||
@@ -108,10 +111,20 @@ class MixPlayer(DefaultPlayer):
|
||||
else:
|
||||
self.listeners.add(member)
|
||||
|
||||
def clear_listeners(self):
|
||||
self.listeners.clear()
|
||||
|
||||
def add_skipper(self, member):
|
||||
if member in self.listeners:
|
||||
self.skip_voters.add(member)
|
||||
|
||||
async def handle_event(self, event):
|
||||
""" Handles the given event as necessary. """
|
||||
if isinstance(event, (TrackStuckEvent, TrackExceptionEvent)) or \
|
||||
isinstance(event, TrackEndEvent) and event.reason == 'FINISHED':
|
||||
self.skip_voters.clear()
|
||||
await self.play()
|
||||
|
||||
async def bassboost(self, boost: bool=False):
|
||||
if boost:
|
||||
gains = [
|
||||
@@ -148,6 +161,7 @@ class MixQueue:
|
||||
def __init__(self):
|
||||
self.queues = OrderedDict()
|
||||
self.priority_queue = []
|
||||
self._history = deque(maxlen=11) # 10 + current
|
||||
|
||||
def __str__(self):
|
||||
tmp = ''
|
||||
@@ -190,11 +204,13 @@ class MixQueue:
|
||||
def pop_first(self):
|
||||
if self.priority_queue:
|
||||
next_track = self.priority_queue.pop(0)
|
||||
self._history.append(next_track)
|
||||
return next_track
|
||||
try:
|
||||
next_track = self.queues[self.first_queue].pop(0)
|
||||
self._shuffle()
|
||||
self._clear_empty()
|
||||
self._history.append(next_track)
|
||||
return next_track
|
||||
except KeyError:
|
||||
pass
|
||||
@@ -264,30 +280,34 @@ class MixQueue:
|
||||
for i in to_remove:
|
||||
self.queues.pop(i)
|
||||
|
||||
# Convert between global and local queue positions
|
||||
# Pretty shit atm, todo: implement algorithm thing I made.
|
||||
def _loc_to_glob(self, requester: int, pos: int):
|
||||
queue = self.queues.get(requester, [])
|
||||
if queue:
|
||||
try:
|
||||
track = queue[pos]
|
||||
for i, t in enumerate(self):
|
||||
if t == track:
|
||||
return i
|
||||
except IndexError:
|
||||
pass
|
||||
def _loc_to_glob(self, requester, pos):
|
||||
globpos = len(self.priority_queue)
|
||||
passed = False
|
||||
for key, queue in self.queues.items():
|
||||
if len(queue) <= pos:
|
||||
globpos += len(queue)
|
||||
else:
|
||||
globpos += pos
|
||||
if key == requester:
|
||||
passed = True
|
||||
if not passed:
|
||||
globpos += 1
|
||||
return globpos
|
||||
|
||||
def _glob_to_loc(self, pos: int):
|
||||
track = None
|
||||
for i, t in enumerate(self):
|
||||
if i == pos:
|
||||
track = t
|
||||
if track is None:
|
||||
try:
|
||||
song = next(islice(self, pos, pos + 1))
|
||||
except (ValueError, StopIteration):
|
||||
return None, None
|
||||
for requester, q in self.queues.items():
|
||||
for i, t in enumerate(q):
|
||||
if t == track:
|
||||
return requester, i
|
||||
user_queue = self.queues.get(song.requester, [])
|
||||
|
||||
# In case song is in the priority queue
|
||||
if not user_queue or song not in user_queue:
|
||||
return None, pos
|
||||
|
||||
for i, s in enumerate(user_queue):
|
||||
if s == song:
|
||||
return song.requester, i
|
||||
|
||||
@property
|
||||
def first_queue(self):
|
||||
@@ -299,3 +319,8 @@ class MixQueue:
|
||||
@property
|
||||
def empty(self):
|
||||
return len(self) == 0
|
||||
|
||||
@property
|
||||
def history(self):
|
||||
return list(reversed(self._history))
|
||||
|
||||
+21
-2
@@ -1,7 +1,7 @@
|
||||
import os
|
||||
import json
|
||||
import codecs
|
||||
|
||||
import locale
|
||||
|
||||
class Settings:
|
||||
def __init__(self, **default_settings):
|
||||
@@ -10,13 +10,19 @@ class Settings:
|
||||
self.default_prefix = default_settings["default_prefix"]
|
||||
self.default_admin = default_settings["default_admin"]
|
||||
self.default_mod = default_settings["default_mod"]
|
||||
self.default_locale = default_settings["default_locale"]
|
||||
self.default_lang_path = default_settings["default_lang_path"] or "./localization"
|
||||
if not self.default_locale:
|
||||
locale, codepage = locale.getlocale()
|
||||
default_locale, default_codepage = locale.getdefaultlocale()
|
||||
self.default_locale = locale or default_locale
|
||||
|
||||
if not os.path.exists(self.DATA_PATH):
|
||||
os.makedirs(self.DATA_PATH)
|
||||
|
||||
if not os.path.isfile(self.SETTINGS_PATH):
|
||||
with codecs.open(self.SETTINGS_PATH, "w+", encoding='utf8') as f:
|
||||
json.dump({"prefixes": {},
|
||||
json.dump({"locale": {}, "prefixes": {},
|
||||
"roles": {}}, f, indent=4)
|
||||
|
||||
with codecs.open(self.SETTINGS_PATH, "r", encoding='utf8') as f:
|
||||
@@ -60,3 +66,16 @@ class Settings:
|
||||
|
||||
with codecs.open(self.SETTINGS_PATH, "w", encoding='utf8') as f:
|
||||
json.dump(self.settings, f, indent=4)
|
||||
|
||||
def set_locale(self, guild_id, locale):
|
||||
guild_id = str(guild_id)
|
||||
self.settings["locale"][guild_id] = locale
|
||||
with codecs.open(self.SETTINGS_PATH, "w", encoding='utf8') as f:
|
||||
json.dump(self.settings, f, indent=4)
|
||||
|
||||
def get_locale(self, guild_id):
|
||||
guild_id = str(guild_id)
|
||||
return self.settings.get("locale", {}).get(guild_id, self.default_locale).lower()
|
||||
|
||||
def get_language_path(self):
|
||||
return self.settings.get("language_path", self.default_lang_path)
|
||||
@@ -3,6 +3,8 @@
|
||||
"default_prefix": [""],
|
||||
"default_admin": "Admin",
|
||||
"default_mod": "Moderator",
|
||||
"default_locale": "en_en",
|
||||
"default_lang_path": "./localization",
|
||||
"playing": "",
|
||||
"description": ""
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"not_available_dm": "This command is not available in DMs"
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"bot": {
|
||||
"name": "What?",
|
||||
"infotext": "Music bot written for use on /r/Norge's Discord Servers. It's open source! You can find it [HERE]({global.vars.repo})",
|
||||
"spectext": "**Python:** [{_python_v}](https://www.python.org/)\n**Discord.py:** [{_discord_v}](https://github.com/Rapptz/discord.py/tree/rewrite)\n**Lavalink:** [{_lavalink_v}](https://github.com/Devoxin/Lavalink.py)",
|
||||
"stattext": "**Guilds:** {_guilds}\n**Users:** {_members}",
|
||||
"what": "What?",
|
||||
"how": "How?",
|
||||
"how_many": "How many?",
|
||||
"how_long": "How long?",
|
||||
"footer_text": "Made by /r/Norge, for /r/Norge"
|
||||
},
|
||||
"music": {
|
||||
"title": "Music info",
|
||||
"players": "Players",
|
||||
"listeners": "Listeners"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"response": {
|
||||
"nothing_found": "Nothing found!",
|
||||
"not_playing": "Not playing.",
|
||||
"nothing_playing": "Nothing playing",
|
||||
"have_to_listen": "You have to be listening to the bot",
|
||||
"playlist_enqued": "Playlist Enqueued!",
|
||||
"requested_by": "Requested by",
|
||||
"tracks": "tracks",
|
||||
"seek": {
|
||||
"missing_amount": "You need to specify the amount of seconds to skip!",
|
||||
"track_moved": "Moved track to **{_position}**"
|
||||
},
|
||||
"skip": {
|
||||
"skipped": "Skipped.",
|
||||
"require_vote": "{_skips} out of {_total} required haters have voted to skip."
|
||||
},
|
||||
"skip_to": {
|
||||
"invalid_pos": "Invalid specified position.",
|
||||
"exceeds_queue": "The position exceeds the queue\\'s length.",
|
||||
"skipped_to": "skipped to `{_title}` at position `{_pos}`"
|
||||
},
|
||||
"stop": "⏹ | Stopped.",
|
||||
"live": "🔴 LIVE",
|
||||
"now": "Now Playing",
|
||||
"queue": {
|
||||
"empty": "The queue is empty",
|
||||
"user_empty": "{_user}'s queue is empty"
|
||||
},
|
||||
"my_queue": "Your queue is empty",
|
||||
"resume": {
|
||||
"resumed": "⏯ | Resumed",
|
||||
"paused": "⏯ | Paused"
|
||||
},
|
||||
"shuffle": "Your queue has been shuffled",
|
||||
"out_of_range": "Positions must be between 1 and {_len}",
|
||||
"move": {
|
||||
"not_in_queue": "Check that the positions exist in your queue",
|
||||
"moved_to": "{_title} moved from position {_from} to {_to} in your queue"
|
||||
},
|
||||
"remove": "**{_title}** removed.",
|
||||
"dj_removed": "**{_title}** queued by {_user} removed.",
|
||||
"dj_remove_user": "Removed all songs queued by <@{_id}>.",
|
||||
"search": "searching",
|
||||
"results": "Results",
|
||||
"time_expired": "Timer Expired",
|
||||
"disconnect": {
|
||||
"not_in_voice": "You\\'re not in my voicechannel!",
|
||||
"disconnected": "Disconnected"
|
||||
},
|
||||
"not_connected": "Not connected.",
|
||||
"volume": {
|
||||
"set_to": "Volume set to {_volume}",
|
||||
"reset": "Volume and equalizer reset",
|
||||
"out_of_range": "you can only set the volume between 50 and 125"
|
||||
},
|
||||
"boost": {
|
||||
"on": "Bass boost is on",
|
||||
"off": "'Bass boost is off"
|
||||
},
|
||||
"scrub": {
|
||||
"add": "Adding controls",
|
||||
"controls": "{music_controls}",
|
||||
"_comment_controls": "scrub.controls read from music_controls.txt"
|
||||
},
|
||||
"enqueue": {
|
||||
"position": "position",
|
||||
"playing_in": "Playing in",
|
||||
"enqueued": "Song enqueued",
|
||||
"estimated": "estimated"
|
||||
},
|
||||
"history": {
|
||||
"title": "Track history",
|
||||
"current": "`Current` **[{_title}]({_uri})** _by <@{_id}>_",
|
||||
"track": "`{_index}.` **[{_title}]({_uri})** _by <@{_id}>_",
|
||||
"previous": "**{_len} Previous songs:**"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
**Controls**
|
||||
Play/pause: ▶ ⏸
|
||||
skip 15 seconds forward/backward: ⏪ ⏩
|
||||
Skip to beginning/end of song: ⏮ ⏭
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"repo": "https://gitlab.com/Ev-1/shite-music-bot"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"not_available_dm": "Denne kommandoen er ikke tilgjengelig i DMs"
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"bot": {
|
||||
"name": "hva?",
|
||||
"infotext": "Musikkbot skrevet for bruk på /r/Norge sine Discord Servere. Kildekoden er åpen! Den kan du finne [HER]({global.vars.repo})",
|
||||
"spectext": "**Python:** [{_python_v}](https://www.python.org/)\n**Discord.py:** [{_discord_v}](https://github.com/Rapptz/discord.py/tree/rewrite)\n**Lavalink:** [{_lavalink_v}](https://github.com/Devoxin/Lavalink.py)",
|
||||
"stattext": "**Tenarar:** {_guilds}\n**Brukere:** {_members}",
|
||||
"what": "Hva?",
|
||||
"how": "Hvordan?",
|
||||
"how_many": "Hvor mange?",
|
||||
"how_long": "Hvor lenge?",
|
||||
"footer_text": "Laget av /r/Norge, for /r/Norge"
|
||||
},
|
||||
"music": {
|
||||
"title": "Musikk info",
|
||||
"players": "Spillere",
|
||||
"listeners": "Lyttere"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"response": {
|
||||
"nothing_found": "Kunne ikke finne sangen!",
|
||||
"not_playing": "Spiller ikke av noe.",
|
||||
"nothing_playing": "Ingenting spiller av",
|
||||
"have_to_listen": "Du må høre på botten",
|
||||
"playlist_enqued": "Spilleliste ble lagt til i køen!",
|
||||
"requested_by": "Etterspurt av",
|
||||
"tracks": "spor",
|
||||
"seek": {
|
||||
"missing_amount": "Du må spesifisere antall sekunder å spole!",
|
||||
"track_moved": "Flyttet spor til **{_position}**"
|
||||
},
|
||||
"skip": {
|
||||
"skipped": "Hoppet over.",
|
||||
"require_vote": "{_skips} av {_total} hatere må stemme for å hoppe over."
|
||||
},
|
||||
"skip_to": {
|
||||
"invalid_pos": "Ugyldig køposisjon.",
|
||||
"exceeds_queue": "Posisjonen overgår køens lengde.",
|
||||
"skipped_to": "hoppet til `{_title}` i posisjon `{_pos}`"
|
||||
},
|
||||
"stop": "⏹ | Stoppet avspillingen.",
|
||||
"live": "🔴 DIREKTE",
|
||||
"now": "Spiller nå",
|
||||
"queue": {
|
||||
"empty": "Køen er tom",
|
||||
"user_empty": "{_user} sin kø er tom"
|
||||
},
|
||||
"my_queue": "Din kø er tom",
|
||||
"resume": {
|
||||
"resumed": "⏯ | Avspilling gjenopptatt",
|
||||
"paused": "⏯ | Pauset avspillingen"
|
||||
},
|
||||
"shuffle": "Køen din har blitt omstokket",
|
||||
"out_of_range": "Posisjonene må være mellom 1 og {_len}",
|
||||
"move": {
|
||||
"not_in_queue": "Sjekk at posisjonen eksisterer i køen din",
|
||||
"moved_to": "`{_title}` flyttet fra plass `{_from}` til `{_to}` i køen din"
|
||||
},
|
||||
"remove": "**{_title}** ble fjernet fra køen.",
|
||||
"dj_removed": "**{_title}** som ble lagt til i køen av {_user}, ble fjernet.",
|
||||
"dj_remove_user": "Fjernet alle sanger lagt til i køen av <@{_id}>.",
|
||||
"search": "søker",
|
||||
"results": "Resultater",
|
||||
"time_expired": "Tid utgått",
|
||||
"disconnect": {
|
||||
"not_in_voice": "Du må være koblet til samme talekanal som meg!",
|
||||
"disconnected": "Frakoblet"
|
||||
},
|
||||
"not_connected": "Du er ikke koblet til en talekanal.",
|
||||
"volume": {
|
||||
"set_to": "Volum satt til {_volume}",
|
||||
"reset": "Volum og equalizer resatt!",
|
||||
"out_of_range": "Du må sette volumet til et tall mellom 50 og 125"
|
||||
},
|
||||
"boost": {
|
||||
"on": "Bassforsterkeren er på",
|
||||
"off": "Bassforsterkeren er av"
|
||||
},
|
||||
"scrub": {
|
||||
"add": "Legger til kontrollere",
|
||||
"controls": "{music_controls}",
|
||||
"_comment_controls": "scrub.controls read from music_controls.txt"
|
||||
},
|
||||
"enqueue": {
|
||||
"position": "Køposisjon",
|
||||
"playing_in": "Spiller av om",
|
||||
"enqueued": "Ny sang lagt til i køen",
|
||||
"estimated": "estimert"
|
||||
},
|
||||
"history": {
|
||||
"title": "Avspillingshistorikk",
|
||||
"current": "`Spiller nå` **[{_title}]({_uri})** _av <@{_id}>_",
|
||||
"track": "`{_index}.` **[{_title}]({_uri})** _av <@{_id}>_",
|
||||
"previous": "**Tidligere sanger ({_len}):**"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
**Kontroller**
|
||||
Spill/pause: ▶ ⏸
|
||||
Hopp 15 sekunder fremover/bakover: ⏪ ⏩
|
||||
Hopp til starten/slutten av sangen: ⏮ ⏭
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"not_available_dm": "Denna kommandoa er ikkje tilgjengeleg i direkte meldingar DMs"
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"bot": {
|
||||
"name": "Kva?",
|
||||
"infotext": "Musikkbot skreve til bruk på /r/Norge sine Discord Serverar. Kjeldekoda er ope! Dei kan du finna [HER]({global.vars.repo})",
|
||||
"spectext": "**Python:** [{_python_v}](https://www.python.org/)\n**Discord.py:** [{_discord_v}](https://github.com/Rapptz/discord.py/tree/rewrite)\n**Lavalink:** [{_lavalink_v}](https://github.com/Devoxin/Lavalink.py)",
|
||||
"stattext": "**Tenarar:** {_guilds}\n**Brukarar:** {_members}",
|
||||
"what": "Kva?",
|
||||
"how": "Korleis?",
|
||||
"how_many": "Kor mange?",
|
||||
"how_long": "Kor lengje?",
|
||||
"footer_text": "Laga av /r/Norge, for /r/Norge"
|
||||
},
|
||||
"music": {
|
||||
"title": "Musikk informasjon",
|
||||
"players": "Spelarar",
|
||||
"listeners": "Lyttarar"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"response": {
|
||||
"nothing_found": "Orsak, kunne ikkje finna songen!",
|
||||
"not_playing": "Spelar ikkje noko.",
|
||||
"nothing_playing": "Ingenting speles av",
|
||||
"have_to_listen": "Dykk må høyra på botten",
|
||||
"playlist_enqued": "Spellista vart lagt til i køen!",
|
||||
"requested_by": "Etterspurd av",
|
||||
"tracks": "spor",
|
||||
"seek": {
|
||||
"missing_amount": "Dykk må klargjera antal sekundar å spola!",
|
||||
"track_moved": "Flytta spor til **{_position}**"
|
||||
},
|
||||
"skip": {
|
||||
"skipped": "Hoppa over.",
|
||||
"require_vote": "{_skips} av {_total} hatarar må stemma for å hoppa over."
|
||||
},
|
||||
"skip_to": {
|
||||
"invalid_pos": "Ugild køposisjon.",
|
||||
"exceeds_queue": "Posisjona overgår køas lengde.",
|
||||
"skipped_to": "hoppa til `{_title}` i posisjon `{_pos}`"
|
||||
},
|
||||
"stop": "⏹ | Stoppa avspelinga.",
|
||||
"live": "🔴 DIREKTE",
|
||||
"now": "Spelar no",
|
||||
"queue": {
|
||||
"empty": "Køa er tom",
|
||||
"user_empty": "{_user} sin kø er tom"
|
||||
},
|
||||
"my_queue": "Din kø er tom",
|
||||
"resume": {
|
||||
"resumed": "⏯ | Avspeling oppattatt",
|
||||
"paused": "⏯ | Pauset avspeling"
|
||||
},
|
||||
"shuffle": "Køa di har vorte omstokka",
|
||||
"out_of_range": "Posisjonane må væra millom 1 og {_len}",
|
||||
"move": {
|
||||
"not_in_queue": "Kontrollar at posisjona eksisterar i køa di",
|
||||
"moved_to": "`{_title}` flytta frå plass `{_from}` til `{_to}` i køa di"
|
||||
},
|
||||
"remove": "**{_title}** vart fjerna frå køa.",
|
||||
"dj_removed": "**{_title}** som vart lagt til i køa av {_user}, vart fjerna.",
|
||||
"dj_remove_user": "Fjerna alle songar lagt til i køa av <@{_id}>.",
|
||||
"search": "søkjar",
|
||||
"results": "Resultatar",
|
||||
"time_expired": "Tid utgått",
|
||||
"disconnect": {
|
||||
"not_in_voice": "Dykk må være tilkopla same talekanal som meg!",
|
||||
"disconnected": "Fråkopla"
|
||||
},
|
||||
"not_connected": "Dykk er ikkje kopla til ein talekanal.",
|
||||
"volume": {
|
||||
"set_to": "Volum satt til {_volume}",
|
||||
"reset": "Volum og utjevnar igjensatt!",
|
||||
"out_of_range": "Du må setja volumet til eit tal mellom femti og hundreogtjuefem"
|
||||
},
|
||||
"boost": {
|
||||
"on": "Bassforsterkaren er på",
|
||||
"off": "Bassforsterkaren er av"
|
||||
},
|
||||
"scrub": {
|
||||
"add": "Legjar til kontrollera",
|
||||
"controls": "{music_controls}",
|
||||
"_comment_controls": "scrub.controls read from music_controls.txt"
|
||||
},
|
||||
"enqueue": {
|
||||
"position": "Køposisjon",
|
||||
"playing_in": "Spelar av om",
|
||||
"enqueued": "Ny song lagt til i køa",
|
||||
"estimated": "estimert"
|
||||
},
|
||||
"history": {
|
||||
"title": "Avspelingshistorikk",
|
||||
"current": "`Spelar no` **[{_title}]({_uri})** _av <@{_id}>_",
|
||||
"track": "`{_index}.` **[{_title}]({_uri})** _av <@{_id}>_",
|
||||
"previous": "**Tidlegare songar ({_len}):**"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
**Kontrollar**
|
||||
Spel/pause: ▶ ⏸
|
||||
Hopp 15 sekundar framover/bakover: ⏪ ⏩
|
||||
Hopp til byrjinga/slutten av songen: ⏮ ⏭
|
||||
Reference in new issue
Block a user