Stop using the patch method for setting roles.

Switch to dedicated add/remove endpoints.
Allow setting max cache age to None for infinite.

Apparently patch has issues.

Thanks @TargetZ3R0 and Discord devs <3
This commit is contained in:
Adarnof 2018-03-19 17:05:29 -04:00
parent b4c395f116
commit 21e896642a
2 changed files with 65 additions and 38 deletions

View File

@ -32,7 +32,7 @@ SCOPES = [
'guilds.join', 'guilds.join',
] ]
GROUP_CACHE_MAX_AGE = int(getattr(settings, 'DISCORD_GROUP_CACHE_MAX_AGE', 2 * 60 * 60)) # 2 hours default GROUP_CACHE_MAX_AGE = getattr(settings, 'DISCORD_GROUP_CACHE_MAX_AGE', 2 * 60 * 60) # 2 hours default
class DiscordApiException(Exception): class DiscordApiException(Exception):
@ -301,13 +301,38 @@ class DiscordOAuthManager:
def _create_group(name): def _create_group(name):
return DiscordOAuthManager.__generate_role(name) return DiscordOAuthManager.__generate_role(name)
@staticmethod
def _get_user(user_id):
custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id)
r = requests.get(path, headers=custom_headers)
r.raise_for_status()
return r.json()
@staticmethod
def _get_user_roles(user_id):
user = DiscordOAuthManager._get_user(user_id)
return user['roles']
@staticmethod
def _modify_user_role(user_id, role_id, method):
custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id) + "/roles/" + str(
role_id)
r = getattr(requests, method)(path, headers=custom_headers)
r.raise_for_status()
logger.debug("%s role %s for user %s" % (method, role_id, user_id))
@staticmethod @staticmethod
@api_backoff @api_backoff
def update_groups(user_id, groups): def update_groups(user_id, groups):
custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
group_ids = [DiscordOAuthManager._group_name_to_id(DiscordOAuthManager._sanitize_group_name(g)) for g in groups] group_ids = [DiscordOAuthManager._group_name_to_id(DiscordOAuthManager._sanitize_group_name(g)) for g in groups]
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id) user_group_ids = DiscordOAuthManager._get_user_roles(user_id)
data = {'roles': group_ids} for g in group_ids:
r = requests.patch(path, headers=custom_headers, json=data) if g not in user_group_ids:
logger.debug("Received status code %s after setting user roles" % r.status_code) DiscordOAuthManager._modify_user_role(user_id, g, 'put')
r.raise_for_status() time.sleep(1) # we're gonna be hammering the API here
for g in user_group_ids:
if g not in group_ids:
DiscordOAuthManager._modify_user_role(user_id, g, 'delete')
time.sleep(1)

View File

@ -327,51 +327,51 @@ class DiscordManagerTestCase(TestCase):
# Assert # Assert
self.assertTrue(result) self.assertTrue(result)
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_user_roles')
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_groups') @mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_groups')
@requests_mock.Mocker() @requests_mock.Mocker()
def test_update_groups(self, group_cache, m): def test_update_groups(self, group_cache, user_roles, m):
# Arrange # Arrange
groups = ['Member', 'Blue', 'SpecialGroup'] groups = ['Member', 'Blue', 'SpecialGroup']
group_cache.return_value = [{'id': 111, 'name': 'Member'}, group_cache.return_value = [{'id': '111', 'name': 'Member'},
{'id': 222, 'name': 'Blue'}, {'id': '222', 'name': 'Blue'},
{'id': 333, 'name': 'SpecialGroup'}, {'id': '333', 'name': 'SpecialGroup'},
{'id': 444, 'name': 'NotYourGroup'}] {'id': '444', 'name': 'NotYourGroup'}]
user_roles.return_value = ['444']
headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN} headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
user_id = 12345 user_id = 12345
request_url = '{}/guilds/{}/members/{}'.format(manager.DISCORD_URL, settings.DISCORD_GUILD_ID, user_id) user_request_url = '{}/guilds/{}/members/{}'.format(manager.DISCORD_URL, settings.DISCORD_GUILD_ID, user_id)
group_request_urls = ['{}/guilds/{}/members/{}/roles/{}'.format(manager.DISCORD_URL, settings.DISCORD_GUILD_ID, user_id, g['id']) for g in group_cache.return_value]
m.patch(request_url, m.patch(user_request_url, request_headers=headers)
request_headers=headers) [m.put(url, request_headers=headers) for url in group_request_urls[:-1]]
m.delete(group_request_urls[-1], request_headers=headers)
# Act # Act
DiscordOAuthManager.update_groups(user_id, groups) DiscordOAuthManager.update_groups(user_id, groups)
# Assert # Assert
self.assertEqual(len(m.request_history), 1, 'Must be one HTTP call made') self.assertEqual(len(m.request_history), 4, 'Must be 4 HTTP calls made')
history = json.loads(m.request_history[0].text)
self.assertIn('roles', history, "'The request must send JSON object with the 'roles' key")
self.assertIn(111, history['roles'], 'The group id 111 must be added to the request')
self.assertIn(222, history['roles'], 'The group id 222 must be added to the request')
self.assertIn(333, history['roles'], 'The group id 333 must be added to the request')
self.assertNotIn(444, history['roles'], 'The group id 444 must NOT be added to the request')
@mock.patch(MODULE_PATH + '.manager.cache') @mock.patch(MODULE_PATH + '.manager.cache')
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_groups') @mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_user_roles')
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._group_name_to_id')
@requests_mock.Mocker() @requests_mock.Mocker()
def test_update_groups_backoff(self, group_cache, djcache, m): def test_update_groups_backoff(self, name_to_id, user_groups, djcache, m):
# Arrange # Arrange
groups = ['Member'] groups = ['Member']
group_cache.return_value = [{'id': 111, 'name': 'Member'}] user_groups.return_value = []
name_to_id.return_value = '111'
headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN} headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
user_id = 12345 user_id = 12345
request_url = '{}/guilds/{}/members/{}'.format(manager.DISCORD_URL, settings.DISCORD_GUILD_ID, user_id) request_url = '{}/guilds/{}/members/{}/roles/{}'.format(manager.DISCORD_URL, settings.DISCORD_GUILD_ID, user_id, name_to_id.return_value)
djcache.get.return_value = None # No existing backoffs in cache djcache.get.return_value = None # No existing backoffs in cache
m.patch(request_url, m.put(request_url,
request_headers=headers, request_headers=headers,
headers={'Retry-After': '200000'}, headers={'Retry-After': '200000'},
status_code=429) status_code=429)
@ -391,20 +391,22 @@ class DiscordManagerTestCase(TestCase):
self.assertTrue(datetime.datetime.strptime(args[1], manager.cache_time_format) > datetime.datetime.now()) self.assertTrue(datetime.datetime.strptime(args[1], manager.cache_time_format) > datetime.datetime.now())
@mock.patch(MODULE_PATH + '.manager.cache') @mock.patch(MODULE_PATH + '.manager.cache')
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_groups') @mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_user_roles')
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._group_name_to_id')
@requests_mock.Mocker() @requests_mock.Mocker()
def test_update_groups_global_backoff(self, group_cache, djcache, m): def test_update_groups_global_backoff(self, name_to_id, user_groups, djcache, m):
# Arrange # Arrange
groups = ['Member'] groups = ['Member']
group_cache.return_value = [{'id': 111, 'name': 'Member'}] user_groups.return_value = []
name_to_id.return_value = '111'
headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN} headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
user_id = 12345 user_id = 12345
request_url = '{}/guilds/{}/members/{}'.format(manager.DISCORD_URL, settings.DISCORD_GUILD_ID, user_id) request_url = '{}/guilds/{}/members/{}/roles/{}'.format(manager.DISCORD_URL, settings.DISCORD_GUILD_ID, user_id, name_to_id.return_value)
djcache.get.return_value = None # No existing backoffs in cache djcache.get.return_value = None # No existing backoffs in cache
m.patch(request_url, m.put(request_url,
request_headers=headers, request_headers=headers,
headers={'Retry-After': '200000', 'X-RateLimit-Global': 'true'}, headers={'Retry-After': '200000', 'X-RateLimit-Global': 'true'},
status_code=429) status_code=429)