<commit_before>"""CardinalityMatching.py

Find maximum cardinality matchings in general undirected graphs.

D. Eppstein, UC Irvine, September 6, 2003.
"""

from unionFind import unionFind

if 'True' not in globals():
    globals()['True'] = not None
    globals()['False'] = not True

def matching(G, initialMatching = {}):
    """Find a maximum cardinality matching in a graph G.
    G is represented in modified GvR form: iter(G) lists its vertices;
    iter(G[v]) lists the neighbors of v; w in G[v] tests adjacency.
    For maximal efficiency, G and G[v] should be dictionaries, so
    that adjacency tests take constant time each.
    The output is a dictionary mapping vertices to their matches;
    unmatched vertices are omitted from the dictionary.

    We use Edmonds' blossom-contraction algorithm, as described e.g.
    in Galil's 1986 Computing Surveys paper.
    """

    # Copy initial matching so we can use it nondestructively
    matching = {}
    for x in initialMatching:
        matching[x] = initialMatching[x]

    # Form greedy matching to avoid some iterations of augmentation
    for v in G:
        if v not in matching:
            for w in G[v]:
                if w not in matching:
                    matching[v] = w
                    matching[w] = v
                    break

    def augment():
        """Search for a single augmenting path.
        Returns true if the matching size was increased, false otherwise.
        """

        # Data structures for augmenting path search:
        #
        # leader: union-find structure; the leader of a blossom is one
        # of its vertices (not necessarily topmost), and leader[v] always
        # points to the leader of the largest blossom containing v
        #
        # S: dictionary of blossoms at even levels of the structure tree.
        # Dictionary keys are names of blossoms (as returned by the union-find
        # data structure) and values are the structure tree parent of the blossom
        # (a T-node, or the top vertex if the blossom is a root of a structure tree).
        #
        # T: dictionary of vertices at odd levels of the structure tree.
        # Dictionary keys are the vertices; T[x] is a vertex with an unmatched
        # edge to x.  To find the parent in the structure tree, use leader[T[x]].
        #
        # unexplored: collection of unexplored vertices within leader of S
        #
        # base: if x was originally a T-vertex, but becomes part of a blossom,
        # base[t] will be the pair (v,w) at the base of the blossom, where v and t
        # are on the same side of the blossom and w is on the other side.

        leader = unionFind()
        S = {}
        T = {}
        unexplored = []
        base = {}

        # Subroutines for augmenting path search.
        # Many of these are called only from one place, but are split out
        # as subroutines to improve modularization and readability.

        def blossom(v,w,a):
            """Create a new blossom from edge v-w with common ancestor a."""

            def findSide(v,w):
                path = [leader[v]]
                b = (v,w)   # new base for all T nodes found on the path
                while path[-1] != a:
                    tnode = S[path[-1]]
                    path.append(tnode)
                    base[tnode] = b
                    unexplored.append(tnode)
                    path.append(leader[T[tnode]])
                return path

            a = leader[a]   # sanity check
            path1,path2 = findSide(v,w), findSide(w,v)
            leader.union(*path1)
            leader.union(*path2)
            S[leader[a]] = S[a] # update structure tree

        topless = object()  # should be unequal to any graph vertex
        def alternatingPath(start, goal = topless):
            """Return sequence of vertices on alternating path from start to goal.
            The goal must be a T node along the path from the start to
            the root of the structure tree. If goal is omitted, we find
            an alternating path to the structure tree root.
            """
            path = []
            while 1:
                while start in T:
                    v, w = base[start]
                    vs = alternatingPath(v, start)
                    vs.reverse()
                    path += vs
                    start = w
                path.append(start)
                if start not in matching:
                    return path     # reached top of structure tree, done!
                tnode = matching[start]
                path.append(tnode)
                if tnode == goal:
                    return path     # finished recursive subpath
                start = T[tnode]

        def alternate(v):
            """Make v unmatched by alternating the path to the root of its structure tree."""
            path = alternatingPath(v)
            path.reverse()
            for i in range(0,len(path)-1,2):
                matching[path[i]] = path[i+1]
                matching[path[i+1]] = path[i]

        def addMatch(v, w):
            """Here with an S-S edge vw connecting vertices in different structure trees.
            Find the corresponding augmenting path and use it to augment the matching.
            """
            alternate(v)
            alternate(w)
            matching[v] = w
            matching[w] = v

        def ss(v,w):
            """Handle detection of an S-S edge in augmenting path search.
            Like augment(), returns true iff the matching size was increased.
            """

            if leader[v] == leader[w]:
                return False        # self-loop within blossom, ignore

            # parallel search up two branches of structure tree
            # until we find a common ancestor of v and w
            path1, head1 = {}, v
            path2, head2 = {}, w

            def step(path, head):
                head = leader[head]
                parent = leader[S[head]]
                if parent == head:
                    return head     # found root of structure tree
                path[head] = parent
                path[parent] = leader[T[parent]]
                return path[parent]

            while 1:
                head1 = step(path1, head1)
                head2 = step(path2, head2)

                if head1 == head2:
                    blossom(v, w, head1)
                    return False

                if leader[S[head1]] == head1 and leader[S[head2]] == head2:
                    addMatch(v, w)
                    return True

                if head1 in path2:
                    blossom(v, w, head1)
                    return False

                if head2 in path1:
                    blossom(v, w, head2)
                    return False

        # Start of main augmenting path search code.

        for v in G:
            if v not in matching:
                S[v] = v
                unexplored.append(v)

        current = 0     # index into unexplored, in FIFO order so we get short paths
        while current < len(unexplored):
            v = unexplored[current]
            current += 1

            for w in G[v]:
                if leader[w] in S:  # S-S edge: blossom or augmenting path
                    if ss(v,w):
                        return True

                elif w not in T:    # previously unexplored node, add as T-node
                    T[w] = v
                    u = matching[w]
                    if leader[u] not in S:
                        S[u] = w    # and add its match as an S-node
                        unexplored.append(u)

        return False    # ran out of graph without finding an augmenting path

    # augment the matching until it is maximum
    while augment():
        pass

    return matching
<commit_msg>Correct capitalization of UnionFind in CardinalityMatching.<commit_after>"""CardinalityMatching.py

Find maximum cardinality matchings in general undirected graphs.

D. Eppstein, UC Irvine, September 6, 2003.
"""

from UnionFind import UnionFind

if 'True' not in globals():
    globals()['True'] = not None
    globals()['False'] = not True

def matching(G, initialMatching = {}):
    """Find a maximum cardinality matching in a graph G.
    G is represented in modified GvR form: iter(G) lists its vertices;
    iter(G[v]) lists the neighbors of v; w in G[v] tests adjacency.
    For maximal efficiency, G and G[v] should be dictionaries, so
    that adjacency tests take constant time each.
    The output is a dictionary mapping vertices to their matches;
    unmatched vertices are omitted from the dictionary.

    We use Edmonds' blossom-contraction algorithm, as described e.g.
    in Galil's 1986 Computing Surveys paper.
    """

    # Copy initial matching so we can use it nondestructively
    matching = {}
    for x in initialMatching:
        matching[x] = initialMatching[x]

    # Form greedy matching to avoid some iterations of augmentation
    for v in G:
        if v not in matching:
            for w in G[v]:
                if w not in matching:
                    matching[v] = w
                    matching[w] = v
                    break

    def augment():
        """Search for a single augmenting path.
        Returns true if the matching size was increased, false otherwise.
        """

        # Data structures for augmenting path search:
        #
        # leader: union-find structure; the leader of a blossom is one
        # of its vertices (not necessarily topmost), and leader[v] always
        # points to the leader of the largest blossom containing v
        #
        # S: dictionary of blossoms at even levels of the structure tree.
        # Dictionary keys are names of blossoms (as returned by the union-find
        # data structure) and values are the structure tree parent of the blossom
        # (a T-node, or the top vertex if the blossom is a root of a structure tree).
        #
        # T: dictionary of vertices at odd levels of the structure tree.
        # Dictionary keys are the vertices; T[x] is a vertex with an unmatched
        # edge to x.  To find the parent in the structure tree, use leader[T[x]].
        #
        # unexplored: collection of unexplored vertices within leader of S
        #
        # base: if x was originally a T-vertex, but becomes part of a blossom,
        # base[t] will be the pair (v,w) at the base of the blossom, where v and t
        # are on the same side of the blossom and w is on the other side.

        leader = UnionFind()
        S = {}
        T = {}
        unexplored = []
        base = {}

        # Subroutines for augmenting path search.
        # Many of these are called only from one place, but are split out
        # as subroutines to improve modularization and readability.

        def blossom(v,w,a):
            """Create a new blossom from edge v-w with common ancestor a."""

            def findSide(v,w):
                path = [leader[v]]
                b = (v,w)   # new base for all T nodes found on the path
                while path[-1] != a:
                    tnode = S[path[-1]]
                    path.append(tnode)
                    base[tnode] = b
                    unexplored.append(tnode)
                    path.append(leader[T[tnode]])
                return path

            a = leader[a]   # sanity check
            path1,path2 = findSide(v,w), findSide(w,v)
            leader.union(*path1)
            leader.union(*path2)
            S[leader[a]] = S[a] # update structure tree

        topless = object()  # should be unequal to any graph vertex
        def alternatingPath(start, goal = topless):
            """Return sequence of vertices on alternating path from start to goal.
            The goal must be a T node along the path from the start to
            the root of the structure tree. If goal is omitted, we find
            an alternating path to the structure tree root.
            """
            path = []
            while 1:
                while start in T:
                    v, w = base[start]
                    vs = alternatingPath(v, start)
                    vs.reverse()
                    path += vs
                    start = w
                path.append(start)
                if start not in matching:
                    return path     # reached top of structure tree, done!
                tnode = matching[start]
                path.append(tnode)
                if tnode == goal:
                    return path     # finished recursive subpath
                start = T[tnode]

        def alternate(v):
            """Make v unmatched by alternating the path to the root of its structure tree."""
            path = alternatingPath(v)
            path.reverse()
            for i in range(0,len(path)-1,2):
                matching[path[i]] = path[i+1]
                matching[path[i+1]] = path[i]

        def addMatch(v, w):
            """Here with an S-S edge vw connecting vertices in different structure trees.
            Find the corresponding augmenting path and use it to augment the matching.
            """
            alternate(v)
            alternate(w)
            matching[v] = w
            matching[w] = v

        def ss(v,w):
            """Handle detection of an S-S edge in augmenting path search.
            Like augment(), returns true iff the matching size was increased.
            """

            if leader[v] == leader[w]:
                return False        # self-loop within blossom, ignore

            # parallel search up two branches of structure tree
            # until we find a common ancestor of v and w
            path1, head1 = {}, v
            path2, head2 = {}, w

            def step(path, head):
                head = leader[head]
                parent = leader[S[head]]
                if parent == head:
                    return head     # found root of structure tree
                path[head] = parent
                path[parent] = leader[T[parent]]
                return path[parent]

            while 1:
                head1 = step(path1, head1)
                head2 = step(path2, head2)

                if head1 == head2:
                    blossom(v, w, head1)
                    return False

                if leader[S[head1]] == head1 and leader[S[head2]] == head2:
                    addMatch(v, w)
                    return True

                if head1 in path2:
                    blossom(v, w, head1)
                    return False

                if head2 in path1:
                    blossom(v, w, head2)
                    return False

        # Start of main augmenting path search code.

        for v in G:
            if v not in matching:
                S[v] = v
                unexplored.append(v)

        current = 0     # index into unexplored, in FIFO order so we get short paths
        while current < len(unexplored):
            v = unexplored[current]
            current += 1

            for w in G[v]:
                if leader[w] in S:  # S-S edge: blossom or augmenting path
                    if ss(v,w):
                        return True

                elif w not in T:    # previously unexplored node, add as T-node
                    T[w] = v
                    u = matching[w]
                    if leader[u] not in S:
                        S[u] = w    # and add its match as an S-node
                        unexplored.append(u)

        return False    # ran out of graph without finding an augmenting path

    # augment the matching until it is maximum
    while augment():
        pass

    return matching
<|endoftext|>


<commit_before>"""
The core Dogbot bot.
"""

import asyncio
import datetime
import importlib
import logging
import sys
import traceback
from typing import Any, List

import aiohttp
import aioredis
import asyncpg
import discord
import raven
from discord.ext import commands

import dog_config as cfg

from . import botcollection, errors
from .utils import pretty_timedelta

logger = logging.getLogger(__name__)

class DogBot(commands.AutoShardedBot):
    """
    The main DogBot bot. It is automatically sharded. All parameters are passed
    to the constructor of :class:`discord.commands.AutoShardedBot`.
    """
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # boot time (for uptime)
        self.boot_time = datetime.datetime.utcnow()

        # hack because __init__ cannot be async
        loop = asyncio.get_event_loop()
        redis_coroutine = aioredis.create_redis(
            (cfg.redis_url, 6379), loop=loop)

        # aioredis connection
        self.redis = loop.run_until_complete(redis_coroutine)

        # asyncpg
        self.pg = loop.run_until_complete(asyncpg.connect(**cfg.postgresql_auth))

        # aiohttp session used for fetching data
        self.session = aiohttp.ClientSession()

        # sentry connection for reporting exceptions
        self.sentry = raven.Client(cfg.raven_client_url)

        # asyncio task that POSTs to bots.discord.pw with the guild count every
        # 10 minutes
        self.report_task = None

    def reload_extension(self, name: str):
        """ Reloads an extension. """
        self.unload_extension(name)
        self.load_extension(name)

    def perform_full_reload(self):
        """ Fully reloads Dogbot.

        This reloads all Dogbot related modules, and all
        extensions.
        """
        logger.info('*** Performing full reload! ***')
        self.reload_all_extensions()
        self.reload_modules()

    def reload_all_extensions(self):
        """ Reloads all extensions. """
        names = self.extensions.keys()
        logger.info('Reloading all %d extensions', len(names))
        for name in names:
            logger.info('Reloading extension: %s', name)
            self.reload_extension(name)

    def reload_modules(self):
        """ Reloads all Dogbot related modules. """
        # get applicable modules to reload
        modules = {k: m for k, m in sys.modules.items() if 'dog' in k and 'ext' not in k and
                   k != 'dog'}
        for name, module in modules.items():
            logger.info('Reloading bot module: %s', name)
            importlib.reload(module)
        logger.info('Finished reloading bot modules!')

    async def pick_from_list(self, ctx: commands.Context, choices: List[Any]) -> Any:
        """ Shows the user a list of items to pick from. Returns the picked item. """
        # format list of stuff
        choices_list = '\n'.join('`{:03d}`: {}'.format(index + 1, value)
                                 for index, value in enumerate(choices))

        # send list of stuff
        await ctx.send('Pick one, or send `cancel`.\n\n' + choices_list)
        picked = None

        while True:
            # wait for a message
            msg = await self.wait_for_response(ctx)

            # user wants to cancel?
            if msg.content == 'cancel':
                await ctx.send('Canceled selection.')
                break

            try:
                chosen_index = int(msg.content) - 1
            except ValueError:
                # they didn't enter a valid number
                await ctx.send('That wasn\'t a number! Send a message that '
                               'solely contains the number of the item that '
                               'you want.')
                continue

            if chosen_index < 0 or chosen_index > len(choices) - 1:
                # out of range
                await ctx.send('Invalid choice! Send a message that solely '
                               'contains the number of the item that you '
                               'want.')
            else:
                # they chose correctly
                picked = choices[chosen_index]
                break

        return picked

    async def command_is_disabled(self, guild: discord.Guild, command_name: str):
        """ Returns whether a command is disabled in a guild. """
        return await self.redis.exists(f'disabled:{guild.id}:{command_name}')

    async def disable_command(self, guild: discord.Guild, command_name: str):
        """ Disables a command in a guild. """
        logger.info('disabling %s in %d', command_name, guild.id)
        await self.redis.set(f'disabled:{guild.id}:{command_name}', 'on')

    async def enable_command(self, guild: discord.Guild, command_name: str):
        """ Enables a command in a guild. """
        logger.info('enabling %s in %d', command_name, guild.id)
        await self.redis.delete(f'disabled:{guild.id}:{command_name}')

    async def wait_for_response(self, ctx: commands.Context):
        """
        Waits for a message response from the message author, then returns the
        new message.

        The message we are waiting for will only be accepted if it was sent by
        the original command invoker, and it was sent in the same channel as
        the command message.
        """
        def check(m):
            return m.channel.id == ctx.channel.id and m.author.id == ctx.author.id
        return await self.wait_for('message', check=check)

    def has_prefix(self, text: str):
        """
        Checks if text starts with a bot prefix.

        .. NOTE::

            This does not rely on `discord.commands.ext.Bot.command_prefix`,
            but rather the list of prefixes present in `dog_config`.
        """
        return any([text.startswith(p) for p in cfg.prefixes])

    async def send_modlog(self, guild: discord.Guild, *args, **kwargs):
        """
        Sends a message to the #mod-log channel of a guild.

        If there is no #mod-log channel, no message is sent. All parameters are
        passed to `send()`.
        """
        mod_log = discord.utils.get(guild.channels, name='mod-log')

        # don't post to mod-log, couldn't find the channel
        if mod_log is None:
            return

        await mod_log.send(*args, **kwargs)

    async def ok(self, ctx: commands.Context, emoji: str='\N{OK HAND SIGN}'):
        """
        Adds a reaction to the command message, or sends it to the channel if
        we can't add reactions. This should be used as feedback to commands,
        just like how most bots send out `:ok_hand:` when a command completes
        successfully.
        """
        try:
            await ctx.message.add_reaction(emoji)
        except discord.Forbidden:
            await ctx.send(emoji)

    async def on_ready(self):
        logger.info('*** Bot is ready! ***')
        logger.info('Owner ID: %s', cfg.owner_id)
        logger.info('Logged in!')
        logger.info(f' Name: {self.user.name}#{self.user.discriminator}')
        logger.info(f' ID:   {self.user.id}')

        # helpful game
        short_prefix = min(cfg.prefixes, key=len)
        help_game = discord.Game(name=f'{short_prefix}help')
        await self.change_presence(game=help_game)

        async def report_guilds_task():
            # bail if we don't have the token
            if not hasattr(cfg, 'discordpw_token'):
                logger.warning('Not going to submit guild count, no discord.pw token.')
                return

            ENDPOINT = f'https://bots.discord.pw/api/bots/{self.user.id}/stats'
            while True:
                guilds = len(self.guilds)
                data = {'server_count': guilds}
                headers = {'Authorization': cfg.discordpw_token}
                logger.info('POSTing guild count to abal\'s website...')
                # HTTP POST to the endpoint
                async with self.session.post(ENDPOINT, json=data, headers=headers) as resp:
                    if resp.status != 200:
                        logger.warning('Failed to post guild count...')
                        logger.warning('Resp: %s', await resp.text())
                    else:
                        logger.info('Posted guild count successfully!'
                                    ' (%d guilds)', guilds)
                await asyncio.sleep(60 * 10)  # only report every 10 minutes

        logger.info('Creating bots.discord.pw task')
        self.report_task = self.loop.create_task(report_guilds_task())

    async def monitor_send(self, *args, **kwargs):
        """
        Sends a message to the monitoring channel.

        The monitoring channel is a channel usually only visible to the bot
        owner that will have messages like "New guild!" and "Left guild..."
        sent to it.

        All parameters are passed to `send()`.
        """
        monitor_channels = getattr(cfg, 'owner_monitor_channels', [])
        channels = [self.get_channel(c) for c in monitor_channels]

        # no monitor channels
        if not channels or not any(channels):
            return

        for channel in channels:
            await channel.send(*args, **kwargs)

    async def notify_think_is_collection(self, guild: discord.Guild):
        """
        Notifies a guild that they are a collection.

        Steps are taken in order to notify a guild:

        - Send it to the default channel, but if that doesn't work, then
        - DM the owner of the guild, but if that doesn't work, then
        - Loop through all channels in the guild and message the first sendable
          one that we find.

        """
        COLL_ISSUES = f'https://github.com/{cfg.github}/issues'
        COLL_HDR_PUBLIC = "Hi there! Somebody added me to this server, "
        COLL_HDR_PRIVATE = ("Hi there! Somebody added me to your server, "
                            f"{guild.name} (`{guild.id}`), ")
        COLL_REST = ("but my algorithms tell me that this is a bot"
                     " collection! (A server dedicated to keeping"
                     " loads of bots in one place, which I don't like"
                     "!) If this server is not a bot collection, "
                     "please open an issue here: <" + COLL_ISSUES +
                     "> and I'll try to get back to you as soon as"
                     " possible (make sure to include the name of"
                     " your server so I can whitelist you)! Thanks!")

        try:
            await guild.default_channel.send(COLL_HDR_PUBLIC + COLL_REST)
            logger.info('Notified %s (%d) that they were a collection.',
                        guild.name, guild.id)
        except discord.Forbidden:
            logger.info('Couldn\'t send to default channel. DMing owner!')
            try:
                await guild.owner.send(COLL_HDR_PRIVATE + COLL_REST)
            except discord.Forbidden:
                logger.info('Couldn\'t DM the owner of the guild. Looping.')
                for channel in guild.channels:
                    # ignore voice channels
                    if not isinstance(channel, discord.TextChannel):
                        continue
                    can_speak = channel.permissions_for(guild.me).send_messages
                    if can_speak:
                        await channel.send(COLL_HDR_PUBLIC + COLL_REST)
                        return
                logger.info('Couldn\'t inform the server at all. Giving up.')

    async def on_guild_join(self, g):
        diff = pretty_timedelta(datetime.datetime.utcnow() - g.created_at)
        ratio = botcollection.user_to_bot_ratio(g)

        if botcollection.is_bot_collection(g):
            # uh oh!
            logger.info('I think %s (%d) by %s is a bot collection! Leaving.',
                        g.name, g.id, str(g.owner))

            # notify that i'm leaving a collection
            await self.notify_think_is_collection(g)

            # leave it
            await g.leave()

            # monitor
            fmt = (f'\N{FACE WITH ROLLING EYES} Left bot collection {g.name}'
                   f' (`{g.id}`), owned by {g.owner.mention} (`{g.owner.id}`)'
                   f' Created {diff}, user to bot ratio: {ratio}')
            return await self.monitor_send(fmt)

        # monitor
        fmt = (f'\N{SMIRKING FACE} Added to new guild "{g.name}" (`{g.id}`)'
               f', {len(g.members)} members, owned by {g.owner.mention}'
               f' (`{g.owner.id}`). This guild was created {diff}.'
               f' User to bot ratio: {ratio}')
        await self.monitor_send(fmt)

    async def on_guild_remove(self, g):
        fmt = (f'\N{LOUDLY CRYING FACE} Removed from guild "{g.name}"'
               f' (`{g.id}`)!')
        await self.monitor_send(fmt)

    async def config_is_set(self, guild: discord.Guild, name: str):
        """
        Returns whether a configuration key for a guild is set or not.

        .. NOTE::

            This does not look at the value of a configuration key; it just
            checks if it exists. In Dogbot, a configuration key existing
            signifies that it is set.
        """
        return await self.redis.exists(f'{guild.id}:{name}')

    async def on_command_error(self, ex, ctx):
        if ctx.command:
            see_help = f'Run `d?help {ctx.command.name}` for more information.'

        if isinstance(ex, commands.errors.BadArgument):
            message = str(ex)
            if not message.endswith('.'):
                message = message + '.'
            await ctx.send(f'Bad argument! {message} {see_help}')
        elif isinstance(ex, commands.errors.MissingRequiredArgument):
            await ctx.send(f'Uh oh! {ex} {see_help}')
        elif isinstance(ex, commands.NoPrivateMessage):
            await ctx.send('You can\'t do that in a private message.')
        elif isinstance(ex, errors.InsufficientPermissions):
            await ctx.send(ex)
        elif isinstance(ex, commands.errors.CommandInvokeError):
            tb = ''.join(traceback.format_exception(
                type(ex.original), ex.original,
                ex.original.__traceback__
            ))

            header = f'Command error: {type(ex.original).__name__}: {ex.original}'
            message = header + '\n' + str(tb)

            # dispatch the message
            self.sentry.captureMessage(message)
            logger.error(message)
<commit_msg>bot: add initial join message<commit_after>"""
The core Dogbot bot.
"""

import asyncio
import datetime
import importlib
import logging
import sys
import traceback
from typing import Any, List

import aiohttp
import aioredis
import asyncpg
import discord
import raven
from discord.ext import commands

import dog_config as cfg

from . import botcollection, errors
from .utils import pretty_timedelta

logger = logging.getLogger(__name__)

class DogBot(commands.AutoShardedBot):
    """
    The main DogBot bot. It is automatically sharded. All parameters are passed
    to the constructor of :class:`discord.commands.AutoShardedBot`.
    """
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # boot time (for uptime)
        self.boot_time = datetime.datetime.utcnow()

        # hack because __init__ cannot be async
        loop = asyncio.get_event_loop()
        redis_coroutine = aioredis.create_redis(
            (cfg.redis_url, 6379), loop=loop)

        # aioredis connection
        self.redis = loop.run_until_complete(redis_coroutine)

        # asyncpg
        self.pg = loop.run_until_complete(asyncpg.connect(**cfg.postgresql_auth))

        # aiohttp session used for fetching data
        self.session = aiohttp.ClientSession()

        # sentry connection for reporting exceptions
        self.sentry = raven.Client(cfg.raven_client_url)

        # asyncio task that POSTs to bots.discord.pw with the guild count every
        # 10 minutes
        self.report_task = None

    def reload_extension(self, name: str):
        """ Reloads an extension. """
        self.unload_extension(name)
        self.load_extension(name)

    def perform_full_reload(self):
        """ Fully reloads Dogbot.

        This reloads all Dogbot related modules, and all
        extensions.
        """
        logger.info('*** Performing full reload! ***')
        self.reload_all_extensions()
        self.reload_modules()

    def reload_all_extensions(self):
        """ Reloads all extensions. """
        names = self.extensions.keys()
        logger.info('Reloading all %d extensions', len(names))
        for name in names:
            logger.info('Reloading extension: %s', name)
            self.reload_extension(name)

    def reload_modules(self):
        """ Reloads all Dogbot related modules. """
        # get applicable modules to reload
        modules = {k: m for k, m in sys.modules.items() if 'dog' in k and 'ext' not in k and
                   k != 'dog'}
        for name, module in modules.items():
            logger.info('Reloading bot module: %s', name)
            importlib.reload(module)
        logger.info('Finished reloading bot modules!')

    async def pick_from_list(self, ctx: commands.Context, choices: List[Any]) -> Any:
        """ Shows the user a list of items to pick from. Returns the picked item. """
        # format list of stuff
        choices_list = '\n'.join('`{:03d}`: {}'.format(index + 1, value)
                                 for index, value in enumerate(choices))

        # send list of stuff
        await ctx.send('Pick one, or send `cancel`.\n\n' + choices_list)
        picked = None

        while True:
            # wait for a message
            msg = await self.wait_for_response(ctx)

            # user wants to cancel?
            if msg.content == 'cancel':
                await ctx.send('Canceled selection.')
                break

            try:
                chosen_index = int(msg.content) - 1
            except ValueError:
                # they didn't enter a valid number
                await ctx.send('That wasn\'t a number! Send a message that '
                               'solely contains the number of the item that '
                               'you want.')
                continue

            if chosen_index < 0 or chosen_index > len(choices) - 1:
                # out of range
                await ctx.send('Invalid choice! Send a message that solely '
                               'contains the number of the item that you '
                               'want.')
            else:
                # they chose correctly
                picked = choices[chosen_index]
                break

        return picked

    async def command_is_disabled(self, guild: discord.Guild, command_name: str):
        """ Returns whether a command is disabled in a guild. """
        return await self.redis.exists(f'disabled:{guild.id}:{command_name}')

    async def disable_command(self, guild: discord.Guild, command_name: str):
        """ Disables a command in a guild. """
        logger.info('disabling %s in %d', command_name, guild.id)
        await self.redis.set(f'disabled:{guild.id}:{command_name}', 'on')

    async def enable_command(self, guild: discord.Guild, command_name: str):
        """ Enables a command in a guild. """
        logger.info('enabling %s in %d', command_name, guild.id)
        await self.redis.delete(f'disabled:{guild.id}:{command_name}')

    async def wait_for_response(self, ctx: commands.Context):
        """
        Waits for a message response from the message author, then returns the
        new message.

        The message we are waiting for will only be accepted if it was sent by
        the original command invoker, and it was sent in the same channel as
        the command message.
        """
        def check(m):
            return m.channel.id == ctx.channel.id and m.author.id == ctx.author.id
        return await self.wait_for('message', check=check)

    def has_prefix(self, text: str):
        """
        Checks if text starts with a bot prefix.

        .. NOTE::

            This does not rely on `discord.commands.ext.Bot.command_prefix`,
            but rather the list of prefixes present in `dog_config`.
        """
        return any([text.startswith(p) for p in cfg.prefixes])

    async def send_modlog(self, guild: discord.Guild, *args, **kwargs):
        """
        Sends a message to the #mod-log channel of a guild.

        If there is no #mod-log channel, no message is sent. All parameters are
        passed to `send()`.
        """
        mod_log = discord.utils.get(guild.channels, name='mod-log')

        # don't post to mod-log, couldn't find the channel
        if mod_log is None:
            return

        await mod_log.send(*args, **kwargs)

    async def ok(self, ctx: commands.Context, emoji: str='\N{OK HAND SIGN}'):
        """
        Adds a reaction to the command message, or sends it to the channel if
        we can't add reactions. This should be used as feedback to commands,
        just like how most bots send out `:ok_hand:` when a command completes
        successfully.
        """
        try:
            await ctx.message.add_reaction(emoji)
        except discord.Forbidden:
            await ctx.send(emoji)

    async def on_ready(self):
        logger.info('*** Bot is ready! ***')
        logger.info('Owner ID: %s', cfg.owner_id)
        logger.info('Logged in!')
        logger.info(f' Name: {self.user.name}#{self.user.discriminator}')
        logger.info(f' ID:   {self.user.id}')

        # helpful game
        short_prefix = min(cfg.prefixes, key=len)
        help_game = discord.Game(name=f'{short_prefix}help')
        await self.change_presence(game=help_game)

        async def report_guilds_task():
            # bail if we don't have the token
            if not hasattr(cfg, 'discordpw_token'):
                logger.warning('Not going to submit guild count, no discord.pw token.')
                return

            ENDPOINT = f'https://bots.discord.pw/api/bots/{self.user.id}/stats'
            while True:
                guilds = len(self.guilds)
                data = {'server_count': guilds}
                headers = {'Authorization': cfg.discordpw_token}
                logger.info('POSTing guild count to abal\'s website...')
                # HTTP POST to the endpoint
                async with self.session.post(ENDPOINT, json=data, headers=headers) as resp:
                    if resp.status != 200:
                        logger.warning('Failed to post guild count...')
                        logger.warning('Resp: %s', await resp.text())
                    else:
                        logger.info('Posted guild count successfully!'
                                    ' (%d guilds)', guilds)
                await asyncio.sleep(60 * 10)  # only report every 10 minutes

        logger.info('Creating bots.discord.pw task')
        self.report_task = self.loop.create_task(report_guilds_task())

    async def monitor_send(self, *args, **kwargs):
        """
        Sends a message to the monitoring channel.

        The monitoring channel is a channel usually only visible to the bot
        owner that will have messages like "New guild!" and "Left guild..."
        sent to it.

        All parameters are passed to `send()`.
        """
        monitor_channels = getattr(cfg, 'owner_monitor_channels', [])
        channels = [self.get_channel(c) for c in monitor_channels]

        # no monitor channels
        if not channels or not any(channels):
            return

        for channel in channels:
            await channel.send(*args, **kwargs)

    async def notify_think_is_collection(self, guild: discord.Guild):
        """
        Notifies a guild that they are a collection.

        Steps are taken in order to notify a guild:

        - Send it to the default channel, but if that doesn't work, then
        - DM the owner of the guild, but if that doesn't work, then
        - Loop through all channels in the guild and message the first sendable
          one that we find.

        """
        COLL_ISSUES = f'https://github.com/{cfg.github}/issues'
        COLL_HDR_PUBLIC = "Hi there! Somebody added me to this server, "
        COLL_HDR_PRIVATE = ("Hi there! Somebody added me to your server, "
                            f"{guild.name} (`{guild.id}`), ")
        COLL_REST = ("but my algorithms tell me that this is a bot"
                     " collection! (A server dedicated to keeping"
                     " loads of bots in one place, which I don't like"
                     "!) If this server is not a bot collection, "
                     "please open an issue here: <" + COLL_ISSUES +
                     "> and I'll try to get back to you as soon as"
                     " possible (make sure to include the name of"
                     " your server so I can whitelist you)! Thanks!")

        try:
            await guild.default_channel.send(COLL_HDR_PUBLIC + COLL_REST)
            logger.info('Notified %s (%d) that they were a collection.',
                        guild.name, guild.id)
        except discord.Forbidden:
            logger.info('Couldn\'t send to default channel. DMing owner!')
            try:
                await guild.owner.send(COLL_HDR_PRIVATE + COLL_REST)
            except discord.Forbidden:
                logger.info('Couldn\'t DM the owner of the guild. Looping.')
                for channel in guild.channels:
                    # ignore voice channels
                    if not isinstance(channel, discord.TextChannel):
                        continue
                    can_speak = channel.permissions_for(guild.me).send_messages
                    if can_speak:
                        await channel.send(COLL_HDR_PUBLIC + COLL_REST)
                        return
                logger.info('Couldn\'t inform the server at all. Giving up.')

    async def on_guild_join(self, g):
        diff = pretty_timedelta(datetime.datetime.utcnow() - g.created_at)
        ratio = botcollection.user_to_bot_ratio(g)

        if botcollection.is_bot_collection(g):
            # uh oh!
            logger.info('I think %s (%d) by %s is a bot collection! Leaving.',
                        g.name, g.id, str(g.owner))

            # notify that i'm leaving a collection
            await self.notify_think_is_collection(g)

            # leave it
            await g.leave()

            # monitor
            fmt = (f'\N{FACE WITH ROLLING EYES} Left bot collection {g.name}'
                   f' (`{g.id}`), owned by {g.owner.mention} (`{g.owner.id}`)'
                   f' Created {diff}, user to bot ratio: {ratio}')
            return await self.monitor_send(fmt)

        # monitor
        fmt = (f'\N{SMIRKING FACE} Added to new guild "{g.name}" (`{g.id}`)'
               f', {len(g.members)} members, owned by {g.owner.mention}'
               f' (`{g.owner.id}`). This guild was created {diff}.'
               f' User to bot ratio: {ratio}')
        await self.monitor_send(fmt)

        WELCOME_MESSAGE = ('\N{DOG FACE} Woof! Hey there! I\'m Dogbot! To get a list of all of my '
                           'commands, type `d?help` in chat, so I can DM you my commands! If you '
                           'need help, need to report a bug, or just want to request a feature, '
                           'please join the support server: https://discord.gg/3dd7czT Thanks!')

        # send welcome message to first available channel
        for channel in g.channels:
            if channel.permissions_for(g.me).send_messages:
                return await channel.send(WELCOME_MESSAGE)

    async def on_guild_remove(self, g):
        fmt = (f'\N{LOUDLY CRYING FACE} Removed from guild "{g.name}"'
               f' (`{g.id}`)!')
        await self.monitor_send(fmt)

    async def config_is_set(self, guild: discord.Guild, name: str):
        """
        Returns whether a configuration key for a guild is set or not.

        .. NOTE::

            This does not look at the value of a configuration key; it just
            checks if it exists. In Dogbot, a configuration key existing
            signifies that it is set.
        """
        return await self.redis.exists(f'{guild.id}:{name}')

    async def on_command_error(self, ex, ctx):
        if ctx.command:
            see_help = f'Run `d?help {ctx.command.name}` for more information.'

        if isinstance(ex, commands.errors.BadArgument):
            message = str(ex)
            if not message.endswith('.'):
                message = message + '.'
            await ctx.send(f'Bad argument! {message} {see_help}')
        elif isinstance(ex, commands.errors.MissingRequiredArgument):
            await ctx.send(f'Uh oh! {ex} {see_help}')
        elif isinstance(ex, commands.NoPrivateMessage):
            await ctx.send('You can\'t do that in a private message.')
        elif isinstance(ex, errors.InsufficientPermissions):
            await ctx.send(ex)
        elif isinstance(ex, commands.errors.CommandInvokeError):
            tb = ''.join(traceback.format_exception(
                type(ex.original), ex.original,
                ex.original.__traceback__
            ))

            header = f'Command error: {type(ex.original).__name__}: {ex.original}'
            message = header + '\n' + str(tb)

            # dispatch the message
            self.sentry.captureMessage(message)
            logger.error(message)
<|endoftext|>


<commit_before>import sublime
from sublime_plugin import WindowCommand, TextCommand

from ..git_command import GitCommand
from ...common import util

LOG_GRAPH_TITLE = "GRAPH"

class GsLogGraphCommand(WindowCommand, GitCommand):

    """
    Open a new window displaying an ASCII-graphic representation
    of the repo's branch relationships.
    """

    def run(self, all_branches=False):
        repo_path = self.repo_path
        view = self.window.new_file()
        view.settings().set("git_savvy.log_graph_view", True)
        view.settings().set("git_savvy.repo_path", repo_path)
        view.settings().set("word_wrap", False)
        view.set_syntax_file("Packages/GitSavvy/syntax/graph.tmLanguage")
        view.set_name(LOG_GRAPH_TITLE)
        view.set_scratch(True)
        view.set_read_only(True)
        view.run_command("gs_log_graph_initialize", {"all_branches": all_branches})

class GsLogGraphInitializeCommand(TextCommand, GitCommand):

    def run(self, edit, all_branches):
        savvy_settings = sublime.load_settings("GitSavvy.sublime-settings")
        args = savvy_settings.get("git_graph_args")
        if all_branches:
            args.append("--all")

        branch_graph = self.git(*args)
        self.view.run_command("gs_replace_view_text", {"text": branch_graph, "nuke_cursors": True})

class GsLogGraphActionCommand(TextCommand, GitCommand):

    """
    Checkout the commit at the selected line.
    """

    def run(self, edit, action):
        self.action = action
        sublime.set_timeout_async(self.run_async, 0)

    def run_async(self):
        selections = self.view.sel()
        if len(selections) != 1:
            return

        lines = util.view.get_lines_from_regions(self.view, selections)
        if not lines:
            return
        line = lines[0]

        commit_hash = line.strip(" /_\|*")[:7]
        if self.action == "checkout":
            self.checkout_ref(commit_hash)
            util.view.refresh_gitsavvy(self.view)
        elif self.action == "view":
            self.view.window().run_command("gs_show_commit", {"commit_hash": commit_hash})

class GsLogGraphMoreInfoCommand(TextCommand, GitCommand):

    """
    Show all info about a commit in a quick panel.
    """

    def run(self, edit):
        savvy_settings = sublime.load_settings("GitSavvy.sublime-settings")
        show_more = savvy_settings.get("graph_show_more_commit_info")
        if not show_more:
            return

        selections = self.view.sel()
        if len(selections) != 1:
            return

        lines = util.view.get_lines_from_regions(self.view, selections)
        if not lines:
            return
        line = lines[0]

        commit_hash = line.strip(" /_\|*")[:7]
        if len(commit_hash) <= 3:
            return

        text = self.git("show", commit_hash, "--format=fuller", "--quiet")
        output_view = self.view.window().create_output_panel("show_commit_info")
        output_view.set_read_only(False)
        output_view.insert(edit, 0, text)
        output_view.set_syntax_file("Packages/GitSavvy/syntax/show_commit.tmLanguage")
        output_view.set_read_only(True)
        self.view.window().run_command("show_panel", {"panel": "output.show_commit_info"})

class GsLogGraphNextCommitCommand(TextCommand, GitCommand):

    """
    Move cursor to next commit.
    """

    def run(self, edit, forward=True):
        selections = self.view.sel()
        if len(selections) != 1:
            return

        self.view.window().run_command("move", {"by": "lines", "forward": forward})
        lines = util.view.get_lines_from_regions(self.view, selections)
        if not lines:
            return
        line = lines[0]

        commit_hash = line.strip(" /_\|*")[:7]
        if len(commit_hash) > 3:
            self.view.window().run_command("gs_log_graph_more_info")
            self.view.window().run_command("show_at_center")
        else:
            self.view.window().run_command("gs_log_graph_next_commit", {"forward": forward})

class GsLogGraphToggleMoreInfoCommand(TextCommand, WindowCommand, GitCommand):

    """
    Toggle `graph_show_more_commit_info` setting.
    """

    def run(self, edit):
        savvy_settings = sublime.load_settings("GitSavvy.sublime-settings")
        show_more = savvy_settings.get("graph_show_more_commit_info")
        savvy_settings.set("graph_show_more_commit_info", not show_more)

        self.view.run_command("gs_log_graph_more_info")
<commit_msg>Hide panel if disabling the extra commit info in graph view.<commit_after>import sublime
from sublime_plugin import WindowCommand, TextCommand

from ..git_command import GitCommand
from ...common import util

LOG_GRAPH_TITLE = "GRAPH"

class GsLogGraphCommand(WindowCommand, GitCommand):

    """
    Open a new window displaying an ASCII-graphic representation
    of the repo's branch relationships.
    """

    def run(self, all_branches=False):
        repo_path = self.repo_path
        view = self.window.new_file()
        view.settings().set("git_savvy.log_graph_view", True)
        view.settings().set("git_savvy.repo_path", repo_path)
        view.settings().set("word_wrap", False)
        view.set_syntax_file("Packages/GitSavvy/syntax/graph.tmLanguage")
        view.set_name(LOG_GRAPH_TITLE)
        view.set_scratch(True)
        view.set_read_only(True)
        view.run_command("gs_log_graph_initialize", {"all_branches": all_branches})

class GsLogGraphInitializeCommand(TextCommand, GitCommand):

    def run(self, edit, all_branches):
        savvy_settings = sublime.load_settings("GitSavvy.sublime-settings")
        args = savvy_settings.get("git_graph_args")
        if all_branches:
            args.append("--all")

        branch_graph = self.git(*args)
        self.view.run_command("gs_replace_view_text", {"text": branch_graph, "nuke_cursors": True})

class GsLogGraphActionCommand(TextCommand, GitCommand):

    """
    Checkout the commit at the selected line.
    """

    def run(self, edit, action):
        self.action = action
        sublime.set_timeout_async(self.run_async, 0)

    def run_async(self):
        selections = self.view.sel()
        if len(selections) != 1:
            return

        lines = util.view.get_lines_from_regions(self.view, selections)
        if not lines:
            return
        line = lines[0]

        commit_hash = line.strip(" /_\|*")[:7]
        if self.action == "checkout":
            self.checkout_ref(commit_hash)
            util.view.refresh_gitsavvy(self.view)
        elif self.action == "view":
            self.view.window().run_command("gs_show_commit", {"commit_hash": commit_hash})

class GsLogGraphMoreInfoCommand(TextCommand, GitCommand):

    """
    Show all info about a commit in a quick panel.
    """

    def run(self, edit):
        savvy_settings = sublime.load_settings("GitSavvy.sublime-settings")
        show_more = savvy_settings.get("graph_show_more_commit_info")
        if not show_more:
            return

        selections = self.view.sel()
        if len(selections) != 1:
            return

        lines = util.view.get_lines_from_regions(self.view, selections)
        if not lines:
            return
        line = lines[0]

        commit_hash = line.strip(" /_\|*")[:7]
        if len(commit_hash) <= 3:
            return

        text = self.git("show", commit_hash, "--format=fuller", "--quiet")
        output_view = self.view.window().create_output_panel("show_commit_info")
        output_view.set_read_only(False)
        output_view.insert(edit, 0, text)
        output_view.set_syntax_file("Packages/GitSavvy/syntax/show_commit.tmLanguage")
        output_view.set_read_only(True)
        self.view.window().run_command("show_panel", {"panel": "output.show_commit_info"})

class GsLogGraphNextCommitCommand(TextCommand, GitCommand):

    """
    Move cursor to next commit.
    """

    def run(self, edit, forward=True):
        selections = self.view.sel()
        if len(selections) != 1:
            return

        self.view.window().run_command("move", {"by": "lines", "forward": forward})
        lines = util.view.get_lines_from_regions(self.view, selections)
        if not lines:
            return
        line = lines[0]

        commit_hash = line.strip(" /_\|*")[:7]
        if len(commit_hash) > 3:
            self.view.window().run_command("gs_log_graph_more_info")
            self.view.window().run_command("show_at_center")
        else:
            self.view.window().run_command("gs_log_graph_next_commit", {"forward": forward})

class GsLogGraphToggleMoreInfoCommand(TextCommand, WindowCommand, GitCommand):

    """
    Toggle `graph_show_more_commit_info` setting.
    """

    def run(self, edit):
        savvy_settings = sublime.load_settings("GitSavvy.sublime-settings")
        show_more = savvy_settings.get("graph_show_more_commit_info")
        savvy_settings.set("graph_show_more_commit_info", not show_more)
        if not show_more:
            self.view.window().run_command("hide_panel")

        self.view.run_command("gs_log_graph_more_info")
<|endoftext|>


<commit_before>import random

from twisted.internet.defer import inlineCallbacks
from twisted.python import log
from vumi.message import Message
from vumi.service import Worker
from vumi.workers.vodacommessaging.utils import VodacomMessagingResponse

TRANSLATIONS = '''
'''

QUIZ = '''
winner:
    headertext: "Thnx 4 taking the Quiz.  U have won R12 airtime! We will send U your airtime voucher. For more info about HIV/AIDS pls phone Aids Helpline 0800012322"

nonwinner:
    headertext: "Thnx 4 taking the HIV Quiz.  Unfortunately U are not a lucky winner. For more info about HIV/AIDS, phone Aids Helpline 0800012322"

continue:
    options:
        1:
            text: "Continue to win!"
        2:
            text: "Exit the quiz"

exit:
    headertext: "Thnx for taking the quiz.  We'll let U know if U R a lucky winner within 48 hours."

demographic1:
    headertext: "Thnx 4 taking the Quiz! Answer easy questions and be 1 of 5000 lucky winners.  Pick your language:"
    options:
        1:
            text: "English"
        2:
            text: "Zulu"
        3:
            text: "Afrikaans"
        4:
            text: "Sotho"

demographic2:
    headertext: "Please pick your gender"
    options:
        1:
            text: "Male"
        2:
            text: "Female"

demographic3:
    headertext: "Please pick your province"
    options:
        1:
            text: "Gauteng"
        2:
            text: "Kwazulu Natal"
        3:
            text: "Limpopo"
        4:
            text: "Eastern Cape"
        5:
            text: "Mpumalanga"
        6:
            text: "Northern Cape"
        7:
            text: "Western Cape"
        8:
            text: "Free State"
        9:
            text: "North West"

demographic4:
    headertext: "Please pick your age group"
    options:
        1:
            text: "younger than 20"
        2:
            text: "between 20-29"
        3:
            text: "between 30-39"
        4:
            text: "40 or older"

question1:
    headertext: "Can U get HIV/AIDS by having sex without a condom?"
    options:
        1:
            text: "Yes"
            reply: "Correct, U can get HIV/AIDS by having sex without a condom."
        2:
            text: "No"
            reply: "Please note: U can get HIV/AIDS by having sex without a condom."

question2:
    headertext: "Can traditional medicine cure HIV/AIDS?"
    options:
        1:
            text: "Yes"
            reply: "Please note: Traditional medicine cannot cure HIV/AIDS. There is no cure for HIV/AIDS."
        2:
            text: "No"
            reply: "Correct, traditional medicine can not cure HIV/AIDS.  There is no cure for HIV/AIDS."

question3:
    headertext: "Is an HIV test at any government clinic free of charge?"
    options:
        1:
            text: "Yes"
            reply: "Correct, an HV test at any government clinic is free of charge."
        2:
            text: "No"
            reply: "Please Note: an HIV test at any government clinic is free of charge."

question4:
    headertext: "Is it possible for a person newly infected with HIV, to test HIV negative?"
    options:
        1:
            text: "Yes"
            reply: "Correct, a newly-infected HIV positive person can test HIV-negative.  This is due to a person testing for HIV in the 'window period'"
        2:
            text: "No"
            reply: "Please Note: a newly-infected HIV positive person can test HIV-negative.  This is due to a person testing for HIV in the 'window period'"

question5:
    headertext: "Can HIV be transmitted by sweat?"
    options:
        1:
            text: "Yes"
            reply: "Please note, HIV can not be transmitted by sweat."
        2:
            text: "No"
            reply: "Correct, HIV can not be transmitted by sweat."

question6:
    headertext: "Is there a herbal medication that can cure HIV/AIDS?"
    options:
        1:
            text: "Yes"
            reply: "Please note, there are no herbal medications that can cure HIV/AIDS.  There is no cure for HIV/AIDS."
        2:
            text: "No"
            reply: "Correct, there are no herbal medications that can cure HIV/AIDS.  There is no cure for HIV/AIDS."

question7:
    headertext: "Does a CD4-count tell the strength of a person's immune system?"
    options:
        1:
            text: "Yes"
            reply: "Correct, a CD4-count does tell the strength of a person's immune system."
        2:
            text: "No"
            reply: "Please note, a CD4-count does tell the strength of a person's immune system."

question8:
    headertext: "Can HIV be transmitted through a mother's breast milk?"
    options:
        1:
            text: "Yes"
            reply: "Correct, HIV can be transmitted via a mother's breast milk."
        2:
            text: "No"
            reply: "Please note, HIV can be transmitted via a mother's breast milk."

question9:
    headertext: "Is it possible for an HIV positive woman to deliver an HIV negative baby?"
    options:
        1:
            text: "Yes"
            reply: "Correct, with treatment and planning, it is possible for an HIV positive mother to deliver an HIV-negative baby."
        2:
            text: "No"
            reply: "Please note, with treatment and planning, it is possible for an HIV positive mother to deliver an HIV-negative baby."

question10:
    headertext: "Do you immediately have to start ARVs when you test HIV positive?"
    options:
        1:
            text: "Yes"
            reply: "Please note, once testing positive for HIV, you first have to get your CD4 test done, to determine if you qualify for ARVs."
        2:
            text: "No"
            reply: "Correct, once testing positive for HIV, you first have to get your CD4 test done, to determine if you qualify for ARVs.
1. Continue to win!"
'''

class IkhweziQuiz():

    def __init__(self, config, quiz, translations, datastore, msisdn):
        self.config = config
        self.quiz = quiz
        self.translations = translations
        self.datastore = datastore
        msisdn = str(msisdn)
        self.retrieve_entrant(msisdn) or self.new_entrant(msisdn)

    def ds_set(self):
        self.datastore[self.data['msisdn']] = self.data

    def ds_get(self, msisdn):
        data = self.datastore.get(msisdn)
        return data

    def lang(self, lang):
        langs = {
                "English": "1",
                "Zulu": "2",
                "Afrikaans": "3",
                "Sotho": "4",
                "1": "English",
                "2": "Zulu",
                "3": "Afrikaans",
                "4": "Sotho"
                }
        return langs.get(str(lang))

    def retrieve_entrant(self, msisdn):
        data = self.ds_get(msisdn)
        if data:
            self.data = data
            self.language = self.lang(data['demographic1'])
            return True
        return False

    def random_ordering(self):
        order = ['demographic1']
        demographics = [
                'demographic2',
                'demographic3',
                'demographic4',
                ]
        random.shuffle(demographics)
        questions = [
                'question1',
                'question2',
                'question3',
                'question4',
                'question5',
                'question6',
                'question7',
                'question8',
                'question9',
                'question10']
        random.shuffle(questions)
        while len(questions):
            order.append(questions.pop())
            if len(demographics):
                order.append(demographics.pop())
        return order

    def new_entrant(self, msisdn):
        self.language = "English"
        self.data = {
                'msisdn': str(msisdn),
                'm_timestamp': None,
                'question1': None,
                'question2': None,
                'question3': None,
                'question4': None,
                'question5': None,
                'question6': None,
                'question7': None,
                'question8': None,
                'question9': None,
                'question10': None,
                'q1_timestamp': None,
                'q2_timestamp': None,
                'q3_timestamp': None,
                'q4_timestamp': None,
                'q5_timestamp': None,
                'q6_timestamp': None,
                'q7_timestamp': None,
                'q8_timestamp': None,
                'q9_timestamp': None,
                'q10_timestamp': None,
                'demographic1': None,
                'demographic2': None,
                'demographic3': None,
                'demographic4': None,
                'd1_timestamp': None,
                'd2_timestamp': None,
                'd3_timestamp': None,
                'd4_timestamp': None,
                'order': self.random_ordering()}
        self.ds_set()
        return True

    def next_context_and_question(self, repeat_context=None):
        context = None
        question = None
        order = self.data['order']
        if len(order):
            context = order[0]
            question = self.quiz.get(context)
        return context, question

    def answer_contextual_question(self, context, answer):
        question = self.quiz.get(context)
        if context in self.data.keys() and context in self.data['order']:
            self.data[context] = answer
            self.data['order'].pop(0)
            self.ds_set()
        reply = question['options'][answer].get('reply')
        return reply

    def formulate_response(self, context, answer):
        try:
            answer = int(answer)
        except Exception, e:
            log.msg(e)
            answer = None

        reply = None
        if context and answer:
            reply = self.answer_contextual_question(context, answer)

        if (context == 'continue' or context == None) and (
                answer == 2 or len(self.data['order']) == 0):
            # don't continue, show exit
            context = 'exit'
            question = self.quiz.get(context)
            vmr = VodacomMessagingResponse(self.config)
            vmr.set_context(context)
            vmr.set_headertext(question['headertext'])
            return vmr

        elif reply and len(self.data['order']) == 0:
            # correct answer and exit
            context = 'exit'
            question = self.quiz.get(context)
            vmr = VodacomMessagingResponse(self.config)
            vmr.set_context(context)
            vmr.set_headertext("%s  %s" % (reply, question['headertext']))
            return vmr

        elif reply:
            # correct answer and offer to continue
            context = 'continue'
            question = self.quiz.get(context)
            vmr = VodacomMessagingResponse(self.config)
            vmr.set_context(context)
            vmr.set_headertext(reply)
            for key, val in question['options'].items():
                vmr.add_option(val['text'], key)
            return vmr

        else:
            # ask another question
            context, question = self.next_context_and_question()
            vmr = VodacomMessagingResponse(self.config)
            vmr.set_context(context)
            vmr.set_headertext(question['headertext'])
            for key, val in question['options'].items():
                vmr.add_option(val['text'], key)
            return vmr

class IkhweziQuizWorker(Worker):
    @inlineCallbacks
    def startWorker(self):
        log.msg("Starting IkhweziQuizWorker with config: %s" % (self.config))
        self.publish_key = self.config['consume_key']  # Swap consume and
        self.consume_key = self.config['publish_key']  # publish keys

        self.publisher = yield self.publish_to(self.publish_key)
        self.consume(self.consume_key, self.consume_message)

    def consume_message(self, message):
        log.msg("IkhweziQuizWorker consuming on %s: %s" % (
            self.consume_key,
            repr(message.payload)))
        reply = "inspect message and create reply"  #TODO
        self.publisher.publish_message(Message(
                uuid=message.payload['uuid'],
                message=reply),
            routing_key=message.payload['return_path'].pop())

    def stopWorker(self):
        log.msg("Stopping the MenuWorker")
<commit_msg>catch answer out of range<commit_after>import random

from twisted.internet.defer import inlineCallbacks
from twisted.python import log
from vumi.message import Message
from vumi.service import Worker
from vumi.workers.vodacommessaging.utils import VodacomMessagingResponse

TRANSLATIONS = '''
'''

QUIZ = '''
winner:
    headertext: "Thnx 4 taking the Quiz.  U have won R12 airtime! We will send U your airtime voucher. For more info about HIV/AIDS pls phone Aids Helpline 0800012322"

nonwinner:
    headertext: "Thnx 4 taking the HIV Quiz.  Unfortunately U are not a lucky winner. For more info about HIV/AIDS, phone Aids Helpline 0800012322"

continue:
    options:
        1:
            text: "Continue to win!"
        2:
            text: "Exit the quiz"

exit:
    headertext: "Thnx for taking the quiz.  We'll let U know if U R a lucky winner within 48 hours."

demographic1:
    headertext: "Thnx 4 taking the Quiz! Answer easy questions and be 1 of 5000 lucky winners.  Pick your language:"
    options:
        1:
            text: "English"
        2:
            text: "Zulu"
        3:
            text: "Afrikaans"
        4:
            text: "Sotho"

demographic2:
    headertext: "Please pick your gender"
    options:
        1:
            text: "Male"
        2:
            text: "Female"

demographic3:
    headertext: "Please pick your province"
    options:
        1:
            text: "Gauteng"
        2:
            text: "Kwazulu Natal"
        3:
            text: "Limpopo"
        4:
            text: "Eastern Cape"
        5:
            text: "Mpumalanga"
        6:
            text: "Northern Cape"
        7:
            text: "Western Cape"
        8:
            text: "Free State"
        9:
            text: "North West"

demographic4:
    headertext: "Please pick your age group"
    options:
        1:
            text: "younger than 20"
        2:
            text: "between 20-29"
        3:
            text: "between 30-39"
        4:
            text: "40 or older"

question1:
    headertext: "Can U get HIV/AIDS by having sex without a condom?"
    options:
        1:
            text: "Yes"
            reply: "Correct, U can get HIV/AIDS by having sex without a condom."
        2:
            text: "No"
            reply: "Please note: U can get HIV/AIDS by having sex without a condom."

question2:
    headertext: "Can traditional medicine cure HIV/AIDS?"
    options:
        1:
            text: "Yes"
            reply: "Please note: Traditional medicine cannot cure HIV/AIDS. There is no cure for HIV/AIDS."
        2:
            text: "No"
            reply: "Correct, traditional medicine can not cure HIV/AIDS.  There is no cure for HIV/AIDS."

question3:
    headertext: "Is an HIV test at any government clinic free of charge?"
    options:
        1:
            text: "Yes"
            reply: "Correct, an HV test at any government clinic is free of charge."
        2:
            text: "No"
            reply: "Please Note: an HIV test at any government clinic is free of charge."

question4:
    headertext: "Is it possible for a person newly infected with HIV, to test HIV negative?"
    options:
        1:
            text: "Yes"
            reply: "Correct, a newly-infected HIV positive person can test HIV-negative.  This is due to a person testing for HIV in the 'window period'"
        2:
            text: "No"
            reply: "Please Note: a newly-infected HIV positive person can test HIV-negative.  This is due to a person testing for HIV in the 'window period'"

question5:
    headertext: "Can HIV be transmitted by sweat?"
    options:
        1:
            text: "Yes"
            reply: "Please note, HIV can not be transmitted by sweat."
        2:
            text: "No"
            reply: "Correct, HIV can not be transmitted by sweat."

question6:
    headertext: "Is there a herbal medication that can cure HIV/AIDS?"
    options:
        1:
            text: "Yes"
            reply: "Please note, there are no herbal medications that can cure HIV/AIDS.  There is no cure for HIV/AIDS."
        2:
            text: "No"
            reply: "Correct, there are no herbal medications that can cure HIV/AIDS.  There is no cure for HIV/AIDS."

question7:
    headertext: "Does a CD4-count tell the strength of a person's immune system?"
    options:
        1:
            text: "Yes"
            reply: "Correct, a CD4-count does tell the strength of a person's immune system."
        2:
            text: "No"
            reply: "Please note, a CD4-count does tell the strength of a person's immune system."

question8:
    headertext: "Can HIV be transmitted through a mother's breast milk?"
    options:
        1:
            text: "Yes"
            reply: "Correct, HIV can be transmitted via a mother's breast milk."
        2:
            text: "No"
            reply: "Please note, HIV can be transmitted via a mother's breast milk."

question9:
    headertext: "Is it possible for an HIV positive woman to deliver an HIV negative baby?"
    options:
        1:
            text: "Yes"
            reply: "Correct, with treatment and planning, it is possible for an HIV positive mother to deliver an HIV-negative baby."
        2:
            text: "No"
            reply: "Please note, with treatment and planning, it is possible for an HIV positive mother to deliver an HIV-negative baby."

question10:
    headertext: "Do you immediately have to start ARVs when you test HIV positive?"
    options:
        1:
            text: "Yes"
            reply: "Please note, once testing positive for HIV, you first have to get your CD4 test done, to determine if you qualify for ARVs."
        2:
            text: "No"
            reply: "Correct, once testing positive for HIV, you first have to get your CD4 test done, to determine if you qualify for ARVs.
1. Continue to win!"
'''

class IkhweziQuiz():

    def __init__(self, config, quiz, translations, datastore, msisdn):
        self.config = config
        self.quiz = quiz
        self.translations = translations
        self.datastore = datastore
        msisdn = str(msisdn)
        self.retrieve_entrant(msisdn) or self.new_entrant(msisdn)

    def ds_set(self):
        self.datastore[self.data['msisdn']] = self.data

    def ds_get(self, msisdn):
        data = self.datastore.get(msisdn)
        return data

    def lang(self, lang):
        langs = {
                "English": "1",
                "Zulu": "2",
                "Afrikaans": "3",
                "Sotho": "4",
                "1": "English",
                "2": "Zulu",
                "3": "Afrikaans",
                "4": "Sotho"
                }
        return langs.get(str(lang))

    def retrieve_entrant(self, msisdn):
        data = self.ds_get(msisdn)
        if data:
            self.data = data
            self.language = self.lang(data['demographic1'])
            return True
        return False

    def random_ordering(self):
        order = ['demographic1']
        demographics = [
                'demographic2',
                'demographic3',
                'demographic4',
                ]
        random.shuffle(demographics)
        questions = [
                'question1',
                'question2',
                'question3',
                'question4',
                'question5',
                'question6',
                'question7',
                'question8',
                'question9',
                'question10']
        random.shuffle(questions)
        while len(questions):
            order.append(questions.pop())
            if len(demographics):
                order.append(demographics.pop())
        return order

    def new_entrant(self, msisdn):
        self.language = "English"
        self.data = {
                'msisdn': str(msisdn),
                'm_timestamp': None,
                'question1': None,
                'question2': None,
                'question3': None,
                'question4': None,
                'question5': None,
                'question6': None,
                'question7': None,
                'question8': None,
                'question9': None,
                'question10': None,
                'q1_timestamp': None,
                'q2_timestamp': None,
                'q3_timestamp': None,
                'q4_timestamp': None,
                'q5_timestamp': None,
                'q6_timestamp': None,
                'q7_timestamp': None,
                'q8_timestamp': None,
                'q9_timestamp': None,
                'q10_timestamp': None,
                'demographic1': None,
                'demographic2': None,
                'demographic3': None,
                'demographic4': None,
                'd1_timestamp': None,
                'd2_timestamp': None,
                'd3_timestamp': None,
                'd4_timestamp': None,
                'order': self.random_ordering()}
        self.ds_set()
        return True

    def next_context_and_question(self, repeat_context=None):
        context = None
        question = None
        order = self.data['order']
        if len(order):
            context = order[0]
            question = self.quiz.get(context)
        return context, question

    def answer_contextual_question(self, context, answer):
        reply = None
        question = self.quiz.get(context)
        if context in self.data.keys() \
                and answer in question['options'].keys() \
                and context in self.data['order']:
            reply = question['options'][answer].get('reply')
            self.data[context] = answer
            self.data['order'].pop(0)
            self.ds_set()
        return reply

    def formulate_response(self, context, answer):
        try:
            answer = int(answer)
        except Exception, e:
            log.msg(e)
            answer = None

        reply = None
        if context and answer:
            reply = self.answer_contextual_question(context, answer)

        if (context == 'continue' or context == None) and (
                answer == 2 or len(self.data['order']) == 0):
            # don't continue, show exit
            context = 'exit'
            question = self.quiz.get(context)
            vmr = VodacomMessagingResponse(self.config)
            vmr.set_context(context)
            vmr.set_headertext(question['headertext'])
            return vmr

        elif reply and len(self.data['order']) == 0:
            # correct answer and exit
            context = 'exit'
            question = self.quiz.get(context)
            vmr = VodacomMessagingResponse(self.config)
            vmr.set_context(context)
            vmr.set_headertext("%s  %s" % (reply, question['headertext']))
            return vmr

        elif reply:
            # correct answer and offer to continue
            context = 'continue'
            question = self.quiz.get(context)
            vmr = VodacomMessagingResponse(self.config)
            vmr.set_context(context)
            vmr.set_headertext(reply)
            for key, val in question['options'].items():
                vmr.add_option(val['text'], key)
            return vmr

        else:
            # ask another question
            context, question = self.next_context_and_question()
            vmr = VodacomMessagingResponse(self.config)
            vmr.set_context(context)
            vmr.set_headertext(question['headertext'])
            for key, val in question['options'].items():
                vmr.add_option(val['text'], key)
            return vmr

class IkhweziQuizWorker(Worker):
    @inlineCallbacks
    def startWorker(self):
        log.msg("Starting IkhweziQuizWorker with config: %s" % (self.config))
        self.publish_key = self.config['consume_key']  # Swap consume and
        self.consume_key = self.config['publish_key']  # publish keys

        self.publisher = yield self.publish_to(self.publish_key)
        self.consume(self.consume_key, self.consume_message)

    def consume_message(self, message):
        log.msg("IkhweziQuizWorker consuming on %s: %s" % (
            self.consume_key,
            repr(message.payload)))
        reply = "inspect message and create reply"  #TODO
        self.publisher.publish_message(Message(
                uuid=message.payload['uuid'],
                message=reply),
            routing_key=message.payload['return_path'].pop())

    def stopWorker(self):
        log.msg("Stopping the MenuWorker")
<|endoftext|>


<commit_before># Copyright (c) 2015 Ultimaker B.V.
# Uranium is released under the terms of the AGPLv3 or higher.

from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal, QUrl
from PyQt5.QtGui import QDesktopServices

from UM.Application import Application
from UM.Logger import Logger
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.SceneNode import SceneNode
from UM.Scene.PointCloudNode import PointCloudNode
from UM.Mesh.MeshData import MeshType
from UM.Mesh.ReadMeshJob import ReadMeshJob
from UM.Mesh.WriteMeshJob import WriteMeshJob
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
from UM.Message import Message

import os.path

from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("uranium")

class MeshFileHandlerProxy(QObject):
    def __init__(self, parent = None):
        super().__init__(parent)
        self._mesh_handler = Application.getInstance().getMeshFileHandler()
        self._scene = Application.getInstance().getController().getScene()

    @pyqtProperty("QStringList", constant=True)
    def supportedReadFileTypes(self):
        file_types = []
        all_types = []

        for ext, desc in self._mesh_handler.getSupportedFileTypesRead().items():
            file_types.append("{0} (*.{1})".format(desc, ext))
            all_types.append("*.{0}".format(ext))

        file_types.sort()
        file_types.insert(0, i18n_catalog.i18nc("@item:inlistbox", "All Supported Types ({0})".format(" ".join(all_types))))
        file_types.append(i18n_catalog.i18nc("@item:inlistbox", "All Files (*)"))

        return file_types

    @pyqtProperty("QStringList", constant=True)
    def supportedWriteFileTypes(self):
        file_types = []

        for item in self._mesh_handler.getSupportedFileTypesWrite():
            file_types.append("{0} (*.{1})".format(item["description"], item["extension"]))

        file_types.sort()

        return file_types

    @pyqtSlot(QUrl)
    def readLocalFile(self, file):
        if not file.isValid():
            return
        job = ReadMeshJob(file.toLocalFile())
        job.finished.connect(self._readMeshFinished)
        job.start()

    def _readMeshFinished(self, job):
        node = job.getResult()
        if node != None:
            node.setSelectable(True)
            node.setName(os.path.basename(job.getFileName()))

            op = AddSceneNodeOperation(node, self._scene.getRoot())
            op.push()

            self._scene.sceneChanged.emit(node)

def createMeshFileHandlerProxy(engine, script_engine):
    return MeshFileHandlerProxy()
<commit_msg>Add uppercase extensions to File Open dialog Fixes CURA-567<commit_after># Copyright (c) 2015 Ultimaker B.V.
# Uranium is released under the terms of the AGPLv3 or higher.

from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal, QUrl
from PyQt5.QtGui import QDesktopServices

from UM.Application import Application
from UM.Logger import Logger
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.SceneNode import SceneNode
from UM.Scene.PointCloudNode import PointCloudNode
from UM.Mesh.MeshData import MeshType
from UM.Mesh.ReadMeshJob import ReadMeshJob
from UM.Mesh.WriteMeshJob import WriteMeshJob
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
from UM.Message import Message

import os.path
import platform

from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("uranium")

class MeshFileHandlerProxy(QObject):
    def __init__(self, parent = None):
        super().__init__(parent)
        self._mesh_handler = Application.getInstance().getMeshFileHandler()
        self._scene = Application.getInstance().getController().getScene()

    @pyqtProperty("QStringList", constant=True)
    def supportedReadFileTypes(self):
        file_types = []
        all_types = []

        if platform.system() == "Linux":
            for ext, desc in self._mesh_handler.getSupportedFileTypesRead().items():
                file_types.append("{0} (*.{1} *.{2})".format(desc, ext.lower(), ext.upper()))
                all_types.append("*.{0} *.{1}".format(ext.lower(), ext.upper()))
        else:
            for ext, desc in self._mesh_handler.getSupportedFileTypesRead().items():
                file_types.append("{0} (*.{1})".format(desc, ext))
                all_types.append("*.{0}".format(ext))

        file_types.sort()
        file_types.insert(0, i18n_catalog.i18nc("@item:inlistbox", "All Supported Types ({0})".format(" ".join(all_types))))
        file_types.append(i18n_catalog.i18nc("@item:inlistbox", "All Files (*)"))

        return file_types

    @pyqtProperty("QStringList", constant=True)
    def supportedWriteFileTypes(self):
        file_types = []

        for item in self._mesh_handler.getSupportedFileTypesWrite():
            file_types.append("{0} (*.{1})".format(item["description"], item["extension"]))

        file_types.sort()

        return file_types

    @pyqtSlot(QUrl)
    def readLocalFile(self, file):
        if not file.isValid():
            return
        job = ReadMeshJob(file.toLocalFile())
        job.finished.connect(self._readMeshFinished)
        job.start()

    def _readMeshFinished(self, job):
        node = job.getResult()
        if node != None:
            node.setSelectable(True)
            node.setName(os.path.basename(job.getFileName()))

            op = AddSceneNodeOperation(node, self._scene.getRoot())
            op.push()

            self._scene.sceneChanged.emit(node)

def createMeshFileHandlerProxy(engine, script_engine):
    return MeshFileHandlerProxy()
<|endoftext|>