<commit_before>#!/usr/bin/env python3
# CrabBot general voice commands
#
# Parts taken from discord.py/examples/playlist.py
# https://github.com/Rapptz/discord.py/blob/async/examples/playlist.py

import asyncio
import importlib.util
import logging
from pathlib import Path
import random
import tempfile

import discord
from discord.ext import commands

import crabbot.common


class Voice(crabbot.common.CrabBotCog):
    has_lists = True

    def __init__(self, bot, memes_path, use_libav=False):
        super().__init__(bot)
        self.use_libav = use_libav

        self.memes_path = Path(memes_path)
        # Initialize lists
        self.update_lists()

        # dict: server->voice_connection
        self.voice_connections = {}

    # Special function used by discord.py for remove_cog
    def __unload(self):
        # Cancel all audio tasks and disconnect
        for server, connection in self.voice_connections.items():
            logging.info("Cancelling connection to {}".format(server))
            try:
                connection.audio_player.cancel()
                if connection.voice is not None:
                    # can take a long time if cog is removed while playing. Is there a fix?
                    self.bot.loop.create_task(connection.voice.disconnect())
            except:
                pass

    def update_lists(self):
        # !memes
        self.the_memes = crabbot.common.read_list_file(self.memes_path / "filelist.txt")

    def remove_voice_connection(self, server):
        logging.info("Removing connection to voice {}".format(server))
        del self.voice_connections[server]

    def get_voice_connection(self, ctx):
        """Either retrieves or creates a voice connection entry for the server of the given ctx"""
        logging.info("Getting a voice connection")

        # Needed for voice playback. Deferred until we start possibly needing it
        if not discord.opus.is_loaded():
            discord.opus.load_opus('opus')

        existing_connection = self.voice_connections.get(ctx.message.server)
        if existing_connection is not None:
            logging.info("Using existing voice connection")
            return existing_connection
        else:
            new_connection = VoiceConnection(self.bot)
            self.voice_connections[ctx.message.server] = new_connection
            logging.info("New voice connection to {} added".format(ctx.message.server))
            return new_connection

    @commands.command(pass_context=True)
    async def volume(self, ctx, new_volume):
        logging.info("Setting volume for {} to {}".format(ctx.message.server, new_volume))
        voice_connection = self.get_voice_connection(ctx)
        # Barring Exceptions, we should always get back something we can set_volume for
        voice_connection.set_volume(new_volume)

    @commands.command(pass_context=True)
    async def maxvolume(self, ctx, new_volume):
        logging.info("Setting max volume for {} to {}".format(ctx.message.server, new_volume))
        voice_connection = self.voice_connection(ctx)
        # Barring Exceptions, we should always get back something we can set_maxvolume for
        voice_connection.set_maxvolume(new_volume)

    @commands.command(aliases=['voice_stop', 'shutup'], pass_context=True)
    async def stop_voice(self, ctx):
        logging.info("Ending voice connection to {}".format(ctx.message.server))
        existing_connection = self.voice_connections.get(ctx.message.server)
        if existing_connection is not None:
            logging.info("Stopping the voice player")

            if existing_connection.current_entry.player is not None:
                # Stop the current audio ASAP
                existing_connection.current_entry.player.stop()

            # Since we can't be sure there isn't more audio queued, force stop the task
            existing_connection.audio_player.cancel()
            logging.info("Disconnecting voice")
            await existing_connection.voice.disconnect()
            logging.info("Voice connection ended")

            # TODO think about persistent volume settings
            self.remove_voice_connection(ctx.message.server)
        else:
            logging.info("No voice connection to end")
            await self.bot.say("No voice connection to {}".format(ctx.message.server))

    @commands.command(aliases=['current_stop', 'skip'], pass_context=True)
    async def stop_current(self, ctx):
        logging.info("Stopping current audio entry for {}".format(ctx.message.server))
        existing_connection = self.voice_connections.get(ctx.message.server)
        if existing_connection is not None:
            logging.info("Stopping the voice player")
            if existing_connection.current_entry.player is not None:
                existing_connection.current_entry.player.stop()
            # Audio player task should now continue

    @commands.command(pass_context=True, help="Lost?")
    async def memes(self, ctx):
        logging.info("Memeing it up")

        target_voice_channel = ctx.message.author.voice_channel

        if target_voice_channel is None:
            logging.info("Not memeing: User not in a voice channel")
            self.bot.loop.create_task(self.bot.reply("Try being in a voice channel first"))
            return

        voice_connection = self.get_voice_connection(ctx)

        chosen_meme = random.choice(self.the_memes)

        # Initialize voice if not already created
        if voice_connection.voice is None:
            logging.info("Initializing voice from memes command")
            await voice_connection.connect(target_voice_channel)
            target_voice_channel = None

        # Build a VoiceEntry
        player = voice_connection.voice.create_ffmpeg_player(
            str(self.memes_path) + '/' + chosen_meme,
            use_avconv=self.use_libav,
            after=voice_connection.toggle_next)
        new_entry = VoiceEntry(player, chosen_meme, target_voice_channel)

        logging.info("Queueing new voice entry for {}".format(new_entry.name))
        await voice_connection.audio_queue.put(new_entry)

    @commands.command(pass_context=True,
                      help="Plays most things supported by youtube-dl")
    async def stream(self, ctx, video=None):
        if video is None:
            # TODO check if video is a valid streamable (YoutubeDL.py simulate?)
            self.bot.reply("Nothing to stream")
            return

        logging.info("Streaming")

        target_voice_channel = ctx.message.author.voice_channel

        if target_voice_channel is None:
            logging.info("Not streaming: User not in a channel")
            self.bot.loop.create_task(self.bot.reply("Try being in a voice channel first"))
            return

        if importlib.util.find_spec("youtube_dl") is None:
            # Preempt import error and silent failure with a more useful message and user feedback
            logging.error("Memes command requires youtube-dl module. Install with pip.")
            self.bot.reply("Bot is not configured to stream")
            return

        voice_connection = self.get_voice_connection(ctx)

        # Initialize voice if not already created
        if voice_connection.voice is None:
            logging.info("Initializing voice from stream command")
            await voice_connection.connect(target_voice_channel)
            target_voice_channel = None

        # Build a VoiceEntry
        # See YoutubeDL.py for ytdl options:
        # https://github.com/rg3/youtube-dl/blob/master/youtube_dl/YoutubeDL.py#L128
        ytdl_opts = {
            "ignoreerrors": True  # Mostly so it won't stop on bad playlist entries
        }
        player = await voice_connection.voice.create_ytdl_player(
            video,
            use_avconv=self.use_libav,
            ytdl_options=ytdl_opts,
            after=voice_connection.toggle_next)
        new_entry = VoiceEntry(
            player, player.title,
            target_voice_channel, ctx.message)

        # Give user feedback when recieved
        await self.bot.reply("Stream queued")
        logging.info("Queueing new voice entry for {}".format(new_entry.name))
        await voice_connection.audio_queue.put(new_entry)


class VoiceEntry:
    def __init__(self, player, name, voice_channel, message=None):<commit_msg>Disable ytdl playlist processing<commit_after>#!/usr/bin/env python3
# CrabBot general voice commands
#
# Parts taken from discord.py/examples/playlist.py
# https://github.com/Rapptz/discord.py/blob/async/examples/playlist.py

import asyncio
import importlib.util
import logging
from pathlib import Path
import random
import tempfile

import discord
from discord.ext import commands

import crabbot.common


class Voice(crabbot.common.CrabBotCog):
    has_lists = True

    def __init__(self, bot, memes_path, use_libav=False):
        super().__init__(bot)
        self.use_libav = use_libav

        self.memes_path = Path(memes_path)
        # Initialize lists
        self.update_lists()

        # dict: server->voice_connection
        self.voice_connections = {}

    # Special function used by discord.py for remove_cog
    def __unload(self):
        # Cancel all audio tasks and disconnect
        for server, connection in self.voice_connections.items():
            logging.info("Cancelling connection to {}".format(server))
            try:
                connection.audio_player.cancel()
                if connection.voice is not None:
                    # can take a long time if cog is removed while playing. Is there a fix?
                    self.bot.loop.create_task(connection.voice.disconnect())
            except:
                pass

    def update_lists(self):
        # !memes
        self.the_memes = crabbot.common.read_list_file(self.memes_path / "filelist.txt")

    def remove_voice_connection(self, server):
        logging.info("Removing connection to voice {}".format(server))
        del self.voice_connections[server]

    def get_voice_connection(self, ctx):
        """Either retrieves or creates a voice connection entry for the server of the given ctx"""
        logging.info("Getting a voice connection")

        # Needed for voice playback. Deferred until we start possibly needing it
        if not discord.opus.is_loaded():
            discord.opus.load_opus('opus')

        existing_connection = self.voice_connections.get(ctx.message.server)
        if existing_connection is not None:
            logging.info("Using existing voice connection")
            return existing_connection
        else:
            new_connection = VoiceConnection(self.bot)
            self.voice_connections[ctx.message.server] = new_connection
            logging.info("New voice connection to {} added".format(ctx.message.server))
            return new_connection

    @commands.command(pass_context=True)
    async def volume(self, ctx, new_volume):
        logging.info("Setting volume for {} to {}".format(ctx.message.server, new_volume))
        voice_connection = self.get_voice_connection(ctx)
        # Barring Exceptions, we should always get back something we can set_volume for
        voice_connection.set_volume(new_volume)

    @commands.command(pass_context=True)
    async def maxvolume(self, ctx, new_volume):
        logging.info("Setting max volume for {} to {}".format(ctx.message.server, new_volume))
        voice_connection = self.voice_connection(ctx)
        # Barring Exceptions, we should always get back something we can set_maxvolume for
        voice_connection.set_maxvolume(new_volume)

    @commands.command(aliases=['voice_stop', 'shutup'], pass_context=True)
    async def stop_voice(self, ctx):
        logging.info("Ending voice connection to {}".format(ctx.message.server))
        existing_connection = self.voice_connections.get(ctx.message.server)
        if existing_connection is not None:
            logging.info("Stopping the voice player")

            if existing_connection.current_entry.player is not None:
                # Stop the current audio ASAP
                existing_connection.current_entry.player.stop()

            # Since we can't be sure there isn't more audio queued, force stop the task
            existing_connection.audio_player.cancel()
            logging.info("Disconnecting voice")
            await existing_connection.voice.disconnect()
            logging.info("Voice connection ended")

            # TODO think about persistent volume settings
            self.remove_voice_connection(ctx.message.server)
        else:
            logging.info("No voice connection to end")
            await self.bot.say("No voice connection to {}".format(ctx.message.server))

    @commands.command(aliases=['current_stop', 'skip'], pass_context=True)
    async def stop_current(self, ctx):
        logging.info("Stopping current audio entry for {}".format(ctx.message.server))
        existing_connection = self.voice_connections.get(ctx.message.server)
        if existing_connection is not None:
            logging.info("Stopping the voice player")
            if existing_connection.current_entry.player is not None:
                existing_connection.current_entry.player.stop()
            # Audio player task should now continue

    @commands.command(pass_context=True, help="Lost?")
    async def memes(self, ctx):
        logging.info("Memeing it up")

        target_voice_channel = ctx.message.author.voice_channel

        if target_voice_channel is None:
            logging.info("Not memeing: User not in a voice channel")
            self.bot.loop.create_task(self.bot.reply("Try being in a voice channel first"))
            return

        voice_connection = self.get_voice_connection(ctx)

        chosen_meme = random.choice(self.the_memes)

        # Initialize voice if not already created
        if voice_connection.voice is None:
            logging.info("Initializing voice from memes command")
            await voice_connection.connect(target_voice_channel)
            target_voice_channel = None

        # Build a VoiceEntry
        player = voice_connection.voice.create_ffmpeg_player(
            str(self.memes_path) + '/' + chosen_meme,
            use_avconv=self.use_libav,
            after=voice_connection.toggle_next)
        new_entry = VoiceEntry(player, chosen_meme, target_voice_channel)

        logging.info("Queueing new voice entry for {}".format(new_entry.name))
        await voice_connection.audio_queue.put(new_entry)

    @commands.command(pass_context=True,
                      help="Plays most things supported by youtube-dl")
    async def stream(self, ctx, video=None):
        if video is None:
            # TODO check if video is a valid streamable (YoutubeDL.py simulate?)
            self.bot.reply("Nothing to stream")
            return

        logging.info("Streaming")

        target_voice_channel = ctx.message.author.voice_channel

        if target_voice_channel is None:
            logging.info("Not streaming: User not in a channel")
            self.bot.loop.create_task(self.bot.reply("Try being in a voice channel first"))
            return

        if importlib.util.find_spec("youtube_dl") is None:
            # Preempt import error and silent failure with a more useful message and user feedback
            logging.error("Memes command requires youtube-dl module. Install with pip.")
            self.bot.reply("Bot is not configured to stream")
            return

        voice_connection = self.get_voice_connection(ctx)

        # Initialize voice if not already created
        if voice_connection.voice is None:
            logging.info("Initializing voice from stream command")
            await voice_connection.connect(target_voice_channel)
            target_voice_channel = None

        # Build a VoiceEntry
        # See YoutubeDL.py for ytdl options:
        # https://github.com/rg3/youtube-dl/blob/master/youtube_dl/YoutubeDL.py#L128
        ytdl_opts = {
            # "ignoreerrors": True  # Mostly so it won't stop on bad playlist entries
            noplaylist: True  # Too many things broke on playlists, so...
        }
        player = await voice_connection.voice.create_ytdl_player(
            video,
            use_avconv=self.use_libav,
            ytdl_options=ytdl_opts,
            after=voice_connection.toggle_next)
        new_entry = VoiceEntry(
            player, player.title,
            target_voice_channel, ctx.message)

        # Give user feedback when recieved
        await self.bot.reply("Stream queued")
        logging.info("Queueing new voice entry for {}".format(new_entry.name))
        await voice_connection.audio_queue.put(new_entry)


class VoiceEntry:
    def __init__(self, player, name, voice_channel, message=None):
{'fraction_non_alphanumeric': 0.08332838391637465, 'fraction_numerical': 0.0005939300350418721, 'mean_word_length': 2.639074994596931, 'pattern_counts': {'":': 2, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2809605', 'n_tokens_mistral': 4566, 'n_tokens_neox': 4399, 'n_words': 1181}