From 4d7822493f8e8b1edaa4609690153582df74cbd3 Mon Sep 17 00:00:00 2001
From: Maya <17090652+XuaTheGrate@users.noreply.github.com>
Date: Sat, 29 May 2021 16:43:33 +1200
Subject: [PATCH] Add support for bot integrations

---
 discord/guild.py        |  11 +-
 discord/integrations.py | 216 +++++++++++++++++++++++++++++++---------
 docs/api.rst            |   9 ++
 3 files changed, 185 insertions(+), 51 deletions(-)

diff --git a/discord/guild.py b/discord/guild.py
index 1d5baf505..c2f8f0de3 100644
--- a/discord/guild.py
+++ b/discord/guild.py
@@ -45,7 +45,7 @@ from .iterators import AuditLogIterator, MemberIterator
 from .widget import Widget
 from .asset import Asset
 from .flags import SystemChannelFlags
-from .integrations import Integration
+from .integrations import BotIntegration, StreamIntegration, _integration_factory
 
 __all__ = (
     'Guild',
@@ -1803,7 +1803,14 @@ class Guild(Hashable):
             The list of integrations that are attached to the guild.
         """
         data = await self._state.http.get_all_integrations(self.id)
-        return [Integration(guild=self, data=d) for d in data]
+        
+        def convert(d):
+            factory, itype = _integration_factory(d['type'])
+            if factory is None:
+                raise InvalidData('Unknown integration type {type!r} for integration ID {id}'.format_map(d))
+            return factory(guild=self, data=d)
+
+        return [convert(d) for d in data]
 
     async def fetch_emojis(self):
         r"""|coro|
diff --git a/discord/integrations.py b/discord/integrations.py
index 8a804bc3a..52f06c30e 100644
--- a/discord/integrations.py
+++ b/discord/integrations.py
@@ -27,19 +27,25 @@ from __future__ import annotations
 import datetime
 from typing import Optional, TYPE_CHECKING, overload
 from .utils import _get_as_snowflake, get, parse_time
+from .role import Role
 from .user import User
 from .errors import InvalidArgument
 from .enums import try_enum, ExpireBehaviour
 
 __all__ = (
     'IntegrationAccount',
+    'IntegrationApplication',
     'Integration',
+    'StreamIntegration',
+    'BotIntegration',
 )
 
 if TYPE_CHECKING:
     from .types.integration import (
         IntegrationAccount as IntegrationAccountPayload,
         Integration as IntegrationPayload,
+        IntegrationType,
+        IntegrationApplication as IntegrationApplicationPayload,
     )
     from .guild import Guild
 
@@ -74,6 +80,75 @@ class Integration:
 
     Attributes
     -----------
+    id: :class:`int`
+        The integration ID.
+    name: :class:`str`
+        The integration name.
+    guild: :class:`Guild`
+        The guild of the integration.
+    type: :class:`str`
+        The integration type (i.e. Twitch).
+    enabled: :class:`bool`
+        Whether the integration is currently enabled.
+    account: :class:`IntegrationAccount`
+        The account linked to this integration.
+    user: :class:`User`
+        The user that added this integration.
+    """
+
+    __slots__ = (
+        'guild',
+        'id',
+        '_state',
+        'type',
+        'name',
+        'account',
+        'user',
+        'enabled',
+    )
+
+    def __init__(self, *, data: IntegrationPayload, guild: Guild) -> None:
+        self.guild = guild
+        self._state = guild._state
+        self._from_data(data)
+
+    def __repr__(self):
+        return f"<{self.__class__.__name__} id={self.id} name={self.name!r}>"
+
+    def _from_data(self, data: IntegrationPayload) -> None:
+        self.id: int = int(data['id'])
+        self.type: IntegrationType = data['type']
+        self.name: str = data['name']
+        self.account: IntegrationAccount = IntegrationAccount(data['account'])
+
+        user = data.get('user')
+        self.user = User(state=self._state, data=user) if user else None
+        self.enabled: bool = data['enabled']
+
+    async def delete(self) -> None:
+        """|coro|
+
+        Deletes the integration.
+
+        You must have the :attr:`~Permissions.manage_guild` permission to
+        do this.
+
+        Raises
+        -------
+        Forbidden
+            You do not have permission to delete the integration.
+        HTTPException
+            Deleting the integration failed.
+        """
+        await self._state.http.delete_integration(self.guild.id, self.id)
+
+class StreamIntegration(Integration):
+    """Represents a stream integration for Twitch or YouTube.
+
+    .. versionadded:: 2.0
+
+    Attributes
+    ----------
     id: :class:`int`
         The integration ID.
     name: :class:`str`
@@ -102,49 +177,30 @@ class Integration:
         An aware UTC datetime representing when the integration was last synced.
     """
 
-    __slots__ = (
-        'id',
-        '_state',
-        'guild',
-        'name',
-        'enabled',
-        'type',
-        'syncing',
-        'role',
+    __slots__ = Integration.__slots__ + (
+        'revoked',
         'expire_behaviour',
         'expire_behavior',
         'expire_grace_period',
         'synced_at',
-        'user',
-        'account',
-        'enable_emoticons',
         '_role_id',
+        'role',
+        'syncing',
+        'enable_emoticons',
+        'subscriber_count'
     )
 
-    def __init__(self, *, data: IntegrationPayload, guild: Guild) -> None:
-        self.guild = guild
-        self._state = guild._state
-        self._from_data(data)
-
-    def __repr__(self) -> str:
-        return f'<Integration id={self.id} name={self.name!r} type={self.type!r}>'
-
-    def _from_data(self, integ: IntegrationPayload):
-        self.id = _get_as_snowflake(integ, 'id')
-        self.name = integ['name']
-        self.type = integ['type']
-        self.enabled = integ['enabled']
-        self.syncing = integ['syncing']
-        self._role_id = _get_as_snowflake(integ, 'role_id')
-        self.role = get(self.guild.roles, id=self._role_id)
-        self.enable_emoticons = integ.get('enable_emoticons')
-        self.expire_behaviour = try_enum(ExpireBehaviour, integ['expire_behavior'])
-        self.expire_behavior = self.expire_behaviour
-        self.expire_grace_period = integ['expire_grace_period']
-        self.synced_at = parse_time(integ['synced_at'])
-
-        self.user = User(state=self._state, data=integ['user'])
-        self.account = IntegrationAccount(integ['account'])
+    def _from_data(self, data: IntegrationPayload) -> None:
+        super()._from_data(data)
+        self.revoked: bool = data['revoked']
+        self.expire_behaviour: ExpireBehaviour = try_enum(ExpireBehaviour, data['expire_behavior'])
+        self.expire_grace_period: int = data['expire_grace_period']
+        self.synced_at: datetime.datetime = parse_time(data['synced_at'])
+        self._role_id: int = int(data['role_id'])
+        self.role: Role = self.guild.get_role(self._role_id)
+        self.syncing: bool = data['syncing']
+        self.enable_emoticons: bool = data['enable_emoticons']
+        self.subscriber_count: int = data['subscriber_count']
 
     @overload
     async def edit(
@@ -231,19 +287,81 @@ class Integration:
         await self._state.http.sync_integration(self.guild.id, self.id)
         self.synced_at = datetime.datetime.now(datetime.timezone.utc)
 
-    async def delete(self) -> None:
-        """|coro|
+class IntegrationApplication:
+    """Represents an application for a bot integration.
 
-        Deletes the integration.
+    .. versionadded:: 2.0
 
-        You must have the :attr:`~Permissions.manage_guild` permission to
-        do this.
+    Attributes
+    ----------
+    id: :class:`int`
+        The ID for this application.
+    name: :class:`str`
+        The application's name.
+    icon: Optional[:class:`str`]
+        The application's icon hash.
+    description: :class:`str`
+        The application's description. Can be an empty string.
+    summary: :class:`str`
+        The summary of the application. Can be an empty string.
+    user: Optional[:class:`User`]
+        The bot user on this application.
+    """
 
-        Raises
-        -------
-        Forbidden
-            You do not have permission to delete the integration.
-        HTTPException
-            Deleting the integration failed.
-        """
-        await self._state.http.delete_integration(self.guild.id, self.id)
+    __slots__ = (
+        'id', 
+        'name', 
+        'icon', 
+        'description', 
+        'summary', 
+        'user',
+    )
+    
+    def __init__(self, *, data: IntegrationApplicationPayload, state):
+        self.id: int = int(data['id'])
+        self.name: str = data['name']
+        self.icon: Optional[str] = data['icon']
+        self.description: str = data['description']
+        self.summary: str = data['summary']
+        user = data.get('bot')
+        self.user: Optional[User] = User(state=state, data=user) if user else None
+
+class BotIntegration(Integration):
+    """Represents a bot integration on discord.
+    
+    .. versionadded:: 2.0
+
+    Attributes
+    ----------
+    id: :class:`int`
+        The integration ID.
+    name: :class:`str`
+        The integration name.
+    guild: :class:`Guild`
+        The guild of the integration.
+    type: :class:`str`
+        The integration type (i.e. Twitch).
+    enabled: :class:`bool`
+        Whether the integration is currently enabled.
+    user: :class:`User`
+        The user that added this integration.
+    account: :class:`IntegrationAccount`
+        The integration account information.
+    application: :class:`IntegrationApplication`
+        The application tied to this integration.
+    """
+
+    __slots__ = Integration.__slots__ + ('application',)
+
+    def _from_data(self, data: IntegrationPayload) -> None:
+        super()._from_data(data)
+        self.application = IntegrationApplication(data=data['application'], state=self._state)
+
+
+def _integration_factory(value):
+    if value == 'discord':
+        return BotIntegration, value
+    elif value in ('twitch', 'youtube'):
+        return StreamIntegration, value
+    else:
+        return Integration, value
diff --git a/docs/api.rst b/docs/api.rst
index 649e8d339..3971d4b7e 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -2986,6 +2986,15 @@ Integration
 .. autoclass:: IntegrationAccount()
     :members:
 
+.. autoclass:: BotIntegration()
+    :members:
+
+.. autoclass:: IntegrationApplication()
+    :members:
+
+.. autoclass:: StreamIntegration()
+    :members:
+
 Interaction
 ~~~~~~~~~~~~