mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2026-02-04 06:06:19 +01:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26eebec918 | ||
|
|
072d1b9db6 | ||
|
|
8c957e9cb7 | ||
|
|
69a686a98a | ||
|
|
c69b41738b | ||
|
|
a096023553 | ||
|
|
5eecee49f5 | ||
|
|
d8f4d56dd8 | ||
|
|
d58ac8a718 | ||
|
|
d503243e12 | ||
|
|
5962f0f29f | ||
|
|
a2f4226381 | ||
|
|
1ce041b90a | ||
|
|
91ec924acc | ||
|
|
0f1535161c | ||
|
|
1caa4b6baa | ||
|
|
0474fa6d17 | ||
|
|
e1907d9d17 | ||
|
|
2e214e442c |
@@ -4,5 +4,5 @@ from __future__ import absolute_import, unicode_literals
|
||||
# Django starts so that shared_task will use this app.
|
||||
from .celeryapp import app as celery_app # noqa
|
||||
|
||||
__version__ = '1.15.6'
|
||||
__version__ = '1.15.8'
|
||||
NAME = 'Alliance Auth v%s' % __version__
|
||||
|
||||
@@ -482,7 +482,6 @@ TEAMSPEAK3_PUBLIC_URL = os.environ.get('AA_TEAMSPEAK3_PUBLIC_URL', 'example.com'
|
||||
######################################
|
||||
# DISCORD_GUILD_ID - ID of the guild to manage
|
||||
# DISCORD_BOT_TOKEN - oauth token of the app bot user
|
||||
# DISCORD_INVITE_CODE - invite code to the server
|
||||
# DISCORD_APP_ID - oauth app client ID
|
||||
# DISCORD_APP_SECRET - oauth app secret
|
||||
# DISCORD_CALLBACK_URL - oauth callback url
|
||||
@@ -490,7 +489,6 @@ TEAMSPEAK3_PUBLIC_URL = os.environ.get('AA_TEAMSPEAK3_PUBLIC_URL', 'example.com'
|
||||
######################################
|
||||
DISCORD_GUILD_ID = os.environ.get('AA_DISCORD_GUILD_ID', '')
|
||||
DISCORD_BOT_TOKEN = os.environ.get('AA_DISCORD_BOT_TOKEN', '')
|
||||
DISCORD_INVITE_CODE = os.environ.get('AA_DISCORD_INVITE_CODE', '')
|
||||
DISCORD_APP_ID = os.environ.get('AA_DISCORD_APP_ID', '')
|
||||
DISCORD_APP_SECRET = os.environ.get('AA_DISCORD_APP_SECRET', '')
|
||||
DISCORD_CALLBACK_URL = os.environ.get('AA_DISCORD_CALLBACK_URL', 'http://example.com/discord/callback')
|
||||
|
||||
@@ -57,7 +57,7 @@ Enter the folder by issuing `cd allianceauth`
|
||||
|
||||
Ensure you're on the latest version with the following:
|
||||
|
||||
git tag | sort -n | tail -1 | xargs git checkout
|
||||
git checkout v1.15.7
|
||||
|
||||
Python package dependencies can be installed from the requirements file:
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ Enter the folder by issuing `cd allianceauth`
|
||||
|
||||
Ensure you're on the latest version with the following:
|
||||
|
||||
git tag | sort -n | tail -1 | xargs git checkout
|
||||
git checkout v1.15.7
|
||||
|
||||
Python package dependencies can be installed from the requirements file:
|
||||
|
||||
|
||||
@@ -21,13 +21,6 @@ with a server ID of `120631096835571712`
|
||||
|
||||
Update settings.py, inputting the server ID as `DISCORD_GUILD_ID`
|
||||
|
||||
### Generating an Invite
|
||||
Still on the Discord site, in your new server, an invite needs to be generated for users to join. If you with for users to initially join a different channel than `#general`, create it and follow the steps below, substituting this channel for `#general`.
|
||||
|
||||
On the left bar under the Text Channels heading, hover over `#general` on the right site. There are two icons, a box with an arrow and a gear. Press the box, then on the bottom left select Advanced Settings. Set the expiration to never, and no limit on uses. Press generate.
|
||||
|
||||
This returns a code that looks like `https://discord.gg/0fmA8MyXV6qt7XAZ`. The part after the last slash, `0fmA8MyXV6qt7XAZ`, is the invite code. Update settings.py, inputting this invite code as `DISCORD_INVITE_CODE`
|
||||
|
||||
### Registering an Application
|
||||
|
||||
Navigate to the [Discord Developers site.](https://discordapp.com/developers/applications/me) Press the plus sign to create a new application.
|
||||
|
||||
@@ -39,13 +39,14 @@ def hr_application_management_view(request):
|
||||
except EveCharacter.DoesNotExist:
|
||||
pass
|
||||
if request.user.is_superuser:
|
||||
corp_applications = Application.objects.filter(approved=None)
|
||||
finished_corp_applications = Application.objects.exclude(approved=None)
|
||||
corp_applications = Application.objects.filter(approved=None).order_by('-created')
|
||||
finished_corp_applications = Application.objects.exclude(approved=None).order_by('-created')
|
||||
elif request.user.has_perm('auth.human_resources') and main_char:
|
||||
if ApplicationForm.objects.filter(corp__corporation_id=main_char.corporation_id).exists():
|
||||
app_form = ApplicationForm.objects.get(corp__corporation_id=main_char.corporation_id)
|
||||
corp_applications = Application.objects.filter(form=app_form).filter(approved=None)
|
||||
finished_corp_applications = Application.objects.filter(form=app_form).filter(approved__in=[True, False])
|
||||
corp_applications = Application.objects.filter(form=app_form).filter(approved=None).order_by('-created')
|
||||
finished_corp_applications = Application.objects.filter(form=app_form).filter(
|
||||
approved__in=[True, False]).order_by('-created')
|
||||
logger.debug("Retrieved %s personal, %s corp applications for %s" % (
|
||||
len(request.user.applications.all()), len(corp_applications), request.user))
|
||||
context = {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from __future__ import unicode_literals
|
||||
import requests
|
||||
import json
|
||||
import re
|
||||
import requests
|
||||
import math
|
||||
from django.conf import settings
|
||||
from requests_oauthlib import OAuth2Session
|
||||
@@ -25,8 +24,8 @@ Previously all we asked for was permission to kick members, manage roles, and ma
|
||||
Users have reported weird unauthorized errors we don't understand. So now we ask for full server admin.
|
||||
It's almost fixed the problem.
|
||||
"""
|
||||
# kick members, manage roles, manage nicknames
|
||||
# BOT_PERMISSIONS = 0x00000002 + 0x10000000 + 0x08000000
|
||||
# kick members, manage roles, manage nicknames, create instant invite
|
||||
# BOT_PERMISSIONS = 0x00000002 + 0x10000000 + 0x08000000 + 0x00000001
|
||||
BOT_PERMISSIONS = 0x00000008
|
||||
|
||||
# get user ID, accept invite
|
||||
@@ -35,7 +34,7 @@ SCOPES = [
|
||||
'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):
|
||||
@@ -118,8 +117,7 @@ def api_backoff(func):
|
||||
global_ratelimit=bool(existing_global_backoff)
|
||||
)
|
||||
logger.debug("Calling API calling function")
|
||||
func(*args, **kwargs)
|
||||
break
|
||||
return func(*args, **kwargs)
|
||||
except requests.HTTPError as e:
|
||||
if e.response.status_code == 429:
|
||||
try:
|
||||
@@ -164,12 +162,11 @@ class DiscordOAuthManager:
|
||||
|
||||
@staticmethod
|
||||
def _sanitize_name(name):
|
||||
return re.sub('[^\w.-]', '', name)[:32]
|
||||
return name[:32]
|
||||
|
||||
@staticmethod
|
||||
def _sanitize_groupname(name):
|
||||
name = name.strip(' _')
|
||||
return DiscordOAuthManager._sanitize_name(name)
|
||||
def _sanitize_group_name(name):
|
||||
return name[:100]
|
||||
|
||||
@staticmethod
|
||||
def generate_bot_add_url():
|
||||
@@ -188,23 +185,33 @@ class DiscordOAuthManager:
|
||||
return token
|
||||
|
||||
@staticmethod
|
||||
def add_user(code):
|
||||
def add_user(code, groups, nickname=None):
|
||||
try:
|
||||
token = DiscordOAuthManager._process_callback_code(code)['access_token']
|
||||
logger.debug("Received token from OAuth")
|
||||
|
||||
custom_headers = {'accept': 'application/json', 'authorization': 'Bearer ' + token}
|
||||
path = DISCORD_URL + "/invites/" + str(settings.DISCORD_INVITE_CODE)
|
||||
r = requests.post(path, headers=custom_headers)
|
||||
logger.debug("Got status code %s after accepting Discord invite" % r.status_code)
|
||||
r.raise_for_status()
|
||||
|
||||
path = DISCORD_URL + "/users/@me"
|
||||
r = requests.get(path, headers=custom_headers)
|
||||
logger.debug("Got status code %s after retrieving Discord profile" % r.status_code)
|
||||
r.raise_for_status()
|
||||
|
||||
user_id = r.json()['id']
|
||||
|
||||
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id)
|
||||
group_ids = [DiscordOAuthManager._group_name_to_id(DiscordOAuthManager._sanitize_group_name(g)) for g in
|
||||
groups]
|
||||
data = {
|
||||
'roles': group_ids,
|
||||
'access_token': token,
|
||||
}
|
||||
if nickname:
|
||||
data['nick'] = DiscordOAuthManager._sanitize_name(nickname)
|
||||
custom_headers['authorization'] = 'Bot ' + settings.DISCORD_BOT_TOKEN
|
||||
r = requests.put(path, headers=custom_headers, json=data)
|
||||
logger.debug("Got status code %s after joining Discord server" % r.status_code)
|
||||
r.raise_for_status()
|
||||
|
||||
logger.info("Added Discord user ID %s to server." % user_id)
|
||||
return user_id
|
||||
except:
|
||||
@@ -212,6 +219,7 @@ class DiscordOAuthManager:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
@api_backoff
|
||||
def update_nickname(user_id, nickname):
|
||||
try:
|
||||
nickname = DiscordOAuthManager._sanitize_name(nickname)
|
||||
@@ -261,7 +269,7 @@ class DiscordOAuthManager:
|
||||
|
||||
@staticmethod
|
||||
def _group_name_to_id(name):
|
||||
name = DiscordOAuthManager._sanitize_groupname(name)
|
||||
name = DiscordOAuthManager._sanitize_group_name(name)
|
||||
|
||||
def get_or_make_role():
|
||||
groups = DiscordOAuthManager._get_groups()
|
||||
@@ -272,42 +280,61 @@ class DiscordOAuthManager:
|
||||
return cache.get_or_set(DiscordOAuthManager._generate_cache_role_key(name), get_or_make_role, GROUP_CACHE_MAX_AGE)
|
||||
|
||||
@staticmethod
|
||||
def __generate_role():
|
||||
def __generate_role(name, **kwargs):
|
||||
custom_headers = {'accept': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/roles"
|
||||
r = requests.post(path, headers=custom_headers)
|
||||
data = {'name': name}
|
||||
data.update(kwargs)
|
||||
r = requests.post(path, headers=custom_headers, json=data)
|
||||
logger.debug("Received status code %s after generating new role." % r.status_code)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
@staticmethod
|
||||
def __edit_role(role_id, name, color=0, hoist=True, permissions=36785152):
|
||||
def __edit_role(role_id, **kwargs):
|
||||
custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||
data = {
|
||||
'color': color,
|
||||
'hoist': hoist,
|
||||
'name': name,
|
||||
'permissions': permissions,
|
||||
}
|
||||
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/roles/" + str(role_id)
|
||||
r = requests.patch(path, headers=custom_headers, data=json.dumps(data))
|
||||
r = requests.patch(path, headers=custom_headers, json=kwargs)
|
||||
logger.debug("Received status code %s after editing role id %s" % (r.status_code, role_id))
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
@staticmethod
|
||||
def _create_group(name):
|
||||
role = DiscordOAuthManager.__generate_role()
|
||||
return DiscordOAuthManager.__edit_role(role['id'], 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
|
||||
@api_backoff
|
||||
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_groupname(g)) for g in groups]
|
||||
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id)
|
||||
data = {'roles': group_ids}
|
||||
r = requests.patch(path, headers=custom_headers, json=data)
|
||||
logger.debug("Received status code %s after setting user roles" % r.status_code)
|
||||
r.raise_for_status()
|
||||
|
||||
group_ids = [DiscordOAuthManager._group_name_to_id(DiscordOAuthManager._sanitize_group_name(g)) for g in groups]
|
||||
user_group_ids = DiscordOAuthManager._get_user_roles(user_id)
|
||||
for g in group_ids:
|
||||
if g not in user_group_ids:
|
||||
DiscordOAuthManager._modify_user_role(user_id, g, 'put')
|
||||
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)
|
||||
|
||||
@@ -6,7 +6,7 @@ from alliance_auth.celeryapp import app
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
from requests.exceptions import HTTPError
|
||||
from eveonline.managers import EveManager
|
||||
from notifications import notify
|
||||
from services.modules.discord.manager import DiscordOAuthManager, DiscordApiBackoff
|
||||
@@ -21,15 +21,16 @@ class DiscordTasks:
|
||||
|
||||
@classmethod
|
||||
def add_user(cls, user, code):
|
||||
user_id = DiscordOAuthManager.add_user(code)
|
||||
groups = DiscordTasks.get_groups(user)
|
||||
nickname = None
|
||||
if settings.DISCORD_SYNC_NAMES:
|
||||
nickname = DiscordTasks.get_nickname(user)
|
||||
user_id = DiscordOAuthManager.add_user(code, groups, nickname=nickname)
|
||||
if user_id:
|
||||
discord_user = DiscordUser()
|
||||
discord_user.user = user
|
||||
discord_user.uid = user_id
|
||||
discord_user.save()
|
||||
if settings.DISCORD_SYNC_NAMES:
|
||||
cls.update_nickname.delay(user.pk)
|
||||
cls.update_groups.delay(user.pk)
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -64,12 +65,7 @@ class DiscordTasks:
|
||||
user = User.objects.get(pk=pk)
|
||||
logger.debug("Updating discord groups for user %s" % user)
|
||||
if DiscordTasks.has_account(user):
|
||||
groups = []
|
||||
for group in user.groups.all():
|
||||
groups.append(str(group.name))
|
||||
if len(groups) == 0:
|
||||
logger.debug("No syncgroups found for user. Adding empty group.")
|
||||
groups.append('empty')
|
||||
groups = DiscordTasks.get_groups(user)
|
||||
logger.debug("Updating user %s discord groups to %s" % (user, groups))
|
||||
try:
|
||||
DiscordOAuthManager.update_groups(user.discord.uid, groups)
|
||||
@@ -77,6 +73,15 @@ class DiscordTasks:
|
||||
logger.info("Discord group sync API back off for %s, "
|
||||
"retrying in %s seconds" % (user, bo.retry_after_seconds))
|
||||
raise task_self.retry(countdown=bo.retry_after_seconds)
|
||||
except HTTPError as e:
|
||||
if e.response.status_code == 404:
|
||||
try:
|
||||
if e.response.json()['code'] == 10007:
|
||||
# user has left the server
|
||||
DiscordTasks.delete_user(user)
|
||||
return
|
||||
finally:
|
||||
raise e
|
||||
except Exception as e:
|
||||
if task_self:
|
||||
logger.exception("Discord group sync failed for %s, retrying in 10 mins" % user)
|
||||
@@ -97,18 +102,22 @@ class DiscordTasks:
|
||||
|
||||
@staticmethod
|
||||
@app.task(bind=True, name='discord.update_nickname')
|
||||
def update_nickname(self, pk):
|
||||
def update_nickname(task_self, pk):
|
||||
user = User.objects.get(pk=pk)
|
||||
logger.debug("Updating discord nickname for user %s" % user)
|
||||
if DiscordTasks.has_account(user):
|
||||
character = EveManager.get_main_character(user)
|
||||
logger.debug("Updating user %s discord nickname to %s" % (user, character.character_name))
|
||||
name = DiscordTasks.get_nickname(user)
|
||||
logger.debug("Updating user %s discord nickname to %s" % (user, name))
|
||||
try:
|
||||
DiscordOAuthManager.update_nickname(user.discord.uid, character.character_name)
|
||||
DiscordOAuthManager.update_nickname(user.discord.uid, name)
|
||||
except DiscordApiBackoff as bo:
|
||||
logger.info("Discord nickname update API back off for %s, "
|
||||
"retrying in %s seconds" % (user, bo.retry_after_seconds))
|
||||
raise task_self.retry(countdown=bo.retry_after_seconds)
|
||||
except Exception as e:
|
||||
if self:
|
||||
if task_self:
|
||||
logger.exception("Discord nickname sync failed for %s, retrying in 10 mins" % user)
|
||||
raise self.retry(countdown=60 * 10)
|
||||
raise task_self.retry(countdown=60 * 10)
|
||||
else:
|
||||
# Rethrow
|
||||
raise e
|
||||
@@ -126,3 +135,11 @@ class DiscordTasks:
|
||||
@classmethod
|
||||
def disable(cls):
|
||||
DiscordUser.objects.all().delete()
|
||||
|
||||
@staticmethod
|
||||
def get_nickname(user):
|
||||
return EveManager.get_main_character(user).character_name
|
||||
|
||||
@staticmethod
|
||||
def get_groups(user):
|
||||
return [g.name for g in user.groups.all()]
|
||||
|
||||
@@ -208,11 +208,11 @@ class DiscordManagerTestCase(TestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def test__sanitize_groupname(self):
|
||||
test_group_name = ' Group Name_Test_'
|
||||
group_name = DiscordOAuthManager._sanitize_groupname(test_group_name)
|
||||
def test__sanitize_group_name(self):
|
||||
test_group_name = str(10**103)
|
||||
group_name = DiscordOAuthManager._sanitize_group_name(test_group_name)
|
||||
|
||||
self.assertEqual(group_name, 'GroupName_Test')
|
||||
self.assertEqual(group_name, test_group_name[:100])
|
||||
|
||||
def test_generate_Bot_add_url(self):
|
||||
from . import manager
|
||||
@@ -267,18 +267,20 @@ class DiscordManagerTestCase(TestCase):
|
||||
|
||||
headers = {'accept': 'application/json', 'authorization': 'Bearer accesstoken'}
|
||||
|
||||
m.register_uri('POST',
|
||||
manager.DISCORD_URL + '/invites/'+str(settings.DISCORD_INVITE_CODE),
|
||||
request_headers=headers,
|
||||
text='{}')
|
||||
|
||||
m.register_uri('GET',
|
||||
manager.DISCORD_URL + "/users/@me",
|
||||
request_headers=headers,
|
||||
text=json.dumps({'id': "123456"}))
|
||||
|
||||
headers = {'accept': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||
|
||||
m.register_uri('PUT',
|
||||
manager.DISCORD_URL + '/guilds/' + str(settings.DISCORD_GUILD_ID) + '/members/123456',
|
||||
request_headers=headers,
|
||||
text='{}')
|
||||
|
||||
# Act
|
||||
return_value = DiscordOAuthManager.add_user('abcdef')
|
||||
return_value = DiscordOAuthManager.add_user('abcdef', [])
|
||||
|
||||
# Assert
|
||||
self.assertEqual(return_value, '123456')
|
||||
@@ -351,59 +353,59 @@ class DiscordManagerTestCase(TestCase):
|
||||
# Assert
|
||||
self.assertTrue(result)
|
||||
|
||||
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_user_roles')
|
||||
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_groups')
|
||||
@requests_mock.Mocker()
|
||||
def test_update_groups(self, group_cache, m):
|
||||
def test_update_groups(self, group_cache, user_roles, m):
|
||||
from . import manager
|
||||
import json
|
||||
|
||||
# Arrange
|
||||
groups = ['Member', 'Blue', 'Special Group']
|
||||
groups = ['Member', 'Blue', 'SpecialGroup']
|
||||
|
||||
group_cache.return_value = [{'id': 111, 'name': 'Member'},
|
||||
{'id': 222, 'name': 'Blue'},
|
||||
{'id': 333, 'name': 'SpecialGroup'},
|
||||
{'id': 444, 'name': 'NotYourGroup'}]
|
||||
group_cache.return_value = [{'id': '111', 'name': 'Member'},
|
||||
{'id': '222', 'name': 'Blue'},
|
||||
{'id': '333', 'name': 'SpecialGroup'},
|
||||
{'id': '444', 'name': 'NotYourGroup'}]
|
||||
user_roles.return_value = ['444']
|
||||
|
||||
headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||
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,
|
||||
request_headers=headers)
|
||||
m.patch(user_request_url, 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
|
||||
DiscordOAuthManager.update_groups(user_id, groups)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(len(m.request_history), 1, 'Must be one HTTP call 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')
|
||||
self.assertEqual(len(m.request_history), 4, 'Must be 4 HTTP calls made')
|
||||
|
||||
@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()
|
||||
def test_update_groups_backoff(self, group_cache, djcache, m):
|
||||
def test_update_groups_backoff(self, name_to_id, user_groups, djcache, m):
|
||||
from . import manager
|
||||
|
||||
# Arrange
|
||||
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}
|
||||
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
|
||||
|
||||
m.patch(request_url,
|
||||
request_headers=headers,
|
||||
headers={'Retry-After': '200000'},
|
||||
status_code=429)
|
||||
m.put(request_url,
|
||||
request_headers=headers,
|
||||
headers={'Retry-After': '200000'},
|
||||
status_code=429)
|
||||
|
||||
# Act & Assert
|
||||
with self.assertRaises(manager.DiscordApiBackoff) as bo:
|
||||
@@ -420,25 +422,27 @@ class DiscordManagerTestCase(TestCase):
|
||||
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.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()
|
||||
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):
|
||||
from . import manager
|
||||
|
||||
# Arrange
|
||||
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}
|
||||
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
|
||||
|
||||
m.patch(request_url,
|
||||
request_headers=headers,
|
||||
headers={'Retry-After': '200000', 'X-RateLimit-Global': 'true'},
|
||||
status_code=429)
|
||||
m.put(request_url,
|
||||
request_headers=headers,
|
||||
headers={'Retry-After': '200000', 'X-RateLimit-Global': 'true'},
|
||||
status_code=429)
|
||||
|
||||
# Act & Assert
|
||||
with self.assertRaises(manager.DiscordApiBackoff) as bo:
|
||||
|
||||
@@ -8,7 +8,7 @@ from hashlib import md5
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
GROUP_CACHE_MAX_AGE = int(getattr(settings, 'DISCOURSE_GROUP_CACHE_MAX_AGE', 2 * 60 * 60)) # default 2 hours
|
||||
GROUP_CACHE_MAX_AGE = getattr(settings, 'DISCOURSE_GROUP_CACHE_MAX_AGE', 2 * 60 * 60) # default 2 hours
|
||||
|
||||
|
||||
class DiscourseError(Exception):
|
||||
@@ -24,7 +24,7 @@ class DiscourseError(Exception):
|
||||
ENDPOINTS = {
|
||||
'groups': {
|
||||
'list': {
|
||||
'path': "/admin/groups.json",
|
||||
'path': "/groups/search.json",
|
||||
'method': 'get',
|
||||
'args': {
|
||||
'required': [],
|
||||
|
||||
@@ -118,9 +118,7 @@
|
||||
<h4 class="panel-title">
|
||||
<a class="collapsed" data-toggle="collapse" data-parent="#accordion"
|
||||
href="#collapseThree" aria-expanded="false"
|
||||
aria-controls="collapseThree">
|
||||
{% blocktrans %}Comments - {{ comments|length }}{% endblocktrans %}
|
||||
</a>
|
||||
aria-controls="collapseThree">{% trans "Comments" %} - {{ comments|length }}</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="collapseThree" class="panel-collapse collapse" role="tabpanel"
|
||||
|
||||
@@ -120,7 +120,6 @@
|
||||
Engineering Complex [XL]
|
||||
</div>
|
||||
{% endifequal %}
|
||||
|
||||
{% ifequal timer.structure "Station" %}
|
||||
<div class="label label-danger">
|
||||
Station
|
||||
@@ -131,6 +130,21 @@
|
||||
TCU
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Refinery[M]" %}
|
||||
<div class="label label-warning">
|
||||
Refinery [M]
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Refinery[L]" %}
|
||||
<div class="label label-warning">
|
||||
Refinery [L]
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Moon Mining Cycle" %}
|
||||
<div class="label label-success">
|
||||
Moon Mining Cycle
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Other" %}
|
||||
<div class="label label-default">
|
||||
Other
|
||||
@@ -265,6 +279,21 @@
|
||||
TCU
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Refinery[M]" %}
|
||||
<div class="label label-warning">
|
||||
Refinery [M]
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Refinery[L]" %}
|
||||
<div class="label label-warning">
|
||||
Refinery [L]
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Moon Mining Cycle" %}
|
||||
<div class="label label-success">
|
||||
Moon Mining Cycle
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Other" %}
|
||||
<div class="label label-default">
|
||||
Other
|
||||
@@ -400,6 +429,21 @@
|
||||
TCU
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Refinery[M]" %}
|
||||
<div class="label label-warning">
|
||||
Refinery [M]
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Refinery[L]" %}
|
||||
<div class="label label-warning">
|
||||
Refinery [L]
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Moon Mining Cycle" %}
|
||||
<div class="label label-success">
|
||||
Moon Mining Cycle
|
||||
</div>
|
||||
{% endifequal %}
|
||||
{% ifequal timer.structure "Other" %}
|
||||
<div class="label label-default">
|
||||
Other
|
||||
|
||||
@@ -5,14 +5,26 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class TimerForm(forms.Form):
|
||||
structure_choices = [('POCO', 'POCO'), ('I-HUB', 'I-HUB'), ('POS[S]', 'POS[S]'),
|
||||
('POS[M]', 'POS[M]'), ('POS[L]', 'POS[L]'), ('Citadel[M]', 'Citadel[M]'),
|
||||
('Citadel[L]', 'Citadel[L]'), ('Citadel[XL]', 'Citadel[XL]'),
|
||||
structure_choices = [('POCO', 'POCO'),
|
||||
('I-HUB', 'I-HUB'),
|
||||
('POS[S]', 'POS[S]'),
|
||||
('POS[M]', 'POS[M]'),
|
||||
('POS[L]', 'POS[L]'),
|
||||
('Citadel[M]', 'Citadel[M]'),
|
||||
('Citadel[L]', 'Citadel[L]'),
|
||||
('Citadel[XL]', 'Citadel[XL]'),
|
||||
('Engineering Complex[M]', 'Engineering Complex[M]'),
|
||||
('Engineering Complex[L]', 'Engineering Complex[L]'),
|
||||
('Engineering Complex[XL]', 'Engineering Complex[XL]'),
|
||||
('Station', 'Station'), ('TCU', 'TCU'), (_('Other'), _('Other'))]
|
||||
objective_choices = [('Friendly', _('Friendly')), ('Hostile', _('Hostile')), ('Neutral', _('Neutral'))]
|
||||
('Refinery[M]', 'Refinery[M]'),
|
||||
('Refinery[L]', 'Refinery[L]'),
|
||||
('Station', 'Station'),
|
||||
('TCU', 'TCU'),
|
||||
('Moon Mining Cycle', 'Moon Mining Cycle'),
|
||||
(_('Other'), _('Other'))]
|
||||
objective_choices = [('Friendly', _('Friendly')),
|
||||
('Hostile', _('Hostile')),
|
||||
('Neutral', _('Neutral'))]
|
||||
|
||||
details = forms.CharField(max_length=254, required=True, label=_('Details'))
|
||||
system = forms.CharField(max_length=254, required=True, label=_("System"))
|
||||
|
||||
Reference in New Issue
Block a user