Multiple changes
Started switching to generics Added tickets
This commit is contained in:
parent
27231fda7b
commit
b8f751bae9
@ -1,25 +1,25 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
base:
|
||||
geeksbot-base:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: "${PWD}/services/Dockerfile-base"
|
||||
image: base:latest
|
||||
|
||||
db:
|
||||
image: geeksbot-base:latest
|
||||
|
||||
geeksbot-db:
|
||||
image: postgres
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- "${PWD}/services/postgresql/postgres.conf:/etc/postgresql/postgresql.conf"
|
||||
- "db:/var/lib/postgresql/data:rw"
|
||||
- "geeksbot-db:/var/lib/postgresql/data:rw"
|
||||
env_file: ${PWD}/.env
|
||||
redis:
|
||||
geeksbot-redis:
|
||||
image: redis:5.0.3
|
||||
ports:
|
||||
- "6379:6379"
|
||||
web:
|
||||
geeksbot-web:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: "${PWD}/services/Dockerfile-web"
|
||||
@ -29,9 +29,9 @@ services:
|
||||
- "8000:8000"
|
||||
- "443:443"
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
- base
|
||||
- geeksbot-db
|
||||
- geeksbot-redis
|
||||
- geeksbot-base
|
||||
environment:
|
||||
- DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
|
||||
- REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT}/${REDIS_DB}
|
||||
@ -43,12 +43,12 @@ services:
|
||||
dockerfile: "${PWD}/services/Dockerfile-geeksbot"
|
||||
env_file: ${PWD}/.env
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
- base
|
||||
- web
|
||||
- geeksbot-db
|
||||
- geeksbot-redis
|
||||
- geeksbot-base
|
||||
- geeksbot-web
|
||||
links:
|
||||
- web:geeksbot.app
|
||||
- geeksbot-web:geeksbot.app
|
||||
volumes:
|
||||
- ${PWD}/geeksbot:/code/geeksbot
|
||||
- ~/.ssh/id_rsa:/root/.ssh/id_rsa
|
||||
@ -56,5 +56,5 @@ services:
|
||||
- ~/.ssh/known_hosts:/root/.ssh/known_hosts
|
||||
|
||||
volumes:
|
||||
db:
|
||||
geeksbot-db:
|
||||
external: true
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
"load_list": [
|
||||
"admin",
|
||||
"exec",
|
||||
"message_events"
|
||||
"message_events",
|
||||
"tickets"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -1 +1,218 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
from geeksbot.imports.utils import Paginator, Book
|
||||
|
||||
|
||||
class Tickets(commands.Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
@commands.command()
|
||||
async def request(self, ctx, *, message=None):
|
||||
if not ctx.guild:
|
||||
await ctx.send('This command must be run from inside a guild.')
|
||||
return
|
||||
|
||||
if not message:
|
||||
await ctx.send('Please include a message containing your request')
|
||||
return
|
||||
|
||||
if len(message) > 1000:
|
||||
await ctx.send('Request is too long, please keep your request to less than 1000 characters.')
|
||||
return
|
||||
|
||||
data = {
|
||||
'author': ctx.author.id,
|
||||
'message': ctx.message.id,
|
||||
'channel': ctx.channel.id,
|
||||
'content': message
|
||||
}
|
||||
msg_resp = await self.bot.aio_session.get(f'{self.bot.api_base}/messages/{ctx.message.id}/wait/', headers=self.bot.auth_header)
|
||||
if msg_resp.status == 404:
|
||||
error = await msg_resp.json()
|
||||
await ctx.send(error['details'])
|
||||
return
|
||||
|
||||
resp = await self.bot.aio_session.post(f'{self.bot.api_base}/messages/{ctx.guild.id}/requests/', headers=self.bot.auth_header, json=data)
|
||||
|
||||
if resp.status == 201:
|
||||
admin_channel_resp = await self.bot.aio_session.get(f'{self.bot.api_base}/channels/{ctx.guild.id}/admin/', headers=self.bot.auth_header)
|
||||
request = await resp.json()
|
||||
|
||||
if admin_channel_resp.status == 200:
|
||||
admin_chan_data = await admin_channel_resp.json()
|
||||
msg = f''
|
||||
admin_roles_resp = await self.bot.aio_session.get(f'{self.bot.api_base}/guilds/{ctx.guild.id}/roles/admin/', headers=self.bot.auth_header)
|
||||
if admin_roles_resp.status == 200:
|
||||
admin_roles_data = await admin_roles_resp.json()
|
||||
for role in admin_roles_data:
|
||||
msg += f'{ctx.guild.get_role(int(role["id"])).mention} '
|
||||
msg += f"New Request ID: {request['id']} " \
|
||||
f"{ctx.author.mention} has requested assistance: \n" \
|
||||
f"```{request['content']}``` \n" \
|
||||
f"Requested at: {request['requested_at'].split('.')[0].replace('T', ' ')} GMT\n" \
|
||||
f"In {ctx.guild.get_channel(int(request['channel'])).name}"
|
||||
admin_chan = ctx.guild.get_channel(int(admin_chan_data['id']))
|
||||
await admin_chan.send(msg)
|
||||
await ctx.send(f'{ctx.author.mention} The admin have received your request.\n'
|
||||
f'If you would like to update or close your request please reference Request ID `{request["id"]}`')
|
||||
|
||||
@commands.command(aliases=['comment'])
|
||||
async def update(self, ctx, request_id=None, *, comment: str = None):
|
||||
try:
|
||||
request_id = int(request_id)
|
||||
except ValueError:
|
||||
await ctx.send("Please include the ID of the request you would like to update as the first thing after the command.")
|
||||
return
|
||||
|
||||
if not comment:
|
||||
await ctx.send("There is nothing to update since you didn't include a message.")
|
||||
return
|
||||
|
||||
data = {
|
||||
'author': ctx.author.id,
|
||||
'content': comment
|
||||
}
|
||||
|
||||
comment_resp = await self.bot.aio_session.post(f'{self.bot.api_base}/messages/{ctx.guild.id}/requests/{request_id}/comments/', headers=self.bot.auth_header, json=data)
|
||||
|
||||
if comment_resp.status == 201:
|
||||
comment = await comment_resp.json()
|
||||
admin_channel_resp = await self.bot.aio_session.get(f'{self.bot.api_base}/channels/{ctx.guild.id}/admin/',
|
||||
headers=self.bot.auth_header)
|
||||
|
||||
if admin_channel_resp.status == 200:
|
||||
admin_channel_data = await admin_channel_resp.json()
|
||||
admin_channel = ctx.guild.get_channel(int(admin_channel_data['id']))
|
||||
if admin_channel:
|
||||
request_resp = await self.bot.aio_session.get(f'{self.bot.api_base}/messages/{ctx.guild.id}/requests/{request_id}/', headers=self.bot.auth_header)
|
||||
pag = Paginator(self.bot, prefix='```md', suffix='```')
|
||||
header = f'{ctx.author.mention} has commented on request {request_id}\n'
|
||||
if request_resp.status == 200:
|
||||
request = await request_resp.json()
|
||||
requestor = ctx.guild.get_member(int(request["author"]))
|
||||
header += (f'Original Request by {requestor.mention if requestor else "`User cannot be found`"}:\n'
|
||||
f'```{request["content"]}```')
|
||||
pag.set_header(header)
|
||||
|
||||
if request.get('comments'):
|
||||
comments = request['comments']
|
||||
for comment in comments:
|
||||
author = ctx.guild.get_member(int(comment['author']))
|
||||
pag.add(f'{author.display_name}: {comment["content"]}', keep_intact=True)
|
||||
if ctx.author != requestor and requestor:
|
||||
for page in pag.pages(page_headers=False):
|
||||
await requestor.send(page)
|
||||
book = Book(pag, (None, admin_channel, self.bot, ctx.message))
|
||||
await book.create_book()
|
||||
await ctx.send(f'{ctx.author.mention} Your comment has been added to the request.')
|
||||
|
||||
@commands.command(name='requests_list', aliases=['rl'])
|
||||
async def _requests_list(self, ctx, closed: str = ''):
|
||||
pag = Paginator(self.bot)
|
||||
admin_roles_resp = await self.bot.aio_session.get(f'{self.bot.api_base}/guilds/{ctx.guild.id}/roles/admin/', headers=self.bot.auth_header)
|
||||
if admin_roles_resp.status == 200:
|
||||
admin_roles_data = await admin_roles_resp.json()
|
||||
admin_roles = [ctx.guild.get_role(int(role['id'])) for role in admin_roles_data]
|
||||
if any([role in ctx.author.roles for role in admin_roles]):
|
||||
requests_resp = await self.bot.aio_session.get(f'{self.bot.api_base}/messages/{ctx.guild.id}/requests/', headers=self.bot.auth_header)
|
||||
if requests_resp.status == 200:
|
||||
requests_data = await requests_resp.json()
|
||||
requests_list = requests_data['requests'] if isinstance(requests_data, dict) else requests_data
|
||||
while isinstance(requests_data, dict) and requests_data.get('next'):
|
||||
requests_resp = await self.bot.aio_session.get(
|
||||
requests_data['next'], headers=self.bot.auth_header)
|
||||
if requests_resp.status == 200:
|
||||
requests_data = await requests_resp.json()
|
||||
requests_list.extend(requests_data['requests'] if isinstance(requests_data, dict) else requests_data)
|
||||
for request in requests_list:
|
||||
member = discord.utils.get(ctx.guild.members, id=int(request['author']))
|
||||
title = (f"<{'Request ID':^20} {'Requested By':^20}>\n"
|
||||
f"<{request['id']:^20} {member.display_name if member else 'None':^20}>")
|
||||
orig_channel = ctx.guild.get_channel(int(request.get('channel')))
|
||||
comments_count_resp = await self.bot.aio_session.get(f'{self.bot.api_base}/messages/{ctx.guild.id}/requests/{request["id"]}/comments/count/', headers=self.bot.auth_header)
|
||||
pag.add(f"\n\n{title}\n"
|
||||
f"{request['content']}\n\n"
|
||||
f"Comments: {await comments_count_resp.text() if comments_count_resp.status == 200 else 0}\n"
|
||||
f"Requested at: "
|
||||
f"{request['requested_at'].split('.')[0].replace('T', ' ')} GMT\n"
|
||||
f"In {orig_channel.name if orig_channel else 'N/A'}", keep_intact=True)
|
||||
pag.add(f'\n\uFFF8\nThere are currently {len(requests_list)} requests open.')
|
||||
else:
|
||||
pag.add('There are no open requests for this guild.', keep_intact=True)
|
||||
else:
|
||||
requests_resp = await self.bot.aio_session.get(f'{self.bot.api_base}/messages/{ctx.guild.id}/requests/user/{ctx.author.id}/',
|
||||
headers=self.bot.auth_header)
|
||||
if requests_resp.status == 200:
|
||||
requests_data = await requests_resp.json()
|
||||
requests_list = requests_data['requests'] if isinstance(requests_data, dict) else requests_data
|
||||
while isinstance(requests_data, dict) and requests_data.get('next'):
|
||||
requests_resp = await self.bot.aio_session.get(
|
||||
requests_data['next'], headers=self.bot.auth_header)
|
||||
if requests_resp.status == 200:
|
||||
requests_data = await requests_resp.json()
|
||||
requests_list.extend(
|
||||
requests_data['requests'] if isinstance(requests_data, dict) else requests_data)
|
||||
for request in requests_list:
|
||||
title = (f"<{'Request ID':^20}>\n"
|
||||
f"<{request['id']:^20}>")
|
||||
orig_channel = ctx.guild.get_channel(int(request.get('channel')))
|
||||
comments_count_resp = await self.bot.aio_session.get(
|
||||
f'{self.bot.api_base}/messages/{ctx.guild.id}/requests/{request["id"]}/comments/count/',
|
||||
headers=self.bot.auth_header)
|
||||
pag.add(f"\n\n{title}\n"
|
||||
f"{request['content']}\n\n"
|
||||
f"Comments: {await comments_count_resp.text() if comments_count_resp.status == 200 else 0}\n"
|
||||
f"Requested at: "
|
||||
f"{request['requested_at'].split('.')[0].replace('T', ' ')} GMT\n"
|
||||
f"In {orig_channel.name if orig_channel else 'N/A'}", keep_intact=True)
|
||||
pag.add(f'\n\uFFF8\nYou currently have {len(requests_list)} requests open.')
|
||||
else:
|
||||
pag.add('You have no open requests for this guild.', keep_intact=True)
|
||||
for page in pag.pages():
|
||||
await ctx.send(page)
|
||||
|
||||
@commands.command()
|
||||
async def close(self, ctx, *, ids=None):
|
||||
if not ids:
|
||||
await ctx.send('Please include at least one Request ID to close.')
|
||||
return
|
||||
|
||||
admin = False
|
||||
admin_roles_resp = await self.bot.aio_session.get(f'{self.bot.api_base}/guilds/{ctx.guild.id}/roles/admin/',
|
||||
headers=self.bot.auth_header)
|
||||
if admin_roles_resp.status == 200:
|
||||
admin_roles_data = await admin_roles_resp.json()
|
||||
admin_roles = [ctx.guild.get_role(int(role['id'])) for role in admin_roles_data]
|
||||
if any([role in ctx.author.roles for role in admin_roles]):
|
||||
admin = True
|
||||
|
||||
ids = [id.strip() for id in ids.replace(' ', '').split(',')]
|
||||
|
||||
for id in ids:
|
||||
request_resp = await self.bot.aio_session.get(f'{self.bot.api_base}/messages/{ctx.guild.id}/requests/{id}/', headers=self.bot.auth_header)
|
||||
if request_resp.status == 200:
|
||||
request = await request_resp.json()
|
||||
requestor = ctx.guild.get_member(int(request['author']))
|
||||
if requestor == ctx.author or admin:
|
||||
data = {
|
||||
'completed_by': ctx.author.id
|
||||
}
|
||||
delete_resp = await self.bot.aio_session.delete(f'{self.bot.api_base}/messages/{ctx.guild.id}/requests/{id}/', headers=self.bot.auth_header, json=data)
|
||||
if delete_resp.status == 202:
|
||||
delete_data = await delete_resp.json()
|
||||
if delete_data['completed']:
|
||||
await ctx.send(f'Request {id} closed.')
|
||||
await requestor.send(f'{ctx.author.display_name} has closed request {id} which was '
|
||||
f'opened by you in the '
|
||||
f'{ctx.guild.get_channel(int(request["channel"])).name} '
|
||||
f'channel.'
|
||||
f'```{request["content"]}```'
|
||||
f'If there are any issues please open a new request.')
|
||||
else:
|
||||
await ctx.send('That is not your request to close.')
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(Tickets(bot))
|
||||
|
||||
158
geeksbot/imports/message_logging.py
Normal file
158
geeksbot/imports/message_logging.py
Normal file
@ -0,0 +1,158 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import discord
|
||||
|
||||
|
||||
async def on_message(bot, message, user_info):
|
||||
if not user_info.get('disable_logging'):
|
||||
if message.guild:
|
||||
msg_data = {
|
||||
'author': str(message.author.id),
|
||||
'channel': str(message.channel.id),
|
||||
'mention_everyone': message.mention_everyone,
|
||||
'created_at': message.created_at
|
||||
}
|
||||
if message.mentions:
|
||||
msg_data['mentions'] = [str(user.id) for user in message.mentions]
|
||||
if message.channel_mentions:
|
||||
msg_data['channel_mentions'] = [str(channel.id) for channel in message.channel_mentions]
|
||||
if message.role_mentions:
|
||||
msg_data['role_mentions'] = [str(role.id) for role in message.role_mentions]
|
||||
if message.embeds:
|
||||
msg_data['embeds'] = [e.to_dict() for e in message.embeds]
|
||||
if message.content:
|
||||
msg_data['content'] = message.content
|
||||
if message.webhook_id:
|
||||
msg_data['webhook_id'] = message.webhook_id
|
||||
if message.tts:
|
||||
msg_data['tts'] = message.tts
|
||||
if message.attachments:
|
||||
msg_data['attachments'] = [{
|
||||
'id': str(a.id),
|
||||
'size': a.size,
|
||||
'filename': a.filename,
|
||||
'url': a.url
|
||||
} for a in message.attachments]
|
||||
|
||||
bot.fs_db.document(f'guilds/{message.guild.id}/messages/{message.id}').set(msg_data)
|
||||
else:
|
||||
msg_data = {
|
||||
'author': str(message.author.id),
|
||||
'created_at': message.created_at
|
||||
}
|
||||
if message.mentions:
|
||||
msg_data['mentions'] = [str(user.id) for user in message.mentions]
|
||||
if message.embeds:
|
||||
msg_data['embeds'] = [e.to_dict() for e in message.embeds]
|
||||
if message.content:
|
||||
msg_data['content'] = message.content
|
||||
if message.webhook_id:
|
||||
msg_data['webhook_id'] = message.webhook_id
|
||||
if message.tts:
|
||||
msg_data['tts'] = message.tts
|
||||
if message.attachments:
|
||||
msg_data['attachments'] = [{
|
||||
'id': str(a.id),
|
||||
'size': a.size,
|
||||
'filename': a.filename,
|
||||
'url': a.url
|
||||
} for a in message.attachments]
|
||||
|
||||
bot.fs_db.document(f'dm_channels/{message.channel.id}/messages/{message.id}').set(msg_data)
|
||||
|
||||
|
||||
async def on_message_edit(bot, before: discord.Message, after: discord.Message, user_config):
|
||||
if not user_config.get('disable_logging'):
|
||||
if after.guild:
|
||||
msg_ref = bot.fs_db.document(f'guilds/{after.guild.id}/messages/{after.id}')
|
||||
msg_data = (await bot.loop.run_in_executor(bot.tpe, msg_ref.get)).to_dict()
|
||||
if before.content != after.content:
|
||||
if before.content:
|
||||
if msg_data.get('previous_content') and isinstance(msg_data['previous_content'], list):
|
||||
msg_data['previous_content'].append(before.content)
|
||||
else:
|
||||
msg_data['previous_content'] = [before.content, ]
|
||||
msg_data['content'] = after.content
|
||||
if before.embeds != after.embeds:
|
||||
if before.embeds:
|
||||
if msg_data.get('previous_embeds') and isinstance(msg_data['previous_embeds'], list):
|
||||
msg_data['previous_embeds'].append(before.embeds[0].to_dict())
|
||||
else:
|
||||
msg_data['previous_embeds'] = [before.embeds[0].to_dict(), ]
|
||||
msg_data['embeds'] = [e.to_dict() for e in after.embeds]
|
||||
if before.pinned != after.pinned:
|
||||
msg_data['pinned'] = after.pinned
|
||||
if before.mentions != after.mentions:
|
||||
msg_data['mentions'] = [str(user.id) for user in after.mentions]
|
||||
if before.channel_mentions != after.channel_mentions:
|
||||
msg_data['channel_mentions'] = [str(user.id) for user in after.channel_mentions]
|
||||
if before.role_mentions != after.role_mentions:
|
||||
msg_data['role_mentions'] = [str(user.id) for user in after.role_mentions]
|
||||
if before.attachments != after.attachments:
|
||||
if before.attachments:
|
||||
if msg_data.get('previous_attachments') and isinstance(msg_data['previous_attachments'], list):
|
||||
msg_data['previous_attachments'].append([{
|
||||
'id': str(a.id),
|
||||
'size': a.size,
|
||||
'filename': a.filename,
|
||||
'url': a.url
|
||||
} for a in before.attachments])
|
||||
else:
|
||||
msg_data['previous_attachments'] = [[{
|
||||
'id': a.id,
|
||||
'size': a.size,
|
||||
'filename': a.filename,
|
||||
'url': a.url
|
||||
} for a in before.attachments], ]
|
||||
msg_data['attachments'] = [{
|
||||
'id': a.id,
|
||||
'size': a.size,
|
||||
'filename': a.filename,
|
||||
'url': a.url
|
||||
} for a in after.attachments]
|
||||
|
||||
bot.fs_db.document(f'guilds/{after.guild.id}/messages/{after.id}').set(msg_data)
|
||||
else:
|
||||
msg_ref = bot.fs_db.document(f'dm_channels/{after.channel.id}/messages/{after.id}')
|
||||
msg_data = (await bot.loop.run_in_executor(bot.tpe, msg_ref.get)).to_dict()
|
||||
if before.content != after.content:
|
||||
if before.content:
|
||||
if msg_data.get('previous_content') and isinstance(msg_data['previous_content'], list):
|
||||
msg_data['previous_content'].append(before.content)
|
||||
else:
|
||||
msg_data['previous_content'] = [before.content, ]
|
||||
msg_data['content'] = after.content
|
||||
if before.embeds != after.embeds:
|
||||
if before.embeds:
|
||||
if msg_data.get('previous_embeds') and isinstance(msg_data['previous_embeds'], list):
|
||||
msg_data['previous_embeds'].append(before.embeds[0].to_dict())
|
||||
else:
|
||||
msg_data['previous_embeds'] = [before.embeds[0].to_dict(), ]
|
||||
msg_data['embeds'] = [e.to_dict() for e in after.embeds]
|
||||
if before.pinned != after.pinned:
|
||||
msg_data['pinned'] = after.pinned
|
||||
if before.mentions != after.mentions:
|
||||
msg_data['mentions'] = [str(user.id) for user in after.mentions]
|
||||
if before.attachments != after.attachments:
|
||||
if before.attachments:
|
||||
if msg_data.get('previous_attachments') and isinstance(msg_data['previous_attachments'], list):
|
||||
msg_data['previous_attachments'].append([{
|
||||
'id': str(a.id),
|
||||
'size': a.size,
|
||||
'filename': a.filename,
|
||||
'url': a.url
|
||||
} for a in before.attachments])
|
||||
else:
|
||||
msg_data['previous_attachments'] = [[{
|
||||
'id': a.id,
|
||||
'size': a.size,
|
||||
'filename': a.filename,
|
||||
'url': a.url
|
||||
} for a in before.attachments], ]
|
||||
msg_data['attachments'] = [{
|
||||
'id': a.id,
|
||||
'size': a.size,
|
||||
'filename': a.filename,
|
||||
'url': a.url
|
||||
} for a in after.attachments]
|
||||
|
||||
bot.fs_db.document(f'dm_channels/{after.channel.id}/messages/{after.id}').set(msg_data)
|
||||
@ -84,7 +84,8 @@ class Paginator:
|
||||
field_name_char: str = '\uFFF6',
|
||||
inline_char: str = '\uFFF5',
|
||||
max_line_length: int = 100,
|
||||
embed=False):
|
||||
embed=False,
|
||||
header: str = ''):
|
||||
_max_len = 6000 if embed else 1980
|
||||
assert 0 < max_lines <= max_chars
|
||||
|
||||
@ -110,6 +111,7 @@ class Paginator:
|
||||
self._embed_thumbnail = None
|
||||
self._embed_url = None
|
||||
self._bot = bot
|
||||
self._header = header
|
||||
|
||||
def set_embed_meta(self, title: str = None,
|
||||
description: str = None,
|
||||
@ -129,7 +131,7 @@ class Paginator:
|
||||
self._embed_thumbnail = thumbnail
|
||||
self._embed_url = url
|
||||
|
||||
def pages(self) -> typing.List[str]:
|
||||
def pages(self, page_headers: bool = True) -> typing.List[str]:
|
||||
_pages = list()
|
||||
_fields = list()
|
||||
_page = ''
|
||||
@ -138,10 +140,16 @@ class Paginator:
|
||||
_field_value = ''
|
||||
_inline = False
|
||||
|
||||
def open_page():
|
||||
def open_page(initial: bool = False):
|
||||
nonlocal _page, _lines, _fields
|
||||
if not self._embed:
|
||||
_page = self._prefix
|
||||
if initial and not page_headers:
|
||||
_page = self._header
|
||||
elif page_headers:
|
||||
_page = self._header
|
||||
else:
|
||||
_page = ''
|
||||
_page += self._prefix
|
||||
_lines = 0
|
||||
else:
|
||||
_fields = list()
|
||||
@ -156,7 +164,7 @@ class Paginator:
|
||||
_pages.append(_fields)
|
||||
open_page()
|
||||
|
||||
open_page()
|
||||
open_page(initial=True)
|
||||
|
||||
if not self._embed:
|
||||
for part in [str(p) for p in self._parts]:
|
||||
@ -254,6 +262,9 @@ class Paginator:
|
||||
# noinspection PyProtectedMember
|
||||
return self.__class__ == other.__class__ and self._parts == other._parts
|
||||
|
||||
def set_header(self, header: str = ''):
|
||||
self._header = header
|
||||
|
||||
def add_page_break(self, *, to_beginning: bool = False) -> None:
|
||||
self.add(self._page_break, to_beginning=to_beginning)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
77
geeksbot_v2/patreon/patron.py
Normal file
77
geeksbot_v2/patreon/patron.py
Normal file
@ -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)
|
||||
10
geeksbot_v2/rcon/api_urls.py
Normal file
10
geeksbot_v2/rcon/api_urls.py
Normal file
@ -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
geeksbot_v2/rcon/rcon_lib/__init__.py
Normal file
0
geeksbot_v2/rcon/rcon_lib/__init__.py
Normal file
118
geeksbot_v2/rcon/rcon_lib/arcon.py
Normal file
118
geeksbot_v2/rcon/rcon_lib/arcon.py
Normal file
@ -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
|
||||
183
geeksbot_v2/rcon/rcon_lib/rcon.py
Normal file
183
geeksbot_v2/rcon/rcon_lib/rcon.py
Normal file
@ -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
geeksbot_v2/shared_libs/TicTacToe/__init__.py
Normal file
0
geeksbot_v2/shared_libs/TicTacToe/__init__.py
Normal file
74
geeksbot_v2/shared_libs/TicTacToe/board.py
Normal file
74
geeksbot_v2/shared_libs/TicTacToe/board.py
Normal file
@ -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
|
||||
|
||||
195
geeksbot_v2/shared_libs/TicTacToe/player.py
Normal file
195
geeksbot_v2/shared_libs/TicTacToe/player.py
Normal file
@ -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
|
||||
0
geeksbot_v2/shared_libs/__init__.py
Normal file
0
geeksbot_v2/shared_libs/__init__.py
Normal file
@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
27
geeksbot_v2/utils/permissions.py
Normal file
27
geeksbot_v2/utils/permissions.py
Normal file
@ -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'],
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
discord.py
|
||||
discord.py>=1.2.5
|
||||
aiohttp<3.6.0
|
||||
aiofiles
|
||||
python-dateutil
|
||||
@ -6,4 +6,4 @@ psutil
|
||||
pytz
|
||||
async_timeout
|
||||
cached_property
|
||||
redis-py
|
||||
redis
|
||||
|
||||
@ -18,3 +18,5 @@ gevent
|
||||
gunicorn==19.9.0 # https://github.com/benoitc/gunicorn
|
||||
psycopg2==2.8.3 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
|
||||
Collectfast==1.0.0 # https://github.com/antonagestam/collectfast
|
||||
|
||||
python-valve
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM python:3.7-alpine AS base
|
||||
FROM python:3.7-alpine AS geeksbot-base
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM base AS geeksbot
|
||||
FROM geeksbot-base AS geeksbot
|
||||
|
||||
WORKDIR /code
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM base AS web
|
||||
FROM geeksbot-base AS geeksbot-web
|
||||
|
||||
WORKDIR /code
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user