<commit_before>import logging

from src.shared import messages
from src.shared.game_state import GameState
from src.shared.ident import UnitId, unitToPlayer, getUnitSubId
from src.shared.logconfig import newLogger
from src.shared.unit_set import UnitSet
from src.shared.message_infrastructure import deserializeMessage, \
    unhandledInternalMessage, InvalidMessageError
from src.client import messages as cmessages

# Constants
GRAPHICS_SCALE = 3

# Logging
log = newLogger(__name__)

class Backend:
    def __init__(self, done):
        # Done is a Twisted Deferred object whose callback can be fired to
        # close down the client cleanly (in theory...).
        self.done = done

        self.stdio    = None
        self.network  = None
        self.graphicsInterface = None

        self.allReady = False

        self.myId = -1

        self.gameState     = None
        self.unitSelection = UnitSet()

    @property
    def allComponents(self):
        return (self.stdio, self.network, self.graphicsInterface)

    ########################################################################
    # Initialization

    # All wings, report in.

    # Red 2, standing by.
    def stdioReady(self, stdioComponent):
        assert self.stdio is None
        self.stdio = stdioComponent
        assert self.stdio is not None
        self.checkIfAllReady()

    # Red 11, standing by.
    def networkReady(self, networkComponent):
        assert self.network is None
        self.network = networkComponent
        assert self.network is not None
        self.checkIfAllReady()

    # Red 5, standing by.
    def graphicsInterfaceReady(self, graphicsInterface):
        assert self.graphicsInterface is None
        self.graphicsInterface = graphicsInterface
        assert self.graphicsInterface is not None
        self.checkIfAllReady()

    def checkIfAllReady(self):
        for component in self.allComponents:
            if component is None:
                # Not ready
                return
        self.allComponentsReady()

    def allComponentsReady(self):
        # I'm keeping this method around, even though all it does is set a
        # flag, because it seems like we might want to put some final
        # initialization code in here.
        # ... or change the implementation somehow so that we don't have to
        # constantly check self.allReady for the entire lifetime of the client.
        self.allReady = True

        # Request obelisks.
        msg = messages.OrderNew((10, 0))
        self.network.backendMessage(msg.serialize())
        msg = messages.OrderNew((0, 10))
        self.network.backendMessage(msg.serialize())

    ########################################################################
    # Incoming messages

    def stdioMessage(self, message):
        if self.allReady:
            self.handleUserInput(message)
        else:
            # TODO: Buffer messages until ready? Don't just drop them....
            log.warning("Input '{}' ignored; client not initialized yet." \
                .format(message))

    def handleUserInput(self, message):
        assert not message.endswith('\n')

        if message == "dump":
            log.info("Dumping all unit info...")
            for uid in sorted(self.gameState.positions):
                pos = self.gameState.positions[uid]
                isSelected = (uid in self.unitSelection)
                log.info("    {sel:1} {player:>2}: {subId:>3} @ {x:>4}, {y:>4}"
                         .format(sel    = "*" if isSelected else "",
                                 player = unitToPlayer(uid),
                                 subId  = getUnitSubId(uid),
                                 x      = pos[0],
                                 y      = pos[1]))
            log.info("End unit dump.")
        else:
            self.network.backendMessage(message)

    def networkMessage(self, messageStr):
        if self.allReady:
            try:
                message = deserializeMessage(messageStr)
                self.tryToHandleNetworkMessage(message)
            except InvalidMessageError as error:
                illFormedMessage(error, log, sender="server")

            # Regardless of whether we were able to handle it, forward the
            # message on to the graphicsInterface (which -- at least for now --
            # also handles the YourIdIs message).
            self.graphicsInterface.backendMessage(messageStr)
        else:
            # TODO: Buffer messages until ready? Don't just drop them....
            log.warning("Server message '{}' ignored; client not " \
                "initialized yet.".format(messageStr))

    def tryToHandleNetworkMessage(self, message):
        if isinstance(message, messages.YourIdIs):
            if self.myId >= 0:
                raise RuntimeError("ID already set; can't change it now.")
            self.myId = message.playerId
            log.info("Your id is {id}.".format(id=self.myId))

            self.unitSelection = UnitSet([])
        elif isinstance(message, messages.NewObelisk):
            uid = message.unitId
            pos = message.pos
            if uid in self.gameState.positions:
                invalidMessageArgument(message, log, sender="server",
                    reason="uid {} already in use".format(uid))
                return
            self.gameState.positions[uid] = pos
        elif isinstance(message, messages.DeleteObelisk):
            uid = message.unitId
            if uid not in self.gameState.positions:
                invalidMessageArgument(message, log, sender="server",
                    reason="No such uid: {}".format(uid))
                return
            if uid in self.unitSelection:
                self.removeFromSelection(uid)
            del self.gameState.positions[uid]
        elif isinstance(message, messages.SetPos):
            uid = message.unitId
            pos = message.pos
            if uid not in self.gameState.positions:
                invalidMessageArgument(message, log, sender="server",
                    reason="No such uid: {}".format(uid))
                return
            self.gameState.positions[uid] = pos
        elif isinstance(message, messages.GroundInfo):
            # Note: this isn't used in any way right now. I think it's right,
            # but it's definitely possible we transposed something, or worse.
            x, y        = message.pos
            terrainType = message.terrainType
            self.gameState.groundTypes[x][y] = terrainType
        elif isinstance(message, messages.MapSize):
            if self.gameState is not None:
                log.warn("Got multiple map_size messages.")
            self.gameState = GameState(message.size)
        else:
            # It's okay if we aren't able to handle a message from the
            # server; maybe the GraphicsInterface will handle it.
            pass

    def graphicsMessage(self, messageStr):
        message = deserializeMessage(messageStr)
        if isinstance(message, cmessages.Click):
            # TODO: Have Click indicate "left" or "right", rather than just a
            # numerical button.
            # TODO: GraphicsInterface should probably translate Click.pos to a
            # uPos before passing it to the backend.
            if message.button == 1:
                # Left mouse button
                gPos = message.pos
                uPos = graphicsToUnit(gPos)
                chosenUnit = self.getUnitAt(uPos)
                self.clearSelection()
                if chosenUnit is not None:
                    self.addToSelection(chosenUnit)
            elif message.button == 3:
                # Right mouse button
                newMsg = messages.OrderMove(self.unitSelection,
                                            graphicsToUnit(message.pos))
                self.network.backendMessage(newMsg.serialize())
        elif isinstance(message, cmessages.ShiftLClick):
            gPos = message.pos
            uPos = graphicsToUnit(gPos)
            chosenUnit = self.getUnitAt(uPos)
            if chosenUnit is not None:
                self.addToSelection(chosenUnit)
        elif isinstance(message, cmessages.ControlLClick):
            gPos = message.pos
            uPos = graphicsToUnit(gPos)
            chosenUnit = self.getUnitAt(uPos)
            if chosenUnit is not None and chosenUnit in self.unitSelection:
                self.removeFromSelection(chosenUnit)
        elif isinstance(message, cmessages.ShiftRClick):
            newMsg = messages.OrderNew(graphicsToUnit(message.pos))
            self.network.backendMessage(newMsg.serialize())
        elif isinstance(message, cmessages.ControlRClick):
            gPos = message.pos
            uPos = graphicsToUnit(gPos)
            chosenUnit = self.getUnitAt(uPos)
            if chosenUnit is not None:
                if chosenUnit in self.unitSelection:
                    self.removeFromSelection(chosenUnit)
                newMsg = messages.OrderDel(UnitSet([chosenUnit]))
                self.network.backendMessage(newMsg.serialize())
        elif isinstance(message, cmessages.DragBox):
            ux1, uy1 = graphicsToUnit(message.corner1)
            ux2, uy2 = graphicsToUnit(message.corner2)
            xMin = min(ux1, ux2)
            xMax = max(ux1, ux2)
            yMin = min(uy1, uy2)
            yMax = max(uy1, uy2)
            self.clearSelection()
            for (uid, uPos) in self.gameState.positions.iteritems():
                ux, uy = uPos
                # TODO: Add a tolerance based on the size of the unit.
                if unitToPlayer(uid) == self.myId:
                    if xMin <= ux <= xMax and yMin <= uy <= yMax:
                        self.addToSelection(uid)
        elif isinstance(message, cmessages.RequestCenter):
            if not self.unitSelection:
                # FIXME: Move to center of world.
                return
            totalX, totalY = 0, 0
            for unitId in self.unitSelection:
                unitX, unitY = self.gameState.positions[unitId]
                totalX += unitX
                totalY += unitY
            centroid = (totalX // len(self.unitSelection),
                        totalY // len(self.unitSelection))
            # FIXME: Write this.
        elif isinstance(message, cmessages.RequestQuit):
            for component in self.allComponents:
                component.cleanup()
            self.done.callback(None)
        else:
            unhandledInternalMessage(message, log)

    def getUnitAt(self, targetUPos):
        targetX, targetY = targetUPos

        # TODO[#3]: Make this a real constant somewhere. Or take into account
        # the size of the unit? This could get ugly.
        MAX_DISTANCE = 30**2

        nearest = None
        # TODO: Compute the diameter of the world in unit coordinates when we
        # first load the map, have the server send that to the client, save
        # that+1 as UPOS_INFINITY or some such, and use that here instead of a
        # float.
        nearestDistance = float('inf')
        for (uid, uPos) in self.gameState.positions.iteritems():
            if unitToPlayer(uid) != self.myId:
                continue

            x, y = uPos
            distance = (x - targetX)**2 + (y - targetY)**2
            if distance < nearestDistance and distance < MAX_DISTANCE:
                nearest         = uid
                nearestDistance = distance

        return nearest

    def addToSelection(self, unitId):
        self.unitSelection.add(unitId)
        msg = cmessages.MarkUnitSelected(unitId, True)
        self.graphicsInterface.backendMessage(msg.serialize())

    def removeFromSelection(self, unitId):
        self.unitSelection.remove(unitId)
        msg = cmessages.MarkUnitSelected(unitId, False)
        self.graphicsInterface.backendMessage(msg.serialize())

    def clearSelection(self):
        for unitId in self.unitSelection:
            msg = cmessages.MarkUnitSelected(unitId, False)
            self.graphicsInterface.backendMessage(msg.serialize())
        self.unitSelection = UnitSet()

# TODO: Shouldn't these be in the GraphicsInterface if they're going to be
# with a single component?
def unitToGraphics(unitCoords):
    """Convert unit (xu,yu) integers tuples to graphics (xg,yg) float tuples
    """
    return tuple(float(x)/GRAPHICS_SCALE for x in unitCoords)

def graphicsToUnit(graphicsCoords):
    """Convert graphics (xg,yg) float tuples to unit (xu,yu) integers tuples
    """
    return tuple(int(round(x*GRAPHICS_SCALE)) for x in graphicsCoords)

<commit_msg>Add in a missing import.<commit_after>import logging

from src.shared import messages
from src.shared.game_state import GameState
from src.shared.ident import UnitId, unitToPlayer, getUnitSubId
from src.shared.logconfig import newLogger
from src.shared.unit_set import UnitSet
from src.shared.message_infrastructure import deserializeMessage, \
    unhandledInternalMessage, illFormedMessage, InvalidMessageError
from src.client import messages as cmessages

# Constants
GRAPHICS_SCALE = 3

# Logging
log = newLogger(__name__)

class Backend:
    def __init__(self, done):
        # Done is a Twisted Deferred object whose callback can be fired to
        # close down the client cleanly (in theory...).
        self.done = done

        self.stdio    = None
        self.network  = None
        self.graphicsInterface = None

        self.allReady = False

        self.myId = -1

        self.gameState     = None
        self.unitSelection = UnitSet()

    @property
    def allComponents(self):
        return (self.stdio, self.network, self.graphicsInterface)

    ########################################################################
    # Initialization

    # All wings, report in.

    # Red 2, standing by.
    def stdioReady(self, stdioComponent):
        assert self.stdio is None
        self.stdio = stdioComponent
        assert self.stdio is not None
        self.checkIfAllReady()

    # Red 11, standing by.
    def networkReady(self, networkComponent):
        assert self.network is None
        self.network = networkComponent
        assert self.network is not None
        self.checkIfAllReady()

    # Red 5, standing by.
    def graphicsInterfaceReady(self, graphicsInterface):
        assert self.graphicsInterface is None
        self.graphicsInterface = graphicsInterface
        assert self.graphicsInterface is not None
        self.checkIfAllReady()

    def checkIfAllReady(self):
        for component in self.allComponents:
            if component is None:
                # Not ready
                return
        self.allComponentsReady()

    def allComponentsReady(self):
        # I'm keeping this method around, even though all it does is set a
        # flag, because it seems like we might want to put some final
        # initialization code in here.
        # ... or change the implementation somehow so that we don't have to
        # constantly check self.allReady for the entire lifetime of the client.
        self.allReady = True

        # Request obelisks.
        msg = messages.OrderNew((10, 0))
        self.network.backendMessage(msg.serialize())
        msg = messages.OrderNew((0, 10))
        self.network.backendMessage(msg.serialize())

    ########################################################################
    # Incoming messages

    def stdioMessage(self, message):
        if self.allReady:
            self.handleUserInput(message)
        else:
            # TODO: Buffer messages until ready? Don't just drop them....
            log.warning("Input '{}' ignored; client not initialized yet." \
                .format(message))

    def handleUserInput(self, message):
        assert not message.endswith('\n')

        if message == "dump":
            log.info("Dumping all unit info...")
            for uid in sorted(self.gameState.positions):
                pos = self.gameState.positions[uid]
                isSelected = (uid in self.unitSelection)
                log.info("    {sel:1} {player:>2}: {subId:>3} @ {x:>4}, {y:>4}"
                         .format(sel    = "*" if isSelected else "",
                                 player = unitToPlayer(uid),
                                 subId  = getUnitSubId(uid),
                                 x      = pos[0],
                                 y      = pos[1]))
            log.info("End unit dump.")
        else:
            self.network.backendMessage(message)

    def networkMessage(self, messageStr):
        if self.allReady:
            try:
                message = deserializeMessage(messageStr)
                self.tryToHandleNetworkMessage(message)
            except InvalidMessageError as error:
                illFormedMessage(error, log, sender="server")

            # Regardless of whether we were able to handle it, forward the
            # message on to the graphicsInterface (which -- at least for now --
            # also handles the YourIdIs message).
            self.graphicsInterface.backendMessage(messageStr)
        else:
            # TODO: Buffer messages until ready? Don't just drop them....
            log.warning("Server message '{}' ignored; client not " \
                "initialized yet.".format(messageStr))

    def tryToHandleNetworkMessage(self, message):
        if isinstance(message, messages.YourIdIs):
            if self.myId >= 0:
                raise RuntimeError("ID already set; can't change it now.")
            self.myId = message.playerId
            log.info("Your id is {id}.".format(id=self.myId))

            self.unitSelection = UnitSet([])
        elif isinstance(message, messages.NewObelisk):
            uid = message.unitId
            pos = message.pos
            if uid in self.gameState.positions:
                invalidMessageArgument(message, log, sender="server",
                    reason="uid {} already in use".format(uid))
                return
            self.gameState.positions[uid] = pos
        elif isinstance(message, messages.DeleteObelisk):
            uid = message.unitId
            if uid not in self.gameState.positions:
                invalidMessageArgument(message, log, sender="server",
                    reason="No such uid: {}".format(uid))
                return
            if uid in self.unitSelection:
                self.removeFromSelection(uid)
            del self.gameState.positions[uid]
        elif isinstance(message, messages.SetPos):
            uid = message.unitId
            pos = message.pos
            if uid not in self.gameState.positions:
                invalidMessageArgument(message, log, sender="server",
                    reason="No such uid: {}".format(uid))
                return
            self.gameState.positions[uid] = pos
        elif isinstance(message, messages.GroundInfo):
            # Note: this isn't used in any way right now. I think it's right,
            # but it's definitely possible we transposed something, or worse.
            x, y        = message.pos
            terrainType = message.terrainType
            self.gameState.groundTypes[x][y] = terrainType
        elif isinstance(message, messages.MapSize):
            if self.gameState is not None:
                log.warn("Got multiple map_size messages.")
            self.gameState = GameState(message.size)
        else:
            # It's okay if we aren't able to handle a message from the
            # server; maybe the GraphicsInterface will handle it.
            pass

    def graphicsMessage(self, messageStr):
        message = deserializeMessage(messageStr)
        if isinstance(message, cmessages.Click):
            # TODO: Have Click indicate "left" or "right", rather than just a
            # numerical button.
            # TODO: GraphicsInterface should probably translate Click.pos to a
            # uPos before passing it to the backend.
            if message.button == 1:
                # Left mouse button
                gPos = message.pos
                uPos = graphicsToUnit(gPos)
                chosenUnit = self.getUnitAt(uPos)
                self.clearSelection()
                if chosenUnit is not None:
                    self.addToSelection(chosenUnit)
            elif message.button == 3:
                # Right mouse button
                newMsg = messages.OrderMove(self.unitSelection,
                                            graphicsToUnit(message.pos))
                self.network.backendMessage(newMsg.serialize())
        elif isinstance(message, cmessages.ShiftLClick):
            gPos = message.pos
            uPos = graphicsToUnit(gPos)
            chosenUnit = self.getUnitAt(uPos)
            if chosenUnit is not None:
                self.addToSelection(chosenUnit)
        elif isinstance(message, cmessages.ControlLClick):
            gPos = message.pos
            uPos = graphicsToUnit(gPos)
            chosenUnit = self.getUnitAt(uPos)
            if chosenUnit is not None and chosenUnit in self.unitSelection:
                self.removeFromSelection(chosenUnit)
        elif isinstance(message, cmessages.ShiftRClick):
            newMsg = messages.OrderNew(graphicsToUnit(message.pos))
            self.network.backendMessage(newMsg.serialize())
        elif isinstance(message, cmessages.ControlRClick):
            gPos = message.pos
            uPos = graphicsToUnit(gPos)
            chosenUnit = self.getUnitAt(uPos)
            if chosenUnit is not None:
                if chosenUnit in self.unitSelection:
                    self.removeFromSelection(chosenUnit)
                newMsg = messages.OrderDel(UnitSet([chosenUnit]))
                self.network.backendMessage(newMsg.serialize())
        elif isinstance(message, cmessages.DragBox):
            ux1, uy1 = graphicsToUnit(message.corner1)
            ux2, uy2 = graphicsToUnit(message.corner2)
            xMin = min(ux1, ux2)
            xMax = max(ux1, ux2)
            yMin = min(uy1, uy2)
            yMax = max(uy1, uy2)
            self.clearSelection()
            for (uid, uPos) in self.gameState.positions.iteritems():
                ux, uy = uPos
                # TODO: Add a tolerance based on the size of the unit.
                if unitToPlayer(uid) == self.myId:
                    if xMin <= ux <= xMax and yMin <= uy <= yMax:
                        self.addToSelection(uid)
        elif isinstance(message, cmessages.RequestCenter):
            if not self.unitSelection:
                # FIXME: Move to center of world.
                return
            totalX, totalY = 0, 0
            for unitId in self.unitSelection:
                unitX, unitY = self.gameState.positions[unitId]
                totalX += unitX
                totalY += unitY
            centroid = (totalX // len(self.unitSelection),
                        totalY // len(self.unitSelection))
            # FIXME: Write this.
        elif isinstance(message, cmessages.RequestQuit):
            for component in self.allComponents:
                component.cleanup()
            self.done.callback(None)
        else:
            unhandledInternalMessage(message, log)

    def getUnitAt(self, targetUPos):
        targetX, targetY = targetUPos

        # TODO[#3]: Make this a real constant somewhere. Or take into account
        # the size of the unit? This could get ugly.
        MAX_DISTANCE = 30**2

        nearest = None
        # TODO: Compute the diameter of the world in unit coordinates when we
        # first load the map, have the server send that to the client, save
        # that+1 as UPOS_INFINITY or some such, and use that here instead of a
        # float.
        nearestDistance = float('inf')
        for (uid, uPos) in self.gameState.positions.iteritems():
            if unitToPlayer(uid) != self.myId:
                continue

            x, y = uPos
            distance = (x - targetX)**2 + (y - targetY)**2
            if distance < nearestDistance and distance < MAX_DISTANCE:
                nearest         = uid
                nearestDistance = distance

        return nearest

    def addToSelection(self, unitId):
        self.unitSelection.add(unitId)
        msg = cmessages.MarkUnitSelected(unitId, True)
        self.graphicsInterface.backendMessage(msg.serialize())

    def removeFromSelection(self, unitId):
        self.unitSelection.remove(unitId)
        msg = cmessages.MarkUnitSelected(unitId, False)
        self.graphicsInterface.backendMessage(msg.serialize())

    def clearSelection(self):
        for unitId in self.unitSelection:
            msg = cmessages.MarkUnitSelected(unitId, False)
            self.graphicsInterface.backendMessage(msg.serialize())
        self.unitSelection = UnitSet()

# TODO: Shouldn't these be in the GraphicsInterface if they're going to be
# with a single component?
def unitToGraphics(unitCoords):
    """Convert unit (xu,yu) integers tuples to graphics (xg,yg) float tuples
    """
    return tuple(float(x)/GRAPHICS_SCALE for x in unitCoords)

def graphicsToUnit(graphicsCoords):
    """Convert graphics (xg,yg) float tuples to unit (xu,yu) integers tuples
    """
    return tuple(int(round(x*GRAPHICS_SCALE)) for x in graphicsCoords)

<|endoftext|>


<commit_before>from datetime import datetime

import warnings
from urllib3 import Retry
import requests
from requests import HTTPError

from django.conf import settings
from django.db.models import Q

from agir.lib.data import anciennes_regions, departement_from_zipcode, regions
from agir.lib.utils import generate_token_params
from urllib.parse import urlencode

params = {"access_token": settings.MAILTRAIN_API_KEY}

s = requests.Session()
a = requests.adapters.HTTPAdapter(max_retries=Retry(total=5, backoff_factor=1))
b = requests.adapters.HTTPAdapter(max_retries=Retry(total=5, backoff_factor=1))
s.mount("https://", a)
s.mount("http://", b)

def data_from_person(person, tmp_tags=None):
    data = {}

    is_animateur = Q(is_referent=True) | Q(is_manager=True)
    inscriptions = [
        ("evenements_yes" if person.events.upcoming().count() > 0 else "evenements_no"),
        ("groupe_yes" if person.supportgroups.active().count() > 0 else "groupe_no"),
        (
            "groupe_certifié_yes"
            if person.supportgroups.active()
            .filter(subtypes__label=settings.CERTIFIED_GROUP_SUBTYPE)
            .count()
            > 0
            else "groupe_certifié_no"
        ),
        (
            "groupe_anim_yes"
            if person.memberships.active().filter(is_animateur).count() > 0
            else "groupe_anim_no"
        ),
        (
            "groupe_certifié_anim_yes"
            if person.memberships.active()
            .filter(
                is_animateur
                & Q(supportgroup__subtypes__label=settings.CERTIFIED_GROUP_SUBTYPE)
            )
            .count()
            > 0
            else "groupe_certifié_anim_no"
        ),
        (
            "country-{}".format(
                person.location_country if person.location_country else "FR"
            )
        ),
    ]

    data["FIRST_NAME"] = person.first_name
    data["LAST_NAME"] = person.last_name
    data["MERGE_ZIPCODE"] = person.location_zip
    data["MERGE_INSCRIPTIONS"] = ",".join(inscriptions)
    data["MERGE_LOGIN_QUERY"] = urlencode(generate_token_params(person))
    data["MERGE_TAGS"] = (
        "," + ",".join(t.label for t in person.tags.filter(exported=True)) + ","
    )

    data["MERGE_REGION"] = " ".join([person.region, person.ancienne_region])

    if tmp_tags:
        data["MERGE_TAGS"] = "," + ",".join(tmp_tags) + data["MERGE_TAGS"]

    if len(data["MERGE_TAGS"]) > 255:
        warnings.warn("Tag string is longer than 255 characters for " + person.email)

    return data

def subscribe_email(email, fields=None):
    data = {
        "EMAIL": email,
        "FORCE_SUBSCRIBE": "yes",
        "MERGE_API_UPDATED": datetime.utcnow(),
    }

    if fields is not None:
        data = {**data, **fields}

    response = None

    if settings.MAILTRAIN_DISABLE:
        return True

    try:
        response = s.post(
            settings.MAILTRAIN_HOST + "/api/subscribe/" + settings.MAILTRAIN_LIST_ID,
            data=data,
            params=params,
            json=True,
        )
        response.raise_for_status()
    except HTTPError as err:
        if response.status_code == 400:
            return False

        raise err

    return True

def unsubscribe_email(email):
    data = {"EMAIL": email, "MERGE_API_UPDATED": datetime.utcnow()}

    if settings.MAILTRAIN_DISABLE:
        return True

    s.post(
        settings.MAILTRAIN_HOST + "/api/unsubscribe/" + settings.MAILTRAIN_LIST_ID,
        data=data,
        params=params,
        json=True,
    ).raise_for_status()

def delete_email(email):
    data = {"EMAIL": email}

    response = None

    if settings.MAILTRAIN_DISABLE:
        return True

    try:
        response = s.post(
            settings.MAILTRAIN_HOST + "/api/delete/" + settings.MAILTRAIN_LIST_ID,
            data=data,
            params=params,
            json=True,
        )
        response.raise_for_status()
    except HTTPError as err:
        if response is not None and response.status_code == 404:
            return False

        raise err

    return True

def update_person(person, tmp_tags=None):
    data = data_from_person(person, tmp_tags)
    emails = list(person.emails.all())

    for i, email in enumerate(emails):
        local_part, domain = email.address.rsplit("@", 1)
        if not email.bounced and domain.lower() not in settings.EMAIL_DISABLED_DOMAINS:
            primary_email = email
            other_emails = emails[:i] + emails[i + 1 :]
            break
    else:
        primary_email = None
        other_emails = emails

    if primary_email is not None:
        if person.subscribed:
            subscribe_email(primary_email.address, data)
        else:
            unsubscribe_email(primary_email.address)

    for email in other_emails:
        delete_email(email.address)

def delete_person(person):
    for email in person.emails.all():
        delete_email(email.address)
<commit_msg>Ajoute un champ année de naissance à Mailtrain<commit_after>from datetime import datetime

import warnings
from urllib3 import Retry
import requests
from requests import HTTPError

from django.conf import settings
from django.db.models import Q

from agir.lib.data import anciennes_regions, departement_from_zipcode, regions
from agir.lib.utils import generate_token_params
from urllib.parse import urlencode

params = {"access_token": settings.MAILTRAIN_API_KEY}

s = requests.Session()
a = requests.adapters.HTTPAdapter(max_retries=Retry(total=5, backoff_factor=1))
b = requests.adapters.HTTPAdapter(max_retries=Retry(total=5, backoff_factor=1))
s.mount("https://", a)
s.mount("http://", b)

def data_from_person(person, tmp_tags=None):
    data = {}

    is_animateur = Q(is_referent=True) | Q(is_manager=True)
    inscriptions = [
        ("evenements_yes" if person.events.upcoming().count() > 0 else "evenements_no"),
        ("groupe_yes" if person.supportgroups.active().count() > 0 else "groupe_no"),
        (
            "groupe_certifié_yes"
            if person.supportgroups.active()
            .filter(subtypes__label=settings.CERTIFIED_GROUP_SUBTYPE)
            .count()
            > 0
            else "groupe_certifié_no"
        ),
        (
            "groupe_anim_yes"
            if person.memberships.active().filter(is_animateur).count() > 0
            else "groupe_anim_no"
        ),
        (
            "groupe_certifié_anim_yes"
            if person.memberships.active()
            .filter(
                is_animateur
                & Q(supportgroup__subtypes__label=settings.CERTIFIED_GROUP_SUBTYPE)
            )
            .count()
            > 0
            else "groupe_certifié_anim_no"
        ),
        (
            "country-{}".format(
                person.location_country if person.location_country else "FR"
            )
        ),
    ]

    data["FIRST_NAME"] = person.first_name
    data["LAST_NAME"] = person.last_name
    data["MERGE_ZIPCODE"] = person.location_zip
    data["MERGE_INSCRIPTIONS"] = ",".join(inscriptions)
    data["MERGE_LOGIN_QUERY"] = urlencode(generate_token_params(person))
    data["MERGE_TAGS"] = (
        "," + ",".join(t.label for t in person.tags.filter(exported=True)) + ","
    )

    data["MERGE_REGION"] = " ".join([person.region, person.ancienne_region])
    data["MERGE_ANNEE_DE_NAISSANCE"] = (
        person.date_of_birth.year if person.date_of_birth else None
    )

    if tmp_tags:
        data["MERGE_TAGS"] = "," + ",".join(tmp_tags) + data["MERGE_TAGS"]

    if len(data["MERGE_TAGS"]) > 255:
        warnings.warn("Tag string is longer than 255 characters for " + person.email)

    return data

def subscribe_email(email, fields=None):
    data = {
        "EMAIL": email,
        "FORCE_SUBSCRIBE": "yes",
        "MERGE_API_UPDATED": datetime.utcnow(),
    }

    if fields is not None:
        data = {**data, **fields}

    response = None

    if settings.MAILTRAIN_DISABLE:
        return True

    try:
        response = s.post(
            settings.MAILTRAIN_HOST + "/api/subscribe/" + settings.MAILTRAIN_LIST_ID,
            data=data,
            params=params,
            json=True,
        )
        response.raise_for_status()
    except HTTPError as err:
        if response.status_code == 400:
            return False

        raise err

    return True

def unsubscribe_email(email):
    data = {"EMAIL": email, "MERGE_API_UPDATED": datetime.utcnow()}

    if settings.MAILTRAIN_DISABLE:
        return True

    s.post(
        settings.MAILTRAIN_HOST + "/api/unsubscribe/" + settings.MAILTRAIN_LIST_ID,
        data=data,
        params=params,
        json=True,
    ).raise_for_status()

def delete_email(email):
    data = {"EMAIL": email}

    response = None

    if settings.MAILTRAIN_DISABLE:
        return True

    try:
        response = s.post(
            settings.MAILTRAIN_HOST + "/api/delete/" + settings.MAILTRAIN_LIST_ID,
            data=data,
            params=params,
            json=True,
        )
        response.raise_for_status()
    except HTTPError as err:
        if response is not None and response.status_code == 404:
            return False

        raise err

    return True

def update_person(person, tmp_tags=None):
    data = data_from_person(person, tmp_tags)
    emails = list(person.emails.all())

    for i, email in enumerate(emails):
        local_part, domain = email.address.rsplit("@", 1)
        if not email.bounced and domain.lower() not in settings.EMAIL_DISABLED_DOMAINS:
            primary_email = email
            other_emails = emails[:i] + emails[i + 1 :]
            break
    else:
        primary_email = None
        other_emails = emails

    if primary_email is not None:
        if person.subscribed:
            subscribe_email(primary_email.address, data)
        else:
            unsubscribe_email(primary_email.address)

    for email in other_emails:
        delete_email(email.address)

def delete_person(person):
    for email in person.emails.all():
        delete_email(email.address)
<|endoftext|>


<commit_before>_PROTOCOL_IDENTIFIER_ = "<agutil><__protocol:1.0.0>"
from .src.socket import Socket
from .src.socket import SocketServer
from .src.queuedsocket import QueuedSocket
<commit_msg>Dumb Protocol typo<commit_after>_PROTOCOL_IDENTIFIER_ = "<agutil><__protocol__:1.0.0>"
from .src.socket import Socket
from .src.socket import SocketServer
from .src.queuedsocket import QueuedSocket
<|endoftext|>


<commit_before>import asyncio
import logging
import os
import signal
import socket
import threading
from textwrap import wrap

from terminaltables import AsciiTable

from .utils import (_format_stack, cancel_task, task_by_id, console_proxy,
                    init_console_server)

__all__ = ('Monitor', 'start_monitor')
log = logging.getLogger(__name__)

MONITOR_HOST = '127.0.0.1'
MONITOR_PORT = 50101
CONSOLE_PORT = 50102

def start_monitor(loop, *, host=MONITOR_HOST, port=MONITOR_PORT,
                  console_port=CONSOLE_PORT, console_enabled=True,
                  locals=None):

    m = Monitor(loop, host=host, port=port, console_port=console_port,
                console_enabled=console_enabled, locals=locals)
    m.start()
    return m

class Monitor:
    def __init__(self, loop, *, host=MONITOR_HOST, port=MONITOR_PORT,
                 console_port=CONSOLE_PORT, console_enabled=True,
                 locals=None):
        self._loop = loop or asyncio.get_event_loop()
        self._host = host
        self._port = port
        self._console_port = console_port
        self._console_enabled = console_enabled
        self._locals = locals

        log.info('Starting aiomonitor at %s:%d', host, port)

        self._ui_thread = threading.Thread(target=self._server, args=(),
                                           daemon=True)
        self._closing = threading.Event()
        self._closed = False
        self._started = False
        self._console_future = None

    def __repr__(self):
        name = self.__class__.__name__
        return '<{name}: {host}:{port}>'.format(
            name=name, host=self._host, port=self._port)

    def start(self):
        assert not self._closed
        assert not self._started

        self._started = True
        h, p = self._host, self._port
        self._ui_thread.start()
        if self._console_enabled:
            log.info('Starting console at %s:%d', h, p)
            self._console_future = init_console_server(
                self._host, self._console_port, self._locals, self._loop)

    @property
    def closed(self):
        return self._closed

    def __enter__(self):
        if not self._started:
            self.start()
        return self

    def __exit__(self, type, value, traceback):
        self.close()

    def close(self):
        if not self._closed:
            self._closing.set()
            self._ui_thread.join()
            if self._console_future:
                self._console_future.result(timeout=15)
            self._closed = True

    def _server(self):
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        try:
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
        except AttributeError:
            pass

        # set the timeout to prevent the server loop from
        # blocking indefinitaly on sock.accept()
        sock.settimeout(0.5)
        sock.bind((self._host, self._port))
        sock.listen(1)
        with sock:
            while not self._closing.is_set():
                try:
                    client, addr = sock.accept()
                    with client:
                        sout = client.makefile('w', encoding='utf-8')
                        sin = client.makefile('r', encoding='utf-8')
                        self._interactive_loop(sout, sin)
                except (socket.timeout, OSError):
                    continue

    def _monitor_commans(self, sin, sout, resp):
        if not resp or resp.startswith(('q', 'exit')):
            self._command_exit(sout)
            return

        elif resp.startswith('p'):
            self._command_ps(sout)

        elif resp.startswith('cancel'):
            _, taskid_s = resp.split()
            self._command_cancel(sout, int(taskid_s))

        elif resp.startswith('signal'):
            _, signame = resp.split()
            self._command_signal(sout, signame)

        elif resp.startswith('w'):
            _, taskid_s = resp.split()
            self._command_where(sout, int(taskid_s))

        elif resp.startswith('h'):
            self._command_help(sout)

        elif resp.startswith('console'):
            self._command_console(sin, sout)

        else:
            sout.write('Unknown command. Type help.\n')

    def _interactive_loop(self, sout, sin):
        """Main interactive loop of the monitor
        """
        (sout.write('\nAsyncio Monitor: %d tasks running\n' %
                    len(asyncio.Task.all_tasks(loop=self._loop))))
        sout.write('Type help for commands\n')
        while not self._closing.is_set():
            sout.write('monitor >>> ')
            sout.flush()
            try:
                resp = sin.readline()
                self._monitor_commans(sin, sout, resp)
            except Exception as e:
                sout.write('Bad command. %s\n' % e)
                sout.flush()

    def _command_help(self, sout):
        sout.write(
         """Commands:
             ps               : Show task table
             where taskid     : Show stack frames for a task
             cancel taskid    : Cancel an indicated task
             signal signame   : Send a Unix signal
             console          : Switch to async Python REPL
             quit             : Leave the monitor
            """)
        sout.write('\n')

    def _command_ps(self, sout):
        headers = ('Task ID', 'State', 'Task')
        table_data = [headers]
        for task in sorted(asyncio.Task.all_tasks(loop=self._loop), key=id):
            taskid = id(task)
            if task:
                t = '\n'.join(wrap(str(task), 80))
                table_data.append((taskid, task._state, t))
        table = AsciiTable(table_data)
        sout.write(table.table)
        sout.write('\n')
        sout.flush()

    def _command_where(self, sout, taskid):
        task = task_by_id(taskid, self._loop)
        if task:
            sout.write(_format_stack(task))
            sout.write('\n')
        else:
            sout.write('No task %d\n' % taskid)

    def _command_signal(self, sout, signame):
        if hasattr(signal, signame):
            os.kill(os.getpid(), getattr(signal, signame))
        else:
            sout.write('Unknown signal %s\n' % signame)

    def _command_cancel(self, sout, taskid):
        task = task_by_id(taskid, self._loop)
        if task:
            fut = asyncio.run_coroutine_threadsafe(
                cancel_task(task), loop=self._loop)
            fut.result(timeout=3)
            sout.write('Cancel task %d\n' % taskid)
        else:
            sout.write('No task %d\n' % taskid)

    def _command_exit(self, sout):
        sout.write('Leaving monitor. Hit Ctrl-C to exit\n')
        sout.flush()

    def _command_console(self, sin, sout):
        if not self._console_enabled:
            sout.write('Python console disabled for this sessiong\n')
            sout.flush()

        self._console_future.result()
        console_proxy(sin, sout, self._host, self._console_port)
<commit_msg>add more typing<commit_after>import asyncio
import logging
import os
import signal
import socket
import threading
from textwrap import wrap
from typing import IO, Dict, Any, Optional
from concurrent.futures import Future  # noqa

from terminaltables import AsciiTable

from .utils import (_format_stack, cancel_task, task_by_id, console_proxy,
                    init_console_server)
from .mypy_types import Loop, OptLocals

__all__ = ('Monitor', 'start_monitor')
log = logging.getLogger(__name__)

MONITOR_HOST = '127.0.0.1'
MONITOR_PORT = 50101
CONSOLE_PORT = 50102

def start_monitor(loop: Loop, *,
                  host: str=MONITOR_HOST,
                  port: int=MONITOR_PORT,
                  console_port: int=CONSOLE_PORT,
                  console_enabled: bool=True,
                  locals=OptLocals) -> 'Monitor':

    m = Monitor(loop, host=host, port=port, console_port=console_port,
                console_enabled=console_enabled, locals=locals)
    m.start()
    return m

class Monitor:
    def __init__(self,
                 loop: asyncio.AbstractEventLoop, *,
                 host: str=MONITOR_HOST,
                 port: int=MONITOR_PORT,
                 console_port: int=CONSOLE_PORT,
                 console_enabled: bool=True,
                 locals: OptLocals=None) -> None:
        self._loop = loop or asyncio.get_event_loop()
        self._host = host
        self._port = port
        self._console_port = console_port
        self._console_enabled = console_enabled
        self._locals = locals

        log.info('Starting aiomonitor at %s:%d', host, port)

        self._ui_thread = threading.Thread(target=self._server, args=(),
                                           daemon=True)
        self._closing = threading.Event()
        self._closed = False
        self._started = False
        self._console_future: Optional[Future[Any]] = None

    def __repr__(self) -> str:
        name = self.__class__.__name__
        return '<{name}: {host}:{port}>'.format(
            name=name, host=self._host, port=self._port)

    def start(self) -> None:
        assert not self._closed
        assert not self._started

        self._started = True
        h, p = self._host, self._port
        self._ui_thread.start()
        if self._console_enabled:
            log.info('Starting console at %s:%d', h, p)
            self._console_future = init_console_server(
                self._host, self._console_port, self._locals, self._loop)

    @property
    def closed(self) -> bool:
        return self._closed

    def __enter__(self) -> 'Monitor':
        if not self._started:
            self.start()
        return self

    def __exit__(self, type, value, traceback) -> None:
        self.close()

    def close(self) -> None:
        if not self._closed:
            self._closing.set()
            self._ui_thread.join()
            if self._console_future is not None:
                self._console_future.result(timeout=15)
            self._closed = True

    def _server(self) -> None:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        try:
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
        except AttributeError:
            pass

        # set the timeout to prevent the server loop from
        # blocking indefinitaly on sock.accept()
        sock.settimeout(0.5)
        sock.bind((self._host, self._port))
        sock.listen(1)
        with sock:
            while not self._closing.is_set():
                try:
                    client, addr = sock.accept()
                    with client:
                        sout = client.makefile('w', encoding='utf-8')
                        sin = client.makefile('r', encoding='utf-8')
                        self._interactive_loop(sout, sin)
                except (socket.timeout, OSError):
                    continue

    def _monitor_commans(self, sin: IO[str], sout: IO[str], resp: str) -> None:
        if not resp or resp.startswith(('q', 'exit')):
            self._command_exit(sout)
            return

        elif resp.startswith('p'):
            self._command_ps(sout)

        elif resp.startswith('cancel'):
            _, taskid_s = resp.split()
            self._command_cancel(sout, int(taskid_s))

        elif resp.startswith('signal'):
            _, signame = resp.split()
            self._command_signal(sout, signame)

        elif resp.startswith('w'):
            _, taskid_s = resp.split()
            self._command_where(sout, int(taskid_s))

        elif resp.startswith('h'):
            self._command_help(sout)

        elif resp.startswith('console'):
            self._command_console(sin, sout)

        else:
            sout.write('Unknown command. Type help.\n')

    def _interactive_loop(self, sout: IO[str], sin: IO[str]) -> None:
        """Main interactive loop of the monitor
        """
        (sout.write('\nAsyncio Monitor: %d tasks running\n' %
                    len(asyncio.Task.all_tasks(loop=self._loop))))
        sout.write('Type help for commands\n')
        while not self._closing.is_set():
            sout.write('monitor >>> ')
            sout.flush()
            try:
                resp = sin.readline()
                self._monitor_commans(sin, sout, resp)
            except Exception as e:
                sout.write('Bad command. %s\n' % e)
                sout.flush()

    def _command_help(self, sout: IO[str]):
        sout.write(
         """Commands:
             ps               : Show task table
             where taskid     : Show stack frames for a task
             cancel taskid    : Cancel an indicated task
             signal signame   : Send a Unix signal
             console          : Switch to async Python REPL
             quit             : Leave the monitor
            """)
        sout.write('\n')

    def _command_ps(self, sout: IO[str]) -> None:
        headers = ('Task ID', 'State', 'Task')
        table_data = [headers]
        for task in sorted(asyncio.Task.all_tasks(loop=self._loop), key=id):
            taskid = str(id(task))
            if task:
                t = '\n'.join(wrap(str(task), 80))
                table_data.append((taskid, task._state, t))
        table = AsciiTable(table_data)
        sout.write(table.table)
        sout.write('\n')
        sout.flush()

    def _command_where(self, sout: IO[str], taskid: int) -> None:
        task = task_by_id(taskid, self._loop)
        if task:
            sout.write(_format_stack(task))
            sout.write('\n')
        else:
            sout.write('No task %d\n' % taskid)

    def _command_signal(self, sout: IO[str], signame: str) -> None:
        if hasattr(signal, signame):
            os.kill(os.getpid(), getattr(signal, signame))
        else:
            sout.write('Unknown signal %s\n' % signame)

    def _command_cancel(self, sout: IO[str], taskid) -> None:
        task = task_by_id(taskid, self._loop)
        if task:
            fut = asyncio.run_coroutine_threadsafe(
                cancel_task(task), loop=self._loop)
            fut.result(timeout=3)
            sout.write('Cancel task %d\n' % taskid)
        else:
            sout.write('No task %d\n' % taskid)

    def _command_exit(self, sout: IO[str]) -> None:
        sout.write('Leaving monitor. Hit Ctrl-C to exit\n')
        sout.flush()

    def _command_console(self, sin: IO[str], sout: IO[str]) -> None:
        if not self._console_enabled:
            sout.write('Python console disabled for this sessiong\n')
            sout.flush()

        if self._console_future is not None:
            self._console_future.result()
        console_proxy(sin, sout, self._host, self._console_port)
<|endoftext|>


<commit_before>"""
Same structure as aiohttp.web.Application
"""
import sys
import asyncio
import logging
import aiodns
from contextlib import suppress
import traceback

__all__ = ['Application']

from collections import MutableMapping

from . import __version__
from .dialog import Dialog
from .dialplan import Dialplan
from .protocol import UDP, TCP, WS
from .peers import UDPConnector, TCPConnector, WSConnector
from .contact import Contact

LOG = logging.getLogger(__name__)

DEFAULTS = {
    'user_agent': 'Python/{0[0]}.{0[1]}.{0[2]} aiosip/{1}'.format(sys.version_info, __version__),
    'override_contact_host': None,
    'dialog_closing_delay': 30
}

class Application(MutableMapping):

    def __init__(self, *,
                 loop=None,
                 dialog_factory=Dialog,
                 middleware=(),
                 defaults=None,
                 debug=False,
                 dialplan=None,
                 dns_resolver=aiodns.DNSResolver()
                 ):

        if loop is None:
            loop = asyncio.get_event_loop()

        if defaults:
            self.defaults = {**DEFAULTS, **defaults}
        else:
            self.defaults = DEFAULTS

        self.debug = debug
        self.dns = dns_resolver
        self._finish_callbacks = []
        self._state = {}
        self._connectors = {UDP: UDPConnector(self, loop=loop),
                            TCP: TCPConnector(self, loop=loop),
                            WS: WSConnector(self, loop=loop)}
        self._middleware = middleware
        self._tasks = list()

        self.dialplan = dialplan or Dialplan()
        self.dialog_factory = dialog_factory
        self.loop = loop

    @property
    def peers(self):
        for connector in self._connectors.values():
            yield from connector._peers.values()

    @property
    def dialogs(self):
        for peer in self.peers:
            yield from peer._dialogs.values()

    async def connect(self, remote_addr, protocol=UDP, *, local_addr=None):
        connector = self._connectors[protocol]
        peer = await connector.create_peer(remote_addr, local_addr=local_addr)
        return peer

    async def run(self, *, local_addr=None, protocol=UDP, sock=None):

        if not local_addr and not sock:
            raise ValueError('One of "local_addr", "sock" is mandatory')
        elif local_addr and sock:
            raise ValueError('local_addr, sock are mutually exclusive')
        elif not local_addr:
            local_addr = None, None

        connector = self._connectors[protocol]
        server = await connector.create_server(local_addr, sock)
        return server

    async def _call_route(self, peer, route, msg):
        for middleware_factory in reversed(self._middleware):
            route = await middleware_factory(route)

        call_id = msg.headers['Call-ID']

        class Request:
            def __init__(self):
                self.dialog = None

            def _create_dialog(self):
                if not self.dialog:
                    self.dialog = peer._create_dialog(
                        method=msg.method,
                        from_details=Contact.from_header(msg.headers['To']),
                        to_details=Contact.from_header(msg.headers['From']),
                        call_id=call_id
                    )
                return self.dialog

            async def prepare(self, status_code, *args, **kwargs):
                dialog = self._create_dialog()

                await dialog.reply(msg, status_code, *args, **kwargs)
                if status_code >= 300:
                    dialog.close()
                    return None

                return dialog

        request = Request()
        await route(request, msg)

    async def _dispatch(self, protocol, msg, addr):
        connector = self._connectors[type(protocol)]
        peer = await connector.get_peer(protocol, addr)
        key = msg.headers['Call-ID']

        dialog = peer._dialogs.get(key)
        if dialog:
            await dialog.receive_message(msg)
            return

        router = await self.dialplan.resolve(
            username=msg.from_details['uri']['user'],
            protocol=peer.protocol,
            local_addr=peer.local_addr,
            remote_addr=peer.peer_addr
        )

        async def reply(*args, **kwargs):
            dialog = peer._create_dialog(
                method=msg.method,
                from_details=Contact.from_header(msg.headers['To']),
                to_details=Contact.from_header(msg.headers['From']),
                call_id=key
            )

            await dialog.reply(*args, **kwargs)
            await dialog.close(fast=True)

        if not router:
            await reply(msg, status_code=501)
            return

        route = router.get(msg.method)
        if not route:
            await reply(msg, status_code=501)
            return

        try:
            t = asyncio.ensure_future(self._call_route(peer, route, msg))
            self._tasks.append(t)
            await t
        except asyncio.CancelledError:
            pass
        except Exception as e:
            LOG.exception(e)
            payload = None
            if self.debug:
                with suppress(Exception):
                    payload = traceback.format_exc()
            await reply(msg, status_code=500, payload=payload)

    def _connection_lost(self, protocol):
        connector = self._connectors[type(protocol)]
        connector.connection_lost(protocol)
        # for task in self._tasks:
        #     task.cancel()

    @asyncio.coroutine
    def finish(self):
        callbacks = self._finish_callbacks
        self._finish_callbacks = []

        for (cb, args, kwargs) in callbacks:
            try:
                res = cb(self, *args, **kwargs)
                if (asyncio.iscoroutine(res) or
                        isinstance(res, asyncio.Future)):
                    yield from res
            except Exception as exc:
                self.loop.call_exception_handler({
                    'message': "Error in finish callback",
                    'exception': exc,
                    'application': self,
                })

    def register_on_finish(self, func, *args, **kwargs):
        self._finish_callbacks.insert(0, (func, args, kwargs))

    async def close(self):
        for connector in self._connectors.values():
            await connector.close()
        for task in self._tasks:
            task.cancel()

    # def __repr__(self):
    #     return "<Application>"

    # MutableMapping API
    def __eq__(self, other):
        return self is other

    def __getitem__(self, key):
        return self._state[key]

    def __setitem__(self, key, value):
        self._state[key] = value

    def __delitem__(self, key):
        del self._state[key]

    def __len__(self):
        return len(self._state)

    def __iter__(self):
        return iter(self._state)
<commit_msg>Make sure dialog requests have access to the application<commit_after>"""
Same structure as aiohttp.web.Application
"""
import sys
import asyncio
import logging
import aiodns
from contextlib import suppress
import traceback

__all__ = ['Application']

from collections import MutableMapping

from . import __version__
from .dialog import Dialog
from .dialplan import Dialplan
from .protocol import UDP, TCP, WS
from .peers import UDPConnector, TCPConnector, WSConnector
from .contact import Contact

LOG = logging.getLogger(__name__)

DEFAULTS = {
    'user_agent': 'Python/{0[0]}.{0[1]}.{0[2]} aiosip/{1}'.format(sys.version_info, __version__),
    'override_contact_host': None,
    'dialog_closing_delay': 30
}

class Application(MutableMapping):

    def __init__(self, *,
                 loop=None,
                 dialog_factory=Dialog,
                 middleware=(),
                 defaults=None,
                 debug=False,
                 dialplan=None,
                 dns_resolver=aiodns.DNSResolver()
                 ):

        if loop is None:
            loop = asyncio.get_event_loop()

        if defaults:
            self.defaults = {**DEFAULTS, **defaults}
        else:
            self.defaults = DEFAULTS

        self.debug = debug
        self.dns = dns_resolver
        self._finish_callbacks = []
        self._state = {}
        self._connectors = {UDP: UDPConnector(self, loop=loop),
                            TCP: TCPConnector(self, loop=loop),
                            WS: WSConnector(self, loop=loop)}
        self._middleware = middleware
        self._tasks = list()

        self.dialplan = dialplan or Dialplan()
        self.dialog_factory = dialog_factory
        self.loop = loop

    @property
    def peers(self):
        for connector in self._connectors.values():
            yield from connector._peers.values()

    @property
    def dialogs(self):
        for peer in self.peers:
            yield from peer._dialogs.values()

    async def connect(self, remote_addr, protocol=UDP, *, local_addr=None):
        connector = self._connectors[protocol]
        peer = await connector.create_peer(remote_addr, local_addr=local_addr)
        return peer

    async def run(self, *, local_addr=None, protocol=UDP, sock=None):

        if not local_addr and not sock:
            raise ValueError('One of "local_addr", "sock" is mandatory')
        elif local_addr and sock:
            raise ValueError('local_addr, sock are mutually exclusive')
        elif not local_addr:
            local_addr = None, None

        connector = self._connectors[protocol]
        server = await connector.create_server(local_addr, sock)
        return server

    async def _call_route(self, peer, route, msg):
        for middleware_factory in reversed(self._middleware):
            route = await middleware_factory(route)

        app = self
        call_id = msg.headers['Call-ID']

        # TODO: refactor
        class Request:
            def __init__(self):
                self.app = app
                self.dialog = None

            def _create_dialog(self):
                if not self.dialog:
                    self.dialog = peer._create_dialog(
                        method=msg.method,
                        from_details=Contact.from_header(msg.headers['To']),
                        to_details=Contact.from_header(msg.headers['From']),
                        call_id=call_id
                    )
                return self.dialog

            async def prepare(self, status_code, *args, **kwargs):
                dialog = self._create_dialog()

                await dialog.reply(msg, status_code, *args, **kwargs)
                if status_code >= 300:
                    dialog.close()
                    return None

                return dialog

        request = Request()
        await route(request, msg)

    async def _dispatch(self, protocol, msg, addr):
        connector = self._connectors[type(protocol)]
        peer = await connector.get_peer(protocol, addr)
        key = msg.headers['Call-ID']

        dialog = peer._dialogs.get(key)
        if dialog:
            await dialog.receive_message(msg)
            return

        router = await self.dialplan.resolve(
            username=msg.from_details['uri']['user'],
            protocol=peer.protocol,
            local_addr=peer.local_addr,
            remote_addr=peer.peer_addr
        )

        async def reply(*args, **kwargs):
            dialog = peer._create_dialog(
                method=msg.method,
                from_details=Contact.from_header(msg.headers['To']),
                to_details=Contact.from_header(msg.headers['From']),
                call_id=key
            )

            await dialog.reply(*args, **kwargs)
            await dialog.close(fast=True)

        if not router:
            await reply(msg, status_code=501)
            return

        route = router.get(msg.method)
        if not route:
            await reply(msg, status_code=501)
            return

        try:
            t = asyncio.ensure_future(self._call_route(peer, route, msg))
            self._tasks.append(t)
            await t
        except asyncio.CancelledError:
            pass
        except Exception as e:
            LOG.exception(e)
            payload = None
            if self.debug:
                with suppress(Exception):
                    payload = traceback.format_exc()
            await reply(msg, status_code=500, payload=payload)

    def _connection_lost(self, protocol):
        connector = self._connectors[type(protocol)]
        connector.connection_lost(protocol)
        # for task in self._tasks:
        #     task.cancel()

    @asyncio.coroutine
    def finish(self):
        callbacks = self._finish_callbacks
        self._finish_callbacks = []

        for (cb, args, kwargs) in callbacks:
            try:
                res = cb(self, *args, **kwargs)
                if (asyncio.iscoroutine(res) or
                        isinstance(res, asyncio.Future)):
                    yield from res
            except Exception as exc:
                self.loop.call_exception_handler({
                    'message': "Error in finish callback",
                    'exception': exc,
                    'application': self,
                })

    def register_on_finish(self, func, *args, **kwargs):
        self._finish_callbacks.insert(0, (func, args, kwargs))

    async def close(self):
        for connector in self._connectors.values():
            await connector.close()
        for task in self._tasks:
            task.cancel()

    # def __repr__(self):
    #     return "<Application>"

    # MutableMapping API
    def __eq__(self, other):
        return self is other

    def __getitem__(self, key):
        return self._state[key]

    def __setitem__(self, key, value):
        self._state[key] = value

    def __delitem__(self, key):
        del self._state[key]

    def __len__(self):
        return len(self._state)

    def __iter__(self):
        return iter(self._state)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
from __future__ import unicode_literals, absolute_import, division

__version_info__ = (0, 3, 1)
__version__ = '.'.join(map(str, __version_info__))
<commit_msg>Bump version.<commit_after># -*- coding: utf-8 -*-
from __future__ import unicode_literals, absolute_import, division

__version_info__ = (0, 3, 2)
__version__ = '.'.join(map(str, __version_info__))
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
from __future__ import unicode_literals, absolute_import, division

__version_info__ = (0, 3, 10)
__version__ = '.'.join(map(str, __version_info__))
<commit_msg>Bump version.<commit_after># -*- coding: utf-8 -*-
from __future__ import unicode_literals, absolute_import, division

__version_info__ = (0, 3, 11)
__version__ = '.'.join(map(str, __version_info__))
<|endoftext|>


<commit_before>from flask import current_app, g, jsonify, request
from flask_cors import cross_origin

from alerta.app import qb
from alerta.auth.decorators import permission
from alerta.auth.utils import not_authorized
from alerta.exceptions import ApiError
from alerta.models.user import User
from alerta.utils.response import jsonp

from . import api

@api.route('/user', methods=['OPTIONS', 'POST'])
@cross_origin()
@permission('admin:users')
@jsonp
def create_user():
    try:
        user = User.parse(request.json)
    except Exception as e:
        raise ApiError(str(e), 400)

    # check allowed domain
    if not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[user.domain]):
        raise ApiError('unauthorized domain', 403)

    if User.find_by_email(email=user.email):
        raise ApiError('username already exists', 409)

    try:
        user = user.create()
    except Exception as e:
        ApiError(str(e), 500)

    # if email verification is enforced, send confirmation email
    if current_app.config['EMAIL_VERIFICATION'] and not user.email_verified:
        user.send_confirmation()

    if user:
        return jsonify(status='ok', id=user.id, data=user.serialize), 201
    else:
        raise ApiError('create user failed', 500)

@api.route('/user/<user_id>', methods=['OPTIONS', 'PUT'])
@cross_origin()
@permission('admin:users')
@jsonp
def update_user(user_id):
    if not request.json:
        raise ApiError('nothing to change', 400)

    user = User.find_by_id(user_id)

    if not user:
        raise ApiError('not found', 404)

    if 'email' in request.json and User.find_by_email(request.json['email']):
        raise ApiError('user with email already exists', 409)

    if user.update(**request.json):
        return jsonify(status='ok')
    else:
        raise ApiError('failed to update user', 500)

@api.route('/user/me', methods=['OPTIONS', 'PUT'])
@cross_origin()
@permission('write:users')
@jsonp
def update_me():
    if not request.json:
        raise ApiError('nothing to change', 400)

    if 'roles' in request.json:
        raise ApiError('not allowed to update roles', 400)
    if 'email_verified' in request.json:
        raise ApiError('not allowed to set email verified', 400)

    user = User.find_by_email(g.user)

    if not user:
        raise ApiError('not found', 404)

    if 'email' in request.json and User.find_by_email(request.json['email']):
        raise ApiError('user with email already exists', 409)

    if user.update(**request.json):
        return jsonify(status='ok')
    else:
        raise ApiError('failed to update user', 500)

@api.route('/user/<user_id>/attributes', methods=['OPTIONS', 'PUT'])
@cross_origin()
@permission('admin:users')
@jsonp
def update_user_attributes(user_id):
    if not request.json.get('attributes', None):
        raise ApiError("must supply 'attributes' as json data", 400)

    user = User.find_by_id(user_id)

    if not user:
        raise ApiError('not found', 404)

    if user.update_attributes(request.json['attributes']):
        return jsonify(status='ok')
    else:
        raise ApiError('failed to update attributes', 500)

@api.route('/user/me/attributes', methods=['OPTIONS', 'PUT'])
@cross_origin()
@permission('write:users')
@jsonp
def update_me_attributes():
    if not request.json.get('attributes', None):
        raise ApiError("must supply 'attributes' as json data", 400)

    user = User.find_by_email(g.user)

    if not user:
        raise ApiError('not found', 404)

    if user.update_attributes(request.json['attributes']):
        return jsonify(status='ok')
    else:
        raise ApiError('failed to update attributes', 500)

@api.route('/users', methods=['OPTIONS', 'GET'])
@cross_origin()
@permission('admin:users')
@jsonp
def search_users():
    query = qb.from_params(request.args)
    users = User.find_all(query)

    if users:
        return jsonify(
            status='ok',
            users=[user.serialize for user in users],
            domains=current_app.config['ALLOWED_EMAIL_DOMAINS'],
            total=len(users)
        )
    else:
        return jsonify(
            status='ok',
            message='not found',
            users=[],
            domains=current_app.config['ALLOWED_EMAIL_DOMAINS'],
            total=0
        )

@api.route('/user/<user_id>', methods=['OPTIONS', 'DELETE'])
@cross_origin()
@permission('admin:users')
@jsonp
def delete_user(user_id):
    user = User.find_by_id(user_id)

    if not user:
        raise ApiError('not found', 404)

    if user.delete():
        return jsonify(status='ok')
    else:
        raise ApiError('failed to delete user', 500)
<commit_msg>Fix create user response (#725)<commit_after>from flask import current_app, g, jsonify, request
from flask_cors import cross_origin

from alerta.app import qb
from alerta.auth.decorators import permission
from alerta.auth.utils import not_authorized
from alerta.exceptions import ApiError
from alerta.models.user import User
from alerta.utils.response import jsonp

from . import api

@api.route('/user', methods=['OPTIONS', 'POST'])
@cross_origin()
@permission('admin:users')
@jsonp
def create_user():
    try:
        user = User.parse(request.json)
    except Exception as e:
        raise ApiError(str(e), 400)

    # check allowed domain
    if not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[user.domain]):
        raise ApiError('unauthorized domain', 403)

    if User.find_by_email(email=user.email):
        raise ApiError('username already exists', 409)

    try:
        user = user.create()
    except Exception as e:
        ApiError(str(e), 500)

    # if email verification is enforced, send confirmation email
    if current_app.config['EMAIL_VERIFICATION'] and not user.email_verified:
        user.send_confirmation()

    if user:
        return jsonify(status='ok', id=user.id, user=user.serialize), 201
    else:
        raise ApiError('create user failed', 500)

@api.route('/user/<user_id>', methods=['OPTIONS', 'PUT'])
@cross_origin()
@permission('admin:users')
@jsonp
def update_user(user_id):
    if not request.json:
        raise ApiError('nothing to change', 400)

    user = User.find_by_id(user_id)

    if not user:
        raise ApiError('not found', 404)

    if 'email' in request.json and User.find_by_email(request.json['email']):
        raise ApiError('user with email already exists', 409)

    if user.update(**request.json):
        return jsonify(status='ok')
    else:
        raise ApiError('failed to update user', 500)

@api.route('/user/me', methods=['OPTIONS', 'PUT'])
@cross_origin()
@permission('write:users')
@jsonp
def update_me():
    if not request.json:
        raise ApiError('nothing to change', 400)

    if 'roles' in request.json:
        raise ApiError('not allowed to update roles', 400)
    if 'email_verified' in request.json:
        raise ApiError('not allowed to set email verified', 400)

    user = User.find_by_email(g.user)

    if not user:
        raise ApiError('not found', 404)

    if 'email' in request.json and User.find_by_email(request.json['email']):
        raise ApiError('user with email already exists', 409)

    if user.update(**request.json):
        return jsonify(status='ok')
    else:
        raise ApiError('failed to update user', 500)

@api.route('/user/<user_id>/attributes', methods=['OPTIONS', 'PUT'])
@cross_origin()
@permission('admin:users')
@jsonp
def update_user_attributes(user_id):
    if not request.json.get('attributes', None):
        raise ApiError("must supply 'attributes' as json data", 400)

    user = User.find_by_id(user_id)

    if not user:
        raise ApiError('not found', 404)

    if user.update_attributes(request.json['attributes']):
        return jsonify(status='ok')
    else:
        raise ApiError('failed to update attributes', 500)

@api.route('/user/me/attributes', methods=['OPTIONS', 'PUT'])
@cross_origin()
@permission('write:users')
@jsonp
def update_me_attributes():
    if not request.json.get('attributes', None):
        raise ApiError("must supply 'attributes' as json data", 400)

    user = User.find_by_email(g.user)

    if not user:
        raise ApiError('not found', 404)

    if user.update_attributes(request.json['attributes']):
        return jsonify(status='ok')
    else:
        raise ApiError('failed to update attributes', 500)

@api.route('/users', methods=['OPTIONS', 'GET'])
@cross_origin()
@permission('admin:users')
@jsonp
def search_users():
    query = qb.from_params(request.args)
    users = User.find_all(query)

    if users:
        return jsonify(
            status='ok',
            users=[user.serialize for user in users],
            domains=current_app.config['ALLOWED_EMAIL_DOMAINS'],
            total=len(users)
        )
    else:
        return jsonify(
            status='ok',
            message='not found',
            users=[],
            domains=current_app.config['ALLOWED_EMAIL_DOMAINS'],
            total=0
        )

@api.route('/user/<user_id>', methods=['OPTIONS', 'DELETE'])
@cross_origin()
@permission('admin:users')
@jsonp
def delete_user(user_id):
    user = User.find_by_id(user_id)

    if not user:
        raise ApiError('not found', 404)

    if user.delete():
        return jsonify(status='ok')
    else:
        raise ApiError('failed to delete user', 500)
<|endoftext|>


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

import datetime
import humanize

import discord
from discord.ext import commands

from ..tools import Cog
DATEFORMAT = "%a, %e %b %Y %H:%M:%S (%-I:%M %p)"

class Utils(Cog):
    """The description for Utils goes here."""
    @commands.command(aliases=['p'])
    async def ping(self, ctx):
        """You know it"""
        start = await ctx.send("Po..")
        assert isinstance(start, discord.Message)
        a = start.created_at
        now = datetime.datetime.utcnow()
        ping = now - a

        await start.edit(content=f"Pong! WS: {ctx.bot.latency * 1000:.2f} ms, rest: {ping.microseconds / 1000:.2f} ms")

    @commands.command()
    async def time(self, ctx):
        """Displays the time in alaska"""
        time = datetime.datetime.now()
        await ctx.send(f'the time in alaska is {time.strftime(DATEFORMAT)}')

    @commands.command()
    async def quote(self, ctx, msg, channel: discord.TextChannel=None):
        """Quotes a message. msg can be message ID or the output of shift clicking the 'copy id' button in the UI."""
        if '-' in msg:
            try:
                channel, msg = [int(i) for i in msg.split('-')]
                channel = self.bot.get_channel(channel)
            except ValueError or discord.errors.NotFound:
                raise commands.BadArgument("your input was not a message id")
        try:
            if channel is None:
                msg = await ctx.channel.get_message(msg)
            else:
                msg = await channel.get_message(msg)
        except (discord.errors.NotFound, discord.errors.HTTPException):
            return await ctx.send("cant find that message. \N{SLIGHTLY FROWNING FACE}")
        assert isinstance(msg, discord.Message)

        if channel is not None and channel.nsfw and not ctx.channel.nsfw:
            return await ctx.send("Cant send message from NSFW channel in SFW channel")

        ret = discord.Embed(color=discord.Color.blurple())

        if msg.content is "" and msg.attachments == []:
            embed = msg.embeds[0]
            try:
                assert isinstance(embed, discord.Embed)
            except AssertionError:
                return
            ret = embed
        else:
            ret.description = msg.content

            # handle images, and images attached with URLs
            try:
                ret.set_image(url=msg.attachments[0].url)
            except IndexError:
                try:
                    ret.set_image(url=msg.embeds[0].thumbnail.url)
                except IndexError:
                    pass

        ret.timestamp = msg.created_at
        ret.set_author(name=msg.author.name, icon_url=msg.author.avatar_url)
        age = ctx.message.created_at - ret.timestamp
        age = humanize.naturaldelta(age)

        ret.set_footer(text=f"Quoted message is {age} old, from ")

        await ctx.send(embed=ret)

    @commands.command()
    async def difference(self, ctx, object_one: int, object_two: int=None):
        """compares the creation of two discord IDs. interprets a missing second arg as the current ID."""
        one = discord.utils.snowflake_time(object_one)
        if object_two is None:
            object_two = ctx.message.id
        two = discord.utils.snowflake_time(object_two)
        if one > two:
            diff = one - two
        else:
            diff = two - one
        await ctx.send(f'time difference from {one} to {two} is {diff}.')

    @commands.command(name='info', aliases='source about git'.split())
    async def info(self, ctx):
        ret = discord.Embed()
        ret.set_author(name=self.bot.user.name, icon_url=self.bot.user.avatar_url)
        ret.add_field(name='Support Server', value='[link](https://discord.gg/jMwPFqp)')
        ret.add_field(name='Source Code', value='[github](https://gitlab.com/mralext20/alex-bot)')
        ret.add_field(name='Servers', value=str(len(self.bot.guilds)))
        ret.add_field(name='Members', value=str(len(list(self.bot.get_all_members()))))
        await ctx.send(embed=ret)

    @commands.command()
    @commands.is_owner()
    async def reloaddebug(self, ctx):
        """Reload the admin cog"""
        try:
            self.bot.unload_extension("alexBot.cogs.debug")
            self.bot.load_extension("alexBot.cogs.debug")
        except Exception as e:
            await ctx.send(f'**`ERROR:`** {type(e).__name__} - {e}')
        else:
            await ctx.send('**`SUCCESS`**')

    @commands.command()
    async def invite(self, ctx):
        await ctx.send(f"<{discord.utils.oauth_url(self.bot.user.id)}>")

    @commands.command()
    async def donate(self, ctx):
        await ctx.send("thanks for the thought! heres a paypal link: <https://paypal.me/mralext20>")

def setup(bot):
    bot.add_cog(Utils(bot))
<commit_msg>add alias for difference -> diff<commit_after># -*- coding: utf-8 -*-

import datetime
import humanize

import discord
from discord.ext import commands

from ..tools import Cog
DATEFORMAT = "%a, %e %b %Y %H:%M:%S (%-I:%M %p)"

class Utils(Cog):
    """The description for Utils goes here."""
    @commands.command(aliases=['p'])
    async def ping(self, ctx):
        """You know it"""
        start = await ctx.send("Po..")
        assert isinstance(start, discord.Message)
        a = start.created_at
        now = datetime.datetime.utcnow()
        ping = now - a

        await start.edit(content=f"Pong! WS: {ctx.bot.latency * 1000:.2f} ms, rest: {ping.microseconds / 1000:.2f} ms")

    @commands.command()
    async def time(self, ctx):
        """Displays the time in alaska"""
        time = datetime.datetime.now()
        await ctx.send(f'the time in alaska is {time.strftime(DATEFORMAT)}')

    @commands.command()
    async def quote(self, ctx, msg, channel: discord.TextChannel=None):
        """Quotes a message. msg can be message ID or the output of shift clicking the 'copy id' button in the UI."""
        if '-' in msg:
            try:
                channel, msg = [int(i) for i in msg.split('-')]
                channel = self.bot.get_channel(channel)
            except ValueError or discord.errors.NotFound:
                raise commands.BadArgument("your input was not a message id")
        try:
            if channel is None:
                msg = await ctx.channel.get_message(msg)
            else:
                msg = await channel.get_message(msg)
        except (discord.errors.NotFound, discord.errors.HTTPException):
            return await ctx.send("cant find that message. \N{SLIGHTLY FROWNING FACE}")
        assert isinstance(msg, discord.Message)

        if channel is not None and channel.nsfw and not ctx.channel.nsfw:
            return await ctx.send("Cant send message from NSFW channel in SFW channel")

        ret = discord.Embed(color=discord.Color.blurple())

        if msg.content is "" and msg.attachments == []:
            embed = msg.embeds[0]
            try:
                assert isinstance(embed, discord.Embed)
            except AssertionError:
                return
            ret = embed
        else:
            ret.description = msg.content

            # handle images, and images attached with URLs
            try:
                ret.set_image(url=msg.attachments[0].url)
            except IndexError:
                try:
                    ret.set_image(url=msg.embeds[0].thumbnail.url)
                except IndexError:
                    pass

        ret.timestamp = msg.created_at
        ret.set_author(name=msg.author.name, icon_url=msg.author.avatar_url)
        age = ctx.message.created_at - ret.timestamp
        age = humanize.naturaldelta(age)

        ret.set_footer(text=f"Quoted message is {age} old, from ")

        await ctx.send(embed=ret)

    @commands.command(aliases=['diff'])
    async def difference(self, ctx, object_one: int, object_two: int=None):
        """Compares the creation time of two IDs. default to comparing to the current time."""
        one = discord.utils.snowflake_time(object_one)
        if object_two is None:
            object_two = ctx.message.id
        two = discord.utils.snowflake_time(object_two)
        if one > two:
            diff = one - two
        else:
            diff = two - one
        await ctx.send(f'time difference from {one} to {two} is {diff}.')

    @commands.command(name='info', aliases='source about git'.split())
    async def info(self, ctx):
        ret = discord.Embed()
        ret.set_author(name=self.bot.user.name, icon_url=self.bot.user.avatar_url)
        ret.add_field(name='Support Server', value='[link](https://discord.gg/jMwPFqp)')
        ret.add_field(name='Source Code', value='[github](https://gitlab.com/mralext20/alex-bot)')
        ret.add_field(name='Servers', value=str(len(self.bot.guilds)))
        ret.add_field(name='Members', value=str(len(list(self.bot.get_all_members()))))
        await ctx.send(embed=ret)

    @commands.command()
    @commands.is_owner()
    async def reloaddebug(self, ctx):
        """Reload the admin cog"""
        try:
            self.bot.unload_extension("alexBot.cogs.debug")
            self.bot.load_extension("alexBot.cogs.debug")
        except Exception as e:
            await ctx.send(f'**`ERROR:`** {type(e).__name__} - {e}')
        else:
            await ctx.send('**`SUCCESS`**')

    @commands.command()
    async def invite(self, ctx):
        await ctx.send(f"<{discord.utils.oauth_url(self.bot.user.id)}>")

    @commands.command()
    async def donate(self, ctx):
        await ctx.send("thanks for the thought! heres a paypal link: <https://paypal.me/mralext20>")

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