1
|
|
|
""" |
2
|
|
|
byceps.blueprints.site.connected_external_accounts.discord.views |
3
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
4
|
|
|
|
5
|
|
|
Connector to Discord |
6
|
|
|
|
7
|
|
|
:Copyright: 2021-2023 Jan Korneffel |
8
|
|
|
:License: Revised BSD (see `LICENSE` file for details) |
9
|
|
|
""" |
10
|
|
|
|
11
|
1 |
|
from datetime import datetime |
12
|
1 |
|
from urllib import parse |
13
|
|
|
|
14
|
1 |
|
from flask import current_app, g, redirect, request, url_for |
15
|
1 |
|
import requests |
16
|
|
|
|
17
|
1 |
|
from byceps.services.connected_external_accounts import ( |
18
|
|
|
connected_external_accounts_service, |
19
|
|
|
) |
20
|
1 |
|
from byceps.util.framework.blueprint import create_blueprint |
21
|
1 |
|
from byceps.util.framework.flash import flash_error, flash_success |
22
|
1 |
|
from byceps.util.views import login_required, redirect_to, respond_no_content |
23
|
|
|
|
24
|
|
|
|
25
|
1 |
|
blueprint = create_blueprint('connected_external_accounts_discord', __name__) |
26
|
|
|
|
27
|
|
|
|
28
|
1 |
|
API_URL_BASE = 'https://discord.com/api/v10' |
29
|
|
|
|
30
|
1 |
|
SERVICE_NAME = 'discord' |
31
|
|
|
|
32
|
|
|
|
33
|
1 |
|
@blueprint.get('/connect') |
34
|
1 |
|
@login_required |
35
|
1 |
|
def connect(): |
36
|
|
|
"""Connect account with Discord via OAuth2.""" |
37
|
|
|
client_id = current_app.config.get('DISCORD_CLIENT_ID') |
38
|
|
|
if not client_id: |
39
|
|
|
flash_error('Verbindung mit Discord derzeit nicht möglich.') |
40
|
|
|
return redirect_to('user_settings.view') |
41
|
|
|
|
42
|
|
|
query_string_data = { |
43
|
|
|
'client_id': client_id, |
44
|
|
|
'redirect_uri': url_for('.connect_verify', _external=True), |
45
|
|
|
'response_type': 'code', |
46
|
|
|
'scope': 'identify', |
47
|
|
|
} |
48
|
|
|
query_string = parse.urlencode(query_string_data) |
49
|
|
|
auth_url = API_URL_BASE + '/oauth2/authorize?' + query_string |
50
|
|
|
return redirect(auth_url) |
51
|
|
|
|
52
|
|
|
|
53
|
1 |
|
def error(): |
54
|
|
|
flash_error('Verbindung mit Discord-Account fehlgeschlagen.') |
55
|
|
|
return redirect_to('user_settings.view') |
56
|
|
|
|
57
|
|
|
|
58
|
1 |
|
@blueprint.get('/connect/verify') |
59
|
1 |
|
@login_required |
60
|
1 |
|
def connect_verify(): |
61
|
|
|
"""Verify signed Discord parameters.""" |
62
|
|
|
params = request.args.to_dict() |
63
|
|
|
|
64
|
|
|
if 'code' not in params: |
65
|
|
|
return error() |
66
|
|
|
|
67
|
|
|
client_id = current_app.config.get('DISCORD_CLIENT_ID') |
68
|
|
|
client_secret = current_app.config.get('DISCORD_CLIENT_SECRET') |
69
|
|
|
if not client_id or not client_secret: |
70
|
|
|
flash_error('Verbindung mit Discord derzeit nicht möglich.') |
71
|
|
|
return redirect_to('user_settings.view') |
72
|
|
|
|
73
|
|
|
auth = (client_id, client_secret) |
74
|
|
|
token_request_data = { |
75
|
|
|
'scope': 'identify', |
76
|
|
|
'redirect_uri': url_for('.connect_verify', _external=True), |
77
|
|
|
'code': params['code'], |
78
|
|
|
'grant_type': 'authorization_code', |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
# Try to obtain access token with given authorization code. |
82
|
|
|
token_response = requests.post( |
83
|
|
|
API_URL_BASE + '/oauth2/token', |
84
|
|
|
auth=auth, |
85
|
|
|
data=token_request_data, |
86
|
|
|
timeout=10, |
87
|
|
|
) |
88
|
|
|
reponse_token = token_response.json() |
89
|
|
|
if not token_response.ok or ('access_token' not in reponse_token): |
90
|
|
|
return error() |
91
|
|
|
|
92
|
|
|
# Get user info. |
93
|
|
|
access_token = reponse_token['access_token'] |
94
|
|
|
headers = {'Authorization': f'Bearer {access_token}'} |
95
|
|
|
user_response = requests.get( |
96
|
|
|
API_URL_BASE + '/users/@me', headers=headers, timeout=10 |
97
|
|
|
) |
98
|
|
|
user_response_data = user_response.json() |
99
|
|
|
if not user_response.ok: |
100
|
|
|
return error() |
101
|
|
|
|
102
|
|
|
discord_id = user_response_data['id'] |
103
|
|
|
discord_username = user_response_data['username'] |
104
|
|
|
discord_discriminator = user_response_data['discriminator'] |
105
|
|
|
|
106
|
|
|
# Store Discord id + user-name. |
107
|
|
|
now = datetime.utcnow() |
108
|
|
|
external_id = discord_id |
109
|
|
|
external_name = f'{discord_username}#{discord_discriminator}' |
110
|
|
|
connected_external_accounts_service.connect_external_account( |
111
|
|
|
now, |
112
|
|
|
g.user.id, |
113
|
|
|
SERVICE_NAME, |
114
|
|
|
external_id=external_id, |
115
|
|
|
external_name=external_name, |
116
|
|
|
) |
117
|
|
|
|
118
|
|
|
flash_success('Discord-Account erfolgreich verbunden.') |
119
|
|
|
return redirect_to('user_settings.view') |
120
|
|
|
|
121
|
|
|
|
122
|
1 |
|
@blueprint.delete('') |
123
|
1 |
|
@login_required |
124
|
1 |
|
@respond_no_content |
125
|
1 |
|
def remove(): |
126
|
|
|
"""Unlink Discord account.""" |
127
|
|
|
connected_external_account = connected_external_accounts_service.find_connected_external_account_for_user_and_service( |
128
|
|
|
g.user.id, SERVICE_NAME |
129
|
|
|
) |
130
|
|
|
if not connected_external_account: |
131
|
|
|
return |
132
|
|
|
|
133
|
|
|
disconnect_result = ( |
134
|
|
|
connected_external_accounts_service.disconnect_external_account( |
135
|
|
|
connected_external_account.id |
136
|
|
|
) |
137
|
|
|
) |
138
|
|
|
disconnect_result.unwrap() |
139
|
|
|
|