<commit_before>import discord
from discord.ext import commands
import time
import datetime
import socket

import os
import psutil

class Basic:
    def __init__(self, bot):
        self.bot = bot
        self.process = psutil.Process(os.getpid())

    @commands.command(aliases=['about'])
    async def info(self, ctx):
        """Returns bot's info."""
        local_time = str(datetime.datetime.now()).split('.')[0]
        total_guild_count = len(self.bot.guilds)
        total_user_count = len(list(self.bot.get_all_members()))
        total_unique_user_count = len(list(set(self.bot.get_all_members())))
        bot_info = await self.bot.application_info()
        owner = str(bot_info.owner)

        mem_bytes = self.process.memory_full_info().rss
        mem_mb = round(mem_bytes / 1024 / 1024, 2)
        cpu_usage = round(self.process.cpu_percent() / psutil.cpu_count(), 2)

        secs_since_boot = int(time.time()) - self.bot.start_time
        uptime = str(datetime.timedelta(seconds=secs_since_boot))

        em = discord.Embed()

        em.add_field(name="Git Hash", value=self.bot.get_git_revision_short_hash())
        em.add_field(name="Last git message", value=self.bot.get_git_commit_text())
        em.add_field(name="Hostname", value=socket.gethostname())
        em.add_field(name="Guild count", value=total_guild_count)
        em.add_field(name="Unique users", value=total_unique_user_count)
        em.add_field(name="Local Time", value=local_time)
        em.add_field(name="RAM Usage", value=f"{mem_mb}MiB")
        em.add_field(name="CPU Percentage", value=f"{cpu_usage}%")
        em.add_field(name="Uptime", value=uptime)
        em.add_field(name="Owner", value=owner)
        em.add_field(name="Invite", value="https://bot.ave.zone/add")

        em.set_author(name='AveBot v3', icon_url='https://s.ave.zone/c7d.png')
        await ctx.send(embed=em)

    @commands.command(aliases=['addavebot'])
    async def invite(self, ctx):
        """Gives a link that can be used to add AveBot."""
        inviteurl = discord.utils.oauth_url(self.bot.user.id)
        await ctx.send(f"You can use the following link to add AveBot to your server:\n<{inviteurl}>")

    @commands.command()
    async def ginvite(self, ctx, discordid: int):
        """Generates a discord invite link."""
        inviteurl = discord.utils.oauth_url(discordid)
        await ctx.send(f"<{inviteurl}>")

    @commands.command()
    async def servercount(self, ctx):
        """Returns the amount of servers AveBot is in."""
        total_guild_count = len(self.bot.guilds)
        total_user_count = len(list(self.bot.get_all_members()))
        total_unique_user_count = len(list(set(self.bot.get_all_members())))
        await ctx.send(f"AveBot is in {total_guild_count} servers with {total_user_count} total users ({total_unique_user_count} unique).")

    @commands.command()
    async def ping(self, ctx):
        """Shows ping values to discord."""
        before = time.monotonic()
        tmp = await ctx.send('Calculating ping...')
        after = time.monotonic()
        rtt_ms = (after - before) * 1000
        gw_ms = self.bot.latency * 1000

        message_text = f":ping_pong: rtt: {rtt_ms:.1f}ms, gw: {gw_ms:.1f}ms"
        self.bot.log.info(message_text)
        await tmp.edit(content=message_text)

    @commands.guild_only()
    @commands.command(aliases=['serverinfo'])
    async def sinfo(self, ctx):
        """Shows info about the current server."""
        current_guild = ctx.guild
        em = discord.Embed(title=f"Server info of {current_guild.name} ({current_guild.id})")

        region_replace = {"us-west": ":flag_us: US West", "us-east": ":flag_us: US East",
        "us-south": ":flag_us: US South", "us-central": ":flag_us: US Central",
        "eu-west": ":flag_eu: EU West", "eu-central": ":flag_eu: EU Central",
        "singapore": ":flag_si: Singapore", "london": ":flag_uk: London, UK",
        "sydney": ":flag_au: Sydney, AU", "amsterdam": ":flag_nl: Amsterdam, NL",
        "frankfurt": ":flag_de: Frankfurt, DE", "brazil": "<:lunadab:406845118105124864> Brazil",
        "hongkong": ":flag_hk: Hong Kong", "russia": ":flag_ru: Russia",
        "vip-us-east": ":flag_us: VIP US East", "vip-us-west": ":flag_us: VIP US West",
        "vip-amsterdam": ":flag_nl: VIP Amsterdam, NL"} # All this effort for :lunahahayes:

        region_text = str(current_guild.region)
        region_text = region_replace[region_text] if region_text in region_replace else region_text

        em.add_field(name="User Count", value=current_guild.member_count)
        em.add_field(name="Region", value=region_text)
        em.add_field(name="Owner", value=current_guild.owner)
        em.add_field(name="Verification Level", value=current_guild.verification_level)
        em.add_field(name="Created at", value=current_guild.created_at)

        em.set_thumbnail(url=current_guild.icon_url)
        await ctx.send(embed=em)

    @commands.command(aliases=['contact'])
    async def feedback(self, ctx, *, contact_text: str):
        """Contacts developers with a message."""
        em = discord.Embed(description=contact_text)

        author_name = f"{ctx.message.author} ({ctx.message.author.id}) "
        author_name += f"on \"{ctx.channel.name}\" at \"{ctx.guild.name}\"" if ctx.guild else "through DMs"

        em.set_author(name=author_name, icon_url=ctx.message.author.avatar_url)

        support_channel = self.bot.get_channel(int(self.bot.config['base']['support-channel']))
        await support_channel.send(embed=em)

        em = discord.Embed(title='Feedback sent!',
                           description='Your message has been delivered to the developers.')
        await ctx.send(embed=em)

    @commands.command(aliases=['userinfo', 'whoami', 'profile'])
    async def uinfo(self, ctx, *, user: discord.User = None):
        """Shows info about the user.

        info: returns info about the user who ran the command
        uinfo <mention/name>: returns info about the mentioned user"""

        if user is None:
            user = ctx.author

        if ctx.guild:
            maybe_member = ctx.guild.get_member(user.id)
            if maybe_member:
                user = maybe_member

        embed = discord.Embed(title=f"User Info of {str(user)}")

        if (ctx.message.author.avatar != ""):
            embed.set_thumbnail(url=ctx.message.author.avatar_url)
        embed.set_footer(text=f"UserID: {user.id}")

        embed.add_field(name="Joined Discord", value=str(user.created_at))
        if ctx.guild and maybe_member:
            embed.add_field(name="Joined Server", value=str(user.joined_at))
            embed.add_field(name="Status", value=str(user.status))
        if user.game:
            embed.add_field(name="Game", value=user.game.name)
        is_bot_display = ":white_check_mark:" if user.bot else ":x:"
        embed.add_field(name="Is bot", value=is_bot_display)

        await ctx.send(embed=embed)

def setup(bot):
    bot.add_cog(Basic(bot))<commit_msg>uinfo: display proper image when checking others'<commit_after>import discord
from discord.ext import commands
import time
import datetime
import socket

import os
import psutil

class Basic:
    def __init__(self, bot):
        self.bot = bot
        self.process = psutil.Process(os.getpid())

    @commands.command(aliases=['about'])
    async def info(self, ctx):
        """Returns bot's info."""
        local_time = str(datetime.datetime.now()).split('.')[0]
        total_guild_count = len(self.bot.guilds)
        total_user_count = len(list(self.bot.get_all_members()))
        total_unique_user_count = len(list(set(self.bot.get_all_members())))
        bot_info = await self.bot.application_info()
        owner = str(bot_info.owner)

        mem_bytes = self.process.memory_full_info().rss
        mem_mb = round(mem_bytes / 1024 / 1024, 2)
        cpu_usage = round(self.process.cpu_percent() / psutil.cpu_count(), 2)

        secs_since_boot = int(time.time()) - self.bot.start_time
        uptime = str(datetime.timedelta(seconds=secs_since_boot))

        em = discord.Embed()

        em.add_field(name="Git Hash", value=self.bot.get_git_revision_short_hash())
        em.add_field(name="Last git message", value=self.bot.get_git_commit_text())
        em.add_field(name="Hostname", value=socket.gethostname())
        em.add_field(name="Guild count", value=total_guild_count)
        em.add_field(name="Unique users", value=total_unique_user_count)
        em.add_field(name="Local Time", value=local_time)
        em.add_field(name="RAM Usage", value=f"{mem_mb}MiB")
        em.add_field(name="CPU Percentage", value=f"{cpu_usage}%")
        em.add_field(name="Uptime", value=uptime)
        em.add_field(name="Owner", value=owner)
        em.add_field(name="Invite", value="https://bot.ave.zone/add")

        em.set_author(name='AveBot v3', icon_url='https://s.ave.zone/c7d.png')
        await ctx.send(embed=em)

    @commands.command(aliases=['addavebot'])
    async def invite(self, ctx):
        """Gives a link that can be used to add AveBot."""
        inviteurl = discord.utils.oauth_url(self.bot.user.id)
        await ctx.send(f"You can use the following link to add AveBot to your server:\n<{inviteurl}>")

    @commands.command()
    async def ginvite(self, ctx, discordid: int):
        """Generates a discord invite link."""
        inviteurl = discord.utils.oauth_url(discordid)
        await ctx.send(f"<{inviteurl}>")

    @commands.command()
    async def servercount(self, ctx):
        """Returns the amount of servers AveBot is in."""
        total_guild_count = len(self.bot.guilds)
        total_user_count = len(list(self.bot.get_all_members()))
        total_unique_user_count = len(list(set(self.bot.get_all_members())))
        await ctx.send(f"AveBot is in {total_guild_count} servers with {total_user_count} total users ({total_unique_user_count} unique).")

    @commands.command()
    async def ping(self, ctx):
        """Shows ping values to discord."""
        before = time.monotonic()
        tmp = await ctx.send('Calculating ping...')
        after = time.monotonic()
        rtt_ms = (after - before) * 1000
        gw_ms = self.bot.latency * 1000

        message_text = f":ping_pong: rtt: {rtt_ms:.1f}ms, gw: {gw_ms:.1f}ms"
        self.bot.log.info(message_text)
        await tmp.edit(content=message_text)

    @commands.guild_only()
    @commands.command(aliases=['serverinfo'])
    async def sinfo(self, ctx):
        """Shows info about the current server."""
        current_guild = ctx.guild
        em = discord.Embed(title=f"Server info of {current_guild.name} ({current_guild.id})")

        region_replace = {"us-west": ":flag_us: US West", "us-east": ":flag_us: US East",
        "us-south": ":flag_us: US South", "us-central": ":flag_us: US Central",
        "eu-west": ":flag_eu: EU West", "eu-central": ":flag_eu: EU Central",
        "singapore": ":flag_si: Singapore", "london": ":flag_uk: London, UK",
        "sydney": ":flag_au: Sydney, AU", "amsterdam": ":flag_nl: Amsterdam, NL",
        "frankfurt": ":flag_de: Frankfurt, DE", "brazil": "<:lunadab:406845118105124864> Brazil",
        "hongkong": ":flag_hk: Hong Kong", "russia": ":flag_ru: Russia",
        "vip-us-east": ":flag_us: VIP US East", "vip-us-west": ":flag_us: VIP US West",
        "vip-amsterdam": ":flag_nl: VIP Amsterdam, NL"} # All this effort for :lunahahayes:

        region_text = str(current_guild.region)
        region_text = region_replace[region_text] if region_text in region_replace else region_text

        em.add_field(name="User Count", value=current_guild.member_count)
        em.add_field(name="Region", value=region_text)
        em.add_field(name="Owner", value=current_guild.owner)
        em.add_field(name="Verification Level", value=current_guild.verification_level)
        em.add_field(name="Created at", value=current_guild.created_at)

        em.set_thumbnail(url=current_guild.icon_url)
        await ctx.send(embed=em)

    @commands.command(aliases=['contact'])
    async def feedback(self, ctx, *, contact_text: str):
        """Contacts developers with a message."""
        em = discord.Embed(description=contact_text)

        author_name = f"{ctx.message.author} ({ctx.message.author.id}) "
        author_name += f"on \"{ctx.channel.name}\" at \"{ctx.guild.name}\"" if ctx.guild else "through DMs"

        em.set_author(name=author_name, icon_url=ctx.message.author.avatar_url)

        support_channel = self.bot.get_channel(int(self.bot.config['base']['support-channel']))
        await support_channel.send(embed=em)

        em = discord.Embed(title='Feedback sent!',
                           description='Your message has been delivered to the developers.')
        await ctx.send(embed=em)

    @commands.command(aliases=['userinfo', 'whoami', 'profile'])
    async def uinfo(self, ctx, *, user: discord.User = None):
        """Shows info about the user.

        info: returns info about the user who ran the command
        uinfo <mention/name>: returns info about the mentioned user"""

        if user is None:
            user = ctx.author

        if ctx.guild:
            maybe_member = ctx.guild.get_member(user.id)
            if maybe_member:
                user = maybe_member

        embed = discord.Embed(title=f"User Info of {str(user)}")

        if (ctx.message.author.avatar != ""):
            embed.set_thumbnail(url=user.avatar_url)
        embed.set_footer(text=f"UserID: {user.id}")

        embed.add_field(name="Joined Discord", value=str(user.created_at))
        if ctx.guild and maybe_member:
            embed.add_field(name="Joined Server", value=str(user.joined_at))
            embed.add_field(name="Status", value=str(user.status))
        if user.game:
            embed.add_field(name="Game", value=user.game.name)
        is_bot_display = ":white_check_mark:" if user.bot else ":x:"
        embed.add_field(name="Is bot", value=is_bot_display)

        await ctx.send(embed=embed)

def setup(bot):
    bot.add_cog(Basic(bot))<|endoftext|>


<commit_before>import json
import logging

from datetime import timedelta
from urllib import urlencode

from django.conf import settings
from django.http import HttpResponse
from django.shortcuts import render, redirect, render_to_response
from django.template import RequestContext
from django.utils import timezone

from api.models import UserPreferences, MaintenanceRecord
from troposphere.version import get_version
from troposphere.auth import has_valid_token
from troposphere.site_metadata import get_site_metadata
from .emulation import is_emulated_session
from .maintenance import get_maintenance, get_notice

logger = logging.getLogger(__name__)

if hasattr(settings, "MAINTENANCE_EXEMPT_USERNAMES"):
    MAINTENANCE_EXEMPT_USERNAMES = settings.MAINTENANCE_EXEMPT_USERNAMES
else:
    logger.error("""
        MAINTENANCE_EXEMPT_USERNAMES has not been set correctly.
        Help look at atmosphere/settings/local.py or
        add to variables.ini under [local.py] & re-run
        the ./configure script
    """)
    logger.warn("Adding fallback MAINTENANCE_EXEMPT_USERNAMES...")
    MAINTENANCE_EXEMPT_USERNAMES = [
        'sgregory', 'lenards', 'tharon',
        'cdosborn', 'julianp', 'amercer',
        'amitj', 'cmart', 'jchansen'
    ]

def root(request):
    return redirect('application')

def should_route_to_maintenace(request, in_maintenance):
    """
    Indicate if a response should be handled by the maintenance view.
    """
    return (in_maintenance
        and request.user.is_staff is not True
        and request.user.username not in MAINTENANCE_EXEMPT_USERNAMES
        and not is_emulated_session(request))

def _should_enabled_new_relic():
    return hasattr(settings, "NEW_RELIC_ENVIRONMENT") and \
        bool(settings.NEW_RELIC_ENVIRONMENT) is True

def _populate_template_params(request, maintenance_records, notice_t, disabled_login, public=False):
    """
    Creates a dict of parameters for later template merge given the arguments,
    request session, and django settings (defined in `default.py` or overidden
    in `local.py`).
    """
    # keep this variable around for the return statement ...
    enable_new_relic = _should_enabled_new_relic()
    notice = ""
    if notice_t and len(notice_t) > 2:
        notice = notice_t[1] if not notice_t[2] else None
    logger.info("maintenance notice tuple: {0}".format(notice_t))

    #NOTE: Now that we've moved this section from .js to Django, sentry configuration _could_ become more dynamic:
    if settings.DEBUG:
        sentry_dict = None
    else:
        sentry_dict = {
            "dsn": "https://27643f06676048be96ad6df686c17da3@app.getsentry.com/73366",
            "release":"205096b5fde3a47303ad8d1fef9ff8052cbbd7d4",
        }

    template_params = {
        'access_token': request.session.get('access_token'),
        'emulator_token': request.session.get('emulator_token'),
        'emulator': request.session.get('emulator'),
        'records': maintenance_records,
        'notice': notice,
        'sentry': sentry_dict,
        'new_relic_enabled': enable_new_relic,
        'show_public_site': public
    }

    if public:
        template_params['disable_login'] = disabled_login
    else:
        template_params['disable_login'] = False
        template_params['SHOW_INSTANCE_METRICS'] = \
            getattr(settings, "SHOW_INSTANCE_METRICS", False)
        # Only include Intercom information when rendering the authenticated
        # version of the site.
        if hasattr(settings, "INTERCOM_APP_ID"):
            template_params['intercom_app_id'] = settings.INTERCOM_APP_ID
            template_params['intercom_company_id'] = \
                settings.INTERCOM_COMPANY_ID
            template_params['intercom_company_name'] = \
                settings.INTERCOM_COMPANY_NAME

    if enable_new_relic:
        template_params['new_relic_browser_snippet'] = \
            settings.NEW_RELIC_BROWSER_SNIPPET

    template_params['SITE_TITLE'] = settings.SITE_TITLE
    template_params['SITE_FOOTER'] = settings.SITE_FOOTER
    template_params['SUPPORT_EMAIL'] = settings.SUPPORT_EMAIL
    template_params['UI_VERSION'] = settings.UI_VERSION
    template_params['BADGE_HOST'] = getattr(settings, "BADGE_HOST", None)
    template_params['USE_MOCK_DATA'] = getattr(settings, "USE_MOCK_DATA", False)
    template_params['USE_ALLOCATION_SOURCES'] = getattr(settings,
            "USE_ALLOCATION_SOURCES", False)
    template_params['THEME_URL'] = "/assets/theme"
    template_params['ORG_NAME'] = settings.ORG_NAME
    template_params['DYNAMIC_ASSET_LOADING'] = settings.DYNAMIC_ASSET_LOADING

    if hasattr(settings, "BASE_URL"):
        template_params['BASE_URL'] = settings.BASE_URL

    metadata = get_site_metadata()

    template_params['DISPLAY_STATUS_PAGE'] = False
    template_params['WEB_DESKTOP_INCLUDE_LINK'] = \
        settings.WEB_DESKTOP_INCLUDE_LINK

    if metadata:
        template_params['DISPLAY_STATUS_PAGE'] = \
            metadata.display_status_page_link
        template_params['STATUS_PAGE_LINK'] = \
            metadata.status_page_link
        template_params['SITE_FOOTER_LINK'] = \
            metadata.site_footer_link
        template_params['USER_PORTAL'] = \
            metadata.get_user_portal_as_json()

    if hasattr(settings, "API_ROOT"):
        template_params['API_ROOT'] = settings.API_ROOT

    if hasattr(settings, "API_V2_ROOT"):
        template_params['API_V2_ROOT'] = settings.API_V2_ROOT

    if hasattr(settings, "USE_GATE_ONE_API"):
        template_params['USE_GATE_ONE_API'] = settings.USE_GATE_ONE_API
        template_params['WEB_SH_URL'] = settings.WEB_SH_URL

    return template_params

def _handle_public_application_request(request, maintenance_records, disabled_login=False):
    """
    Deal with unauthenticated requests:
    - there is only the opportunity to browser the Public Image Catalog.

    """
    template_params = _populate_template_params(request, maintenance_records,
                                                None, disabled_login, True)

    if 'new_relic_enabled' in template_params:
        logger.info("New Relic enabled? %s" % template_params['new_relic_enabled'])
    else:
        logger.info("New Relic key missing from `template_params`")

    response = render_to_response(
        'index.html',
        template_params,
        context_instance=RequestContext(request)
    )

    return response

def _handle_authenticated_application_request(request, maintenance_records,
        notice_info):
    """
    Deals with request verified identities via `iplantauth` module.
    """
    if notice_info and notice_info[1]:
        notice_info = (notice_info[0], notice_info[1],
            'maintenance_notice' in request.COOKIES)

    template_params = _populate_template_params(request, maintenance_records,
                                                notice_info,
                                                disabled_login=False,
                                                public=False)

    # UserPreferences currently do not have a reason to be fetched.
    # This model was used when we need to decide which user interface to
    # given what the community member preferred.
    #
    # I leave this for now as an example of how to fetch this model:
    # ---
    # user_preferences, created = UserPreferences.objects.get_or_create(
    #    user=request.user)

    if 'new_relic_enabled' in template_params:
        logger.info("New Relic enabled? %s" % template_params['new_relic_enabled'])
    else:
        logger.info("New Relic key missing from `template_params`")

    response = render_to_response(
        'index.html',
        template_params,
        context_instance=RequestContext(request)
    )

    if 'maintenance_notice' not in request.COOKIES:
        response.set_cookie('maintenance_notice', 'true',
            expires=(timezone.now() + timedelta(hours=3)))

    return response

def application_backdoor(request):
    maintenance_records, _, in_maintenance = get_maintenance(request)
    # This should only apply when in maintenance//login is disabled
    if maintenance_records.count() == 0:
        logger.info('No maintenance, Go to /application - do not collect $100')
        return redirect('application')

    if request.user.is_authenticated() and request.user.username not in MAINTENANCE_EXEMPT_USERNAMES:
        logger.warn('[Backdoor] %s is NOT in staff_list_usernames' % request.user.username)
        return redirect('maintenance')

    # route a potential VIP to login
    query_arguments = {
        'redirect_to': '/application',
        'bsp': 'true'
    }
    # I'm on the Guest List! Backstage Pass!
    return redirect('/login?%s' % (urlencode(query_arguments),))

def application(request):
    maintenance_records, disabled_login, in_maintenance = \
        get_maintenance(request)
    notice_info = get_notice(request)

    if should_route_to_maintenace(request, in_maintenance):
        logger.warn('%s has actice session but is NOT in staff_list_usernames'
            % request.user.username)
        logger.warn('- routing user')
        return redirect('maintenance')

    if request.user.is_authenticated() and has_valid_token(request.user):
        return _handle_authenticated_application_request(request,
            maintenance_records,
            notice_info)
    else:
        return _handle_public_application_request(request, maintenance_records,
            disabled_login=disabled_login)

def forbidden(request):
    """
    View used when someone tries to log in and is an authenticated iPlant
    user, but was found to be unauthorized to use Atmosphere by OAuth.
    Returns HTTP status code 403 Forbidden
    """
    metadata = get_site_metadata()
    template_params = {}

    template_params['THEME_URL'] = "/assets/theme"
    template_params['ORG_NAME'] = settings.ORG_NAME
    template_params['SITE_TITLE'] = settings.SITE_TITLE
    template_params['SITE_FOOTER'] = settings.SITE_FOOTER
    template_params['SUPPORT_EMAIL'] = settings.SUPPORT_EMAIL
    template_params['USER_PORTAL_LINK'] = metadata.user_portal_link
    template_params['USER_PORTAL_LINK_TEXT'] = metadata.user_portal_link_text
    template_params['ACCOUNT_INSTRUCTIONS_LINK'] = \
        metadata.account_instructions_link

    if hasattr(settings, "BASE_URL"):
        template_params['BASE_URL'] = settings.BASE_URL

    # If banner message in query params, pass it into the template
    if "banner" in request.GET:
        template_params['banner'] = request.GET['banner']

    response = render_to_response(
        'no_user.html',
        template_params,
        context_instance=RequestContext(request)
    )
    return response

def version(request):
    v = get_version()
    v["commit_date"] = v["commit_date"].isoformat()
    v_json = json.dumps(v)
    return HttpResponse(v_json, content_type='application/json')

def tests(request):

    template_params = {
        'test': request.GET['test'].lower()
    }

    return render(request, 'tests.html', template_params)
<commit_msg>Update Sentry release<commit_after>import json
import logging

from datetime import timedelta
from urllib import urlencode

from django.conf import settings
from django.http import HttpResponse
from django.shortcuts import render, redirect, render_to_response
from django.template import RequestContext
from django.utils import timezone

from api.models import UserPreferences, MaintenanceRecord
from troposphere.version import get_version
from troposphere.auth import has_valid_token
from troposphere.site_metadata import get_site_metadata
from .emulation import is_emulated_session
from .maintenance import get_maintenance, get_notice

logger = logging.getLogger(__name__)

if hasattr(settings, "MAINTENANCE_EXEMPT_USERNAMES"):
    MAINTENANCE_EXEMPT_USERNAMES = settings.MAINTENANCE_EXEMPT_USERNAMES
else:
    logger.error("""
        MAINTENANCE_EXEMPT_USERNAMES has not been set correctly.
        Help look at atmosphere/settings/local.py or
        add to variables.ini under [local.py] & re-run
        the ./configure script
    """)
    logger.warn("Adding fallback MAINTENANCE_EXEMPT_USERNAMES...")
    MAINTENANCE_EXEMPT_USERNAMES = [
        'sgregory', 'lenards', 'tharon',
        'cdosborn', 'julianp', 'amercer',
        'amitj', 'cmart', 'jchansen'
    ]

def root(request):
    return redirect('application')

def should_route_to_maintenace(request, in_maintenance):
    """
    Indicate if a response should be handled by the maintenance view.
    """
    return (in_maintenance
        and request.user.is_staff is not True
        and request.user.username not in MAINTENANCE_EXEMPT_USERNAMES
        and not is_emulated_session(request))

def _should_enabled_new_relic():
    return hasattr(settings, "NEW_RELIC_ENVIRONMENT") and \
        bool(settings.NEW_RELIC_ENVIRONMENT) is True

def _populate_template_params(request, maintenance_records, notice_t, disabled_login, public=False):
    """
    Creates a dict of parameters for later template merge given the arguments,
    request session, and django settings (defined in `default.py` or overidden
    in `local.py`).
    """
    # keep this variable around for the return statement ...
    enable_new_relic = _should_enabled_new_relic()
    notice = ""
    if notice_t and len(notice_t) > 2:
        notice = notice_t[1] if not notice_t[2] else None
    logger.info("maintenance notice tuple: {0}".format(notice_t))

    #NOTE: Now that we've moved this section from .js to Django, sentry configuration _could_ become more dynamic:
    if settings.DEBUG:
        sentry_dict = None
    else:
        sentry_dict = {
            "dsn": "https://27643f06676048be96ad6df686c17da3@app.getsentry.com/73366",
            "release":"0ec17fad757f44cbdda8ceace19139fd",
        }

    template_params = {
        'access_token': request.session.get('access_token'),
        'emulator_token': request.session.get('emulator_token'),
        'emulator': request.session.get('emulator'),
        'records': maintenance_records,
        'notice': notice,
        'sentry': sentry_dict,
        'new_relic_enabled': enable_new_relic,
        'show_public_site': public
    }

    if public:
        template_params['disable_login'] = disabled_login
    else:
        template_params['disable_login'] = False
        template_params['SHOW_INSTANCE_METRICS'] = \
            getattr(settings, "SHOW_INSTANCE_METRICS", False)
        # Only include Intercom information when rendering the authenticated
        # version of the site.
        if hasattr(settings, "INTERCOM_APP_ID"):
            template_params['intercom_app_id'] = settings.INTERCOM_APP_ID
            template_params['intercom_company_id'] = \
                settings.INTERCOM_COMPANY_ID
            template_params['intercom_company_name'] = \
                settings.INTERCOM_COMPANY_NAME

    if enable_new_relic:
        template_params['new_relic_browser_snippet'] = \
            settings.NEW_RELIC_BROWSER_SNIPPET

    template_params['SITE_TITLE'] = settings.SITE_TITLE
    template_params['SITE_FOOTER'] = settings.SITE_FOOTER
    template_params['SUPPORT_EMAIL'] = settings.SUPPORT_EMAIL
    template_params['UI_VERSION'] = settings.UI_VERSION
    template_params['BADGE_HOST'] = getattr(settings, "BADGE_HOST", None)
    template_params['USE_MOCK_DATA'] = getattr(settings, "USE_MOCK_DATA", False)
    template_params['USE_ALLOCATION_SOURCES'] = getattr(settings,
            "USE_ALLOCATION_SOURCES", False)
    template_params['THEME_URL'] = "/assets/theme"
    template_params['ORG_NAME'] = settings.ORG_NAME
    template_params['DYNAMIC_ASSET_LOADING'] = settings.DYNAMIC_ASSET_LOADING

    if hasattr(settings, "BASE_URL"):
        template_params['BASE_URL'] = settings.BASE_URL

    metadata = get_site_metadata()

    template_params['DISPLAY_STATUS_PAGE'] = False
    template_params['WEB_DESKTOP_INCLUDE_LINK'] = \
        settings.WEB_DESKTOP_INCLUDE_LINK

    if metadata:
        template_params['DISPLAY_STATUS_PAGE'] = \
            metadata.display_status_page_link
        template_params['STATUS_PAGE_LINK'] = \
            metadata.status_page_link
        template_params['SITE_FOOTER_LINK'] = \
            metadata.site_footer_link
        template_params['USER_PORTAL'] = \
            metadata.get_user_portal_as_json()

    if hasattr(settings, "API_ROOT"):
        template_params['API_ROOT'] = settings.API_ROOT

    if hasattr(settings, "API_V2_ROOT"):
        template_params['API_V2_ROOT'] = settings.API_V2_ROOT

    if hasattr(settings, "USE_GATE_ONE_API"):
        template_params['USE_GATE_ONE_API'] = settings.USE_GATE_ONE_API
        template_params['WEB_SH_URL'] = settings.WEB_SH_URL

    return template_params

def _handle_public_application_request(request, maintenance_records, disabled_login=False):
    """
    Deal with unauthenticated requests:
    - there is only the opportunity to browser the Public Image Catalog.

    """
    template_params = _populate_template_params(request, maintenance_records,
                                                None, disabled_login, True)

    if 'new_relic_enabled' in template_params:
        logger.info("New Relic enabled? %s" % template_params['new_relic_enabled'])
    else:
        logger.info("New Relic key missing from `template_params`")

    response = render_to_response(
        'index.html',
        template_params,
        context_instance=RequestContext(request)
    )

    return response

def _handle_authenticated_application_request(request, maintenance_records,
        notice_info):
    """
    Deals with request verified identities via `iplantauth` module.
    """
    if notice_info and notice_info[1]:
        notice_info = (notice_info[0], notice_info[1],
            'maintenance_notice' in request.COOKIES)

    template_params = _populate_template_params(request, maintenance_records,
                                                notice_info,
                                                disabled_login=False,
                                                public=False)

    # UserPreferences currently do not have a reason to be fetched.
    # This model was used when we need to decide which user interface to
    # given what the community member preferred.
    #
    # I leave this for now as an example of how to fetch this model:
    # ---
    # user_preferences, created = UserPreferences.objects.get_or_create(
    #    user=request.user)

    if 'new_relic_enabled' in template_params:
        logger.info("New Relic enabled? %s" % template_params['new_relic_enabled'])
    else:
        logger.info("New Relic key missing from `template_params`")

    response = render_to_response(
        'index.html',
        template_params,
        context_instance=RequestContext(request)
    )

    if 'maintenance_notice' not in request.COOKIES:
        response.set_cookie('maintenance_notice', 'true',
            expires=(timezone.now() + timedelta(hours=3)))

    return response

def application_backdoor(request):
    maintenance_records, _, in_maintenance = get_maintenance(request)
    # This should only apply when in maintenance//login is disabled
    if maintenance_records.count() == 0:
        logger.info('No maintenance, Go to /application - do not collect $100')
        return redirect('application')

    if request.user.is_authenticated() and request.user.username not in MAINTENANCE_EXEMPT_USERNAMES:
        logger.warn('[Backdoor] %s is NOT in staff_list_usernames' % request.user.username)
        return redirect('maintenance')

    # route a potential VIP to login
    query_arguments = {
        'redirect_to': '/application',
        'bsp': 'true'
    }
    # I'm on the Guest List! Backstage Pass!
    return redirect('/login?%s' % (urlencode(query_arguments),))

def application(request):
    maintenance_records, disabled_login, in_maintenance = \
        get_maintenance(request)
    notice_info = get_notice(request)

    if should_route_to_maintenace(request, in_maintenance):
        logger.warn('%s has actice session but is NOT in staff_list_usernames'
            % request.user.username)
        logger.warn('- routing user')
        return redirect('maintenance')

    if request.user.is_authenticated() and has_valid_token(request.user):
        return _handle_authenticated_application_request(request,
            maintenance_records,
            notice_info)
    else:
        return _handle_public_application_request(request, maintenance_records,
            disabled_login=disabled_login)

def forbidden(request):
    """
    View used when someone tries to log in and is an authenticated iPlant
    user, but was found to be unauthorized to use Atmosphere by OAuth.
    Returns HTTP status code 403 Forbidden
    """
    metadata = get_site_metadata()
    template_params = {}

    template_params['THEME_URL'] = "/assets/theme"
    template_params['ORG_NAME'] = settings.ORG_NAME
    template_params['SITE_TITLE'] = settings.SITE_TITLE
    template_params['SITE_FOOTER'] = settings.SITE_FOOTER
    template_params['SUPPORT_EMAIL'] = settings.SUPPORT_EMAIL
    template_params['USER_PORTAL_LINK'] = metadata.user_portal_link
    template_params['USER_PORTAL_LINK_TEXT'] = metadata.user_portal_link_text
    template_params['ACCOUNT_INSTRUCTIONS_LINK'] = \
        metadata.account_instructions_link

    if hasattr(settings, "BASE_URL"):
        template_params['BASE_URL'] = settings.BASE_URL

    # If banner message in query params, pass it into the template
    if "banner" in request.GET:
        template_params['banner'] = request.GET['banner']

    response = render_to_response(
        'no_user.html',
        template_params,
        context_instance=RequestContext(request)
    )
    return response

def version(request):
    v = get_version()
    v["commit_date"] = v["commit_date"].isoformat()
    v_json = json.dumps(v)
    return HttpResponse(v_json, content_type='application/json')

def tests(request):

    template_params = {
        'test': request.GET['test'].lower()
    }

    return render(request, 'tests.html', template_params)
<|endoftext|>


<commit_before>from django.db.models.signals import post_save
from django.contrib.auth.models import User
from .models import Horchata
from django.dispatch import receiver
from rest_framework.authtoken.models import Token
import environ
import twitter

root = environ.Path(__file__) -2
path = str(root) + '/HorchataClub/.env'
env = environ.Env(DEBUG=(bool, False),)
environ.Env.read_env(path)

@receiver(post_save, sender=User)
def init_new_user(sender, instance, signal, created, **kwargs):
    if created:
        print ('Token created')
        Token.objects.create(user=instance)

@receiver(post_save, sender=Horchata)
def init_new_horchata(sender, instance, signal, created, **kwargs):
    if created:
        print ('Horchata Created')
        api = twitter.Api(
            consumer_key= env('TW_CONSUMER_KEY'),
            consumer_secret= env('TW_CONSUMER_SECRET'),
            access_token_key= env('TW_TOKEN_KEY'),
            access_token_secret= env('TW_TOKEN_SECRET')
        )
        text = 'Check out ' + instance.name + 'on http://horchata.club/'
        media = instance.image.url
        # status = api.PostUpdate(text, media)
        status = api.PostUpdate(text)
        print(status)
        instance.tweet = status.id_str
        instance.save()
<commit_msg>Deactivated Twitter post<commit_after>from django.db.models.signals import post_save
from django.contrib.auth.models import User
from .models import Horchata
from django.dispatch import receiver
from rest_framework.authtoken.models import Token
import environ
import twitter

root = environ.Path(__file__) -2
path = str(root) + '/HorchataClub/.env'
env = environ.Env(DEBUG=(bool, False),)
environ.Env.read_env(path)

@receiver(post_save, sender=User)
def init_new_user(sender, instance, signal, created, **kwargs):
    if created:
        print ('Token created')
        Token.objects.create(user=instance)

# @receiver(post_save, sender=Horchata)
# def init_new_horchata(sender, instance, signal, created, **kwargs):
#     if created:
#         print ('Horchata Created')
#         api = twitter.Api(
#             consumer_key= env('TW_CONSUMER_KEY'),
#             consumer_secret= env('TW_CONSUMER_SECRET'),
#             access_token_key= env('TW_TOKEN_KEY'),
#             access_token_secret= env('TW_TOKEN_SECRET')
#         )
#         text = 'Check out ' + instance.name + 'on http://horchata.club/'
#         media = instance.image.url
#         # status = api.PostUpdate(text, media)
#         status = api.PostUpdate(text)
#         print(status)
#         instance.tweet = status.id_str
#         instance.save()
<|endoftext|>


<commit_before>import discord
from discord.ext import commands

class ErrorFormatter:
    def __init__(self, bot):
        self.bot = bot

    @commands.command(name='error', hidden=True)
    async def error_formatter(self, ctx, *, err: str):
        """ Send a nice embed error with a message
        Usage: In other cogs
        cmd = bot.get_command('error')
        await ctx.invoke(cmd, 'error message here')
        """
        em = discord.Embed(title=':x: Error', color=discord.Color.red(), description=err)
        await ctx.send(embed=em)

def setup(bot):
    bot.add_cog(ErrorFormatter(bot))<commit_msg>darker red<commit_after>import discord
from discord.ext import commands

class ErrorFormatter:
    def __init__(self, bot):
        self.bot = bot

    @commands.command(name='error', hidden=True)
    async def error_formatter(self, ctx, *, err: str):
        """ Send a nice embed error with a message
        Usage: In other cogs
        cmd = bot.get_command('error')
        await ctx.invoke(cmd, 'error message here')
        """
        em = discord.Embed(title=':x: Error', color=discord.Color.dark_red(), description=err)
        await ctx.send(embed=em)

def setup(bot):
    bot.add_cog(ErrorFormatter(bot))<|endoftext|>


<commit_before>"""EasyEngine package installation using apt-get module."""
import apt
import apt_pkg
import sys
from ee.core.logging import Log
from sh import apt_get

class EEAptGet():
    """Generic apt-get intialisation"""

    def update(self):
        try:
            apt_cache = apt.cache.Cache()
            apt_cache.update()
            success = (apt_cache.commit(
                       apt.progress.text.AcquireProgress(),
                       apt.progress.base.InstallProgress()))
            #apt_cache.close()
            return success
        except AttributeError as e:
            Log.error(self, 'AttributeError: ' + str(e))

    def dist_upgrade():
        apt_cache = apt.cache.Cache()
        apt_cache.update()
        apt_cache.open(None)
        apt_cache.upgrade(True)
        success = (apt_cache.commit(
                   apt.progress.text.AcquireProgress(),
                   apt.progress.base.InstallProgress()))
        #apt_cache.close()
        return success

    def install(self, packages):
        """Installation of packages"""
        def install_package(self, package_name):
            apt_pkg.init()
            # #apt_pkg.PkgSystemLock()
            apt_cache = apt.cache.Cache()
            pkg = apt_cache[package_name.strip()]
            if package_name.strip() in apt_cache:
                if pkg.is_installed:
                    #apt_pkg.PkgSystemUnLock()
                    Log.info(self, 'Trying to install a package that '
                             'is already installed (' +
                             package_name.strip() + ')')
                    #apt_cache.close()
                    return False
                else:
                    pkg.mark_install()
                    try:
                        #apt_pkg.PkgSystemUnLock()
                        result = apt_cache.commit()
                        #apt_cache.close()
                        return result
                    except SystemError as e:
                        Log.error(self, 'SystemError: ' + str(e))
                        #apt_cache.close()
            else:
                #apt_cache.close()
                Log.error(self, 'Unknown package selected (' +
                          package_name.strip() + ')')

        for package in packages:
            if not install_package(self, package):
                continue

    def remove(self, packages, auto=False, purge=False):
        def remove_package(self, package_name, purge=False):
            apt_pkg.init()
            # apt_pkg.PkgSystemLock()
            apt_cache = apt.cache.Cache()
            pkg = apt_cache[package_name.strip()]
            if package_name.strip() in apt_cache:
                if not pkg.is_installed:
                    # apt_pkg.PkgSystemUnLock()
                    Log.info(self, 'Trying to uninstall a package '
                             'that is not installed (' +
                             package_name.strip() + ')')
                    return False
                else:
                    try:
                        pkg.mark_delete(purge)
                    except SystemError as e:
                        Log.debug(self, 'SystemError: ' + str(e))
                        return False
                    try:
                        # apt_pkg.PkgSystemUnLock()
                        result = apt_cache.commit()
                        # apt_cache.close()
                        return result
                    except SystemError as e:
                        Log.debug(self, 'SystemError: ' + str(e))
                        return False
                        # apt_cache.close()
            else:
                # apt_cache.close()
                Log.error(self, 'Unknown package selected (' +
                          package_name.strip() + ')')

        for package in packages:
            if not remove_package(self, package, purge=purge):
                continue

    def auto_clean(self):
        try:
            apt_get.autoclean("-y")
        except ErrorReturnCode as e:
            Log.debug(self, "{0}".format(e))
            Log.error(self, "Unable to apt-get autoclean")

    def auto_remove(self):
        try:
            Log.debug(self, "Running apt-get autoremove")
            apt_get.autoremove("-y")
        except ErrorReturnCode as e:
            Log.debug(self, "{0}".format(e))
            Log.error(self, "Unable to apt-get autoremove")

    def is_installed(self, package_name):
        apt_cache = apt.cache.Cache()
        apt_cache.open()
        if (package_name.strip() in apt_cache and
           apt_cache[package_name.strip()].is_installed):
            # apt_cache.close()
            return True
        # apt_cache.close()
        return False
<commit_msg>apt-get unable to fork bug solved<commit_after>"""EasyEngine package installation using apt-get module."""
import apt
import apt_pkg
import sys
from ee.core.logging import Log
from sh import apt_get

class EEAptGet():
    """Generic apt-get intialisation"""

    def update(self):
        try:
            apt_cache = apt.cache.Cache()
            apt_cache.update()
            success = (apt_cache.commit(
                       apt.progress.text.AcquireProgress(),
                       apt.progress.base.InstallProgress()))
            #apt_cache.close()
            return success
        except AttributeError as e:
            Log.error(self, 'AttributeError: ' + str(e))

    def dist_upgrade():
        apt_cache = apt.cache.Cache()
        apt_cache.update()
        apt_cache.open(None)
        apt_cache.upgrade(True)
        success = (apt_cache.commit(
                   apt.progress.text.AcquireProgress(),
                   apt.progress.base.InstallProgress()))
        #apt_cache.close()
        return success

    def install(self, packages):
        """Installation of packages"""
        apt_pkg.init()
        # #apt_pkg.PkgSystemLock()
        apt_cache = apt.cache.Cache()

        def install_package(self, package_name):
            global apt_cache
            pkg = apt_cache[package_name.strip()]
            if package_name.strip() in apt_cache:
                if pkg.is_installed:
                    #apt_pkg.PkgSystemUnLock()
                    Log.debug(self, 'Trying to install a package that '
                              'is already installed (' +
                              package_name.strip() + ')')
                    #apt_cache.close()
                    return False
                else:
                    pkg.mark_install()
                    try:
                        #apt_pkg.PkgSystemUnLock()
                        result = apt_cache.commit()
                        #apt_cache.close()
                        return result
                    except SystemError as e:
                        Log.error(self, 'SystemError: ' + str(e))
                        #apt_cache.close()
            else:
                #apt_cache.close()
                Log.error(self, 'Unknown package selected (' +
                          package_name.strip() + ')')

        for package in packages:
            if not install_package(self, package):
                continue

    def remove(self, packages, auto=False, purge=False):
        apt_pkg.init()
        # apt_pkg.PkgSystemLock()
        apt_cache = apt.cache.Cache()

        def remove_package(self, package_name, purge=False):
            global apt_cache
            pkg = apt_cache[package_name.strip()]
            if package_name.strip() in apt_cache:
                if not pkg.is_installed:
                    # apt_pkg.PkgSystemUnLock()
                    Log.debug(self, 'Trying to uninstall a package '
                              'that is not installed (' +
                              package_name.strip() + ')')
                    return False
                else:
                    try:
                        pkg.mark_delete(purge)
                    except SystemError as e:
                        Log.debug(self, 'SystemError: ' + str(e))
                        return False
                    try:
                        # apt_pkg.PkgSystemUnLock()
                        result = apt_cache.commit()
                        # apt_cache.close()
                        return result
                    except SystemError as e:
                        Log.debug(self, 'SystemError: ' + str(e))
                        return False
                        # apt_cache.close()
            else:
                # apt_cache.close()
                Log.error(self, 'Unknown package selected (' +
                          package_name.strip() + ')')

        for package in packages:
            if not remove_package(self, package, purge=purge):
                continue

    def auto_clean(self):
        try:
            apt_get.autoclean("-y")
        except ErrorReturnCode as e:
            Log.debug(self, "{0}".format(e))
            Log.error(self, "Unable to apt-get autoclean")

    def auto_remove(self):
        try:
            Log.debug(self, "Running apt-get autoremove")
            apt_get.autoremove("-y")
        except ErrorReturnCode as e:
            Log.debug(self, "{0}".format(e))
            Log.error(self, "Unable to apt-get autoremove")

    def is_installed(self, package_name):
        apt_cache = apt.cache.Cache()
        apt_cache.open()
        if (package_name.strip() in apt_cache and
           apt_cache[package_name.strip()].is_installed):
            # apt_cache.close()
            return True
        # apt_cache.close()
        return False
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-

import base64
import configparser
import xml.etree.ElementTree as ET

class SamlAssertion:

    def __init__(self, assertion):
        self.assertion = assertion

    @staticmethod
    def split_roles(roles):
        return [(y.strip())
                for y
                in roles.text.split(',')]

    @staticmethod
    def sort_roles(roles):
        return sorted(roles,
                      key=lambda role: 'saml-provider' in role)

    def roles(self):
        attributes = ET.fromstring(self.assertion).getiterator('{urn:oasis:names:tc:SAML:2.0:assertion}Attribute')

        roles_attributes = [x for x
                            in attributes
                            if x.get('Name') == 'https://aws.amazon.com/SAML/Attributes/Role']

        roles_values = [(x.getiterator('{urn:oasis:names:tc:SAML:2.0:assertion}AttributeValue'))
                        for x
                        in roles_attributes]

        return [(dict(zip(['role', 'principle'],
                          self.sort_roles(self.split_roles(x)))))
                for x
                in roles_values[0]]

    def encode(self):
        return base64.b64encode(self.assertion.encode('utf8'))

class AwsCredentialsFile:

    def __init__(self, filename):
        self.filename = filename
        return

    def _add_profile(self, name, profile):

        config = configparser.ConfigParser(interpolation=None)
        try:
            config.read_file(open(self.filename, 'r'))
        except:
            pass

        if not config.has_section(name):
            config.add_section(name)

        [(config.set(name, k, v))
         for k, v in profile.items()]

        with open(self.filename, 'w+') as configfile:
            config.write(configfile)

    def add_profile(self, name, region, credentials):
        name = unicode(name)
        self._add_profile(name, {u'output': u'json',
                                 u'region': unicode(region),
                                 u'aws_access_key_id': unicode(credentials.access_key),
                                 u'aws_secret_access_key': unicode(credentials.secret_key),
                                 u'aws_security_token': unicode(credentials.session_token),
                                 u'aws_session_token': unicode(credentials.session_token)})
<commit_msg>unicode is str in Python 3<commit_after># -*- coding: utf-8 -*-

import base64
import configparser
import xml.etree.ElementTree as ET

class SamlAssertion:

    def __init__(self, assertion):
        self.assertion = assertion

    @staticmethod
    def split_roles(roles):
        return [(y.strip())
                for y
                in roles.text.split(',')]

    @staticmethod
    def sort_roles(roles):
        return sorted(roles,
                      key=lambda role: 'saml-provider' in role)

    def roles(self):
        attributes = ET.fromstring(self.assertion).getiterator('{urn:oasis:names:tc:SAML:2.0:assertion}Attribute')

        roles_attributes = [x for x
                            in attributes
                            if x.get('Name') == 'https://aws.amazon.com/SAML/Attributes/Role']

        roles_values = [(x.getiterator('{urn:oasis:names:tc:SAML:2.0:assertion}AttributeValue'))
                        for x
                        in roles_attributes]

        return [(dict(zip(['role', 'principle'],
                          self.sort_roles(self.split_roles(x)))))
                for x
                in roles_values[0]]

    def encode(self):
        return base64.b64encode(self.assertion.encode('utf8'))

class AwsCredentialsFile:

    def __init__(self, filename):
        self.filename = filename
        return

    def _add_profile(self, name, profile):

        config = configparser.ConfigParser(interpolation=None)
        try:
            config.read_file(open(self.filename, 'r'))
        except:
            pass

        if not config.has_section(name):
            config.add_section(name)

        [(config.set(name, k, v))
         for k, v in profile.items()]

        with open(self.filename, 'w+') as configfile:
            config.write(configfile)

    def add_profile(self, name, region, credentials):
        name = str(name)
        self._add_profile(
            name, {
                u'output': u'json',
                u'region': str(region),
                u'aws_access_key_id': str(credentials.access_key),
                u'aws_secret_access_key': str(credentials.secret_key),
                u'aws_security_token': str(credentials.session_token),
                u'aws_session_token': str(credentials.session_token)
            })
<|endoftext|>


<commit_before>'''
Created on Feb 1, 2011

@author: Shreyas Joshi
@organization: Georgia Institute of Technology
@contact: shreyasj@gatech.edu
@summary: This is an implementation of the K nearest neighbor learning algorithm. The implementation is trivial in that the near neighbors are
          calculated naively- without any smart tricks. Euclidean distance is used to calculate the distance between two points. The implementation
          also provides some coarse parallelism. If the par_query function is used then the query points are split up equally amongst threads and their
          near neighbors are calculated in parallel. If the number of threads to use is not specified then no of threads  = no of cores as returned by
          the cpu_count function. This may not be ideal.
@status: complete. "mode" untested
'''

import numpy as np
import math
import sys
import time
from multiprocessing import Pool
from multiprocessing import cpu_count
data = np.zeros (0)#this is the global data

'''
@param allQueries: is another 2D numpy array. Each row here is one query point. It has no 'y' values. These have to be calculated.
@param k: no. of neighbors to consider
@param method: method of combining the 'y' values of the nearest neighbors. Default is mean.
@param noOfThreads: optional paramter that specifies how many threads to create. Default value: no. of threads = value returned by cpu_count
@return: A numpy array with the predicted 'y' values for the query points. The ith element in the array is the 'y' value for the ith query point.
'''
def par_query (allQueries, k, method='mean', noOfThreads=None):
    #Here we basically start 'noOfThreads' threads. Each thread calculates the neighbors for (noOfQueryPoints / noOfThreads) query points.

    if (noOfThreads == None):
        noOfThreads = cpu_count()
        #if ends

    print "No of threads: " + str (noOfThreads)
    pool = Pool (processes=noOfThreads)

    resultList = []
    query_per_thread = allQueries.shape[0] / noOfThreads

    time_start = time.time();
    for thread_ctr in range (0, noOfThreads - 1):
        resultList.append(pool.apply_async(query, (allQueries[math.floor(query_per_thread * thread_ctr): (math.floor(query_per_thread * (thread_ctr + 1 ))),:], k,)))
        #NOTE: we may need a -1 in above. Possible bug
        #for ends
    #the "remaining" query points go to the last thread
    resultList.append (pool.apply_async(query , (allQueries[(math.floor(query_per_thread* (noOfThreads - 1))): -1, :] ,k ,)))

    pool.close()
    pool.join()

    time_finish = time.time()
    print "Time taken (secs): " + str (time_finish - time_start)

    answer = resultList[0].get()

    for thread_ctr  in range (1, noOfThreads):
        answer = np.hstack((answer, resultList[thread_ctr].get()))
        #for ends

    print "par_query done"
    return answer
    #par_query ends

'''
@param allQueries: is another 2D numpy array. Each row here is one query point. It has no 'y' values. These have to be calculated.
@param k: no. of neighbors to consider
@param method: method of combining the 'y' values of the nearest neighbors. Default is mean.
@return: A numpy array with the predicted 'y' values for the query points. The ith element in the array is the 'y' value for the ith query point.
'''
def query(allQueries, k, method='mean'):
    limit = allQueries.shape [0]
    data_limit = data.shape[0]
    omitLastCol = data.shape [1] - 1; #It must have two columns at least. Possibly add a check for this?
    answer = np.zeros (limit) #initialize the answer array to all zeros
    temp1 = np.zeros ((data.shape[0], (data.shape[1] -1)))
    temp2= np.zeros (data.shape[0])

    if (allQueries.shape[1] != (data.shape[1] -1) ):
        print "ERROR: Data and query points are not of the same dimension"
        raise ValueError
        #if ends
    if (k < 1):
        print "ERROR: K should be >= 1"
        raise ValueError
        #if ends
    if (k > data.shape[0]):
        print "ERROR: K is greater than the total number of data points."
        raise ValueError
        #if ends

    for ctr in range (0, limit): #for every query point...
        #if (ctr % 10 == 0):
         #   print ctr
            #if ends

        #for i in range (0 , data_limit): #for every data point
        temp1[0:data_limit, :] = data [0:data_limit ,0:omitLastCol] - allQueries[ctr, :]
        #for loop done

        temp1 = temp1*temp1; #square each element in temp1
        for ctr2 in range (0, data_limit):
            temp2[ctr2] = math.sqrt(sum (temp1[ctr2,:]))
            #for ends

        index = temp2.argsort () #This is actually overkill because we need to sort only the top 'k' terms- but this sorts all of them
        #following loop for debugging only
        #for j in range (0, k):
        #   print str(data [index[j], :])
            #for ends

        if (method == 'mean'):
            #Now we need to find the average of the k top most 'y' values
            answer[ctr] = sum (data[ index [0:k], -1]) / k  #0 to (k-1)th index will be k values. But for this we have to give [0:k] because it stops one short of the last index
            #if method == mean ends
        if (method == 'median'):
            answer [ctr] = np.median (data[index [0:k], -1])
            # if median ends
        if (method == 'mode'):
            maxCount = 0
            curCount = 0
            modeVal = 0
            #we need to count the #times each element occours and then return the element having max count..
            for i in range (1, k):
                if (data[index[i], -1] == data [index[i-1], -1]):
                    curCount= curCount + 1
                    if (curCount >= maxCount):
                        maxCount = curCount
                        modeVal = data [index[i] , -1]
                        #if ends
                else:
                    #this number is different from the last one...
                    curCount = 1
                    #else ends
                #for loop ends
            #endif mode
        #for ctr in range (0, limit) ends
    return answer
    #getAnswer ends

'''
@summary: This is the funtion to be called to add data. This function can be called multiple times- to add data whenever you like.
@note: Any dimensional data can be added the first time. After that- the data must have the same number of columns as the data that was added the first time.
@param newData: A 2D numpy array. Each row is a data point and each column is a dimension
'''
def addEvidence (newData):
    global data
    if (data.shape[0] == 0):
        data = newData
    else:
        try:
            data= np.vstack ((data, newData))
        except Exception as ex:
            print "Type of exception: "+ str(type (ex))
            print "args: " + str(ex.args)
            #except ends
    #addEvidence ends

'''
@param dataFile: Specifies the location of the data file.
@param queryFile: Specifies the location of the query file.
@param method: Possible values are mean, mode
@summary: This function is just for testing. Will not be used as such...
'''
def main(args):

    #Below code just for testing
    a = np.loadtxt ("/nethome/sjoshi42/knn_naive/data/3_D_1000_diskQueryPoints.txt")
    b= np.loadtxt ("/nethome/sjoshi42/knn_naive/data/3_D_128000_diskDataPoints.txt")
    addEvidence(b)

    answer = par_query(a, 5 ,'mean')
    #answer = query(a, 5, 'mean')

    for i in range (0, answer.shape[0]):
        print answer[i]
    #end for

    print "The answer is: "

if __name__ == '__main__':
    main (sys.argv)<commit_msg>Removed timing calculation Removed some prints Added some comments<commit_after>'''
Created on Feb 1, 2011
@author: Shreyas Joshi
@organization: Georgia Institute of Technology
@contact: shreyasj@gatech.edu
@summary: This is an implementation of the K nearest neighbor learning algorithm. The implementation is trivial in that the near neighbors are
          calculated naively- without any smart tricks. Euclidean distance is used to calculate the distance between two points. The implementation
          also provides some coarse parallelism. If the par_query function is used then the query points are split up equally amongst threads and their
          near neighbors are calculated in parallel. If the number of threads to use is not specified then no of threads  = no of cores as returned by
          the cpu_count function. This may not be ideal.
@status: complete. "mode" untested
'''

import numpy as np
import math
import sys
import time
from multiprocessing import Pool
from multiprocessing import cpu_count
data = np.zeros (0)#this is the global data

'''
@summary: Finds the k- nearest nrighbors in parallel. Based on function "query"
@param allQueries: is another 2D numpy array. Each row here is one query point. It has no 'y' values. These have to be calculated.
@param k: no. of neighbors to consider
@param method: method of combining the 'y' values of the nearest neighbors. Default is mean.
@param noOfThreads: optional parameter that specifies how many threads to create. Default value: no. of threads = value returned by cpu_count
@return: A numpy array with the predicted 'y' values for the query points. The ith element in the array is the 'y' value for the ith query point.
'''
def par_query (allQueries, k, method='mean', noOfThreads=None):
    #Here we basically start 'noOfThreads' threads. Each thread calculates the neighbors for (noOfQueryPoints / noOfThreads) query points.

    if (noOfThreads == None):
        noOfThreads = cpu_count()
        #if ends

    #print "No of threads: " + str (noOfThreads)
    pool = Pool (processes=noOfThreads)

    resultList = []
    query_per_thread = allQueries.shape[0] / noOfThreads

    #time_start = time.time();
    for thread_ctr in range (0, noOfThreads - 1):
        resultList.append(pool.apply_async(query, (allQueries[math.floor(query_per_thread * thread_ctr): (math.floor(query_per_thread * (thread_ctr + 1 ))),:], k,)))
        #NOTE: we may need a -1 in above. Possible bug
        #for ends
    #the "remaining" query points go to the last thread
    resultList.append (pool.apply_async(query , (allQueries[(math.floor(query_per_thread* (noOfThreads - 1))): -1, :] ,k ,)))

    pool.close()
    pool.join()

    #time_finish = time.time()
    #print "Time taken (secs): " + str (time_finish - time_start)

    answer = resultList[0].get()

    for thread_ctr  in range (1, noOfThreads):
        answer = np.hstack((answer, resultList[thread_ctr].get()))
        #for ends

    #print "par_query done"
    return answer
    #par_query ends

'''
@summary: A serial implementation of k-nearest neighbors.
@param allQueries: is another 2D numpy array. Each row here is one query point. It has no 'y' values. These have to be calculated.
@param k: no. of neighbors to consider
@param method: method of combining the 'y' values of the nearest neighbors. Default is mean.
@return: A numpy array with the predicted 'y' values for the query points. The ith element in the array is the 'y' value for the ith query point.
'''
def query(allQueries, k, method='mean'):
    limit = allQueries.shape [0]
    data_limit = data.shape[0]
    omitLastCol = data.shape [1] - 1; #It must have two columns at least. Possibly add a check for this?
    answer = np.zeros (limit) #initialize the answer array to all zeros
    temp1 = np.zeros ((data.shape[0], (data.shape[1] -1)))
    temp2= np.zeros (data.shape[0])

    if (allQueries.shape[1] != (data.shape[1] -1) ):
        print "ERROR: Data and query points are not of the same dimension"
        raise ValueError
        #if ends
    if (k < 1):
        print "ERROR: K should be >= 1"
        raise ValueError
        #if ends
    if (k > data.shape[0]):
        print "ERROR: K is greater than the total number of data points."
        raise ValueError
        #if ends

    for ctr in range (0, limit): #for every query point...
        #if (ctr % 10 == 0):
         #   print ctr
            #if ends

        #for i in range (0 , data_limit): #for every data point
        temp1[0:data_limit, :] = data [0:data_limit ,0:omitLastCol] - allQueries[ctr, :]
        #for loop done

        temp1 = temp1*temp1; #square each element in temp1
        for ctr2 in range (0, data_limit):
            temp2[ctr2] = math.sqrt(sum (temp1[ctr2,:]))
            #for ends

        index = temp2.argsort () #This is actually overkill because we need to sort only the top 'k' terms- but this sorts all of them
        #following loop for debugging only
        #for j in range (0, k):
        #   print str(data [index[j], :])
            #for ends

        if (method == 'mean'):
            #Now we need to find the average of the k top most 'y' values
            answer[ctr] = sum (data[ index [0:k], -1]) / k  #0 to (k-1)th index will be k values. But for this we have to give [0:k] because it stops one short of the last index
            #if method == mean ends
        if (method == 'median'):
            answer [ctr] = np.median (data[index [0:k], -1])
            # if median ends
        if (method == 'mode'):
            maxCount = 0
            curCount = 0
            modeVal = 0
            #we need to count the #times each element occours and then return the element having max count..
            for i in range (1, k):
                if (data[index[i], -1] == data [index[i-1], -1]):
                    curCount= curCount + 1
                    if (curCount >= maxCount):
                        maxCount = curCount
                        modeVal = data [index[i] , -1]
                        #if ends
                else:
                    #this number is different from the last one...
                    curCount = 1
                    #else ends
                #for loop ends
            #endif mode
        #for ctr in range (0, limit) ends
    return answer
    #getAnswer ends

'''
@summary: This is the funtion to be called to add data. This function can be called multiple times- to add data whenever you like.
@note: Any dimensional data can be added the first time. After that- the data must have the same number of columns as the data that was added the first time.
@param newData: A 2D numpy array. Each row is a data point and each column is a dimension
'''
def addEvidence (newData):
    global data
    if (data.shape[0] == 0):
        data = newData
    else:
        try:
            data= np.vstack ((data, newData))
        except Exception as ex:
            print "Type of exception: "+ str(type (ex))
            print "args: " + str(ex.args)
            #except ends
    #addEvidence ends

'''
@param dataFile: Specifies the location of the data file.
@param queryFile: Specifies the location of the query file.
@param method: Possible values are mean, mode
@summary: This function is just for testing. Will not be used as such...
'''
def main(args):

    #Below code just for testing
    a = np.loadtxt ("/nethome/sjoshi42/knn_naive/data/3_D_1000_diskQueryPoints.txt")
    b= np.loadtxt ("/nethome/sjoshi42/knn_naive/data/3_D_128000_diskDataPoints.txt")
    addEvidence(b)

    answer = par_query(a, 5 ,'mean')
    #answer = query(a, 5, 'mean')

    for i in range (0, answer.shape[0]):
        print answer[i]
    #end for

    print "The answer is: "

if __name__ == '__main__':
    main (sys.argv)<|endoftext|>


<commit_before>import asyncio
import functools
import logging
import os
import pathlib

import discord
import discord.ext.commands as commands
import youtube_dl

def setup(bot):
    """Extension's entry point."""
    bot.add_cog(Music(bot))

def duration_to_str(duration):
    """Converts a timestamp to a string representation."""
    minutes, seconds = divmod(duration, 60)
    hours, minutes = divmod(minutes, 60)
    days, hours = divmod(hours, 24)

    duration = []
    if days > 0: duration.append(f'{days} days')
    if hours > 0: duration.append(f'{hours} hours')
    if minutes > 0: duration.append(f'{minutes} minutes')
    if seconds > 0 or len(duration) == 0: duration.append(f'{seconds} seconds')

    return ', '.join(duration)

class MusicError(commands.UserInputError):
    """Base exception for errors involving the Music cog."""
    pass

class Song(discord.PCMVolumeTransformer):
    """Represents a song to play."""

    def __init__(self, song_info, volume=1.0):
        self.info = song_info.info
        self.requester = song_info.requester
        self.channel = song_info.channel
        self.filename = song_info.filename
        super().__init__(discord.FFmpegPCMAudio(self.filename, before_options='-nostdin', options='-vn'), volume=volume)

class SongInfo:
    """Represents a Song's info."""
    ytdl_opts = {
        'default_search': 'auto',
        'format': 'bestaudio/best',
        'ignoreerrors': True,
        'nocheckcertificate': True,
        'restrictfilenames': True,
        'logger': logging.getLogger(__name__),
        'logtostderr': False,
        'no_warnings': True,
        'quiet': True,
        'outtmpl': '%(extractor)s-%(id)s-%(title)s.%(ext)s',
        'noplaylist': True
    }
    ytdl = youtube_dl.YoutubeDL(ytdl_opts)

    def __init__(self, info, requester, channel):
        self.info = info
        self.requester = requester
        self.channel = channel
        self.filename = info.get('_filename', self.ytdl.prepare_filename(self.info))
        self.downloaded = asyncio.Event()
        self.local_file = '_filename' in info

    @classmethod
    async def create(cls, query, requester, channel, loop=None):
        """Class method to create a SongInfo."""
        try:
            # Path.is_file() can throw a OSError on syntactically incorrect paths, like urls.
            if pathlib.Path(query).is_file():
                return cls.from_file(query, requester, channel)
        except OSError:
            pass

        return await cls.from_ytdl(query, requester, channel, loop=loop)

    @classmethod
    def from_file(cls, file, requester, channel):
        """Class method to create a SongInfo from a file on disk."""
        path = pathlib.Path(file)
        if not path.exists():
            raise MusicError(f'File {file} not found.')

        info = {
            '_filename': file,
            'title': path.stem,
            'creator': 'local file',
        }
        return cls(info, requester, channel)

    @classmethod
    async def from_ytdl(cls, request, requester, channel, loop=None):
        """Class method to create a SongInfo using ytdl."""
        loop = loop or asyncio.get_event_loop()

        # Get sparse info about our query
        partial = functools.partial(cls.ytdl.extract_info, request, download=False, process=False)
        sparse_info = await loop.run_in_executor(None, partial)

        if sparse_info is None:
            raise MusicError(f'Could not retrieve info from input : {request}')

        # If we get a playlist, select its first valid entry
        if "entries" not in sparse_info:
            info_to_process = sparse_info
        else:
            info_to_process = None
            for entry in sparse_info['entries']:
                if entry is not None:
                    info_to_process = entry
                    break
            if info_to_process is None:
                raise MusicError(f'Could not retrieve info from input : {request}')

        # Process full video info
        url = info_to_process.get('url', info_to_process.get('webpage_url', info_to_process.get('id')))
        partial = functools.partial(cls.ytdl.extract_info, url, download=False)
        processed_info = await loop.run_in_executor(None, partial)

        if processed_info is None:
            raise MusicError(f'Could not retrieve info from input : {request}')

        # Select the first search result if any
        if "entries" not in processed_info:
            info = processed_info
        else:
            info = None
            while info is None:
                try:
                    info = processed_info['entries'].pop(0)
                except IndexError:
                    raise MusicError(f'Could not retrieve info from url : {info_to_process["url"]}')

        return cls(info, requester, channel)

    async def download(self, loop):
        """Downloads the song file with ytdl."""
        if not pathlib.Path(self.filename).exists():
            partial = functools.partial(self.ytdl.extract_info, self.info['webpage_url'], download=True)
            self.info = await loop.run_in_executor(None, partial)
        self.downloaded.set()

    async def wait_until_downloaded(self):
        """Helper function to wait until the song file has been downloaded."""
        await self.downloaded.wait()

    def __str__(self):
        title = f"**{self.info['title']}**"
        creator = f"**{self.info.get('creator') or self.info['uploader']}**"
        duration = f" (duration: {duration_to_str(self.info['duration'])})" if 'duration' in self.info else ''
        return f'{title} from {creator}{duration}'

class Playlist(asyncio.Queue):
    """Represents a playlist."""

    def __iter__(self):
        return self._queue.__iter__()

    def clear(self):
        """Clears the playlist from its items."""
        for song in self._queue:
            os.remove(song.filename)
        self._queue.clear()

    def get_song(self):
        """Gets the first item of the playlist."""
        return self.get_nowait()

    def add_song(self, song):
        """Adds an item to the playlist."""
        self.put_nowait(song)

    def __str__(self):
        info = 'Current playlist:\n'
        info_len = len(info)

        for song in self:
            song_repr = f'{song}\n'
            song_repr_len = len(song_repr)

            if info_len + song_repr_len > 1995:
                info += '[...]'
                break

            info += song_repr
            info_len += song_repr_len

        return info

class GuildMusicState:
    """The music state of a guild."""

    def __init__(self, loop):
        self.playlist = Playlist(maxsize=50)
        self.voice_client = None
        self.loop = loop
        self.player_volume = 0.5
        self.skips = set()
        self.min_skips = 5

    @property
    def current_song(self):
        """Returns the song that is currently played."""
        return self.voice_client.source

    @property
    def volume(self):
        """Returns the volume of the audio player."""
        return self.player_volume

    @volume.setter
    def volume(self, value):
        """Sets the volume of the audio player."""
        self.player_volume = value
        if self.voice_client:
            self.voice_client.source.volume = value

    async def stop(self):
        """Clears the playlist and stops the player."""
        self.playlist.clear()
        if self.voice_client:
            await self.voice_client.disconnect()
            self.voice_client = None

    def is_playing(self):
        """Indicates if we're currently playing audio."""
        return self.voice_client and self.voice_client.is_playing()

    async def play_next_song(self, song=None, error=None):
        """Callback called after the voice_client has finished playing a source."""
        if error:
            await self.current_song.channel.send(f'An error has occurred while playing {self.current_song}: {error}')

        if song and not song.local_file and song.filename not in [s.filename for s in self.playlist]:
            os.remove(song.filename)

        if self.playlist.empty():
            await self.stop()
        else:
            next_song_info = self.playlist.get_song()
            await next_song_info.wait_until_downloaded()
            source = Song(next_song_info, self.player_volume)
            self.voice_client.play(source, after=lambda e: asyncio.run_coroutine_threadsafe(self.play_next_song(next_song_info, e), self.loop).result())
            await next_song_info.channel.send(f'Now playing {next_song_info}')

class Music(commands.Cog):
    """🎵🐼"""

    def __init__(self, bot):
        self.bot = bot
        self.music_states = {}

    def cog_unload(self):
        """Handles special unloading."""
        for state in self.music_states.values():
            self.bot.loop.create_task(state.stop())

    def cog_check(self, ctx):
        """Extra checks for the cog's commands."""
        if not ctx.guild:
            raise commands.NoPrivateMessage('This command cannot be used in a private message.')
        return True

    async def cog_before_invoke(self, ctx):
        """Pre invoke hook for the cog's commands."""
        ctx.music_state = self.music_states.setdefault(ctx.guild.id, GuildMusicState(self.bot.loop))

    async def cog_command_error(self, ctx, error):
        """Error handler for the cog's commands."""
        if not isinstance(error, (commands.UserInputError, commands.CheckFailure)):
            raise error

        try:
            await ctx.send(error)
        except discord.Forbidden:
            pass # /shrug

    @commands.command()
    async def status(self, ctx):
        """Displays the currently played song."""
        if ctx.music_state.is_playing():
            song = ctx.music_state.current_song
            await ctx.send(f'Playing {song}. Volume at {song.volume * 100}% in {ctx.voice_client.channel.mention}')
        else:
            await ctx.send('Not playing.')

    @commands.command()
    async def playlist(self, ctx):
        """Shows info about the current playlist."""
        await ctx.send(f'{ctx.music_state.playlist}')

    @commands.command()
    @commands.has_permissions(manage_guild=True)
    async def join(self, ctx, *, channel: discord.VoiceChannel = None):
        """Summons the bot to a voice channel.

        If no channel is given, summons it to your current voice channel.
        """
        if channel is None and not ctx.author.voice:
            raise MusicError('You are not in a voice channel nor specified a voice channel for me to join.')

        destination = channel or ctx.author.voice.channel

        if ctx.voice_client:
            await ctx.voice_client.move_to(destination)
        else:
            ctx.music_state.voice_client = await destination.connect()

    @commands.command()
    async def play(self, ctx, *, request: str):
        """Plays a song or adds it to the playlist.

        Automatically searches with youtube_dl
        List of supported sites :
        https://github.com/rg3/youtube-dl/blob/1b6712ab2378b2e8eb59f372fb51193f8d3bdc97/docs/supportedsites.md
        """
        await ctx.message.add_reaction('\N{HOURGLASS}')

        # Create the SongInfo
        song = await SongInfo.create(request, ctx.author, ctx.channel, loop=ctx.bot.loop)

        # Connect to the voice channel if needed
        if ctx.voice_client is None or not ctx.voice_client.is_connected():
            await ctx.invoke(self.join)

        # Add the info to the playlist
        try:
            ctx.music_state.playlist.add_song(song)
        except asyncio.QueueFull:
            raise MusicError('Playlist is full, try again later.')

        if not ctx.music_state.is_playing():
            # Download the song and play it
            await song.download(ctx.bot.loop)
            await ctx.music_state.play_next_song()
        else:
            # Schedule the song's download
            ctx.bot.loop.create_task(song.download(ctx.bot.loop))
            await ctx.send(f'Queued {song} in position **#{ctx.music_state.playlist.qsize()}**')

        await ctx.message.remove_reaction('\N{HOURGLASS}', ctx.me)
        await ctx.message.add_reaction('\N{WHITE HEAVY CHECK MARK}')

    @play.error
    async def play_error(self, ctx, error):
        """Error handler for the `play ` command."""
        await ctx.message.remove_reaction('\N{HOURGLASS}', ctx.me)
        await ctx.message.add_reaction('\N{CROSS MARK}')

    @commands.command()
    @commands.has_permissions(manage_guild=True)
    async def pause(self, ctx):
        """Pauses the player."""
        if ctx.voice_client:
            ctx.voice_client.pause()

    @commands.command()
    @commands.has_permissions(manage_guild=True)
    async def resume(self, ctx):
        """Resumes the player."""
        if ctx.voice_client:
            ctx.voice_client.resume()

    @commands.command()
    @commands.has_permissions(manage_guild=True)
    async def stop(self, ctx):
        """Stops the player, clears the playlist and leaves the voice channel."""
        await ctx.music_state.stop()

    @commands.command()
    async def volume(self, ctx, volume: int = None):
        """Sets the volume of the player, scales from 0 to 100."""
        if volume < 0 or volume > 100:
            raise MusicError('The volume level has to be between 0 and 100.')
        ctx.music_state.volume = volume / 100

    @commands.command()
    async def clear(self, ctx):
        """Clears the playlist."""
        ctx.music_state.playlist.clear()

    @commands.command()
    async def skip(self, ctx):
        """Votes to skip the current song.

        To configure the minimum number of votes needed, use `minskips`
        """
        if not ctx.music_state.is_playing():
            raise MusicError('Not playing anything to skip.')

        if ctx.author.id in ctx.music_state.skips:
            raise MusicError(f'{ctx.author.mention} You already voted to skip that song')

        # Count the vote
        ctx.music_state.skips.add(ctx.author.id)
        await ctx.message.add_reaction('\N{WHITE HEAVY CHECK MARK}')

        # Check if the song has to be skipped
        if len(ctx.music_state.skips) > ctx.music_state.min_skips or ctx.author == ctx.music_state.current_song.requester:
            ctx.music_state.skips.clear()
            ctx.voice_client.stop()

    @commands.command()
    @commands.has_permissions(manage_guild=True)
    async def minskips(self, ctx, number: int):
        """Sets the minimum number of votes to skip a song.

        Requires the `Manage Guild` permission.
        """
        ctx.music_state.min_skips = number
<commit_msg>Tweak options for FFmpeg and Ytdl.<commit_after>import asyncio
import functools
import logging
import os
import pathlib

import discord
import discord.ext.commands as commands
import youtube_dl

def setup(bot):
    """Extension's entry point."""
    bot.add_cog(Music(bot))

def duration_to_str(duration):
    """Converts a timestamp to a string representation."""
    minutes, seconds = divmod(duration, 60)
    hours, minutes = divmod(minutes, 60)
    days, hours = divmod(hours, 24)

    duration = []
    if days > 0: duration.append(f'{days} days')
    if hours > 0: duration.append(f'{hours} hours')
    if minutes > 0: duration.append(f'{minutes} minutes')
    if seconds > 0 or len(duration) == 0: duration.append(f'{seconds} seconds')

    return ', '.join(duration)

class MusicError(commands.UserInputError):
    """Base exception for errors involving the Music cog."""
    pass

class Song(discord.PCMVolumeTransformer):
    """Represents a song to play."""

    def __init__(self, song_info, volume=1.0):
        self.info = song_info.info
        self.requester = song_info.requester
        self.channel = song_info.channel
        self.filename = song_info.filename
        super().__init__(discord.FFmpegPCMAudio(self.filename, options='-vn'), volume=volume)

class SongInfo:
    """Represents a Song's info."""
    ytdl_opts = {
        'default_search': 'auto',
        'format': 'bestaudio/best',
        'ignoreerrors': True,
        'source_address': '0.0.0.0',
        'nocheckcertificate': True,
        'restrictfilenames': True,
        'logger': logging.getLogger(__name__),
        'logtostderr': False,
        'no_warnings': True,
        'quiet': True,
        'outtmpl': '%(extractor)s-%(id)s-%(title)s.%(ext)s',
        'noplaylist': True
    }
    ytdl = youtube_dl.YoutubeDL(ytdl_opts)

    def __init__(self, info, requester, channel):
        self.info = info
        self.requester = requester
        self.channel = channel
        self.filename = info.get('_filename', self.ytdl.prepare_filename(self.info))
        self.downloaded = asyncio.Event()
        self.local_file = '_filename' in info

    @classmethod
    async def create(cls, query, requester, channel, loop=None):
        """Class method to create a SongInfo."""
        try:
            # Path.is_file() can throw a OSError on syntactically incorrect paths, like urls.
            if pathlib.Path(query).is_file():
                return cls.from_file(query, requester, channel)
        except OSError:
            pass

        return await cls.from_ytdl(query, requester, channel, loop=loop)

    @classmethod
    def from_file(cls, file, requester, channel):
        """Class method to create a SongInfo from a file on disk."""
        path = pathlib.Path(file)
        if not path.exists():
            raise MusicError(f'File {file} not found.')

        info = {
            '_filename': file,
            'title': path.stem,
            'creator': 'local file',
        }
        return cls(info, requester, channel)

    @classmethod
    async def from_ytdl(cls, request, requester, channel, loop=None):
        """Class method to create a SongInfo using ytdl."""
        loop = loop or asyncio.get_event_loop()

        # Get sparse info about our query
        partial = functools.partial(cls.ytdl.extract_info, request, download=False, process=False)
        sparse_info = await loop.run_in_executor(None, partial)

        if sparse_info is None:
            raise MusicError(f'Could not retrieve info from input : {request}')

        # If we get a playlist, select its first valid entry
        if "entries" not in sparse_info:
            info_to_process = sparse_info
        else:
            info_to_process = None
            for entry in sparse_info['entries']:
                if entry is not None:
                    info_to_process = entry
                    break
            if info_to_process is None:
                raise MusicError(f'Could not retrieve info from input : {request}')

        # Process full video info
        url = info_to_process.get('url', info_to_process.get('webpage_url', info_to_process.get('id')))
        partial = functools.partial(cls.ytdl.extract_info, url, download=False)
        processed_info = await loop.run_in_executor(None, partial)

        if processed_info is None:
            raise MusicError(f'Could not retrieve info from input : {request}')

        # Select the first search result if any
        if "entries" not in processed_info:
            info = processed_info
        else:
            info = None
            while info is None:
                try:
                    info = processed_info['entries'].pop(0)
                except IndexError:
                    raise MusicError(f'Could not retrieve info from url : {info_to_process["url"]}')

        return cls(info, requester, channel)

    async def download(self, loop):
        """Downloads the song file with ytdl."""
        if not pathlib.Path(self.filename).exists():
            partial = functools.partial(self.ytdl.extract_info, self.info['webpage_url'], download=True)
            self.info = await loop.run_in_executor(None, partial)
        self.downloaded.set()

    async def wait_until_downloaded(self):
        """Helper function to wait until the song file has been downloaded."""
        await self.downloaded.wait()

    def __str__(self):
        title = f"**{self.info['title']}**"
        creator = f"**{self.info.get('creator') or self.info['uploader']}**"
        duration = f" (duration: {duration_to_str(self.info['duration'])})" if 'duration' in self.info else ''
        return f'{title} from {creator}{duration}'

class Playlist(asyncio.Queue):
    """Represents a playlist."""

    def __iter__(self):
        return self._queue.__iter__()

    def clear(self):
        """Clears the playlist from its items."""
        for song in self._queue:
            os.remove(song.filename)
        self._queue.clear()

    def get_song(self):
        """Gets the first item of the playlist."""
        return self.get_nowait()

    def add_song(self, song):
        """Adds an item to the playlist."""
        self.put_nowait(song)

    def __str__(self):
        info = 'Current playlist:\n'
        info_len = len(info)

        for song in self:
            song_repr = f'{song}\n'
            song_repr_len = len(song_repr)

            if info_len + song_repr_len > 1995:
                info += '[...]'
                break

            info += song_repr
            info_len += song_repr_len

        return info

class GuildMusicState:
    """The music state of a guild."""

    def __init__(self, loop):
        self.playlist = Playlist(maxsize=50)
        self.voice_client = None
        self.loop = loop
        self.player_volume = 0.5
        self.skips = set()
        self.min_skips = 5

    @property
    def current_song(self):
        """Returns the song that is currently played."""
        return self.voice_client.source

    @property
    def volume(self):
        """Returns the volume of the audio player."""
        return self.player_volume

    @volume.setter
    def volume(self, value):
        """Sets the volume of the audio player."""
        self.player_volume = value
        if self.voice_client:
            self.voice_client.source.volume = value

    async def stop(self):
        """Clears the playlist and stops the player."""
        self.playlist.clear()
        if self.voice_client:
            await self.voice_client.disconnect()
            self.voice_client = None

    def is_playing(self):
        """Indicates if we're currently playing audio."""
        return self.voice_client and self.voice_client.is_playing()

    async def play_next_song(self, song=None, error=None):
        """Callback called after the voice_client has finished playing a source."""
        if error:
            await self.current_song.channel.send(f'An error has occurred while playing {self.current_song}: {error}')

        if song and not song.local_file and song.filename not in [s.filename for s in self.playlist]:
            os.remove(song.filename)

        if self.playlist.empty():
            await self.stop()
        else:
            next_song_info = self.playlist.get_song()
            await next_song_info.wait_until_downloaded()
            source = Song(next_song_info, self.player_volume)
            self.voice_client.play(source, after=lambda e: asyncio.run_coroutine_threadsafe(self.play_next_song(next_song_info, e), self.loop).result())
            await next_song_info.channel.send(f'Now playing {next_song_info}')

class Music(commands.Cog):
    """🎵🐼"""

    def __init__(self, bot):
        self.bot = bot
        self.music_states = {}

    def cog_unload(self):
        """Handles special unloading."""
        for state in self.music_states.values():
            self.bot.loop.create_task(state.stop())

    def cog_check(self, ctx):
        """Extra checks for the cog's commands."""
        if not ctx.guild:
            raise commands.NoPrivateMessage('This command cannot be used in a private message.')
        return True

    async def cog_before_invoke(self, ctx):
        """Pre invoke hook for the cog's commands."""
        ctx.music_state = self.music_states.setdefault(ctx.guild.id, GuildMusicState(self.bot.loop))

    async def cog_command_error(self, ctx, error):
        """Error handler for the cog's commands."""
        if not isinstance(error, (commands.UserInputError, commands.CheckFailure)):
            raise error

        try:
            await ctx.send(error)
        except discord.Forbidden:
            pass # /shrug

    @commands.command()
    async def status(self, ctx):
        """Displays the currently played song."""
        if ctx.music_state.is_playing():
            song = ctx.music_state.current_song
            await ctx.send(f'Playing {song}. Volume at {song.volume * 100}% in {ctx.voice_client.channel.mention}')
        else:
            await ctx.send('Not playing.')

    @commands.command()
    async def playlist(self, ctx):
        """Shows info about the current playlist."""
        await ctx.send(f'{ctx.music_state.playlist}')

    @commands.command()
    @commands.has_permissions(manage_guild=True)
    async def join(self, ctx, *, channel: discord.VoiceChannel = None):
        """Summons the bot to a voice channel.

        If no channel is given, summons it to your current voice channel.
        """
        if channel is None and not ctx.author.voice:
            raise MusicError('You are not in a voice channel nor specified a voice channel for me to join.')

        destination = channel or ctx.author.voice.channel

        if ctx.voice_client:
            await ctx.voice_client.move_to(destination)
        else:
            ctx.music_state.voice_client = await destination.connect()

    @commands.command()
    async def play(self, ctx, *, request: str):
        """Plays a song or adds it to the playlist.

        Automatically searches with youtube_dl
        List of supported sites : https://ytdl-org.github.io/youtube-dl/supportedsites.html
        """
        await ctx.message.add_reaction('\N{HOURGLASS}')

        # Create the SongInfo
        song = await SongInfo.create(request, ctx.author, ctx.channel, loop=ctx.bot.loop)

        # Connect to the voice channel if needed
        if ctx.voice_client is None or not ctx.voice_client.is_connected():
            await ctx.invoke(self.join)

        # Add the info to the playlist
        try:
            ctx.music_state.playlist.add_song(song)
        except asyncio.QueueFull:
            raise MusicError('Playlist is full, try again later.')

        if not ctx.music_state.is_playing():
            # Download the song and play it
            await song.download(ctx.bot.loop)
            await ctx.music_state.play_next_song()
        else:
            # Schedule the song's download
            ctx.bot.loop.create_task(song.download(ctx.bot.loop))
            await ctx.send(f'Queued {song} in position **#{ctx.music_state.playlist.qsize()}**')

        await ctx.message.remove_reaction('\N{HOURGLASS}', ctx.me)
        await ctx.message.add_reaction('\N{WHITE HEAVY CHECK MARK}')

    @play.error
    async def play_error(self, ctx, error):
        """Error handler for the `play ` command."""
        await ctx.message.remove_reaction('\N{HOURGLASS}', ctx.me)
        await ctx.message.add_reaction('\N{CROSS MARK}')

    @commands.command()
    @commands.has_permissions(manage_guild=True)
    async def pause(self, ctx):
        """Pauses the player."""
        if ctx.voice_client:
            ctx.voice_client.pause()

    @commands.command()
    @commands.has_permissions(manage_guild=True)
    async def resume(self, ctx):
        """Resumes the player."""
        if ctx.voice_client:
            ctx.voice_client.resume()

    @commands.command()
    @commands.has_permissions(manage_guild=True)
    async def stop(self, ctx):
        """Stops the player, clears the playlist and leaves the voice channel."""
        await ctx.music_state.stop()

    @commands.command()
    async def volume(self, ctx, volume: int = None):
        """Sets the volume of the player, scales from 0 to 100."""
        if volume < 0 or volume > 100:
            raise MusicError('The volume level has to be between 0 and 100.')
        ctx.music_state.volume = volume / 100

    @commands.command()
    async def clear(self, ctx):
        """Clears the playlist."""
        ctx.music_state.playlist.clear()

    @commands.command()
    async def skip(self, ctx):
        """Votes to skip the current song.

        To configure the minimum number of votes needed, use `minskips`
        """
        if not ctx.music_state.is_playing():
            raise MusicError('Not playing anything to skip.')

        if ctx.author.id in ctx.music_state.skips:
            raise MusicError(f'{ctx.author.mention} You already voted to skip that song')

        # Count the vote
        ctx.music_state.skips.add(ctx.author.id)
        await ctx.message.add_reaction('\N{WHITE HEAVY CHECK MARK}')

        # Check if the song has to be skipped
        if len(ctx.music_state.skips) > ctx.music_state.min_skips or ctx.author == ctx.music_state.current_song.requester:
            ctx.music_state.skips.clear()
            ctx.voice_client.stop()

    @commands.command()
    @commands.has_permissions(manage_guild=True)
    async def minskips(self, ctx, number: int):
        """Sets the minimum number of votes to skip a song.

        Requires the `Manage Guild` permission.
        """
        ctx.music_state.min_skips = number
<|endoftext|>