PK!sn``concord/__init__.py""" The MIT License (MIT) Copyright (c) 2017-2018 Nariman Safiulin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ __version__ = "0.10.1" PK!"( ( concord/client.py""" The MIT License (MIT) Copyright (c) 2017-2018 Nariman Safiulin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ import logging from typing import Type import discord from concord.constants import EventType from concord.context import Context from concord.extension import Manager from concord.utils import empty_next_callable log = logging.getLogger(__name__) class Client(discord.Client): """Wrapper around default discord.py library client. Args: extension_manager: Extension manager instance associated with this client. """ extension_manager: Manager def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.extension_manager = Manager() log.info("Concord client initialized") def dispatch(self, event: str, *args, **kwargs): # noqa: D401 """Wrapper around default event dispatcher for a client.""" super().dispatch(event, *args, **kwargs) try: event_type = EventType(event) except ValueError: event_type = EventType.UNKNOWN # ctx = Context(self, event_type, *args, **kwargs) log.debug(f"Dispatching event `{event_type}`") self.loop.create_task( self._run_event( self.extension_manager.root_middleware.run, event, ctx=ctx, next=empty_next_callable, ) ) def create_client(client: Type[discord.Client]): """Get an instance of client. It returns an instance of subclass, that is based on your provided class and a wrapper class. It's needed to add some specific functionality into client in order to be able to process events. Args: client: Client class to base on. """ class MixedClient(Client, client): pass return MixedClient() PK!S concord/constants.py""" The MIT License (MIT) Copyright (c) 2017-2018 Nariman Safiulin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ import enum class EventType(enum.Enum): """List of event types which can be received.""" UNKNOWN = None CONNECT = "connect" ERROR = "error" GROUP_JOIN = "group_join" GROUP_REMOVE = "group_remove" GUILD_AVAILABLE = "guild_available" GUILD_CHANNEL_CREATE = "guild_channel_create" GUILD_CHANNEL_DELETE = "guild_channel_delete" GUILD_CHANNEL_PINS_UPDATE = "guild_channel_pins_update" GUILD_CHANNEL_UPDATE = "guild_channel_update" GUILD_EMOJIS_UPDATE = "guild_emojis_update" GUILD_JOIN = "guild_join" GUILD_REMOVE = "guild_remove" GUILD_ROLE_CREATE = "guild_role_create" GUILD_ROLE_DELETE = "guild_role_delete" GUILD_ROLE_UPDATE = "guild_role_update" GUILD_UNAVAILABLE = "guild_unavailable" GUILD_UPDATE = "guild_update" MEMBER_BAN = "member_ban" MEMBER_JOIN = "member_join" MEMBER_REMOVE = "member_remove" MEMBER_UNBAN = "member_unban" MEMBER_UPDATE = "member_update" MESSAGE = "message" MESSAGE_DELETE = "message_delete" MESSAGE_EDIT = "message_edit" PRIVATE_CHANNEL_CREATE = "private_channel_create" PRIVATE_CHANNEL_DELETE = "private_channel_delete" PRIVATE_CHANNEL_PINS_UPDATE = "private_channel_pins_update" PRIVATE_CHANNEL_UPDATE = "private_channel_update" RAW_BULK_MESSAGE_DELETE = "raw_bulk_message_delete" RAW_MESSAGE_DELETE = "raw_message_delete" RAW_MESSAGE_EDIT = "raw_message_edit" RAW_REACTION_ADD = "raw_reaction_add" RAW_REACTION_CLEAR = "raw_reaction_clear" RAW_REACTION_REMOVE = "raw_reaction_remove" REACTION_ADD = "reaction_add" REACTION_CLEAR = "reaction_clear" REACTION_REMOVE = "reaction_remove" READY = "ready" RELATIONSHIP_ADD = "relationship_add" RELATIONSHIP_REMOVE = "relationship_remove" RELATIONSHIP_UPDATE = "relationship_update" RESUMED = "resumed" SHARD_READY = "shard_ready" SOCKET_RAW_RECEIVE = "socket_raw_receive" SOCKET_RAW_SEND = "socket_raw_send" SOCKET_RESPONSE = "socket_response" TYPING = "typing" VOICE_STATE_UPDATE = "voice_state_update" WEBHOOKS_UPDATE = "webhooks_update" PK!D,2ooconcord/context.py""" The MIT License (MIT) Copyright (c) 2017-2018 Nariman Safiulin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ from typing import Any, Dict, List import discord from concord.constants import EventType class Context: """Event processing context. .. note:: Attributes ``client`` and ``event`` are not supposed to be changed. Args: client: A discord.py client instance. event: Event's type context is creating for. *args: Unnamed / positional arguments, which was provided with event. **kwargs: Keyword arguments, which was provided with event. Attributes: client: The discord.py client instance. event: Event's type context is created for. args: Unnamed / positional arguments, which was provided with event. kwargs: Keyword arguments, which was provided with event. """ client: discord.Client event: EventType args: List kwargs: Dict[str, Any] def __init__( self, client: discord.Client, event: EventType, *args, **kwargs ): self.client = client self.event = event self.args = list(args) self.kwargs = kwargs PK!{:%concord/exceptions.py""" The MIT License (MIT) Copyright (c) 2017-2018 Nariman Safiulin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ class ConcordError(Exception): """Base exception class for the library.""" pass class ExtensionError(ConcordError): """Exception class for third-party extension's errors.""" pass class ExtensionManagerError(ConcordError): """Exception class for extension manager's errors.""" pass PK!Fconcord/ext/base/__init__.py""" The MIT License (MIT) Copyright (c) 2017-2018 Nariman Safiulin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ from concord import __version__ from concord.ext.base.event import EventNormalization from concord.ext.base.filters import * # it's okay, we control it PK!l@MMconcord/ext/base/event.py""" The MIT License (MIT) Copyright (c) 2017-2018 Nariman Safiulin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ from typing import Any, Callable, Dict, Tuple, Union from concord.constants import EventType from concord.context import Context from concord.middleware import Middleware, MiddlewareResult, MiddlewareState class EventNormalizationContextState(MiddlewareState.ContextState): """State with information about already processed event normalization. Attributes: is_processed: Is normalization has been applied. """ is_processed: bool def __init__(self): self.is_processed = False class EventNormalization(Middleware): """Event parameters normalization. A middleware for parsing positional event' fields into keyword for known events. Positional fields will be left as is. Attributes: EVENT_FIELDS: Fields list each event has. """ EVENT_FIELDS: Dict[EventType, Tuple[str, ...]] = { EventType.CONNECT: tuple(), EventType.ERROR: tuple(), EventType.GROUP_JOIN: ("channel", "user"), EventType.GROUP_REMOVE: ("channel", "user"), EventType.GUILD_AVAILABLE: ("guild",), EventType.GUILD_CHANNEL_CREATE: ("channel",), EventType.GUILD_CHANNEL_DELETE: ("channel",), EventType.GUILD_CHANNEL_PINS_UPDATE: ("channel", "last_pin"), EventType.GUILD_CHANNEL_UPDATE: ("before", "after"), EventType.GUILD_EMOJIS_UPDATE: ("guild", "before", "after"), EventType.GUILD_JOIN: ("guild",), EventType.GUILD_REMOVE: ("guild",), EventType.GUILD_ROLE_CREATE: ("role",), EventType.GUILD_ROLE_DELETE: ("role",), EventType.GUILD_ROLE_UPDATE: ("before", "after"), EventType.GUILD_UNAVAILABLE: ("guild",), EventType.GUILD_UPDATE: ("before", "after"), EventType.MEMBER_BAN: ("guild", "user"), EventType.MEMBER_JOIN: ("member",), EventType.MEMBER_REMOVE: ("member",), EventType.MEMBER_UNBAN: ("guild", "user"), EventType.MEMBER_UPDATE: ("before", "after"), EventType.MESSAGE: ("message",), EventType.MESSAGE_DELETE: ("message",), EventType.MESSAGE_EDIT: ("before", "after"), EventType.PRIVATE_CHANNEL_CREATE: ("channel",), EventType.PRIVATE_CHANNEL_DELETE: ("channel",), EventType.PRIVATE_CHANNEL_PINS_UPDATE: ("channel", "last_pin"), EventType.PRIVATE_CHANNEL_UPDATE: ("before", "after"), EventType.RAW_BULK_MESSAGE_DELETE: ("payload",), EventType.RAW_MESSAGE_DELETE: ("payload",), EventType.RAW_MESSAGE_EDIT: ("payload",), EventType.RAW_REACTION_ADD: ("payload",), EventType.RAW_REACTION_CLEAR: ("payload",), EventType.RAW_REACTION_REMOVE: ("payload",), EventType.REACTION_ADD: ("reaction", "user"), EventType.REACTION_CLEAR: ("message", "reactions"), EventType.REACTION_REMOVE: ("reaction, user",), EventType.READY: tuple(), EventType.RELATIONSHIP_ADD: ("relationship",), EventType.RELATIONSHIP_REMOVE: ("relationship",), EventType.RELATIONSHIP_UPDATE: ("before", "after"), EventType.RESUMED: tuple(), EventType.SHARD_READY: ("shard_id",), EventType.SOCKET_RAW_RECEIVE: ("msg",), EventType.SOCKET_RAW_SEND: ("payload",), EventType.SOCKET_RESPONSE: ("playload",), EventType.TYPING: ("channel", "user", "timestamp"), EventType.VOICE_STATE_UPDATE: ("member", "before", "after"), EventType.WEBHOOKS_UPDATE: ("channel",), } @staticmethod def _get_state(ctx: Context) -> EventNormalizationContextState: state = MiddlewareState.get_state(ctx, EventNormalizationContextState) if state is None: state = EventNormalizationContextState() MiddlewareState.set_state(ctx, state) # return state async def run( self, *args, ctx: Context, next: Callable, **kwargs ) -> Union[MiddlewareResult, Any]: # noqa: D102 if ctx.event not in self.EVENT_FIELDS: return await next(*args, ctx=ctx, **kwargs) state = self._get_state(ctx) if state.is_processed: return await next(*args, ctx=ctx, **kwargs) for i, parameter in enumerate(self.EVENT_FIELDS[ctx.event]): ctx.kwargs[parameter] = ctx.args[i] state.is_processed = True return await next(*args, ctx=ctx, **kwargs) PK!\h$concord/ext/base/filters/__init__.py""" The MIT License (MIT) Copyright (c) 2017-2018 Nariman Safiulin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ from concord.ext.base.filters.command import Command, CommandContextState from concord.ext.base.filters.common import ( BotFilter, ChannelTypeFilter, EventTypeFilter, PatternFilter, ) PK!L#concord/ext/base/filters/command.py""" The MIT License (MIT) Copyright (c) 2017-2018 Nariman Safiulin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ import re from typing import Any, Callable, Optional, Union from concord.context import Context from concord.middleware import Middleware, MiddlewareResult, MiddlewareState class CommandContextState(MiddlewareState.ContextState): """State with information about already processed parts of a message. Attributes: last_position: Position in a message string where last match has found and processed. """ last_position: int def __init__(self): self.last_position = 0 class Command(Middleware): """Message context filter. Args: name: Command name that should be present in a message. prefix: Allow command name to be a prefix of the full word (full command name). Useful for global command prefix. rest_pattern: The regex string to process the rest part of a message. Attributes: name: Command name that should be present in a message. prefix: Is command name could be a prefix of the full word (full command name). Useful for global command prefix. rest_pattern: The regex string that will process the rest part of a message. """ name: str prefix: bool rest_pattern: Optional[str] def __init__( self, name: str, *, prefix: bool = False, rest_pattern: Optional[str] = None, ): super().__init__() self.name = name self.prefix = prefix self.rest_pattern = rest_pattern @staticmethod def _get_state(ctx: Context) -> CommandContextState: state = MiddlewareState.get_state(ctx, CommandContextState) if state is None: state = CommandContextState() MiddlewareState.set_state(ctx, state) # return state # TODO: What about arabic text? async def run( self, *args, ctx: Context, next: Callable, **kwargs ) -> Union[MiddlewareResult, Any]: # noqa: D102 state = self._get_state(ctx) message = ctx.kwargs["message"] name_pattern = rf"{self.name}" if self.prefix else rf"{self.name}\b" # We should restore last position after processing. position = 0 # And we should care about whitespaces on the start and do not forget to # count this into state. part = message.content[state.last_position :] clean = part.lstrip() result = re.match(name_pattern, clean, re.I) if not result: return MiddlewareResult.IGNORE # position += len(part) - len(clean) + result.end() if self.rest_pattern: part = part[position:] clean = part.lstrip() result = re.match(self.rest_pattern, clean) if not result: return MiddlewareResult.IGNORE kwargs.update(result.groupdict()) position += len(part) - len(clean) + result.end() # state.last_position += position result = await next(*args, ctx=ctx, **kwargs) state.last_position -= position return result PK!z||"concord/ext/base/filters/common.py""" The MIT License (MIT) Copyright (c) 2017-2018 Nariman Safiulin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ import re from typing import Any, Callable, Union import discord from concord.constants import EventType from concord.context import Context from concord.middleware import Middleware, MiddlewareResult class EventTypeFilter(Middleware): """Event type filter. Args: event: Event type to allow. Attributes: event: Event type to allow. """ event: EventType def __init__(self, event: EventType): super().__init__() self.event = event async def run( self, *args, ctx: Context, next: Callable, **kwargs ) -> Union[MiddlewareResult, Any]: # noqa: D102 if ctx.event == self.event: return await next(*args, ctx=ctx, **kwargs) return MiddlewareResult.IGNORE class PatternFilter(Middleware): """Message context filter. The message should match the given regex pattern to invoke the next middleware. Only named subgroups will be passed to the next middleware. TODO: Work with different event types. Args: pattern: The source regex string. Attributes: pattern: The source regex string. """ pattern: str def __init__(self, pattern: str): super().__init__() self.pattern = pattern async def run( self, *args, ctx: Context, next: Callable, **kwargs ) -> Union[MiddlewareResult, Any]: # noqa: D102 result = re.search(self.pattern, ctx.kwargs["message"].content) if result: kwargs.update(result.groupdict()) return await next(*args, ctx=ctx, **kwargs) # return MiddlewareResult.IGNORE class BotFilter(Middleware): """Message context filter. The message should be authored by or not authored by a real user to invoke the next middleware. Args: authored_by_bot: Is the message should be authored by bot or not. Attributes: authored_by_bot: Is the message should be authored by bot or not. """ authored_by_bot: bool def __init__(self, *, authored_by_bot: bool): super().__init__() self.authored_by_bot = authored_by_bot async def run( self, *args, ctx: Context, next: Callable, **kwargs ) -> Union[MiddlewareResult, Any]: # noqa: D102 if not self.authored_by_bot ^ ctx.kwargs["message"].author.bot: return await next(*args, ctx=ctx, **kwargs) return MiddlewareResult.IGNORE class ChannelTypeFilter(Middleware): """Message context filter. The message should be sent in the given channel types to invoke the next middleware. TODO: Work with different event types. Args: guild: Is the channel should be a guild channel. private: Is the channel should be a private channel (DM or group). text: Is the channel should be a guild text channel. voice: Is the channel should be a guild voice channel. dm: Is the channel should be a private DM channel. group: Is the channel should be a private group channel. Attributes: guild: Is the channel should be a guild channel. private: Is the channel should be a private channel (DM or group). text: Is the channel should be a guild text channel. voice: Is the channel should be a guild voice channel. dm: Is the channel should be a private DM channel. group: Is the channel should be a private group channel. """ guild: bool private: bool text: bool voice: bool dm: bool group: bool def __init__( self, *, guild: bool = False, private: bool = False, text: bool = False, voice: bool = False, dm: bool = False, group: bool = False, ): super().__init__() self.text = guild or text self.voice = guild or voice self.dm = private or dm self.group = private or group async def run( self, *args, ctx: Context, next: Callable, **kwargs ) -> Union[MiddlewareResult, Any]: # noqa: D102 channel = ctx.kwargs["message"].channel # fmt: off if ( self.text and isinstance(channel, discord.TextChannel) or self.voice and isinstance(channel, discord.VoiceChannel) or self.dm and isinstance(channel, discord.DMChannel) or self.group and isinstance(channel, discord.GroupChannel) ): return await next(*args, ctx=ctx, **kwargs) # fmt: on return MiddlewareResult.IGNORE PK!#"#"concord/extension.py""" The MIT License (MIT) Copyright (c) 2017-2018 Nariman Safiulin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ import logging from typing import Dict, Sequence, Type, Optional from concord.exceptions import ExtensionManagerError from concord.middleware import ( Middleware, MiddlewareChain, chain_of, sequence_of, ) log = logging.getLogger(__name__) class Extension: """Abstract extension class. TODO: What about dependencies of extension? It would be cool, and seems like not hard to implement. """ NAME = "Extension name is empty." DESCRIPTION = "Extension description is empty." VERSION = "1.0.0" @property def client_middleware(self) -> Sequence[Middleware]: """Middleware list, associated with this extension, and that should be registered and applied on every event processing. It is especially useful for sharing states between different extensions. .. note:: Keep in mind, that this list can be requested multiple times as well as cached and optimized. You should return the same list on every request and avoid any changes in the list after first request due to this changes may be unexpected for other code. .. warning:: Keep in mind, that client middleware will be executed by middleware chain. """ return [] @property def extension_middleware(self) -> Sequence[Middleware]: """Middleware list, associated with this extension, and that should be registered for event handling. .. note:: Keep in mind, that this list can be requested multiple times as well as cached and optimized. You should return the same list on every request and avoid any changes in the list after first request due to this changes may be unexpected for other code. .. warning:: Keep in mind, that all extension middleware will be executed on every event. Properly filter events before processing them. """ return [] def on_register(self, manager: "Manager"): """Listener invoked on registering extension in a manager. If there's global states and middleware, associated with this extension, all of this will be registered after invoking this listener. Args: manager: Manager instance where extension has been registered. """ pass # pragma: no cover def on_unregister(self, manager: "Manager"): """Listener invoked on unregistering extension in a manager. If there's global states and middleware, associated with this extension, all of this is already unregistered before invoking this listener. Args: manager: Manager instance where extension has been unregistered. """ pass # pragma: no cover class Manager: """Extension manager. Attributes: _extensions: List of registered extensions. Key is an extension class (subclass of :class:`Extension`), value is extension instance. _client_middleware_cache: Cached list of client middleware. _extension_middleware_cache: Cached list of extension middleware. _root_middleware_cache: Cached root middleware. """ _extensions: Dict[Type[Extension], Extension] _client_middleware_cache: Optional[Sequence[Middleware]] _extension_middleware_cache: Optional[Sequence[Middleware]] _root_middleware_cache: Optional[Middleware] def __init__(self): self._extensions = {} self._client_middleware_cache = None self._extension_middleware_cache = None self._root_middleware_cache = None @property def client_middleware(self) -> Sequence[Middleware]: """States list, provided by extensions, and that should be applied on every event processing.""" if self._client_middleware_cache is None: self._client_middleware_cache = [ mw for extension in self._extensions.values() for mw in extension.client_middleware ] return self._client_middleware_cache @property def extension_middleware(self) -> Sequence[Middleware]: """Middleware list, provided by extensions for event handling.""" if self._extension_middleware_cache is None: self._extension_middleware_cache = [ mw for extension in self._extensions.values() for mw in extension.extension_middleware ] return self._extension_middleware_cache @property def root_middleware(self) -> MiddlewareChain: """Root middleware, a built chain of client and extension middleware.""" if self._root_middleware_cache is None: chain = chain_of([sequence_of(self.extension_middleware)]) for mw in self.client_middleware: chain.add_middleware(mw) self._root_middleware_cache = chain return self._root_middleware_cache def is_extension_registered(self, extension: Type[Extension]) -> bool: """Checks is extension registered in the manager. Args: extension: Extension to check. Returns: ``True``, if extensions is registered, otherwise ``False``. """ return extension in self._extensions def register_extension(self, extension: Type[Extension]): """Register extension in the manager. Args: extension: Extension to register. Raises: ValueError: If not a type provided or if provided type is not a subclass of :class:`Extension` provided. concord.exceptions.ExtensionManagerError: If this extension is already registered in this manager. """ if not isinstance(extension, type): raise ValueError("Not a type") if not issubclass(extension, Extension): raise ValueError("Not an extension") if self.is_extension_registered(extension): raise ExtensionManagerError("Already registered") instance = extension() instance.on_register(self) self._extensions[extension] = instance self._client_middleware_cache = None self._extension_middleware_cache = None self._root_middleware_cache = None log.info( f'Extension "{extension.NAME} "' f"(version {extension.VERSION}) has been registered" ) def unregister_extension(self, extension: Type[Extension]): """Unregister extension in the manager. Args: extension: Extension to unregister. Raises: ValueError: If not a type provided or if provided type is not a subclass of :class:`Extension` provided. concord.exceptions.ExtensionManagerError: If this extension is not registered in this manager. """ if not isinstance(extension, type): raise ValueError("Not a type") if not issubclass(extension, Extension): raise ValueError("Not an extension") if not self.is_extension_registered(extension): raise ExtensionManagerError("Not registered") instance = self._extensions.pop(extension) self._client_middleware_cache = None self._extension_middleware_cache = None self._root_middleware_cache = None instance.on_unregister(self) log.info( f'Extension "{extension.NAME} "' f"(version {extension.VERSION}) has been unregistered" ) PK!;:T::concord/middleware.py""" The MIT License (MIT) Copyright (c) 2017-2018 Nariman Safiulin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ import abc import asyncio import enum from typing import Any, Callable, List, Optional, Sequence, Type, TypeVar, Union from concord.context import Context class MiddlewareResult(enum.Enum): """Enum values for middleware results. One of these values can be returned by a middleware instead of actual data. Anything returned by the middleware and is not an enum value is considered as successful result (``OK`` value). """ OK = enum.auto() IGNORE = enum.auto() def is_successful_result(value: Union[MiddlewareResult, Any]) -> bool: """Returns ``True``, if given value is a successful middleware result.""" if value == MiddlewareResult.IGNORE: return False return True class Middleware(abc.ABC): """Event processing middleware. Middleware are useful for filtering events or extending functionality. They could return something or nothing (``None``) to indicate success, otherwise :class:`MiddlewareResult` values can be used. Functions can also be converted into a middleware by using :class:`MiddlewareFunction` or :func:`as_middleware` decorator. Attributes: fn: A source function, when a last middleware in a middleware chain is a :class:`MiddlewareFunction` or it is the :class:`MiddlewareFunction` itself. Can be not present, if the middleware chain is created not with :func:`middleware` decorator, or there is complex middleware tree. .. seealso:: :class:`MiddlewareChain`. """ fn: Optional[Callable] def __init__(self): self.fn = None @abc.abstractmethod async def run( self, *args, ctx: Context, next: Callable, **kwargs ) -> Union[MiddlewareResult, Any]: """Middleware's main logic. .. note:: Context ``ctx`` and ``next`` callable are keyword parameters. Args: ctx: Event processing context. .. note:: Provided context can be replaced and passed to the ``next`` callable, but do it only if needed. next: The next function to call. Not necessarily a middleware. Pass context, all positional and keyword parameters, even if unused. Must be awaited. Returns: Middleware data or :class:`MiddlewareResult` enum value. """ pass # pragma: no cover @staticmethod def is_successful_result(value: Union[MiddlewareResult, Any]) -> bool: """Returns ``True``, if given value is a successful middleware result.""" return is_successful_result(value) async def __call__( self, *args, ctx: Context, next: Callable, **kwargs ) -> Union[MiddlewareResult, Any]: """Invokes the middleware with given parameters.""" return await self.run(*args, ctx=ctx, next=next, **kwargs) class MiddlewareFunction(Middleware): """Middleware to use function (any callable) as a valid middleware. Args: fn: A function to use as a middleware. Should be a coroutine. Raises: ValueError: If the given function is not a coroutine. Attributes: fn: The source function to use as a middleware. A coroutine. """ def __init__(self, fn: Callable): super().__init__() if not asyncio.iscoroutinefunction(fn): raise ValueError("Not a coroutine") self.fn = fn async def run( self, *args, ctx: Context, next: Callable, **kwargs ) -> Union[MiddlewareResult, Any]: return await self.fn(*args, ctx=ctx, next=next, **kwargs) class MiddlewareState(Middleware): """Middleware that can provide a given state within a middleware tree. It is an alternative to middleware as class methods. Every state will be saved in a context and could be found by state's type (see :meth:`get_state`). If you also want to pass the state as a parameter, provide a ``key`` parameter name. You can use :meth:`get_state` helper to get the state from the context. It is especially useful, when ``key`` is not provided. If a class provided as the state, it will be instantiated for you on first middleware run. If a :class:`ContextState` subclass provided as the state, it will be instantiated for you on every middleware run. Args: state: A state to provide. key: A parameter name, by which the state will be provided. Attributes: state: The state for providing. key: The parameter name, by which the state will be provided as a parameter, if present. """ StateType = TypeVar("StateType") class ContextState: """State that should be instantiated on every middleware run. Your state should subclass it. """ pass state: Any key: Optional[str] def __init__(self, state: Any, *, key: Optional[str] = None): super().__init__() self.state = state self.key = key async def run( self, *args, ctx: Context, next: Callable, **kwargs ) -> Union[MiddlewareResult, Any]: # noqa: D102 state = self.state if isinstance(state, type): if issubclass(state, MiddlewareState.ContextState): state = state() else: self.state = state = state() if self.key: kwargs[self.key] = state self.set_state(ctx, state) return await next(*args, ctx=ctx, **kwargs) @staticmethod def get_state( ctx: Context, state_type: Type[StateType] ) -> Optional[StateType]: """Returns a state from the context.""" MiddlewareState._ensure_context(ctx) return ctx.states.get(state_type) @staticmethod def set_state(ctx: Context, state: Any) -> None: """Sets the state to the context.""" MiddlewareState._ensure_context(ctx) ctx.states[type(state)] = state @staticmethod def _ensure_context(ctx: Context) -> None: """Checks is the context has states storage, and creates it if necessary.""" if getattr(ctx, "states", None) is None: ctx.states = dict() class MiddlewareCollection(Middleware, abc.ABC): """Abstract class for grouping middleware. It is a middleware itself. Method :meth:`run` is abstract. Each subclass should implement own behavior of how to run group of middleware. For example, run middleware in specific order, run middleware until desired results is obtained, etc. Useful, when it is known, what middleware can return. Attributes: collection: List of middleware to run. Take a note that order of middleware in the list can be used in an implementation. """ collection: List[Middleware] def __init__(self): super().__init__() self.collection = [] def add_middleware(self, middleware: Middleware) -> Middleware: """Adds middleware to the list. Can be used as a decorator. Args: middleware: A middleware to add to the list. Returns: The given middleware. Raises: ValueError: If given parameter is not a middleware. """ if not isinstance(middleware, Middleware): raise ValueError("Not a middleware") # self.collection.append(middleware) return middleware @abc.abstractmethod async def run( self, *args, ctx: Context, next: Callable, **kwargs ) -> Union[MiddlewareResult, Any]: # noqa: D102 pass # pragma: no cover class MiddlewareChain(MiddlewareCollection): """Middleware collection for chaining middleware. Attributes: collection: List of middleware to run in a certain order. The first items is a last-to-call middleware (in other words, list is reversed). """ def add_middleware( self, middleware: Middleware ) -> Middleware: # noqa: D102 super().add_middleware(middleware) if len(self.collection) == 1: self.fn = middleware.fn return middleware async def run( self, *args, ctx: Context, next: Callable, **kwargs ) -> Union[MiddlewareResult, Any]: # noqa: D102 for current in self.collection: # We need to save `current` and `next` middleware in a separate # context for each step. Lambda is a life-hack. # It constructs first lambda with last-to-call middleware and `next` # callable, given by outer scope and runs it. Result is an another # lambda, that is can be used as an `next` callable. # Result lambda overwrites `next` in our scope, next cycle uses the # next middleware in chain order and overwritten `next` callable. # In the end, a lambda chain will be constructed. next = ( lambda current, next: lambda *args, ctx, **kwargs: current.run( *args, ctx=ctx, next=next, **kwargs ) )(current, next) return await next(*args, ctx=ctx, **kwargs) class MiddlewareSequence(MiddlewareCollection): """Middleware collection for sequencing middleware. It processes all of the middleware list and returns a tuple of results. But it returns unsuccessful result if all of the results is unsuccessful. See :class:`Middleware` for information about successful results. """ async def run( self, *args, ctx: Context, next: Callable, **kwargs ) -> Union[MiddlewareResult, Any]: # noqa: D102 results = [] successful = False for mw in self.collection: result = await mw.run(*args, ctx=ctx, next=next, **kwargs) results.append(result) if not successful and self.is_successful_result(result): successful = True # if successful: return tuple(results) return MiddlewareResult.IGNORE def as_middleware(fn: Callable) -> MiddlewareFunction: """Creates a middleware for given function (or any callable). If you are planning to chain this function with another middleware, just use :func:`middleware` helper. It will create a middleware from the function for you, if needed. Args: fn: A function to use as a middleware. Returns: Middleware for given function. """ # We don't care, when somebody is converting a middleware into another one... return MiddlewareFunction(fn) def collection_of( collection_class: Type[MiddlewareCollection], middleware: Sequence[Union[Middleware, Callable]], ) -> MiddlewareCollection: """Creates a new collection of given middleware. If any of given parameters is not a middleware, a middleware will be created for it for you. Args: collection_class: A collection class to create collection of. middleware: List of middleware to create collection of. Returns: Instance of given collection class with the list of middleware in the collection. """ collection = collection_class() for mw in middleware: if not isinstance(mw, Middleware): mw = as_middleware(mw) collection.add_middleware(mw) # return collection def chain_of( middleware: Sequence[Union[Middleware, Callable]] ) -> MiddlewareChain: """Creates a new chain (:class:`MiddlewareChain`) of given middleware. If any of given parameters is not a middleware, a middleware will be created for it for you. Args: middleware: List of middleware to create chain of. Returns: Chain of given middleware. """ return collection_of(MiddlewareChain, middleware) def sequence_of( middleware: Sequence[Union[Middleware, Callable]] ) -> MiddlewareChain: """Creates a new sequence (:class:`MiddlewareSequence`) of given middleware. If any of given parameters is not a middleware, a middleware will be created for it for you. Args: middleware: A list of middleware to create chain of. Returns: Sequence of given middleware. """ return collection_of(MiddlewareSequence, middleware) def middleware(outer_middleware: Middleware): """Appends a middleware to the chain. A decorator. If decorated function is not a middleware, a middleware will be created for it by the decorator. Args: outer_middleware: A middleware to append to the chain. """ if not isinstance(outer_middleware, Middleware): outer_middleware = as_middleware(outer_middleware) def decorator(inner_middleware: Middleware) -> MiddlewareChain: # If we already have a chain under the decorator, just add to it. if isinstance(inner_middleware, MiddlewareChain): inner_middleware.add_middleware(outer_middleware) return inner_middleware # return chain_of([inner_middleware, outer_middleware]) return decorator class OneOfAll(MiddlewareCollection): """Middleware collection with "first success" condition. It processes the middleware list until one of them returns a successful result. See :class:`Middleware` for information about successful results. """ async def run( self, *args, ctx: Context, next: Callable, **kwargs ) -> Union[MiddlewareResult, Any]: # noqa: D102 for mw in self.collection: result = await mw.run(*args, ctx=ctx, next=next, **kwargs) if self.is_successful_result(result): return result # return MiddlewareResult.IGNORE PK!4/concord/utils.py""" The MIT License (MIT) Copyright (c) 2017-2018 Nariman Safiulin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ from concord.context import Context async def empty_next_callable( *args, ctx: Context, **kwargs ) -> None: # noqa: D401 """Empty callable to provide as the ``next`` parameter. In theory, event handlers should ignore ``next`` callable since it makes no sense. But there may be cases when middleware does not know whether to call the ``next`` or not. Empty callable can be provided as a workaround, and middleware can call ``next`` without thinking about it. Empty callable just immediately returns. """ pass # pragma: no cover PK!S66cncrd-0.10.1.dist-info/LICENSEMIT License Copyright (c) 2017-2018 Nariman Safiulin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!H,TTcncrd-0.10.1.dist-info/WHEEL 1 0 =Rn>ZD:_dhRUQTk-k]m`Q'iPK!H$)!cncrd-0.10.1.dist-info/METADATAWoF-zܝԈqVښewR,_6 C"gfg޼y;zQ:8;onxF-|/ɟOuM}?7,kjR.7l#faJ+ CWcll:L\w˼pjom.)ؿ߫r0r8&Kut㋌mm[ߕ|;3CvwϿQ/jzV%]HwQ.lFSFρl2 gdn#]wa[GnN-uٽ>qy鵶UV 뿴{ᵶ'v?#)w} [[*FC˧>>=qqZSȋ;ݓfp+Á$ s30o{D~"f߽tܺ`r%›6b2}߂S cV'£䢾hrzP!}}&wm:4S\3+szxޙ4Uֆ2MΕ=-yMj{.0չ( Wr6LO/Ng $1t"2WWO]ƶ K%WmK(DrZA4q vJ݃;xiE'c1%#j%4v{Xkwԫ4Ea2$&]6uċ@87e2!xՃ+ye,|أ>ϜC-/Mڂˎ`SHT%pAީK: n:P|/{^xt!^KCW Bt 4 HLMΡ2 nMkU=>5.q[F&x6ս~`~Q^{銁 H!wXաݔ6ш#D"vߣMK:x.)/_]G6:@dL.3sL=:aAݿ#i=ϊ39Pϊ<..-u<_[`䁦n@z=+@Zȁ2PݦS @D meo" 52g=;SjX^9,x`PI!1%Ȩc.R|M7# 9:Å,1ћe0A1p5킦Dx@pǵ 2O=.9QYXF Иvr(QX$b" XL[}֓@RJ5yytR u XIR{hs4{nk(lx:0gGki+uh|X~7=vJP$q\{WC=a Au8Ա QSڭ6Z4+BI,D =z(5h WhJv΢%ȍz](ɍh4W^e԰(y Q4@ ٴBzdi_dVBxd_uKt dq1FVȺdqPwTuP/nݐN$cIPK!Hm! >cncrd-0.10.1.dist-info/RECORD}K:&Yr6Ab_?.L5ӧ*맾TMw~O\!Iڙꋔğѱt[fi1[{$QQ5Z5E AQe~,XaT/R*w7Ec6ƋR,RSCZ ݲ3J`2*;e>$me&aoSr؀]xSubuR[m :2AC vu/Z fnX D]=TV1R 林GzKbx7ޞF"1s宊=U) %+Qt̎,&Ye,I%5=E O -fPkdA)8קLmCT= UrیWǦӸ<*8l״>6uNTܼGiFZ\ϲMB+7`3SVu\n‘Ρ7m%2=Ov*s˝ch pYWoȔ,qU]\!vE#& MׇJ;:7tTLkg%Ϫd〫>%va:zbn*e0%Mg54uּ[kƟژKpK1vOJ`HhvBI= jҥ+x3Mi4I:@wހ iTzJ#m#Tu5Tڜt6eT.6~ '췮ohSԏ_PK!sn``concord/__init__.pyPK!"( ( concord/client.pyPK!S concord/constants.pyPK!D,2ooconcord/context.pyPK!{:%P%concord/exceptions.pyPK!F+concord/ext/base/__init__.pyPK!l@MM"0concord/ext/base/event.pyPK!\h$Econcord/ext/base/filters/__init__.pyPK!L#Jconcord/ext/base/filters/command.pyPK!z||"[concord/ext/base/filters/common.pyPK!#"#"rconcord/extension.pyPK!;:T::Xconcord/middleware.pyPK!4/concord/utils.pyPK!S66Ocncrd-0.10.1.dist-info/LICENSEPK!H,TTcncrd-0.10.1.dist-info/WHEELPK!H$)!Ocncrd-0.10.1.dist-info/METADATAPK!Hm! >ecncrd-0.10.1.dist-info/RECORDPK