-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
/
Copy pathdiscord.py
144 lines (119 loc) · 6.02 KB
/
discord.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
from __future__ import annotations
import logging
from collections.abc import Sequence
import sentry_sdk
from django.http import HttpResponse, JsonResponse
from rest_framework import status
from rest_framework.request import Request
from sentry.hybridcloud.outbox.category import WebhookProviderIdentifier
from sentry.integrations.discord.message_builder.base.flags import EPHEMERAL_FLAG
from sentry.integrations.discord.requests.base import DiscordRequest, DiscordRequestError
from sentry.integrations.discord.views.link_identity import DiscordLinkIdentityView
from sentry.integrations.discord.views.unlink_identity import DiscordUnlinkIdentityView
from sentry.integrations.discord.webhooks.base import DiscordInteractionsEndpoint
from sentry.integrations.discord.webhooks.types import DiscordResponseTypes
from sentry.integrations.middleware.hybrid_cloud.parser import (
BaseRequestParser,
create_async_request_payload,
)
from sentry.integrations.models.integration import Integration
from sentry.integrations.models.organization_integration import OrganizationIntegration
from sentry.integrations.types import EXTERNAL_PROVIDERS, ExternalProviders
from sentry.integrations.web.discord_extension_configuration import (
DiscordExtensionConfigurationView,
)
from sentry.middleware.integrations.tasks import convert_to_async_discord_response
from sentry.types.region import Region
logger = logging.getLogger(__name__)
class DiscordRequestParser(BaseRequestParser):
provider = EXTERNAL_PROVIDERS[ExternalProviders.DISCORD]
webhook_identifier = WebhookProviderIdentifier.DISCORD
control_classes = [
DiscordLinkIdentityView,
DiscordUnlinkIdentityView,
DiscordExtensionConfigurationView,
]
# Dynamically set to avoid RawPostDataException from double reads
_discord_request: DiscordRequest | None = None
# https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-interaction-callback-type
async_response_data = {
"type": DiscordResponseTypes.DEFERRED_MESSAGE,
"data": {"flags": EPHEMERAL_FLAG},
}
@property
def discord_request(self) -> DiscordRequest | None:
if self._discord_request is not None:
return self._discord_request
if self.view_class != DiscordInteractionsEndpoint:
return None
drf_request: Request = DiscordInteractionsEndpoint().initialize_request(self.request)
self._discord_request: DiscordRequest = self.view_class.discord_request_class(drf_request)
return self._discord_request
def get_async_region_response(self, regions: Sequence[Region]) -> HttpResponse:
if self.discord_request:
convert_to_async_discord_response.apply_async(
kwargs={
"region_names": [r.name for r in regions],
"payload": create_async_request_payload(self.request),
"response_url": self.discord_request.response_url,
}
)
return JsonResponse(data=self.async_response_data, status=status.HTTP_200_OK)
def get_integration_from_request(self) -> Integration | None:
if self.view_class in self.control_classes:
# We don't need to identify an integration since we're handling these on Control
return None
discord_request = self.discord_request
if self.view_class == DiscordInteractionsEndpoint and discord_request:
if discord_request.guild_id is None:
return None
return Integration.objects.filter(
provider=self.provider,
external_id=discord_request.guild_id,
).first()
with sentry_sdk.isolation_scope() as scope:
scope.set_extra("path", self.request.path)
scope.set_extra("guild_id", str(discord_request.guild_id if discord_request else None))
sentry_sdk.capture_exception(
Exception(
f"Unexpected view class in {self.provider} request parser: {self.view_class.__name__ if self.view_class else None}"
)
)
logger.info(
"%s.get_integration_from_request.no_view_class",
self.provider,
extra={"path": self.request.path},
)
return None
def get_response(self):
if self.view_class in self.control_classes:
return self.get_response_from_control_silo()
is_discord_interactions_endpoint = self.view_class == DiscordInteractionsEndpoint
# Handle any Requests that doesn't depend on Integration/Organization prior to fetching the Regions.
if is_discord_interactions_endpoint and self.discord_request:
# Discord will do automated, routine security checks against the interactions endpoint, including
# purposefully sending invalid signatures.
try:
self.discord_request.validate()
except DiscordRequestError:
return HttpResponse(status=status.HTTP_401_UNAUTHORIZED)
if self.discord_request.is_ping():
return DiscordInteractionsEndpoint.respond_ping()
try:
regions = self.get_regions_from_organizations()
except (Integration.DoesNotExist, OrganizationIntegration.DoesNotExist):
return self.get_default_missing_integration_response()
if is_discord_interactions_endpoint and self.discord_request:
if self.discord_request.is_command():
return (
self.get_async_region_response(regions=[regions[0]])
if self.discord_request.response_url
else self.get_response_from_first_region()
)
if self.discord_request.is_message_component():
return (
self.get_async_region_response(regions=regions)
if self.discord_request.response_url
else self.get_response_from_all_regions()
)
return self.get_response_from_control_silo()