Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

admin button to auto-gen & link Discord roles #746

Merged
merged 11 commits into from
Jan 10, 2024
3 changes: 3 additions & 0 deletions cardboard/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@
{% if message.level == DEFAULT_MESSAGE_LEVELS.WARNING %}
<div class="alert alert-warning" style="white-space: pre-line" role="alert">{{ message }}</div>
{% endif %}
{% if message.level == DEFAULT_MESSAGE_LEVELS.SUCCESS %}
<div class="alert alert-success" style="white-space: pre-line" role="alert">{{ message }}</div>
{% endif %}
{% endfor %}

<div class="container-fluid pb-5">
Expand Down
60 changes: 59 additions & 1 deletion chat/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@

from answers.models import Answer
from cardboard.settings import TaskPriority
from puzzles.models import Puzzle
from hunts.models import Hunt
from puzzles.models import Puzzle, PuzzleTag, PuzzleTagColor

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -166,3 +167,60 @@ def handle_sheet_created(puzzle_id):
puzzle.chat_room.send_message(msg, embedded_urls={"Sheet": puzzle.sheet})
except Exception as e:
logger.warn(f"handle_sheet_created failed with error: {e}")


DISCORD_ROLE_COLOR_BLUE = 0x3498DB
DISCORD_ROLE_COLOR_WHITE = 0xFFFFFF


@shared_task(rate_limit="6/m", acks_late=True)
def sync_roles(hunt_slug, service_name):
from django.conf import settings

from chat.models import ChatRole

hunt = Hunt.get_object_or_404(slug=hunt_slug)

chat_service = settings.CHAT_SERVICES[service_name].get_instance()
guild_id = hunt.settings.discord_guild_id

discord_roles_by_name = {r["name"]: r for r in chat_service.get_all_roles(guild_id)}

cardboard_tags = PuzzleTag.objects.filter(hunt=hunt)
existing_chat_roles = ChatRole.objects.filter(hunt=hunt)

default_tag_names = [n[0] for n in PuzzleTag.DEFAULT_TAGS]

for tag in cardboard_tags:
if (
tag.color != PuzzleTagColor.BLUE and tag.color != PuzzleTagColor.WHITE
) or tag.name not in default_tag_names:
continue

# Create corresponding Discord tag, if needed
if tag.name not in discord_roles_by_name:
discord_tag_color = (
DISCORD_ROLE_COLOR_BLUE
if tag.color == PuzzleTagColor.BLUE
else DISCORD_ROLE_COLOR_WHITE
)
new_role_info = chat_service.create_role(
guild_id, tag.name, discord_tag_color
)
discord_roles_by_name[tag.name] = new_role_info
logger.info(f"Created new Discord role {tag.name}")

# Copy tag info into a ChatRole
existing_chat_role = existing_chat_roles.filter(name=tag.name)
if existing_chat_role.exists():
obj = existing_chat_role.first()
if obj.role_id != discord_roles_by_name[tag.name]["id"]:
obj.role_id = discord_roles_by_name[tag.name]["id"]
obj.save()
else:
obj = ChatRole(
hunt=hunt,
name=tag.name,
role_id=discord_roles_by_name[tag.name]["id"],
)
obj.save()
27 changes: 27 additions & 0 deletions discord_lib/discord_chat_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,3 +273,30 @@ def handle_puzzle_rename(self, channel_id, new_name):
)
except Exception as e:
print(f"Error renaming channel: {e}")

def get_all_roles(self, guild_id):
try:
response = requests.get(
f"{DISCORD_BASE_API_URL}/guilds/{guild_id}/roles",
headers=self._headers,
timeout=5,
)
return json.loads(response.content.decode("utf-8"))
except Exception as e:
print(f"Error getting roles from Discord: {e}")

def create_role(self, guild_id, role_name, color):
try:
response = requests.post(
f"{DISCORD_BASE_API_URL}/guilds/{guild_id}/roles",
headers=self._headers,
json={
"name": role_name,
"color": color,
"mentionable": True,
},
timeout=5,
)
return json.loads(response.content.decode("utf-8"))
except Exception as e:
print(f"Error creating Discord role: {e}")
5 changes: 5 additions & 0 deletions hunts/templates/edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,10 @@ <h1>Hunt Settings</h1>
<button type="submit" class="btn btn-primary mx-2">Submit</button>
</div>
</form>

<form action="sync_discord_roles" method="post">
{% csrf_token %}
<button type="submit" class="btn btn-primary mx-2">Sync Discord & Cardboard roles</button>
</form>
</div>
{% endblock %}
5 changes: 5 additions & 0 deletions hunts/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,10 @@
path("<slug:hunt_slug>/", views.ReactHuntView.as_view(), name="all_puzzles_react"),
path("<slug:hunt_slug>/edit", views.edit, name="edit"),
path("<slug:hunt_slug>/stats", views.stats, name="stats"),
path(
"<slug:hunt_slug>/sync_discord_roles",
views.sync_discord_roles,
name="sync_discord_roles",
),
path("<slug:hunt_slug>/drive", views.redirect_to_drive, name="drive"),
]
18 changes: 18 additions & 0 deletions hunts/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,24 @@ def redirect_to_drive(request, hunt_slug):
return redirect("/")


@login_required(login_url="/")
def sync_discord_roles(request, hunt_slug):
if request.method != "POST":
return HttpResponseForbidden()
if not request.user.is_staff:
return HttpResponseForbidden()

import chat.tasks

chat.tasks.sync_roles.delay(
hunt_slug,
settings.CHAT_DEFAULT_SERVICE,
)

messages.success(request, "Discord roles created.")
return redirect(f"/hunts/{hunt_slug}/edit")


class LastAccessedHuntRedirectView(LoginRequiredMixin, RedirectView):
login_url = "/"
pattern_name = "hunts:all_puzzles_react"
Expand Down
Loading