Multiple changes
Started switching to generics Added tickets
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
from django.urls import path
|
||||
|
||||
from .views import ChannelsAPI, ChannelDetail
|
||||
from .views import ChannelsAPI, ChannelDetail, AdminChannelAPI
|
||||
|
||||
app_name = "channels_api"
|
||||
urlpatterns = [
|
||||
path("", view=ChannelsAPI.as_view(), name="list"),
|
||||
path("<str:id>/", view=ChannelDetail.as_view(), name='detail')
|
||||
path("<str:id>/", view=ChannelDetail.as_view(), name='detail'),
|
||||
path("<str:guild_id>/admin/", view=AdminChannelAPI.as_view(), name='admin')
|
||||
]
|
||||
|
||||
@@ -31,7 +31,7 @@ class Channel(models.Model):
|
||||
self.new_patron = data.get('new_patron')
|
||||
if data.get('admin'):
|
||||
self.admin = data.get('admin')
|
||||
|
||||
|
||||
self.save()
|
||||
return self
|
||||
|
||||
@@ -61,16 +61,25 @@ class Channel(models.Model):
|
||||
return create_success_response(channel, status.HTTP_201_CREATED, many=False)
|
||||
|
||||
@classmethod
|
||||
def get_channel_by_id(cls, id):
|
||||
def get_channel_by_id(cls, guild_id, channel_id):
|
||||
try:
|
||||
return cls.objects.get(id=id)
|
||||
return cls.get_guild_channels(guild_id).get(id=channel_id)
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_guild_channels(cls, guild):
|
||||
if isinstance(guild, Guild):
|
||||
return cls.objects.filter(guild=guild)
|
||||
elif isinstance(guild, (str, int)):
|
||||
return cls.objects.filter(guild__id=guild)
|
||||
|
||||
@classmethod
|
||||
def get_admin_channel(cls, guild_id):
|
||||
try:
|
||||
return cls.get_guild_channels(guild_id).get(admin=True)
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return str(id)
|
||||
|
||||
@@ -16,25 +16,53 @@ from .utils import create_success_response
|
||||
class ChannelsAPI(PaginatedAPIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, format=None):
|
||||
guilds = Channel.objects.all()
|
||||
page = self.paginate_queryset(guilds)
|
||||
def get(self, request, guild_id, format=None):
|
||||
channels = Channel.get_guild_channels(guild_id)
|
||||
page = self.paginate_queryset(channels)
|
||||
if page is not None:
|
||||
return create_success_response(page, status.HTTP_200_OK, many=True)
|
||||
|
||||
return create_success_response(guilds, status.HTTP_200_OK, many=True)
|
||||
return create_success_response(channels, status.HTTP_200_OK, many=True)
|
||||
|
||||
def post(self, request, format=None):
|
||||
data = dict(request.data)
|
||||
return Channel.add_new_channel(data)
|
||||
|
||||
|
||||
class AdminChannelAPI(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, guild_id, format=None):
|
||||
channel = Channel.get_admin_channel(guild_id)
|
||||
if channel:
|
||||
return create_success_response(channel, status=status.HTTP_200_OK)
|
||||
return create_error_response('There is no admin channel configured for that guild',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def put(self, request, guild_id, format=None):
|
||||
data = dict(request.data)
|
||||
channel = Channel.get_channel_by_id(guild_id, data['channel'])
|
||||
if channel:
|
||||
channel = channel.update_channel({'admin': True})
|
||||
return create_success_response(channel, status=status.HTTP_202_ACCEPTED)
|
||||
return create_error_response("That channel does not exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def delete(self, request, guild_id, format=None):
|
||||
channel = Channel.get_admin_channel(guild_id)
|
||||
if channel:
|
||||
channel = channel.update_channel({'admin': False})
|
||||
return create_success_response(channel, status=status.HTTP_202_ACCEPTED)
|
||||
return create_error_response("There is no admin channel configured",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
class ChannelDetail(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, id, format=None):
|
||||
def get(self, request, guild_id, channel_id, format=None):
|
||||
try:
|
||||
guild = Channel.objects.get(id=id)
|
||||
guild = Channel.get_channel_by_id(guild_id, channel_id)
|
||||
except ObjectDoesNotExist:
|
||||
return create_error_response("Channel Does not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
@@ -42,8 +70,8 @@ class ChannelDetail(APIView):
|
||||
return create_success_response(guild,
|
||||
status=status.HTTP_200_OK)
|
||||
|
||||
def put(self, request, id, format=None):
|
||||
channel = Channel.get_channel_by_id(id)
|
||||
def put(self, request, guild_id, channel_id, format=None):
|
||||
channel = Channel.get_channel_by_id(guild_id, channel_id)
|
||||
|
||||
if channel:
|
||||
data = dict(request.data)
|
||||
@@ -54,14 +82,14 @@ class ChannelDetail(APIView):
|
||||
return create_error_response('Channel Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def delete(self, request, id, format=None):
|
||||
guild = Channel.get_guild_by_id(id)
|
||||
def delete(self, request, guild_id, channel_id, format=None):
|
||||
channel = Channel.get_channel_by_id(guild_id, channel_id)
|
||||
|
||||
if guild:
|
||||
if channel:
|
||||
# data = dict(request.data)
|
||||
# TODO Add a check to verify user is allowed to delete...
|
||||
# Possibly in object permissions...
|
||||
guild.delete()
|
||||
channel.delete()
|
||||
return create_success_response(guild,
|
||||
status=status.HTTP_200_OK)
|
||||
else:
|
||||
|
||||
@@ -24,7 +24,7 @@ DEBUG = env.bool("DJANGO_DEBUG", False)
|
||||
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
||||
# though not all of them may be available with every OS.
|
||||
# In Windows, this must be set to your system time zone.
|
||||
TIME_ZONE = "America/Anchorage"
|
||||
TIME_ZONE = "UTC"
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#language-code
|
||||
LANGUAGE_CODE = "en-us"
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#site-id
|
||||
|
||||
@@ -20,6 +20,7 @@ urlpatterns = [
|
||||
path("api/guilds/", include("geeksbot_v2.guilds.api_urls", namespace="guilds_api")),
|
||||
path("api/channels/", include("geeksbot_v2.channels.api_urls", namespace="channels_api")),
|
||||
path("api/messages/", include("geeksbot_v2.dmessages.api_urls", namespace="messages_api")),
|
||||
path("api/rcon/", include("geeksbot_v2.rcon.api_urls", namespace="rcon_api")),
|
||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
if settings.DEBUG:
|
||||
|
||||
@@ -2,14 +2,20 @@ from django.urls import path
|
||||
|
||||
from .views import MessageDetailAPI, MessagesAPI
|
||||
from .views import RequestDetailAPI, RequestsAPI
|
||||
from .views import CommentDetailAPI, CommentsAPI
|
||||
from .views import CommentDetailAPI, CommentsAPI, CommentsCountAPI
|
||||
from .views import WaitForMessageAPI
|
||||
from .views import UserRequestsAPI
|
||||
|
||||
app_name = "channels_api"
|
||||
app_name = "messages_api"
|
||||
urlpatterns = [
|
||||
path("", view=MessagesAPI.as_view(), name="message_list"),
|
||||
path("<str:id>/", view=MessageDetailAPI.as_view(), name='message_detail'),
|
||||
path("requests/", view=RequestsAPI.as_view(), name="requests_list"),
|
||||
path("requests/<str:id>/", view=RequestDetailAPI.as_view(), name='request_detail'),
|
||||
path("requests/<str:request_id>/comments/", view=CommentsAPI.as_view(), name="comments_list"),
|
||||
path("requests/<str:request_id>/comments/<str:comment_id>/", view=CommentDetailAPI.as_view(), name='comment_detail'),
|
||||
path("<str:guild_id>/requests/", view=RequestsAPI.as_view(), name="requests_list"),
|
||||
path("<str:guild_id>/requests/<str:request_id>/", view=RequestDetailAPI.as_view(), name='request_detail'),
|
||||
path("<str:guild_id>/requests/<str:request_id>/comments/", view=CommentsAPI.as_view(), name="comments_list"),
|
||||
path("<str:guild_id>/requests/<str:request_id>/comments/count/", view=CommentsCountAPI.as_view(), name="comments_count"),
|
||||
path("<str:guild_id>/requests/<str:request_id>/comments/<str:comment_id>/", view=CommentDetailAPI.as_view(), name='comment_detail'),
|
||||
path("<str:guild_id>/requests/user/<str:author_id>/", view=UserRequestsAPI.as_view(), name='user_requests_list'),
|
||||
path("<str:id>/wait/", view=WaitForMessageAPI.as_view(), name='wait_for_message'),
|
||||
path("<str:id>/wait/<int:timeout>/", view=WaitForMessageAPI.as_view(), name='wait_for_message_timeout'),
|
||||
]
|
||||
|
||||
@@ -57,7 +57,7 @@ class Message(models.Model):
|
||||
if not isinstance(guild, Guild):
|
||||
return create_error_response("Guild Does Not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
channel = Channel.get_channel_by_id(channel_id)
|
||||
channel = Channel.get_channel_by_id(guild_id, channel_id)
|
||||
if not isinstance(channel, Channel):
|
||||
return create_error_response("Channel Does Not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
@@ -89,7 +89,7 @@ class Message(models.Model):
|
||||
if data.get('tagged_channels'):
|
||||
tagged_channels = data.get('tagged_channels')
|
||||
for channel_id in tagged_channels:
|
||||
channel = Channel.get_channel_by_id(channel_id)
|
||||
channel = Channel.get_channel_by_id(guild_id, channel_id)
|
||||
if channel:
|
||||
message.tagged_channels.add(channel)
|
||||
|
||||
@@ -179,8 +179,9 @@ class AdminRequest(models.Model):
|
||||
def update_request(self, data):
|
||||
completed = data.get('completed', False)
|
||||
completed_by_id = data.get('completed_by')
|
||||
completed_message = data.get('message')
|
||||
completed_message = data.get('message', '')
|
||||
if not self.completed and completed:
|
||||
self.completed = completed
|
||||
self.completed_at = datetime.utcnow()
|
||||
self.completed_message = completed_message
|
||||
user = User.get_user_by_id(completed_by_id)
|
||||
@@ -192,8 +193,7 @@ class AdminRequest(models.Model):
|
||||
return create_request_success_response(self, status.HTTP_202_ACCEPTED)
|
||||
|
||||
@classmethod
|
||||
def add_new_request(cls, data):
|
||||
guild_id = data.get('guild')
|
||||
def add_new_request(cls, guild_id, data):
|
||||
author_id = data.get('author')
|
||||
message_id = data.get('message')
|
||||
channel_id = data.get('channel')
|
||||
@@ -205,7 +205,7 @@ class AdminRequest(models.Model):
|
||||
if not isinstance(guild, Guild):
|
||||
return create_error_response('Guild Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
author = User.get_author_by_id(author_id)
|
||||
author = User.get_user_by_id(author_id)
|
||||
if not isinstance(author, User):
|
||||
return create_error_response('Author Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
@@ -213,11 +213,13 @@ class AdminRequest(models.Model):
|
||||
if not isinstance(message, Message):
|
||||
return create_error_response('Message Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
channel = Channel.get_channel_by_id(channel_id)
|
||||
channel = Channel.get_channel_by_id(guild_id, channel_id)
|
||||
if not isinstance(channel, Channel):
|
||||
return create_error_response('Channel Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
print('test')
|
||||
|
||||
request = cls(
|
||||
guild=guild,
|
||||
author=author,
|
||||
@@ -233,15 +235,19 @@ class AdminRequest(models.Model):
|
||||
return cls.objects.filter(guild__id=guild_id).filter(completed=False)
|
||||
|
||||
@classmethod
|
||||
def get_request_by_id(cls, id):
|
||||
def get_open_request_by_id(cls, guild_id, request_id):
|
||||
try:
|
||||
return cls.objects.get(id=id)
|
||||
return cls.get_open_requests_by_guild(guild_id).get(id=request_id)
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.guild.id} | {self.requested_at} | By {self.author.id}"
|
||||
|
||||
@classmethod
|
||||
def get_open_requests_by_guild_author(cls, guild_id, author_id):
|
||||
return cls.get_open_requests_by_guild(guild_id).filter(author__id=author_id)
|
||||
|
||||
|
||||
class AdminComment(models.Model):
|
||||
request = models.ForeignKey(AdminRequest, on_delete=models.CASCADE)
|
||||
@@ -250,13 +256,13 @@ class AdminComment(models.Model):
|
||||
updated_at = models.DateTimeField(auto_now_add=True, blank=True)
|
||||
|
||||
@classmethod
|
||||
def add_new_comment(cls, data, request_id):
|
||||
def add_new_comment(cls, data, guild_id, request_id):
|
||||
author_id = data.get('author')
|
||||
content = data.get('content')
|
||||
if not (request_id and author_id and content):
|
||||
return create_error_response('Request, Author, and Content are required fields',
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
request = AdminRequest.get_request_by_id(request_id)
|
||||
request = AdminRequest.get_open_request_by_id(guild_id, request_id)
|
||||
if not isinstance(request, AdminRequest):
|
||||
return create_error_response("Admin Request Does Not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
@@ -274,12 +280,12 @@ class AdminComment(models.Model):
|
||||
return create_comment_success_response(comment, status.HTTP_201_CREATED, many=False)
|
||||
|
||||
@classmethod
|
||||
def get_comment_by_id(cls, id):
|
||||
def get_comment_by_id(cls, comment_id):
|
||||
try:
|
||||
return cls.objects.get(id=id)
|
||||
return cls.objects.get(id=comment_id)
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_comments_by_request(cls, request):
|
||||
return cls.objects.filter(request=request)
|
||||
return cls.objects.filter(request=request).order_by('updated_at')
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from time import sleep
|
||||
from datetime import datetime
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework import status
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
@@ -59,27 +62,57 @@ class MessageDetailAPI(APIView):
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
class WaitForMessageAPI(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, id, timeout: int = 3, format=None):
|
||||
message = Message.get_message_by_id(id)
|
||||
try_count = 0
|
||||
while not message:
|
||||
sleep(0.1)
|
||||
try_count += 1
|
||||
if try_count > timeout * 10:
|
||||
return create_error_response("Timeout reached before message is available.",
|
||||
statu=status.HTTP_404_NOT_FOUND)
|
||||
message = Message.get_message_by_id(id)
|
||||
return create_success_response(message, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class RequestsAPI(PaginatedAPIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, guild, format=None):
|
||||
requests = AdminRequest.get_open_requests_by_guild(guild)
|
||||
def get(self, request, guild_id, format=None):
|
||||
requests = AdminRequest.get_open_requests_by_guild(guild_id)
|
||||
page = self.paginate_queryset(requests)
|
||||
if page is not None:
|
||||
return create_request_success_response(page, status.HTTP_200_OK, many=True)
|
||||
if requests:
|
||||
return create_request_success_response(requests, status.HTTP_200_OK, many=True)
|
||||
return create_error_response("No requests found")
|
||||
|
||||
return create_request_success_response(requests, status.HTTP_200_OK, many=True)
|
||||
|
||||
def post(self, request, format=None):
|
||||
def post(self, request, guild_id, format=None):
|
||||
data = dict(request.data)
|
||||
return AdminRequest.add_new_request(data)
|
||||
return AdminRequest.add_new_request(guild_id, data)
|
||||
|
||||
|
||||
class UserRequestsAPI(PaginatedAPIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, guild_id, author_id, format=None):
|
||||
requests = AdminRequest.get_open_requests_by_guild_author(guild_id, author_id)
|
||||
page = self.paginate_queryset(requests)
|
||||
if page is not None:
|
||||
return create_request_success_response(page, status.HTTP_200_OK, many=True)
|
||||
if requests:
|
||||
return create_request_success_response(requests, status.HTTP_200_OK, many=True)
|
||||
return create_error_response("No requests found")
|
||||
|
||||
|
||||
class RequestDetailAPI(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, req, id, format=None):
|
||||
req = AdminRequest.get_request_by_id(id)
|
||||
def get(self, req, guild_id, request_id, format=None):
|
||||
req = AdminRequest.get_open_request_by_id(guild_id, request_id)
|
||||
if req:
|
||||
comments = AdminComment.get_comments_by_request(req)
|
||||
if comments:
|
||||
@@ -92,21 +125,44 @@ class RequestDetailAPI(APIView):
|
||||
return create_error_response("That Request Does Not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def put(self, request, id, format=None):
|
||||
req = AdminRequest.get_request_by_id(id)
|
||||
def put(self, request, guild_id, request_id, format=None):
|
||||
req = AdminRequest.get_open_request_by_id(guild_id, request_id)
|
||||
if req:
|
||||
data = dict(request.data)
|
||||
return req.update_request(data)
|
||||
return create_error_response("That Request Does Not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def delete(self, request, guild_id, request_id, format=None):
|
||||
data = dict(request.data)
|
||||
request = AdminRequest.get_open_request_by_id(guild_id, request_id)
|
||||
data['completed'] = True
|
||||
data['completed_at'] = datetime.utcnow()
|
||||
return request.update_request(data)
|
||||
|
||||
|
||||
class CommentsAPI(PaginatedAPIView):
|
||||
permissions_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request, request_id, format=None):
|
||||
def get(self, request, guild_id, request_id, format=None):
|
||||
comments = AdminComment.get_comments_by_request(request_id)
|
||||
if comments:
|
||||
return create_comment_success_response(comments, status=status.HTTP_200_OK, many=True)
|
||||
return create_error_response("No Comments found")
|
||||
|
||||
def post(self, request, guild_id, request_id, format=None):
|
||||
data = dict(request.data)
|
||||
return AdminComment.add_new_comment(data, request_id)
|
||||
return AdminComment.add_new_comment(data, guild_id, request_id)
|
||||
|
||||
|
||||
class CommentsCountAPI(PaginatedAPIView):
|
||||
permissions_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, guild_id, request_id, format=None):
|
||||
comments = AdminComment.get_comments_by_request(request_id)
|
||||
if comments:
|
||||
return Response(len(comments), status=status.HTTP_200_OK)
|
||||
return Response(0, status.HTTP_200_OK)
|
||||
|
||||
|
||||
class CommentDetailAPI(APIView):
|
||||
|
||||
@@ -2,11 +2,13 @@ from django.urls import path
|
||||
|
||||
from .views import GuildsAPI, GuildDetail
|
||||
from .views import RolesAPI, RoleDetailAPI
|
||||
from .views import AdminRolesAPI
|
||||
|
||||
app_name = "guilds_api"
|
||||
urlpatterns = [
|
||||
path("", view=GuildsAPI.as_view(), name="list"),
|
||||
path("<str:id>/", view=GuildDetail.as_view(), name='detail'),
|
||||
path("<str:guild_id>/roles/", view=RolesAPI.as_view(), name="list"),
|
||||
path("<str:guild_id>/roles/admin/", view=AdminRolesAPI.as_view(), name='admin'),
|
||||
path("<str:guild_id>/roles/<str:id>/", view=RoleDetailAPI.as_view(), name='detail'),
|
||||
]
|
||||
|
||||
@@ -73,7 +73,7 @@ class Role(models.Model):
|
||||
self.role_type = data.get('role_type')
|
||||
|
||||
self.save()
|
||||
return create_role_success_response(self, status=status.HTTP_202_ACCEPTED, many=False)
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def add_new_role(cls, guild_id, data):
|
||||
@@ -112,9 +112,9 @@ class Role(models.Model):
|
||||
return create_role_success_response(role, status.HTTP_201_CREATED, many=False)
|
||||
|
||||
@classmethod
|
||||
def get_role_by_id(cls, id):
|
||||
def get_role_by_id(cls, guild_id, role_id):
|
||||
try:
|
||||
return cls.objects.get(id=id)
|
||||
return cls.get_guild_roles(guild_id).get(id=role_id)
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
@@ -122,5 +122,12 @@ class Role(models.Model):
|
||||
def get_guild_roles(cls, guild):
|
||||
return cls.objects.filter(guild__id=guild)
|
||||
|
||||
@classmethod
|
||||
def get_admin_roles(cls, guild_id):
|
||||
try:
|
||||
return cls.get_guild_roles(guild_id).filter(role_type__gte=90)
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.guild.id} | {self.id}"
|
||||
|
||||
@@ -87,6 +87,26 @@ class RolesAPI(PaginatedAPIView):
|
||||
return Role.add_new_role(guild_id, data)
|
||||
|
||||
|
||||
class AdminRolesAPI(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, guild_id, format=None):
|
||||
roles = Role.get_admin_roles(guild_id)
|
||||
if roles:
|
||||
return create_role_success_response(roles, status=status.HTTP_200_OK, many=True)
|
||||
return create_error_response('There are no admin roles configured',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def put(self, request, guild_id, format=None):
|
||||
data = dict(request.data)
|
||||
role = Role.get_role_by_id(guild_id, data['role'])
|
||||
if role:
|
||||
role = role.update_role({'role_type': 100})
|
||||
return create_role_success_response(role, status=status.HTTP_202_ACCEPTED)
|
||||
return create_error_response("That role does not exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
class RoleDetailAPI(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@@ -101,7 +121,7 @@ class RoleDetailAPI(APIView):
|
||||
status=status.HTTP_200_OK)
|
||||
|
||||
def put(self, request, guild_id, id, format=None):
|
||||
role = Role.get_role_by_id(id)
|
||||
role = Role.get_role_by_id(guild_id, id)
|
||||
|
||||
if role:
|
||||
data = dict(request.data)
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import discord
|
||||
import gspread
|
||||
from oauth2client.service_account import ServiceAccountCredentials
|
||||
|
||||
|
||||
class Patron:
|
||||
def __init__(self, *, discord_name: str=None, steam_id: int=None, patreon_tier: str=None, patron_of: str=None,
|
||||
discord_discrim: int=None, discord_id: int=None, patreon_name: str=None, steam_name: str=None):
|
||||
self.discord_name = discord_name
|
||||
self.discord_discrim = discord_discrim
|
||||
self.steam_id = steam_id
|
||||
self.discord_id = discord_id
|
||||
self.patreon_tier = patreon_tier
|
||||
self.patron_of = patron_of
|
||||
self.patreon_name = patreon_name
|
||||
self.steam_name = steam_name
|
||||
|
||||
@classmethod
|
||||
async def from_id(cls, bot, steam_id: int, *, discord_id: int=None):
|
||||
scope = ['https://spreadsheets.google.com/feeds',
|
||||
'https://www.googleapis.com/auth/drive']
|
||||
credentials = ServiceAccountCredentials.from_json_keyfile_dict(bot.google_secret, scope)
|
||||
|
||||
gc = gspread.authorize(credentials)
|
||||
sh = gc.open_by_key(bot.bot_secrets['sheet'])
|
||||
ws = sh.worksheet('Current Whitelist')
|
||||
try:
|
||||
cell = ws.find(f'{steam_id}')
|
||||
except gspread.CellNotFound:
|
||||
return -1
|
||||
else:
|
||||
steam_name = None
|
||||
if discord_id:
|
||||
user_ref = bot.fs_db.document(f'users/{discord_id}')
|
||||
user_info = (await bot.loop.run_in_executor(bot.tpe, user_ref.get)).to_dict()
|
||||
if user_info:
|
||||
steam_name = user_info.get('steam_name')
|
||||
row = ws.row_values(cell.row)
|
||||
return cls(patreon_name=row[1],
|
||||
discord_name=row[2],
|
||||
steam_id=row[5],
|
||||
patreon_tier=row[4].split(' (')[1].strip(')') if len(row[4].split(' (')) > 1 else row[4],
|
||||
patron_of=row[3].split(' (')[0],
|
||||
discord_id=discord_id,
|
||||
steam_name=steam_name)
|
||||
|
||||
@classmethod
|
||||
async def from_name(cls, bot, discord_name: discord.Member, *, discord_id: int=None):
|
||||
scope = ['https://spreadsheets.google.com/feeds',
|
||||
'https://www.googleapis.com/auth/drive']
|
||||
credentials = ServiceAccountCredentials.from_json_keyfile_dict(bot.google_secret, scope)
|
||||
|
||||
gc = gspread.authorize(credentials)
|
||||
sh = gc.open_by_key(bot.bot_secrets['sheet'])
|
||||
ws = sh.worksheet('Current Whitelist')
|
||||
try:
|
||||
cell = ws.find(f'{discord_name.name if isinstance(discord_name, discord.Member) else discord_name}')
|
||||
except gspread.CellNotFound:
|
||||
try:
|
||||
cell = ws.find(f'{discord_name.nick if isinstance(discord_name, discord.Member) else discord_name}')
|
||||
except gspread.CellNotFound:
|
||||
return -1
|
||||
steam_name = None
|
||||
discord_id = discord_name.id if isinstance(discord_name, discord.Member) else discord_id
|
||||
if discord_id:
|
||||
user_ref = bot.fs_db.document(f'users/{discord_id}')
|
||||
user_info = (await bot.loop.run_in_executor(bot.tpe, user_ref.get)).to_dict()
|
||||
if user_info:
|
||||
steam_name = user_info.get('steam_name')
|
||||
row = ws.row_values(cell.row)
|
||||
return cls(patreon_name=row[1],
|
||||
discord_name=row[2],
|
||||
discord_id=discord_id,
|
||||
steam_id=row[5],
|
||||
patreon_tier=row[4].split(' (')[1].strip(')') if len(row[4].split(' (')) > 1 else row[4],
|
||||
patron_of=row[3].split(' (')[0],
|
||||
steam_name=steam_name)
|
||||
@@ -0,0 +1,10 @@
|
||||
from django.urls import path
|
||||
|
||||
from .views import RCONServersAPI, RCONServerDetailAPI, ListPlayers
|
||||
|
||||
app_name = "rcon_api"
|
||||
urlpatterns = [
|
||||
path("<str:guild_id>/", view=RCONServersAPI.as_view(), name='guild_servers'),
|
||||
path("<str:guild_id>/<str:name>/", view=RCONServerDetailAPI.as_view(), name="server_detail"),
|
||||
path("<str:guild_id>/<str:name>/listplayers", view=ListPlayers.as_view(), name='listplayers'),
|
||||
]
|
||||
@@ -120,7 +120,7 @@ class RconServer(models.Model):
|
||||
@classmethod
|
||||
def get_guild_servers(cls, guild_id):
|
||||
guild = Guild.get_guild_by_id(guild_id)
|
||||
if not isinstance(guild, guild):
|
||||
if not isinstance(guild, Guild):
|
||||
return None
|
||||
return cls.objects.filter(guild=guild)
|
||||
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
from . import rcon
|
||||
import asyncio
|
||||
from typing import Union
|
||||
import logging
|
||||
|
||||
arcon_log = logging.getLogger('arcon_lib')
|
||||
|
||||
|
||||
class ARKServer(rcon.RCONConnection):
|
||||
def __init__(self, *args, monitor_chat: bool=False, server_chat_channel: int=None,
|
||||
server_messages_channel: int=None, **kwargs):
|
||||
self.monitor_chat = monitor_chat
|
||||
self.server_chat_channel = server_chat_channel
|
||||
self.server_messages_channel = server_messages_channel
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
async def run_command(self, command: str, multi_packet: bool=False, reconnect_counter: int=0) \
|
||||
-> Union[rcon.RCONPacket, str]:
|
||||
arcon_log.debug(f'Command requested: {command}')
|
||||
if self.authenticated:
|
||||
packet = rcon.RCONPacket(next(self.packet_id), rcon.SERVERDATA_EXECCOMMAND, command)
|
||||
with await self.lock:
|
||||
try:
|
||||
arcon_log.debug(f'Sending packet {packet.packet_id}')
|
||||
await self.send_packet(packet)
|
||||
arcon_log.debug(f'Packet Sent.')
|
||||
except ConnectionResetError:
|
||||
arcon_log.info(f'Connection to {self.host}:{self.port} lost, Reconnecting...')
|
||||
self.lock.release()
|
||||
await self._reconnect_and_resend(packet)
|
||||
await self.lock.acquire()
|
||||
finally:
|
||||
arcon_log.debug(f'Waiting for response to packet {packet.packet_id}')
|
||||
try:
|
||||
response = await self.read(packet, multi_packet=multi_packet)
|
||||
except asyncio.TimeoutError as e:
|
||||
if reconnect_counter > 5:
|
||||
return 'Reached max reconnects. Closing connection.'
|
||||
arcon_log.warning(f'No response received: {e}\nAttempting to reconnect #{reconnect_counter}')
|
||||
self.lock.release()
|
||||
await self._reconnect()
|
||||
await self.lock.acquire()
|
||||
response = await self.run_command(command=command, multi_packet=multi_packet,
|
||||
reconnect_counter=reconnect_counter + 1)
|
||||
arcon_log.debug(f'Response Received:\n{response.packet_type}:{response.packet_id}:{response.body}')
|
||||
response.body = response.body.strip('\x00\x00').strip()
|
||||
return response
|
||||
else:
|
||||
return 'Server is not Authenticated. Please let the Admin know of this issue.'
|
||||
|
||||
async def getchat(self) -> str:
|
||||
response = await self.run_command(command='getchat', multi_packet=True)
|
||||
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||
|
||||
async def saveworld(self) -> str:
|
||||
response = await self.run_command(command='saveworld')
|
||||
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||
|
||||
async def serverchat(self, message: str) -> str:
|
||||
response = await self.run_command(command=f'serverchat {message}')
|
||||
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||
|
||||
async def broadcast(self, message: str) -> str:
|
||||
response = await self.run_command(command=f'broadcast {message}')
|
||||
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||
|
||||
async def listplayers(self) -> str:
|
||||
response = await self.run_command(command=f'listplayers')
|
||||
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||
|
||||
async def whitelist(self, steam_id: str) -> str:
|
||||
response = await self.run_command(command=f'AllowPlayerToJoinNoCheck {steam_id}')
|
||||
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||
|
||||
async def ban_player(self, steam_id: int) -> str:
|
||||
response = await self.run_command(command=f'BanPlayer {steam_id}')
|
||||
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||
|
||||
async def unban_player(self, steam_id: int) -> str:
|
||||
response = await self.run_command(command=f'UnbanPlayer {steam_id}')
|
||||
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||
|
||||
async def kick_player(self, steam_id: int) -> str:
|
||||
response = await self.run_command(command=f'KickPlayer {steam_id}')
|
||||
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||
|
||||
async def stop_server(self) -> int:
|
||||
saved = await self.saveworld()
|
||||
if saved == 'World Saved':
|
||||
await self.serverchat(saved)
|
||||
await asyncio.sleep(10)
|
||||
response = await self.run_command(command='DoExit')
|
||||
if response.body == 'Exiting...':
|
||||
return 0
|
||||
else:
|
||||
return 2
|
||||
else:
|
||||
return 1
|
||||
|
||||
async def get_logs(self):
|
||||
response = await self.run_command(command=f'GetGameLog', multi_packet=True)
|
||||
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||
|
||||
async def server_chat_to_steam_id(self, steam_id: int, message: str) -> str:
|
||||
response = await self.run_command(command=f'ServerChatTo {steam_id} {message}')
|
||||
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||
|
||||
async def server_chat_to_player_name(self, player_name: str, message: str) -> str:
|
||||
response = await self.run_command(command=f'ServerChatToPlayer "{player_name}" {message}')
|
||||
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||
|
||||
async def set_time_of_day(self, hour: int, minute: int=00, seconds: int=00) -> str:
|
||||
response = await self.run_command(command=f'SetTimeOfDay {hour}:{minute}:{seconds}')
|
||||
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||
|
||||
async def destroy_wild_dinos(self):
|
||||
response = await self.run_command(command='DestroyWildDinos')
|
||||
return response.body if isinstance(response, rcon.RCONPacket) else response
|
||||
@@ -0,0 +1,183 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import itertools
|
||||
import struct
|
||||
|
||||
# Packet types
|
||||
SERVERDATA_AUTH = 3
|
||||
SERVERDATA_AUTH_RESPONSE = 2
|
||||
SERVERDATA_EXECCOMMAND = 2
|
||||
SERVERDATA_RESPONSE_VALUE = 0
|
||||
|
||||
__all__ = ['RCONPacket', 'RCONConnection']
|
||||
|
||||
rcon_log = logging.getLogger('rcon_lib')
|
||||
|
||||
|
||||
class RCONPacket:
|
||||
def __init__(self, packet_id: int=0, packet_type: int=-1, body: str=''):
|
||||
self.packet_id = packet_id
|
||||
self.packet_type = packet_type
|
||||
self.body = body
|
||||
|
||||
def __str__(self):
|
||||
"""Return the body of the packet"""
|
||||
return self.body
|
||||
|
||||
def size(self):
|
||||
"""Return the size of the packet"""
|
||||
return len(self.body) + 10
|
||||
|
||||
def pack(self):
|
||||
"""Return the packed packet"""
|
||||
return struct.pack(f'<3i{len(self.body) + 2}s',
|
||||
self.size(),
|
||||
self.packet_id,
|
||||
self.packet_type,
|
||||
bytearray(self.body, 'utf-8'))
|
||||
|
||||
|
||||
class RCONConnection:
|
||||
"""Connection to an RCON server"""
|
||||
|
||||
def __init__(self, host: str, port: int, password: str='', single_packet: bool=False, loop=None):
|
||||
"""Create a New RCON Connection
|
||||
|
||||
Parameters:
|
||||
host (str): The hostname or IP address of the server to connect to
|
||||
port (int): The port to connect to on the server
|
||||
password (str): The password to authenticate with the server
|
||||
single_packet (bool): True for servers who don't give 0 length SERVERDATA_RESPONSE_VALUE requests
|
||||
"""
|
||||
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.password = password
|
||||
self.single_packet = single_packet
|
||||
self.packet_id = itertools.count(1)
|
||||
self.loop = loop or asyncio.get_event_loop()
|
||||
self.reader = None
|
||||
self.writer = None
|
||||
self.lock = asyncio.Lock()
|
||||
self.authenticated = False
|
||||
|
||||
async def connect(self):
|
||||
"""Returns -1 if connection times out
|
||||
Returns 1 if connection and auth are successful
|
||||
Returns 0 if auth fails"""
|
||||
try:
|
||||
rcon_log.debug(f'Connecting to {self.host}:{self.port}...')
|
||||
self.reader, self.writer = await asyncio.open_connection(self.host, self.port, loop=self.loop)
|
||||
except TimeoutError as e:
|
||||
rcon_log.error(f'Timeout error: {e}')
|
||||
return -1
|
||||
else:
|
||||
rcon_log.debug('Connected. Attempting to Authenticate...')
|
||||
auth_packet = RCONPacket(next(self.packet_id), SERVERDATA_AUTH, self.password)
|
||||
with await self.lock:
|
||||
await self.send_packet(auth_packet)
|
||||
response = await self.read()
|
||||
if response.packet_type == SERVERDATA_AUTH_RESPONSE and response.packet_id != -1:
|
||||
rcon_log.debug(f'Authorized {response.packet_type}:{response.packet_id}:{response.body}')
|
||||
self.authenticated = True
|
||||
return 1
|
||||
else:
|
||||
rcon_log.debug(f'Not Authorized {response.packet_type}:{response.packet_id}:{response.body}')
|
||||
self.authenticated = False
|
||||
return 0
|
||||
|
||||
async def _reconnect(self):
|
||||
self.writer = None
|
||||
self.reader = None
|
||||
connected = await self.connect()
|
||||
rcon_log.info(f'Connection completed with a return of {connected}')
|
||||
if connected != -1:
|
||||
rcon_log.info('Connected')
|
||||
else:
|
||||
rcon_log.warning('Connection Failed')
|
||||
return connected
|
||||
|
||||
async def _reconnect_and_resend(self, packet):
|
||||
connected = await self._reconnect()
|
||||
if connected != -1:
|
||||
await asyncio.sleep(0.1)
|
||||
rcon_log.info(f'Re-sending packet {packet.packet_id}')
|
||||
await self.send_packet(packet)
|
||||
rcon_log.info(f'Packet Sent.')
|
||||
return connected
|
||||
else:
|
||||
return connected
|
||||
|
||||
async def keep_alive(self):
|
||||
while True:
|
||||
await asyncio.sleep(60)
|
||||
ka_packet = RCONPacket(next(self.packet_id), SERVERDATA_EXECCOMMAND, '')
|
||||
try:
|
||||
with await self.lock:
|
||||
await asyncio.wait_for(self.send_packet(ka_packet), 10, loop=self.loop)
|
||||
await asyncio.wait_for(self.read(ka_packet), 10, loop=self.loop)
|
||||
except asyncio.TimeoutError:
|
||||
self.reader = None
|
||||
self.writer = None
|
||||
await self.connect()
|
||||
|
||||
async def send_packet(self, packet):
|
||||
if packet.size() > 4096:
|
||||
rcon_log.error('Packet Size is larger than 4096 bytes. Cannot send packet.')
|
||||
raise RuntimeWarning('Packet Size is larger than 4096 bytes. Cannot send packet.')
|
||||
if self.writer is None:
|
||||
await self.connect()
|
||||
rcon_log.debug(f'Sending Packet {packet.packet_id}: {packet.pack() if packet.packet_type is not SERVERDATA_AUTH else "Censored for Password Security."}')
|
||||
self.writer.write(packet.pack())
|
||||
await self.writer.drain()
|
||||
rcon_log.debug(f'Packet {packet.packet_id} Sent.')
|
||||
|
||||
async def read(self, request: RCONPacket=None, multi_packet=False) -> RCONPacket:
|
||||
rcon_log.debug(f'Waiting to receive response to packet {request.packet_id if request else None}')
|
||||
response = RCONPacket()
|
||||
try:
|
||||
if request:
|
||||
while response.packet_id != request.packet_id and response.packet_id < request.packet_id:
|
||||
if multi_packet:
|
||||
if request is None:
|
||||
rcon_log.warning('A request packet is required to receive a multi packet response')
|
||||
raise ValueError('A request packet is required to receive a multi packet response')
|
||||
await asyncio.sleep(.01)
|
||||
response = await self._receive_multi_packet()
|
||||
rcon_log.debug(f'Received Multi-Packet response to packet {request.packet_id}:\n'
|
||||
f'{response.packet_type}:{response.packet_id}:{response.body}')
|
||||
else:
|
||||
response = await self.receive_packet()
|
||||
rcon_log.debug(f'Received Single-Packet response to packet {request.packet_id}:\n'
|
||||
f'{response.packet_type}:{response.packet_id}:{response.body}')
|
||||
else:
|
||||
response = await self.receive_packet()
|
||||
rcon_log.debug(f'Received Single-Packet response:\n'
|
||||
f'{response.packet_type}:{response.packet_id}:{response.body}')
|
||||
except struct.error as e:
|
||||
rcon_log.error(f'Struct Error: {e}')
|
||||
response = RCONPacket(body='Error receiving data from the server. Attempting to reconnect. '
|
||||
'Please try again in a little bit.')
|
||||
self.lock.release()
|
||||
await self._reconnect()
|
||||
await self.lock.acquire()
|
||||
except AttributeError as e:
|
||||
rcon_log.error(f'Attribute Error: {e}')
|
||||
response = RCONPacket(body='Error receiving data from the server. Attempting to reconnect. '
|
||||
'Please try again in a little bit.')
|
||||
self.lock.release()
|
||||
await self._reconnect()
|
||||
await self.lock.acquire()
|
||||
return response
|
||||
|
||||
async def receive_packet(self):
|
||||
header = await self.reader.read(struct.calcsize('<3i'))
|
||||
(packet_size, packet_id, packet_type) = struct.unpack('<3i', header)
|
||||
body = await self.reader.read(packet_size - 8)
|
||||
return RCONPacket(packet_id, packet_type, body.decode('ascii'))
|
||||
|
||||
async def _receive_multi_packet(self):
|
||||
header = await self.reader.read(struct.calcsize('<3i'))
|
||||
(packet_size, packet_id, packet_type) = struct.unpack('<3i', header)
|
||||
body = await self.reader.readuntil(separator=b'\x00\x00')
|
||||
return RCONPacket(packet_id, packet_type, body.decode('ascii'))
|
||||
@@ -11,4 +11,9 @@ def create_success_response(rcon_data, status, many: bool = False):
|
||||
from .serializers import RconServerSerializer
|
||||
|
||||
return Response(RconServerSerializer(rcon_data, many=many).data,
|
||||
status=status)
|
||||
status=status)
|
||||
|
||||
|
||||
def create_rcon_response(message, status):
|
||||
msg_list = message.split('\n')
|
||||
return Response(msg_list, status=status)
|
||||
|
||||
@@ -1,3 +1,78 @@
|
||||
from django.shortcuts import render
|
||||
import asyncio
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from .rcon_lib import arcon
|
||||
|
||||
from .models import RconServer
|
||||
from .utils import create_error_response, create_success_response, create_rcon_response
|
||||
from geeksbot_v2.utils.api_utils import PaginatedAPIView
|
||||
from .serializers import RconServerSerializer
|
||||
|
||||
# Create your views here.
|
||||
|
||||
# API Views
|
||||
|
||||
|
||||
class RCONServersAPI(PaginatedAPIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, guild_id, format=None):
|
||||
servers = RconServer.get_guild_servers(guild_id)
|
||||
page = self.paginate_queryset(servers)
|
||||
if page:
|
||||
return create_success_response(page, status.HTTP_200_OK, many=True)
|
||||
return create_success_response(servers, status.HTTP_200_OK, many=True)
|
||||
|
||||
def post(self, request, guild_id, format=None):
|
||||
data = dict(request.data)
|
||||
data['guild'] = guild_id
|
||||
return RconServer.add_new_server(data)
|
||||
|
||||
|
||||
class RCONServerDetailAPI(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, guild_id, name, format=None):
|
||||
server = RconServer.get_server(guild_id, name)
|
||||
if server:
|
||||
return create_success_response(server, status.HTTP_200_OK, many=False)
|
||||
else:
|
||||
return create_error_response("RCON Server Does Not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def put(self, request, guild_id, name, format=None):
|
||||
data = dict(request.data)
|
||||
server = RconServer.get_server(guild_id, name)
|
||||
if server:
|
||||
return server.update_server(data)
|
||||
else:
|
||||
return create_error_response('RCON Server Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
class ListPlayers(PaginatedAPIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, guild_id, name, format=None):
|
||||
server: RconServer = RconServer.get_server(guild_id, name)
|
||||
if server:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
loop = asyncio.get_event_loop()
|
||||
ark = arcon.ARKServer(host=server.ip, port=server.port, password=server.password, loop=loop)
|
||||
connected = loop.run_until_complete(ark.connect())
|
||||
if connected == 1:
|
||||
resp = loop.run_until_complete(ark.listplayers())
|
||||
if resp == 'No Players Connected':
|
||||
return create_rcon_response(resp, status=status.HTTP_204_NO_CONTENT)
|
||||
else:
|
||||
return create_rcon_response(resp, status=status.HTTP_200_OK)
|
||||
else:
|
||||
return create_error_response('Connection failure',
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
return create_error_response('RCON Server Does Not Exist',
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
from src.shared_libs.guid import Guid
|
||||
from src.shared_libs.TicTacToe.player import Player
|
||||
|
||||
|
||||
class Board:
|
||||
def __init__(self):
|
||||
self.id = Guid()
|
||||
self.board = [[' ', ' ', ' '],
|
||||
[' ', ' ', ' '],
|
||||
[' ', ' ', ' ']]
|
||||
self.history = []
|
||||
self.winner = False
|
||||
self.draw = False
|
||||
self.play_count = 0
|
||||
self.remaining_moves = [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
self.winning_states = [[(0, 0), (0, 1), (0, 2)],
|
||||
[(0, 0), (1, 1), (2, 2)],
|
||||
[(1, 0), (1, 1), (1, 2)],
|
||||
[(2, 0), (2, 1), (2, 2)],
|
||||
[(0, 0), (1, 0), (2, 0)],
|
||||
[(0, 1), (1, 1), (2, 1)],
|
||||
[(0, 2), (1, 2), (2, 2)],
|
||||
[(2, 0), (1, 1), (0, 2)]]
|
||||
|
||||
def __repr__(self):
|
||||
return f'<TicTacToe Board id="{self.id}">'
|
||||
|
||||
def __str__(self):
|
||||
return '┌───┬───┬───┐\n' \
|
||||
'│ {0[0][0]} │ {0[0][1]} │ {0[0][2]} │\n' \
|
||||
'├───┼───┼───┤\n' \
|
||||
'│ {0[1][0]} │ {0[1][1]} │ {0[1][2]} │\n' \
|
||||
'├───┼───┼───┤\n' \
|
||||
'│ {0[2][0]} │ {0[2][1]} │ {0[2][2]} │\n' \
|
||||
'└───┴───┴───┘\n'.format(self.board)
|
||||
|
||||
def make_play(self, player: Player, position: int):
|
||||
assert 1 <= position <= 9
|
||||
assert isinstance(player, Player)
|
||||
move = ((position - 1) // 3, (position - 1) % 3)
|
||||
if not self.board[move[0]][move[1]] == ' ':
|
||||
raise Warning("That cell is already taken. Please try again.")
|
||||
self.history.append(self.board)
|
||||
self.board[move[0]][move[1]] = player
|
||||
self.play_count += 1
|
||||
self.winner = self.check_winner()
|
||||
self.draw = self.check_draw()
|
||||
self.remaining_moves.remove(position)
|
||||
|
||||
def check_winner(self):
|
||||
for state in self.winning_states:
|
||||
if (self.board[state[0][0]][state[0][1]] ==
|
||||
self.board[state[1][0]][state[1][1]] ==
|
||||
self.board[state[2][0]][state[2][1]]) and \
|
||||
self.board[state[0][0]][state[0][1]] != ' ':
|
||||
return self.board[state[0][0]][state[0][1]]
|
||||
return False
|
||||
|
||||
def check_draw(self):
|
||||
for row in self.board:
|
||||
for cell in row:
|
||||
if cell == ' ':
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def clear(self):
|
||||
self.board = [[' ', ' ', ' '],
|
||||
[' ', ' ', ' '],
|
||||
[' ', ' ', ' ']]
|
||||
self.history = []
|
||||
self.winner = False
|
||||
self.play_count = 0
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
from src.shared_libs.guid import Guid
|
||||
import random
|
||||
from copy import deepcopy
|
||||
|
||||
__all__ = ['Player', 'AIPlayer']
|
||||
|
||||
|
||||
class Player:
|
||||
def __init__(self, token: str, *, name: str=None, id: str=None, discord_id: int=None):
|
||||
if len(token) != 1:
|
||||
raise Warning('Token must be exactly one character long.')
|
||||
self.token = token
|
||||
self.name = name or f'Player {self.token}'
|
||||
self.id = id or Guid()
|
||||
self.starting_player = False
|
||||
self.discord_id = discord_id
|
||||
|
||||
def __repr__(self):
|
||||
return f'<TicTacToe Player name="{self.name}" id="{self.id}">'
|
||||
|
||||
def __str__(self):
|
||||
return self.token
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Player) and other.id == self.id:
|
||||
return True
|
||||
elif isinstance(other, str):
|
||||
return self.token == other
|
||||
|
||||
|
||||
class AIPlayer(Player):
|
||||
def __init__(self, token: str=None, name: str=None, human: Player=None, *, id: str=None):
|
||||
token = token or '🇽'
|
||||
if human:
|
||||
if human.token == token and human.token != '🇴':
|
||||
token = '🇴'
|
||||
elif human.token == '🇴':
|
||||
token = '🇽'
|
||||
super().__init__(token, name=name or f'Robot {token}', id=id)
|
||||
self._corner_moves = [1, 3, 7, 9]
|
||||
self._side_moves = [2, 4, 6, 8]
|
||||
self._center_move = 5
|
||||
self.remaining_corners = deepcopy(self._corner_moves)
|
||||
self.remaining_sides = deepcopy(self._side_moves)
|
||||
|
||||
def make_selection(self, board, last_play: int=None) -> int:
|
||||
if last_play in self.remaining_corners:
|
||||
self.remaining_corners.remove(last_play)
|
||||
elif last_play in self.remaining_sides:
|
||||
self.remaining_sides.remove(last_play)
|
||||
|
||||
winning_move = self.check_winning_move(board)
|
||||
if winning_move:
|
||||
move = winning_move
|
||||
else:
|
||||
blocking_move = self.check_blocking_move(board)
|
||||
if blocking_move:
|
||||
move = blocking_move
|
||||
else:
|
||||
trap_move = self.attempt_trap(board)
|
||||
if trap_move:
|
||||
move = trap_move
|
||||
else:
|
||||
starting_move = self.starting_strategy(board)
|
||||
if self.starting_player and starting_move:
|
||||
move = starting_move
|
||||
else:
|
||||
if board.board[1][1] == ' ':
|
||||
move = 5
|
||||
else:
|
||||
if self.check_corner_trap(board):
|
||||
move = random.choice(self.remaining_sides)
|
||||
else:
|
||||
if self.remaining_corners:
|
||||
move = random.choice(self.remaining_corners)
|
||||
else:
|
||||
move = random.choice(self.remaining_sides)
|
||||
if move in self.remaining_corners:
|
||||
self.remaining_corners.remove(move)
|
||||
elif move in self.remaining_sides:
|
||||
self.remaining_sides.remove(move)
|
||||
print(move)
|
||||
return move
|
||||
|
||||
def starting_strategy(self, board):
|
||||
move = False
|
||||
if board.play_count == 0:
|
||||
move = random.choice(self.remaining_corners)
|
||||
self.remaining_corners.remove(move)
|
||||
elif board.play_count == 2:
|
||||
if (board.board[0][0] == self and ' ' != board.board[2][2] != self) \
|
||||
or (board.board[2][2] == self and ' ' != board.board[0][0] != self) \
|
||||
or (board.board[2][0] == self and ' ' != board.board[0][2] != self) \
|
||||
or (board.board[0][2] == self and ' ' != board.board[2][0] != self):
|
||||
move = random.choice(self.remaining_corners)
|
||||
else:
|
||||
if board.board[0][0] == self:
|
||||
move = 9
|
||||
elif board.board[2][2] == self:
|
||||
move = 1
|
||||
elif board.board[0][2] == self:
|
||||
move = 7
|
||||
elif board.board[2][0] == self:
|
||||
move = 3
|
||||
self.remaining_corners.remove(move)
|
||||
elif board.play_count == 4 and self.remaining_corners:
|
||||
move = random.choice(self.remaining_corners)
|
||||
self.remaining_corners.remove(move)
|
||||
return move
|
||||
|
||||
|
||||
def check_corner_trap(self, board):
|
||||
if ' ' != board.board[0][0] == board.board[2][2] != self:
|
||||
return True
|
||||
elif ' ' != board.board[0][2] == board.board[2][0] != self:
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_blocking_move(self, board):
|
||||
for position in board.winning_states:
|
||||
if ' ' != board.board[position[0][0]][position[0][1]] == \
|
||||
board.board[position[1][0]][position[1][1]] != self \
|
||||
and board.board[position[2][0]][position[2][1]] == ' ':
|
||||
return ((position[2][0] * 3) + position[2][1]) + 1
|
||||
elif ' ' != board.board[position[0][0]][position[0][1]] == \
|
||||
board.board[position[2][0]][position[2][1]] != self \
|
||||
and board.board[position[1][0]][position[1][1]] == ' ':
|
||||
return ((position[1][0] * 3) + position[1][1]) + 1
|
||||
elif ' ' != board.board[position[2][0]][position[2][1]] == \
|
||||
board.board[position[1][0]][position[1][1]] != self \
|
||||
and board.board[position[0][0]][position[0][1]] == ' ':
|
||||
return ((position[0][0] * 3) + position[0][1]) + 1
|
||||
return False
|
||||
|
||||
def check_winning_move(self, board):
|
||||
for position in board.winning_states:
|
||||
if board.board[position[0][0]][position[0][1]] == board.board[position[1][0]][position[1][1]] == self \
|
||||
and board.board[position[2][0]][position[2][1]] == ' ':
|
||||
return ((position[2][0] * 3) + position[2][1]) + 1
|
||||
elif board.board[position[0][0]][position[0][1]] == board.board[position[2][0]][position[2][1]] == self \
|
||||
and board.board[position[1][0]][position[1][1]] == ' ':
|
||||
return ((position[1][0] * 3) + position[1][1]) + 1
|
||||
elif board.board[position[2][0]][position[2][1]] == board.board[position[1][0]][position[1][1]] == self \
|
||||
and board.board[position[0][0]][position[0][1]] == ' ':
|
||||
return ((position[0][0] * 3) + position[0][1]) + 1
|
||||
return False
|
||||
|
||||
def attempt_trap(self, board):
|
||||
if board.board[1][1] == self:
|
||||
if board.board[0][0] == self and \
|
||||
board.board[0][1] == ' ' and \
|
||||
board.board[0][2] == ' ' and \
|
||||
board.board[2][0] == ' ':
|
||||
return 3
|
||||
elif board.board[0][0] == self and \
|
||||
board.board[1][0] == ' ' and \
|
||||
board.board[0][2] == ' ' and \
|
||||
board.board[2][0] == ' ':
|
||||
return 7
|
||||
elif board.board[0][2] == self and \
|
||||
board.board[0][1] == ' ' and \
|
||||
board.board[0][0] == ' ' and \
|
||||
board.board[2][2] == ' ':
|
||||
return 1
|
||||
elif board.board[0][2] == self and \
|
||||
board.board[1][2] == ' ' and \
|
||||
board.board[0][0] == ' ' and \
|
||||
board.board[2][2] == ' ':
|
||||
return 9
|
||||
elif board.board[2][0] == self and \
|
||||
board.board[0][0] == ' ' and \
|
||||
board.board[0][1] == ' ' and \
|
||||
board.board[2][2] == ' ':
|
||||
return 1
|
||||
elif board.board[2][0] == self and \
|
||||
board.board[2][1] == ' ' and \
|
||||
board.board[2][2] == ' ' and \
|
||||
board.board[0][0] == ' ':
|
||||
return 9
|
||||
elif board.board[2][2] == self and \
|
||||
board.board[2][1] == ' ' and \
|
||||
board.board[2][0] == ' ' and \
|
||||
board.board[0][2] == ' ':
|
||||
return 7
|
||||
elif board.board[2][2] == self and \
|
||||
board.board[1][2] == ' ' and \
|
||||
board.board[0][2] == ' ' and \
|
||||
board.board[2][0] == ' ':
|
||||
return 3
|
||||
return False
|
||||
|
||||
def reset_game(self):
|
||||
self.remaining_sides = deepcopy(self._side_moves)
|
||||
self.remaining_corners = deepcopy(self._corner_moves)
|
||||
self.starting_player = False
|
||||
@@ -1,9 +1,11 @@
|
||||
from django.urls import path
|
||||
|
||||
from geeksbot_v2.users.views import UsersAPI, UserDetail
|
||||
from geeksbot_v2.users.views import UsersAPI, UserDetail, UserLogList, UserLogDetail
|
||||
|
||||
app_name = "users_api"
|
||||
urlpatterns = [
|
||||
path("", view=UsersAPI.as_view(), name="list"),
|
||||
path("<str:id>/", view=UserDetail.as_view(), name="detail"),
|
||||
path("<str:id>/logs/", view=UserLogList.as_view(), name="log_list"),
|
||||
path("<str:id>/logs/<str:log>", view=UserLogDetail.as_view(), name="log_detail"),
|
||||
]
|
||||
|
||||
@@ -4,7 +4,7 @@ from geeksbot_v2.users.models import User
|
||||
from geeksbot_v2.users.models import UserLog
|
||||
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
class UserSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = [
|
||||
@@ -21,11 +21,37 @@ class UserSerializer(serializers.ModelSerializer):
|
||||
'avatar',
|
||||
'bot',
|
||||
'banned',
|
||||
'logging_enabled'
|
||||
'logging_enabled',
|
||||
'is_staff',
|
||||
'is_superuser',
|
||||
'url'
|
||||
]
|
||||
extra_kwargs = {
|
||||
'url': {
|
||||
'view_name': 'users_api:detail',
|
||||
'lookup_field': 'id'
|
||||
},
|
||||
'guilds': {
|
||||
'view_name': 'guilds_api:detail',
|
||||
'lookup_field': 'id'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class UserLogSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = UserLog
|
||||
fields = "__all__"
|
||||
fields = [
|
||||
'user',
|
||||
'time',
|
||||
'action',
|
||||
'description',
|
||||
'url'
|
||||
]
|
||||
extra_fields = {
|
||||
'url': {
|
||||
'view_name': 'users_api:log_detail',
|
||||
'lookup_field': 'id',
|
||||
'lookup_url_kwarg': 'log'
|
||||
}
|
||||
}
|
||||
|
||||
+74
-53
@@ -4,6 +4,7 @@ from django.views.generic import DetailView, RedirectView, UpdateView
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework import generics
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework import status
|
||||
|
||||
@@ -11,6 +12,10 @@ from rest_framework import status
|
||||
from .models import UserLog
|
||||
from geeksbot_v2.utils.api_utils import PaginatedAPIView
|
||||
from .models import User
|
||||
from .serializers import UserSerializer
|
||||
from .serializers import UserLogSerializer
|
||||
from geeksbot_v2.utils.permissions import CustomDjangoModelPermissions
|
||||
from geeksbot_v2.utils.permissions import CustomDjangoObjectPermissions
|
||||
from .utils import create_error_response
|
||||
from .utils import create_success_response
|
||||
from .utils import create_log_success_response
|
||||
@@ -66,73 +71,89 @@ user_redirect_view = UserRedirectView.as_view()
|
||||
# API Views
|
||||
|
||||
|
||||
class UsersAPI(PaginatedAPIView):
|
||||
class UsersAPI(generics.ListCreateAPIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = UserSerializer
|
||||
|
||||
def get(self, request, guild=None, format=None):
|
||||
if guild:
|
||||
users = User.objects.filter(guilds__id=guild)
|
||||
else:
|
||||
users = User.objects.all()
|
||||
page = self.paginate_queryset(users)
|
||||
if page is not None:
|
||||
return create_success_response(page, status.HTTP_200_OK, many=True)
|
||||
def get_queryset(self):
|
||||
return User.objects.filter(guilds__id=self.request.data.get('guild'))
|
||||
|
||||
return create_success_response(users, status.HTTP_200_OK, many=True)
|
||||
|
||||
def post(self, request, format=None):
|
||||
data = dict(request.data)
|
||||
return User.add_new_user(data)
|
||||
# def get(self, request, guild=None, format=None):
|
||||
# if guild:
|
||||
# users = User.objects.filter(guilds__id=guild)
|
||||
# else:
|
||||
# users = User.objects.all()
|
||||
# page = self.paginate_queryset(users)
|
||||
# if page is not None:
|
||||
# return create_success_response(page, status.HTTP_200_OK, many=True)
|
||||
#
|
||||
# return create_success_response(users, status.HTTP_200_OK, many=True)
|
||||
#
|
||||
# def post(self, request, format=None):
|
||||
# data = dict(request.data)
|
||||
# return User.add_new_user(data)
|
||||
|
||||
|
||||
class UserDetail(APIView):
|
||||
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = UserSerializer
|
||||
lookup_field = 'id'
|
||||
|
||||
def get(self, request, id, format=None):
|
||||
user = User.get_user_by_id(id)
|
||||
if not isinstance(user, User):
|
||||
return create_error_response("User Does not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
return create_success_response(user,
|
||||
status=status.HTTP_200_OK)
|
||||
def get_queryset(self):
|
||||
return User.objects.all()
|
||||
|
||||
def put(self, request, id, format=None):
|
||||
user = User.get_user_by_id(id)
|
||||
if isinstance(user, User):
|
||||
data = dict(request.data)
|
||||
return user.update_user(data)
|
||||
else:
|
||||
return create_error_response("User Does Not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
# def get(self, request, id, format=None):
|
||||
# user = User.get_user_by_id(id)
|
||||
# if not isinstance(user, User):
|
||||
# return create_error_response("User Does not Exist",
|
||||
# status=status.HTTP_404_NOT_FOUND)
|
||||
# return create_success_response(user,
|
||||
# status=status.HTTP_200_OK)
|
||||
#
|
||||
# def put(self, request, id, format=None):
|
||||
# user = User.get_user_by_id(id)
|
||||
# if isinstance(user, User):
|
||||
# data = dict(request.data)
|
||||
# return user.update_user(data)
|
||||
# else:
|
||||
# return create_error_response("User Does Not Exist",
|
||||
# status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
class UserLogList(PaginatedAPIView):
|
||||
class UserLogList(generics.ListCreateAPIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = UserLogSerializer
|
||||
|
||||
def get(self, request, user, action=None, format=None):
|
||||
if action:
|
||||
user_logs = UserLog.get_logs_by_user_action(user, action)
|
||||
else:
|
||||
user_logs = UserLog.get_logs_by_user(user)
|
||||
def get_queryset(self):
|
||||
return UserLog.objects.all()
|
||||
|
||||
page = self.paginate_queryset(user_logs)
|
||||
if page is not None:
|
||||
return create_log_success_response(page, status.HTTP_200_OK, many=True)
|
||||
|
||||
return create_log_success_response(user_logs, status.HTTP_200_OK, many=True)
|
||||
|
||||
def post(self, request, user, format=None):
|
||||
data = dict(request.data)
|
||||
return UserLog.add_new_log(user, data)
|
||||
# def get(self, request, user, action=None, format=None):
|
||||
# if action:
|
||||
# user_logs = UserLog.get_logs_by_user_action(user, action)
|
||||
# else:
|
||||
# user_logs = UserLog.get_logs_by_user(user)
|
||||
#
|
||||
# page = self.paginate_queryset(user_logs)
|
||||
# if page is not None:
|
||||
# return create_log_success_response(page, status.HTTP_200_OK, many=True)
|
||||
#
|
||||
# return create_log_success_response(user_logs, status.HTTP_200_OK, many=True)
|
||||
#
|
||||
# def post(self, request, user, format=None):
|
||||
# data = dict(request.data)
|
||||
# return UserLog.add_new_log(user, data)
|
||||
|
||||
|
||||
class UserLogDetail(APIView):
|
||||
class UserLogDetail(generics.RetrieveUpdateAPIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = UserLogSerializer
|
||||
lookup_url_kwarg = 'log'
|
||||
lookup_field = 'id'
|
||||
|
||||
def get(self, request, id, format=None):
|
||||
user_log = UserLog.get_log_by_id(id)
|
||||
if isinstance(user_log, UserLog):
|
||||
return create_log_success_response(user_log, status.HTTP_200_OK, many=False)
|
||||
else:
|
||||
return create_error_response("Log Does Not Exist",
|
||||
status=status.HTTP_404_NOT_FOUND)
|
||||
# def get(self, request, id, format=None):
|
||||
# user_log = UserLog.get_log_by_id(id)
|
||||
# if isinstance(user_log, UserLog):
|
||||
# return create_log_success_response(user_log, status.HTTP_200_OK, many=False)
|
||||
# else:
|
||||
# return create_error_response("Log Does Not Exist",
|
||||
# status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
from rest_framework.permissions import DjangoModelPermissions, DjangoObjectPermissions
|
||||
|
||||
|
||||
class CustomDjangoModelPermissions(DjangoModelPermissions):
|
||||
# Overriding to require view permissions
|
||||
perms_map = {
|
||||
'GET': ['%(app_label)s.view_%(model_name)s'],
|
||||
'OPTIONS': ['%(app_label)s.view_%(model_name)s'],
|
||||
'HEAD': ['%(app_label)s.view_%(model_name)s'],
|
||||
'POST': ['%(app_label)s.add_%(model_name)s'],
|
||||
'PUT': ['%(app_label)s.change_%(model_name)s'],
|
||||
'PATCH': ['%(app_label)s.change_%(model_name)s'],
|
||||
'DELETE': ['%(app_label)s.delete_%(model_name)s'],
|
||||
}
|
||||
|
||||
|
||||
class CustomDjangoObjectPermissions(DjangoObjectPermissions):
|
||||
# Overriding to require view permissions
|
||||
perms_map = {
|
||||
'GET': ['%(app_label)s.view_%(model_name)s'],
|
||||
'OPTIONS': ['%(app_label)s.view_%(model_name)s'],
|
||||
'HEAD': ['%(app_label)s.view_%(model_name)s'],
|
||||
'POST': ['%(app_label)s.add_%(model_name)s'],
|
||||
'PUT': ['%(app_label)s.change_%(model_name)s'],
|
||||
'PATCH': ['%(app_label)s.change_%(model_name)s'],
|
||||
'DELETE': ['%(app_label)s.delete_%(model_name)s'],
|
||||
}
|
||||
Reference in New Issue
Block a user