diff --git a/.envs/.local/.django b/.envs/.local/.django deleted file mode 100644 index bcde257..0000000 --- a/.envs/.local/.django +++ /dev/null @@ -1,4 +0,0 @@ -# General -# ------------------------------------------------------------------------------ -USE_DOCKER=yes -IPYTHONDIR=/app/.ipython diff --git a/.envs/.local/.postgres b/.envs/.local/.postgres deleted file mode 100644 index 76130c4..0000000 --- a/.envs/.local/.postgres +++ /dev/null @@ -1,7 +0,0 @@ -# PostgreSQL -# ------------------------------------------------------------------------------ -POSTGRES_HOST=postgres -POSTGRES_PORT=5432 -POSTGRES_DB=geeksbot_v2 -POSTGRES_USER=eJujPHAXiaktuvRsJQlNbdhzHbkEGdgs -POSTGRES_PASSWORD=hrjQlc8cfRVa0pgeJ1OWcsh4ZbRpTS1bJbMP2Atx3zKivMDJWKO71Dly0jFmRUWq diff --git a/.idea/geeksbot_v2.iml b/.idea/geeksbot_v2.iml deleted file mode 100644 index b21232c..0000000 --- a/.idea/geeksbot_v2.iml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index cb27393..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 54abf96..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/.idea/runConfigurations/merge_production_dotenvs_in_dotenv.xml b/.idea/runConfigurations/merge_production_dotenvs_in_dotenv.xml deleted file mode 100644 index 56e1755..0000000 --- a/.idea/runConfigurations/merge_production_dotenvs_in_dotenv.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - diff --git a/.idea/runConfigurations/migrate.xml b/.idea/runConfigurations/migrate.xml deleted file mode 100644 index 22f8d2e..0000000 --- a/.idea/runConfigurations/migrate.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - diff --git a/.idea/runConfigurations/pytest___.xml b/.idea/runConfigurations/pytest___.xml deleted file mode 100644 index 88bf101..0000000 --- a/.idea/runConfigurations/pytest___.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/pytest__users.xml b/.idea/runConfigurations/pytest__users.xml deleted file mode 100644 index 401c728..0000000 --- a/.idea/runConfigurations/pytest__users.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/runserver.xml b/.idea/runConfigurations/runserver.xml deleted file mode 100644 index a4781f6..0000000 --- a/.idea/runConfigurations/runserver.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - diff --git a/.idea/runConfigurations/runserver_plus.xml b/.idea/runConfigurations/runserver_plus.xml deleted file mode 100644 index 055b235..0000000 --- a/.idea/runConfigurations/runserver_plus.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 5ace414..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/.idea/webResources.xml b/.idea/webResources.xml deleted file mode 100644 index eab2601..0000000 --- a/.idea/webResources.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/compose/local/django/Dockerfile b/compose/local/django/Dockerfile deleted file mode 100644 index d3e1b32..0000000 --- a/compose/local/django/Dockerfile +++ /dev/null @@ -1,32 +0,0 @@ -FROM python:3.6-alpine - -ENV PYTHONUNBUFFERED 1 - -RUN apk update \ - # psycopg2 dependencies - && apk add --virtual build-deps gcc python3-dev musl-dev \ - && apk add postgresql-dev \ - # Pillow dependencies - && apk add jpeg-dev zlib-dev freetype-dev lcms2-dev openjpeg-dev tiff-dev tk-dev tcl-dev \ - # CFFI dependencies - && apk add libffi-dev py-cffi \ - # Translations dependencies - && apk add gettext \ - # https://docs.djangoproject.com/en/dev/ref/django-admin/#dbshell - && apk add postgresql-client - -# Requirements are installed here to ensure they will be cached. -COPY ./requirements /requirements -RUN pip install -r /requirements/local.txt - -COPY ./compose/production/django/entrypoint /entrypoint -RUN sed -i 's/\r$//g' /entrypoint -RUN chmod +x /entrypoint - -COPY ./compose/local/django/start /start -RUN sed -i 's/\r$//g' /start -RUN chmod +x /start - -WORKDIR /app - -ENTRYPOINT ["/entrypoint"] diff --git a/compose/local/django/start b/compose/local/django/start deleted file mode 100644 index 921604d..0000000 --- a/compose/local/django/start +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -set -o errexit -set -o pipefail -set -o nounset - - -python manage.py migrate -python manage.py runserver_plus 0.0.0.0:8000 diff --git a/compose/production/aws/Dockerfile b/compose/production/aws/Dockerfile deleted file mode 100644 index 8282047..0000000 --- a/compose/production/aws/Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM garland/aws-cli-docker:1.15.47 - -COPY ./compose/production/aws/maintenance /usr/local/bin/maintenance -COPY ./compose/production/postgres/maintenance/_sourced /usr/local/bin/maintenance/_sourced - -RUN chmod +x /usr/local/bin/maintenance/* - -RUN mv /usr/local/bin/maintenance/* /usr/local/bin \ - && rmdir /usr/local/bin/maintenance diff --git a/compose/production/aws/maintenance/download b/compose/production/aws/maintenance/download deleted file mode 100644 index 8d5ea09..0000000 --- a/compose/production/aws/maintenance/download +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh - -### Download a file from your Amazon S3 bucket to the postgres /backups folder -### -### Usage: -### $ docker-compose -f production.yml run --rm awscli <1> - -set -o errexit -set -o pipefail -set -o nounset - -working_dir="$(dirname ${0})" -source "${working_dir}/_sourced/constants.sh" -source "${working_dir}/_sourced/messages.sh" - -export AWS_ACCESS_KEY_ID="${DJANGO_AWS_ACCESS_KEY_ID}" -export AWS_SECRET_ACCESS_KEY="${DJANGO_AWS_SECRET_ACCESS_KEY}" -export AWS_STORAGE_BUCKET_NAME="${DJANGO_AWS_STORAGE_BUCKET_NAME}" - - -aws s3 cp s3://${AWS_STORAGE_BUCKET_NAME}${BACKUP_DIR_PATH}/${1} ${BACKUP_DIR_PATH}/${1} - -message_success "Finished downloading ${1}." - diff --git a/compose/production/aws/maintenance/upload b/compose/production/aws/maintenance/upload deleted file mode 100644 index 4a89dcb..0000000 --- a/compose/production/aws/maintenance/upload +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/sh - -### Upload the /backups folder to Amazon S3 -### -### Usage: -### $ docker-compose -f production.yml run --rm awscli upload - -set -o errexit -set -o pipefail -set -o nounset - -working_dir="$(dirname ${0})" -source "${working_dir}/_sourced/constants.sh" -source "${working_dir}/_sourced/messages.sh" - -export AWS_ACCESS_KEY_ID="${DJANGO_AWS_ACCESS_KEY_ID}" -export AWS_SECRET_ACCESS_KEY="${DJANGO_AWS_SECRET_ACCESS_KEY}" -export AWS_STORAGE_BUCKET_NAME="${DJANGO_AWS_STORAGE_BUCKET_NAME}" - - -message_info "Upload the backups directory to S3 bucket {$AWS_STORAGE_BUCKET_NAME}" - -aws s3 cp ${BACKUP_DIR_PATH} s3://${AWS_STORAGE_BUCKET_NAME}${BACKUP_DIR_PATH} --recursive - -message_info "Cleaning the directory ${BACKUP_DIR_PATH}" - -rm -rf ${BACKUP_DIR_PATH}/* - -message_success "Finished uploading and cleaning." - diff --git a/compose/production/django/Dockerfile b/compose/production/django/Dockerfile deleted file mode 100644 index 29e6d2e..0000000 --- a/compose/production/django/Dockerfile +++ /dev/null @@ -1,40 +0,0 @@ - -FROM python:3.6-alpine - -ENV PYTHONUNBUFFERED 1 - -RUN apk update \ - # psycopg2 dependencies - && apk add --virtual build-deps gcc python3-dev musl-dev \ - && apk add postgresql-dev \ - # Pillow dependencies - && apk add jpeg-dev zlib-dev freetype-dev lcms2-dev openjpeg-dev tiff-dev tk-dev tcl-dev \ - # CFFI dependencies - && apk add libffi-dev py-cffi - -RUN addgroup -S django \ - && adduser -S -G django django - -# Requirements are installed here to ensure they will be cached. -COPY ./requirements /requirements -RUN pip install --no-cache-dir -r /requirements/production.txt \ - && rm -rf /requirements - -COPY ./compose/production/django/entrypoint /entrypoint -RUN sed -i 's/\r$//g' /entrypoint -RUN chmod +x /entrypoint -RUN chown django /entrypoint - -COPY ./compose/production/django/start /start -RUN sed -i 's/\r$//g' /start -RUN chmod +x /start -RUN chown django /start -COPY . /app - -RUN chown -R django /app - -USER django - -WORKDIR /app - -ENTRYPOINT ["/entrypoint"] diff --git a/compose/production/django/entrypoint b/compose/production/django/entrypoint deleted file mode 100644 index 929744d..0000000 --- a/compose/production/django/entrypoint +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/sh - -set -o errexit -set -o pipefail -set -o nounset - - - - -if [ -z "${POSTGRES_USER}" ]; then - base_postgres_image_default_user='postgres' - export POSTGRES_USER="${base_postgres_image_default_user}" -fi -export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}" - -postgres_ready() { -python << END -import sys - -import psycopg2 - -try: - psycopg2.connect( - dbname="${POSTGRES_DB}", - user="${POSTGRES_USER}", - password="${POSTGRES_PASSWORD}", - host="${POSTGRES_HOST}", - port="${POSTGRES_PORT}", - ) -except psycopg2.OperationalError: - sys.exit(-1) -sys.exit(0) - -END -} -until postgres_ready; do - >&2 echo 'Waiting for PostgreSQL to become available...' - sleep 1 -done ->&2 echo 'PostgreSQL is available' - -exec "$@" diff --git a/compose/production/django/start b/compose/production/django/start deleted file mode 100644 index 0ad39df..0000000 --- a/compose/production/django/start +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -set -o errexit -set -o pipefail -set -o nounset - - -python /app/manage.py collectstatic --noinput -/usr/local/bin/gunicorn config.wsgi --bind 0.0.0.0:5000 --chdir=/app diff --git a/compose/production/postgres/Dockerfile b/compose/production/postgres/Dockerfile deleted file mode 100644 index 7cf4173..0000000 --- a/compose/production/postgres/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM postgres:11.3 - -COPY ./compose/production/postgres/maintenance /usr/local/bin/maintenance -RUN chmod +x /usr/local/bin/maintenance/* -RUN mv /usr/local/bin/maintenance/* /usr/local/bin \ - && rmdir /usr/local/bin/maintenance diff --git a/compose/production/postgres/maintenance/_sourced/constants.sh b/compose/production/postgres/maintenance/_sourced/constants.sh deleted file mode 100644 index 6ca4f0c..0000000 --- a/compose/production/postgres/maintenance/_sourced/constants.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - - -BACKUP_DIR_PATH='/backups' -BACKUP_FILE_PREFIX='backup' diff --git a/compose/production/postgres/maintenance/_sourced/countdown.sh b/compose/production/postgres/maintenance/_sourced/countdown.sh deleted file mode 100644 index e6cbfb6..0000000 --- a/compose/production/postgres/maintenance/_sourced/countdown.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - - -countdown() { - declare desc="A simple countdown. Source: https://superuser.com/a/611582" - local seconds="${1}" - local d=$(($(date +%s) + "${seconds}")) - while [ "$d" -ge `date +%s` ]; do - echo -ne "$(date -u --date @$(($d - `date +%s`)) +%H:%M:%S)\r"; - sleep 0.1 - done -} diff --git a/compose/production/postgres/maintenance/_sourced/messages.sh b/compose/production/postgres/maintenance/_sourced/messages.sh deleted file mode 100644 index f6be756..0000000 --- a/compose/production/postgres/maintenance/_sourced/messages.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash - - -message_newline() { - echo -} - -message_debug() -{ - echo -e "DEBUG: ${@}" -} - -message_welcome() -{ - echo -e "\e[1m${@}\e[0m" -} - -message_warning() -{ - echo -e "\e[33mWARNING\e[0m: ${@}" -} - -message_error() -{ - echo -e "\e[31mERROR\e[0m: ${@}" -} - -message_info() -{ - echo -e "\e[37mINFO\e[0m: ${@}" -} - -message_suggestion() -{ - echo -e "\e[33mSUGGESTION\e[0m: ${@}" -} - -message_success() -{ - echo -e "\e[32mSUCCESS\e[0m: ${@}" -} diff --git a/compose/production/postgres/maintenance/_sourced/yes_no.sh b/compose/production/postgres/maintenance/_sourced/yes_no.sh deleted file mode 100644 index fd9cae1..0000000 --- a/compose/production/postgres/maintenance/_sourced/yes_no.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - - -yes_no() { - declare desc="Prompt for confirmation. \$\"\{1\}\": confirmation message." - local arg1="${1}" - - local response= - read -r -p "${arg1} (y/[n])? " response - if [[ "${response}" =~ ^[Yy]$ ]] - then - exit 0 - else - exit 1 - fi -} diff --git a/compose/production/postgres/maintenance/backup b/compose/production/postgres/maintenance/backup deleted file mode 100644 index ee0c9d6..0000000 --- a/compose/production/postgres/maintenance/backup +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash - - -### Create a database backup. -### -### Usage: -### $ docker-compose -f .yml (exec |run --rm) postgres backup - - -set -o errexit -set -o pipefail -set -o nounset - - -working_dir="$(dirname ${0})" -source "${working_dir}/_sourced/constants.sh" -source "${working_dir}/_sourced/messages.sh" - - -message_welcome "Backing up the '${POSTGRES_DB}' database..." - - -if [[ "${POSTGRES_USER}" == "postgres" ]]; then - message_error "Backing up as 'postgres' user is not supported. Assign 'POSTGRES_USER' env with another one and try again." - exit 1 -fi - -export PGHOST="${POSTGRES_HOST}" -export PGPORT="${POSTGRES_PORT}" -export PGUSER="${POSTGRES_USER}" -export PGPASSWORD="${POSTGRES_PASSWORD}" -export PGDATABASE="${POSTGRES_DB}" - -backup_filename="${BACKUP_FILE_PREFIX}_$(date +'%Y_%m_%dT%H_%M_%S').sql.gz" -pg_dump | gzip > "${BACKUP_DIR_PATH}/${backup_filename}" - - -message_success "'${POSTGRES_DB}' database backup '${backup_filename}' has been created and placed in '${BACKUP_DIR_PATH}'." diff --git a/compose/production/postgres/maintenance/backups b/compose/production/postgres/maintenance/backups deleted file mode 100644 index 0484ccf..0000000 --- a/compose/production/postgres/maintenance/backups +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - - -### View backups. -### -### Usage: -### $ docker-compose -f .yml (exec |run --rm) postgres backups - - -set -o errexit -set -o pipefail -set -o nounset - - -working_dir="$(dirname ${0})" -source "${working_dir}/_sourced/constants.sh" -source "${working_dir}/_sourced/messages.sh" - - -message_welcome "These are the backups you have got:" - -ls -lht "${BACKUP_DIR_PATH}" diff --git a/compose/production/postgres/maintenance/restore b/compose/production/postgres/maintenance/restore deleted file mode 100644 index 9661ca7..0000000 --- a/compose/production/postgres/maintenance/restore +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash - - -### Restore database from a backup. -### -### Parameters: -### <1> filename of an existing backup. -### -### Usage: -### $ docker-compose -f .yml (exec |run --rm) postgres restore <1> - - -set -o errexit -set -o pipefail -set -o nounset - - -working_dir="$(dirname ${0})" -source "${working_dir}/_sourced/constants.sh" -source "${working_dir}/_sourced/messages.sh" - - -if [[ -z ${1+x} ]]; then - message_error "Backup filename is not specified yet it is a required parameter. Make sure you provide one and try again." - exit 1 -fi -backup_filename="${BACKUP_DIR_PATH}/${1}" -if [[ ! -f "${backup_filename}" ]]; then - message_error "No backup with the specified filename found. Check out the 'backups' maintenance script output to see if there is one and try again." - exit 1 -fi - -message_welcome "Restoring the '${POSTGRES_DB}' database from the '${backup_filename}' backup..." - -if [[ "${POSTGRES_USER}" == "postgres" ]]; then - message_error "Restoring as 'postgres' user is not supported. Assign 'POSTGRES_USER' env with another one and try again." - exit 1 -fi - -export PGHOST="${POSTGRES_HOST}" -export PGPORT="${POSTGRES_PORT}" -export PGUSER="${POSTGRES_USER}" -export PGPASSWORD="${POSTGRES_PASSWORD}" -export PGDATABASE="${POSTGRES_DB}" - -message_info "Dropping the database..." -dropdb "${PGDATABASE}" - -message_info "Creating a new database..." -createdb --owner="${POSTGRES_USER}" - -message_info "Applying the backup to the new database..." -gunzip -c "${backup_filename}" | psql "${POSTGRES_DB}" - -message_success "The '${POSTGRES_DB}' database has been restored from the '${backup_filename}' backup." diff --git a/compose/production/traefik/Dockerfile b/compose/production/traefik/Dockerfile deleted file mode 100644 index 7088e6f..0000000 --- a/compose/production/traefik/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM traefik:alpine -RUN mkdir -p /etc/traefik/acme -RUN touch /etc/traefik/acme/acme.json -RUN chmod 600 /etc/traefik/acme/acme.json -COPY ./compose/production/traefik/traefik.toml /etc/traefik diff --git a/compose/production/traefik/traefik.toml b/compose/production/traefik/traefik.toml deleted file mode 100644 index a275c7d..0000000 --- a/compose/production/traefik/traefik.toml +++ /dev/null @@ -1,41 +0,0 @@ -logLevel = "INFO" -defaultEntryPoints = ["http", "https"] - -# Entrypoints, http and https -[entryPoints] - # http should be redirected to https - [entryPoints.http] - address = ":80" - [entryPoints.http.redirect] - entryPoint = "https" - # https is the default - [entryPoints.https] - address = ":443" - [entryPoints.https.tls] - -# Enable ACME (Let's Encrypt): automatic SSL -[acme] -# Email address used for registration -email = "dusty.p@geeksbot.app" -storage = "/etc/traefik/acme/acme.json" -entryPoint = "https" -onDemand = false -OnHostRule = true - # Use a HTTP-01 acme challenge rather than TLS-SNI-01 challenge - [acme.httpChallenge] - entryPoint = "http" - -[file] -[backends] - [backends.django] - [backends.django.servers.server1] - url = "http://django:5000" - -[frontends] - [frontends.django] - backend = "django" - passHostHeader = true - [frontends.django.headers] - HostsProxyHeaders = ['X-CSRFToken'] - [frontends.django.routes.dr1] - rule = "Host:geeksbot.app" diff --git a/docker-compose.yml b/docker-compose.yml index 2ccf48c..ab4cee1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,8 +36,6 @@ services: - DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} - REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT}/${REDIS_DB} volumes: - - ${PWD}/config:/code/config - - ${PWD}/geeksbot_web:/code/geeksbot_web - ${PWD}/geeksbot_v2:/code/geeksbot_v2 geeksbot: build: @@ -49,6 +47,8 @@ services: - redis - base - web + links: + - web:geeksbot.app volumes: - ${PWD}/geeksbot:/code/geeksbot - ~/.ssh/id_rsa:/root/.ssh/id_rsa diff --git a/geeksbot/__main__.py b/geeksbot/__main__.py index 4c5a9ff..07ce153 100644 --- a/geeksbot/__main__.py +++ b/geeksbot/__main__.py @@ -53,8 +53,6 @@ logger.info(f'Process Libs Import Complete - Took {(datetime.utcnow() - start).t start = datetime.utcnow() import re # noqa: E402 logger.info('re Imported') -from typing import Dict # noqa: E402 -logger.info('Typing Dict Imported') import json # noqa: E402 logger.info('JSON Imported') import aiohttp # noqa: E402 @@ -98,7 +96,7 @@ class Geeksbot(commands.Bot): async def unload_ext(self, mod): self.unload_extension(f'geeksbot.{self.extension_dir}.{mod}') - logger.info(f'Extension Loaded: {mod}') + logger.info(f'Extension Unloaded: {mod}') def load_default_extensions(self): for load_item in self.bot_config['load_list']: diff --git a/geeksbot/exts/exec.py b/geeksbot/exts/exec.py new file mode 100644 index 0000000..9ba442a --- /dev/null +++ b/geeksbot/exts/exec.py @@ -0,0 +1,221 @@ +from discord.ext import commands +import asyncio +import traceback +import discord +import inspect +import textwrap +import time +import os +from datetime import datetime +from contextlib import redirect_stdout +import io +from geeksbot.imports.utils import run_command, format_output, Paginator, Book +import logging + +repl_log = logging.getLogger('repl') + + +class Exec(commands.Cog): + + def __init__(self, bot): + self.bot = bot + self._last_result = None + self.sessions = set() + + @staticmethod + def cleanup_code(content): + """Automatically removes code blocks from the code.""" + if content.startswith('```') and content.endswith('```'): + return '\n'.join(content.split('\n')[1:(- 1)]) + return content.strip('` \n') + + @staticmethod + def get_syntax_error(e): + if e.text is None: + return '```py\n{0.__class__.__name__}: {0}\n```'.format(e) + return '```py\n{0.text}{1:>{0.offset}}\n{2}: {0}```'.format(e, '^', type(e).__name__) + + @commands.command(hidden=True, name='exec') + async def _eval(self, ctx, *, body: str): + if ctx.author.id != self.bot.owner_id: + return + pag = Paginator(self.bot) + env = { + 'bot': self.bot, + 'ctx': ctx, + 'channel': ctx.channel, + 'author': ctx.author, + 'server': ctx.guild, + 'message': ctx.message, + '_': self._last_result, + } + env.update(globals()) + body = self.cleanup_code(body) + stdout = io.StringIO() + to_compile = 'async def func():\n%s' % textwrap.indent(body, ' ') + try: + exec(to_compile, env) + except SyntaxError as e: + return await ctx.send(self.get_syntax_error(e)) + func = env['func'] + # noinspection PyBroadException + try: + with redirect_stdout(stdout): + ret = await func() + except Exception: + pag.add(stdout.getvalue()) + pag.add(traceback.format_exc()) + for page in pag.pages(): + await ctx.send(page) + else: + value = stdout.getvalue() + # noinspection PyBroadException + try: + await ctx.message.add_reaction('✅') + except Exception: + pass + value = format_output(value) + pag.add(value) + pag.add(f'\nReturned: {ret}') + self._last_result = ret + for page in pag.pages(): + await ctx.send(page) + + @commands.command(hidden=True) + async def repl(self, ctx): + if ctx.author.id != self.bot.owner_id: + return + msg = ctx.message + variables = { + 'ctx': ctx, + 'bot': self.bot, + 'message': msg, + 'server': msg.guild, + 'channel': msg.channel, + 'author': msg.author, + '_': None, + } + if msg.channel.id in self.sessions: + await ctx.send('Already running a REPL session in this channel. Exit it with `quit`.') + return + self.sessions.add(msg.channel.id) + await ctx.send('Enter code to execute or evaluate. `exit()` or `quit` to exit.') + while True: + response = await self.bot.wait_for('message', check=(lambda m: m.content.startswith('`'))) + if response.author.id == self.bot.owner_id: + cleaned = self.cleanup_code(response.content) + if cleaned in ('quit', 'exit', 'exit()'): + await response.channel.send('Exiting.') + self.sessions.remove(msg.channel.id) + return + executor = exec + if cleaned.count('\n') == 0: + try: + code = compile(cleaned, '', 'eval') + except SyntaxError: + pass + else: + executor = eval + if executor is exec: + try: + code = compile(cleaned, '', 'exec') + except SyntaxError as e: + await response.channel.send(self.get_syntax_error(e)) + continue + variables['message'] = response + fmt = None + stdout = io.StringIO() + # noinspection PyBroadException + try: + with redirect_stdout(stdout): + result = executor(code, variables) + if inspect.isawaitable(result): + result = await result + except Exception: + value = stdout.getvalue() + fmt = '{}{}'.format(value, traceback.format_exc()) + else: + value = stdout.getvalue() + if result is not None: + fmt = '{}{}'.format(value, result) + variables['_'] = result + elif value: + fmt = '{}'.format(value) + try: + if fmt is not None: + pag = Paginator(self.bot) + pag.add(fmt) + for page in pag.pages(): + await response.channel.send(page) + await ctx.send(response.channel) + except discord.Forbidden: + pass + except discord.HTTPException as e: + await msg.channel.send('Unexpected error: `{}`'.format(e)) + + @commands.command(hidden=True) + async def os(self, ctx, *, body: str): + if ctx.author.id != self.bot.owner_id: + return + try: + body = self.cleanup_code(body) + pag = Paginator(self.bot) + pag.add(await asyncio.wait_for(self.bot.loop.create_task(run_command(body)), 120)) + for page in pag.pages(): + await ctx.send(page) + await ctx.message.add_reaction('✅') + except asyncio.TimeoutError: + await ctx.send(f"Command did not complete in the time allowed.") + await ctx.message.add_reaction('❌') + + @commands.command(name='haskell', aliases=['hs']) + async def haskell_compiler(self, ctx, *, body: str = None): + if ctx.author.id != self.bot.owner_id: + return + + if body is None: + await ctx.send('Nothing to do.') + return + + async with ctx.typing(): + msg = await ctx.send('Warming up GHC... Please wait.') + try: + body = self.cleanup_code(body) + file_name = f'haskell_{datetime.utcnow().strftime("%Y%m%dT%H%M%S%f")}' + with open(f'{file_name}.hs', 'w') as f: + f.write(body) + pag = Paginator(self.bot) + compile_start = time.time() + pag.add(await asyncio.wait_for( + self.bot.loop.create_task(run_command(f'ghc -o {file_name} {file_name}.hs')), timeout=60)) + compile_end = time.time() + compile_real = compile_end - compile_start + book = Book(pag, (msg, ctx.channel, ctx.bot, ctx.message)) + await book.create_book() + pag = Paginator(self.bot) + if file_name in os.listdir(): + run_start = time.time() + pag.add(await asyncio.wait_for(self.bot.loop.create_task(run_command(f'./{file_name}')), + timeout=600)) + run_end = time.time() + run_real = run_end - run_start + total_real = run_real + compile_real + pag.add(f'\n\nCompile took {compile_real:.2f} seconds') + pag.add(f'Total Time {total_real:.2f} seconds') + book = Book(pag, (None, ctx.channel, ctx.bot, ctx.message)) + await msg.delete() + await book.create_book() + os.remove(file_name) + os.remove(f'{file_name}.hs') + os.remove(f'{file_name}.o') + os.remove(f'{file_name}.hi') + except asyncio.TimeoutError: + await msg.delete() + await ctx.send(f"Command did not complete in the time allowed.") + await ctx.message.add_reaction('❌') + except FileNotFoundError as e: + repl_log.warning(e) + + +def setup(bot): + bot.add_cog(Exec(bot)) diff --git a/geeksbot/imports/utils.py b/geeksbot/imports/utils.py index a06c92d..7926b94 100644 --- a/geeksbot/imports/utils.py +++ b/geeksbot/imports/utils.py @@ -3,6 +3,69 @@ import asyncio import typing +# noinspection PyDefaultArgument +def to_list_of_str(items, out: list = list(), level=1, recurse=0): + # noinspection PyShadowingNames + def rec_loop(item, key, out, level): + quote = '"' + if type(item) == list: + out.append(f'{" "*level}{quote+key+quote+": " if key else ""}[') + new_level = level + 1 + out = to_list_of_str(item, out, new_level, 1) + out.append(f'{" "*level}]') + elif type(item) == dict: + out.append(f'{" "*level}{quote+key+quote+": " if key else ""}{{') + new_level = level + 1 + out = to_list_of_str(item, out, new_level, 1) + out.append(f'{" "*level}}}') + else: + out.append(f'{" "*level}{quote+key+quote+": " if key else ""}{repr(item)},') + + if type(items) == list: + if not recurse: + out = list() + out.append('[') + for item in items: + rec_loop(item, None, out, level) + if not recurse: + out.append(']') + elif type(items) == dict: + if not recurse: + out = list() + out.append('{') + for key in items: + rec_loop(items[key], key, out, level) + if not recurse: + out.append('}') + + return out + + +def format_output(text): + if type(text) == list: + text = to_list_of_str(text) + elif type(text) == dict: + text = to_list_of_str(text) + return text + + +async def run_command(args): + # Create subprocess + process = await asyncio.create_subprocess_shell( + f'time -f "Process took %e seconds (%U user | %S system) and used %P of the CPU" {args}', + # stdout must a pipe to be accessible as process.stdout + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE) + # Wait for the subprocess to finish + stdout, stderr = await process.communicate() + # Return stdout + if stderr and stderr.strip() != '': + output = f'{stdout.decode().strip()}\n{stderr.decode().strip()}' + else: + output = stdout.decode().strip() + return output + + # noinspection PyShadowingNames class Paginator: def __init__(self, @@ -44,12 +107,12 @@ class Paginator: self._embed_url = None self._bot = bot - def set_embed_meta(self, title: str=None, - description: str=None, - color: discord.Colour=None, - thumbnail: str=None, - footer: str='', - url: str=None): + def set_embed_meta(self, title: str = None, + description: str = None, + color: discord.Colour = None, + thumbnail: str = None, + footer: str = '', + url: str = None): if title and len(title) > self._max_field_name: raise RuntimeError('Provided Title is too long') else: @@ -111,7 +174,7 @@ class Paginator: _field_name = name _field_value = self._prefix - def close_field(next_name: str=None): + def close_field(next_name: str = None): nonlocal _field_name, _field_value, _fields _field_value += self._suffix if _field_value != self._prefix + self._suffix: @@ -188,10 +251,10 @@ class Paginator: # noinspection PyProtectedMember return self.__class__ == other.__class__ and self._parts == other._parts - def add_page_break(self, *, to_beginning: bool=False) -> None: + def add_page_break(self, *, to_beginning: bool = False) -> None: self.add(self._page_break, to_beginning=to_beginning) - def add(self, item: typing.Any, *, to_beginning: bool=False, keep_intact: bool=False, truncate=False) -> None: + def add(self, item: typing.Any, *, to_beginning: bool = False, keep_intact: bool = False, truncate=False) -> None: item = str(item) i = 0 if not keep_intact and not item == self._page_break: diff --git a/config/__init__.py b/geeksbot_v2/config/__init__.py similarity index 100% rename from config/__init__.py rename to geeksbot_v2/config/__init__.py diff --git a/config/settings/__init__.py b/geeksbot_v2/config/settings/__init__.py similarity index 100% rename from config/settings/__init__.py rename to geeksbot_v2/config/settings/__init__.py diff --git a/config/settings/base.py b/geeksbot_v2/config/settings/base.py similarity index 93% rename from config/settings/base.py rename to geeksbot_v2/config/settings/base.py index a03a820..d7c1cba 100644 --- a/config/settings/base.py +++ b/geeksbot_v2/config/settings/base.py @@ -7,7 +7,7 @@ import environ ROOT_DIR = ( environ.Path(__file__) - 3 ) # (geeksbot_v2/config/settings/base.py - 3 = geeksbot_v2/) -APPS_DIR = ROOT_DIR.path("geeksbot_v2") +APPS_DIR = ROOT_DIR env = environ.Env() @@ -68,11 +68,17 @@ THIRD_PARTY_APPS = [ "allauth", "allauth.account", "allauth.socialaccount", + "allauth.socialaccount.providers.discord", "rest_framework", + "rest_framework.authtoken", ] LOCAL_APPS = [ "geeksbot_v2.users.apps.UsersConfig", + "geeksbot_v2.guilds.apps.GuildsConfig", + "geeksbot_v2.dmessages.apps.MessagesConfig", + "geeksbot_v2.patreon.apps.PatreonConfig", + "geeksbot_v2.rcon.apps.RconConfig", # Your stuff: custom apps go here ] # https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps @@ -243,16 +249,16 @@ LOGGING = { "root": {"level": "INFO", "handlers": ["console"]}, } - # django-allauth # ------------------------------------------------------------------------------ -ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True) +ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", False) +SOCIAL_ACCOUNT_ALLOW_REGISTRATION = env.bool('DJANGO_SOCIAL_ACCOUNT_ALLOW_REGISTRATION', True) # https://django-allauth.readthedocs.io/en/latest/configuration.html ACCOUNT_AUTHENTICATION_METHOD = "username" # https://django-allauth.readthedocs.io/en/latest/configuration.html -ACCOUNT_EMAIL_REQUIRED = True +ACCOUNT_EMAIL_REQUIRED = False # https://django-allauth.readthedocs.io/en/latest/configuration.html -ACCOUNT_EMAIL_VERIFICATION = "mandatory" +ACCOUNT_EMAIL_VERIFICATION = "optional" # https://django-allauth.readthedocs.io/en/latest/configuration.html ACCOUNT_ADAPTER = "geeksbot_v2.users.adapters.AccountAdapter" # https://django-allauth.readthedocs.io/en/latest/configuration.html @@ -261,3 +267,14 @@ SOCIALACCOUNT_ADAPTER = "geeksbot_v2.users.adapters.SocialAccountAdapter" # Your stuff... # ------------------------------------------------------------------------------ + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework.authentication.TokenAuthentication', + ], + "DEFAULT_RENDERER_CLASSES": [ + "rest_framework.renderers.JSONRenderer", + ], + "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", + "PAGE_SIZE": 100, +} diff --git a/config/settings/local.py b/geeksbot_v2/config/settings/local.py similarity index 100% rename from config/settings/local.py rename to geeksbot_v2/config/settings/local.py diff --git a/config/settings/production.py b/geeksbot_v2/config/settings/production.py similarity index 100% rename from config/settings/production.py rename to geeksbot_v2/config/settings/production.py diff --git a/config/settings/test.py b/geeksbot_v2/config/settings/test.py similarity index 100% rename from config/settings/test.py rename to geeksbot_v2/config/settings/test.py diff --git a/config/urls.py b/geeksbot_v2/config/urls.py similarity index 90% rename from config/urls.py rename to geeksbot_v2/config/urls.py index 950fe09..3920996 100644 --- a/config/urls.py +++ b/geeksbot_v2/config/urls.py @@ -16,6 +16,8 @@ urlpatterns = [ path("users/", include("geeksbot_v2.users.urls", namespace="users")), path("accounts/", include("allauth.urls")), # Your stuff: custom urls includes go here + path("api/users", include("geeksbot_v2.users.api_urls", namespace="users_api")), + path("api/guilds", include("geeksbot_v2.guilds.api_urls", namespace="guilds_api")) ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) if settings.DEBUG: diff --git a/config/wsgi.py b/geeksbot_v2/config/wsgi.py similarity index 100% rename from config/wsgi.py rename to geeksbot_v2/config/wsgi.py diff --git a/geeksbot_v2/users/migrations/__init__.py b/geeksbot_v2/dmessages/__init__.py similarity index 100% rename from geeksbot_v2/users/migrations/__init__.py rename to geeksbot_v2/dmessages/__init__.py diff --git a/geeksbot_v2/dmessages/admin.py b/geeksbot_v2/dmessages/admin.py new file mode 100644 index 0000000..e14fb8b --- /dev/null +++ b/geeksbot_v2/dmessages/admin.py @@ -0,0 +1,10 @@ +from django.contrib import admin + +from .models import Message +from .models import GuildInfo +from .models import AdminRequest + +# Register your models here. +admin.site.register(Message) +admin.site.register(GuildInfo) +admin.site.register(AdminRequest) \ No newline at end of file diff --git a/geeksbot_v2/dmessages/apps.py b/geeksbot_v2/dmessages/apps.py new file mode 100644 index 0000000..1355c6c --- /dev/null +++ b/geeksbot_v2/dmessages/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class MessagesConfig(AppConfig): + name = 'geeksbot_v2.dmessages' + verbose_name = _("DMessages") diff --git a/geeksbot_v2/dmessages/migrations/0001_initial.py b/geeksbot_v2/dmessages/migrations/0001_initial.py new file mode 100644 index 0000000..a0b9602 --- /dev/null +++ b/geeksbot_v2/dmessages/migrations/0001_initial.py @@ -0,0 +1,64 @@ +# Generated by Django 2.2.4 on 2019-09-16 05:23 + +from django.conf import settings +import django.contrib.postgres.fields +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('guilds', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Message', + fields=[ + ('id', models.CharField(max_length=30, primary_key=True, serialize=False)), + ('channel', models.CharField(max_length=30)), + ('created_at', models.DateTimeField()), + ('modified_at', models.DateTimeField(null=True)), + ('deleted_at', models.DateTimeField(null=True)), + ('content', models.CharField(max_length=2000)), + ('previous_content', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=2000), size=None)), + ('tagged_users', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=30), size=None)), + ('tagged_channels', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=30), size=None)), + ('tagged_roles', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=30), size=None)), + ('tagged_everyone', models.BooleanField()), + ('embeds', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), size=None)), + ('previous_embeds', django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), size=None), size=None)), + ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')), + ], + ), + migrations.CreateModel( + name='GuildInfo', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('type', models.PositiveSmallIntegerField()), + ('text', models.TextField(max_length=1980)), + ('format', models.PositiveSmallIntegerField()), + ('channel', models.CharField(max_length=30)), + ('message_number', models.PositiveSmallIntegerField()), + ('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')), + ('message', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='dmessages.Message')), + ], + ), + migrations.CreateModel( + name='AdminRequest', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('completed', models.BooleanField()), + ('requested_at', models.DateTimeField()), + ('completed_at', models.DateTimeField()), + ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')), + ('message', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dmessages.Message')), + ], + ), + ] diff --git a/geeksbot_v2/dmessages/migrations/__init__.py b/geeksbot_v2/dmessages/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/geeksbot_v2/dmessages/models.py b/geeksbot_v2/dmessages/models.py new file mode 100644 index 0000000..6b23008 --- /dev/null +++ b/geeksbot_v2/dmessages/models.py @@ -0,0 +1,58 @@ +from django.db import models +from django.contrib.postgres.fields import ArrayField + +from geeksbot_v2.guilds.models import Guild +from geeksbot_v2.users.models import User + +# Create your models here. + + +class Message(models.Model): + id = models.CharField(max_length=30, primary_key=True) + author = models.ForeignKey(User, on_delete=models.CASCADE) + guild = models.ForeignKey(Guild, on_delete=models.CASCADE) + channel = models.CharField(max_length=30) + created_at = models.DateTimeField() + modified_at = models.DateTimeField(null=True) + deleted_at = models.DateTimeField(null=True) + content = models.CharField(max_length=2000) + previous_content = ArrayField(models.CharField(max_length=2000)) + tagged_users = ArrayField(models.CharField(max_length=30)) + tagged_channels = ArrayField(models.CharField(max_length=30)) + tagged_roles = ArrayField(models.CharField(max_length=30)) + tagged_everyone = models.BooleanField() + embeds = ArrayField(models.TextField()) + previous_embeds = ArrayField(ArrayField(models.TextField())) + + def __str__(self): + return (f'{self.created_at} | ' + f'{self.author.id}' + f'{" | Modified" if self.modified_at else ""}' + f'{" | Deleted" if self.deleted_at else ""}') + + +class GuildInfo(models.Model): + message = models.ForeignKey( + Message, on_delete=models.CASCADE, blank=True, null=True + ) + guild = models.ForeignKey(Guild, on_delete=models.CASCADE) + type = models.PositiveSmallIntegerField() + text = models.TextField(max_length=1980) + format = models.PositiveSmallIntegerField() + channel = models.CharField(max_length=30) + message_number = models.PositiveSmallIntegerField() + + def __str__(self): + return f"{self.guild.id} | {self.text[:25]}" + + +class AdminRequest(models.Model): + guild = models.ForeignKey(Guild, on_delete=models.CASCADE) + author = models.ForeignKey(User, on_delete=models.CASCADE) + message = models.ForeignKey(Message, on_delete=models.CASCADE) + completed = models.BooleanField() + requested_at = models.DateTimeField() + completed_at = models.DateTimeField() + + def __str__(self): + return f"{self.guild.id} | {self.requested_at} | By {self.author.id}" diff --git a/geeksbot_v2/dmessages/serializers.py b/geeksbot_v2/dmessages/serializers.py new file mode 100644 index 0000000..52bd27b --- /dev/null +++ b/geeksbot_v2/dmessages/serializers.py @@ -0,0 +1,22 @@ +from rest_framework import serializers + +from .models import Message +from .models import GuildInfo +from .models import AdminRequest + + +class MessageSerializer(serializers.ModelSerializer): + class Meta: + model = Message + fields = "__all__" + +class GuildInfoSerializer(serializers.ModelSerializer): + class Meta: + model = GuildInfo + fields = "__all__" + + +class AdminRequestSerializer(serializers.ModelSerializer): + class Meta: + model = AdminRequest + fields = "__all__" diff --git a/geeksbot_v2/dmessages/tests.py b/geeksbot_v2/dmessages/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/geeksbot_v2/dmessages/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/geeksbot_v2/dmessages/views.py b/geeksbot_v2/dmessages/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/geeksbot_v2/dmessages/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/entrypoint b/geeksbot_v2/entrypoint old mode 100644 new mode 100755 similarity index 99% rename from entrypoint rename to geeksbot_v2/entrypoint index 941bd8e..d75e631 --- a/entrypoint +++ b/geeksbot_v2/entrypoint @@ -4,9 +4,6 @@ set -o errexit set -o pipefail set -o nounset - - - if [ -z "${POSTGRES_USER}" ]; then base_postgres_image_default_user='postgres' export POSTGRES_USER="${base_postgres_image_default_user}" diff --git a/geeksbot_v2/guilds/__init__.py b/geeksbot_v2/guilds/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/geeksbot_v2/guilds/admin.py b/geeksbot_v2/guilds/admin.py new file mode 100644 index 0000000..a85e098 --- /dev/null +++ b/geeksbot_v2/guilds/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin + +from geeksbot_v2.guilds.models import Guild +from geeksbot_v2.guilds.models import Role + +# Register your models here. +admin.site.register(Guild) +admin.site.register(Role) \ No newline at end of file diff --git a/geeksbot_v2/guilds/api_urls.py b/geeksbot_v2/guilds/api_urls.py new file mode 100644 index 0000000..dc65d71 --- /dev/null +++ b/geeksbot_v2/guilds/api_urls.py @@ -0,0 +1,8 @@ +from django.urls import path + +from .views import GuildsAPI + +app_name = "users_api" +urlpatterns = [ + path("/", view=GuildsAPI.as_view(), name="list") +] diff --git a/geeksbot_v2/guilds/apps.py b/geeksbot_v2/guilds/apps.py new file mode 100644 index 0000000..6789cc4 --- /dev/null +++ b/geeksbot_v2/guilds/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class GuildsConfig(AppConfig): + name = 'geeksbot_v2.guilds' + verbose_name = _("Guilds") diff --git a/geeksbot_v2/guilds/migrations/0001_initial.py b/geeksbot_v2/guilds/migrations/0001_initial.py new file mode 100644 index 0000000..c66c8a4 --- /dev/null +++ b/geeksbot_v2/guilds/migrations/0001_initial.py @@ -0,0 +1,35 @@ +# Generated by Django 2.2.4 on 2019-09-16 05:23 + +import django.contrib.postgres.fields +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Guild', + fields=[ + ('id', models.CharField(max_length=30, primary_key=True, serialize=False)), + ('admin_chat', models.CharField(max_length=30)), + ('new_patron_message', models.TextField(blank=True, max_length=1000)), + ('default_channel', models.CharField(max_length=30)), + ('new_patron_channel', models.CharField(max_length=30)), + ('prefixes', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=10), size=None)), + ], + ), + migrations.CreateModel( + name='Role', + fields=[ + ('id', models.CharField(max_length=30, primary_key=True, serialize=False)), + ('type', models.PositiveSmallIntegerField()), + ('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')), + ], + ), + ] diff --git a/geeksbot_v2/guilds/migrations/0002_auto_20190917_0508.py b/geeksbot_v2/guilds/migrations/0002_auto_20190917_0508.py new file mode 100644 index 0000000..60272ae --- /dev/null +++ b/geeksbot_v2/guilds/migrations/0002_auto_20190917_0508.py @@ -0,0 +1,28 @@ +# Generated by Django 2.2.4 on 2019-09-17 05:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('guilds', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='guild', + name='admin_chat', + field=models.CharField(blank=True, max_length=30, null=True), + ), + migrations.AlterField( + model_name='guild', + name='new_patron_channel', + field=models.CharField(blank=True, max_length=30, null=True), + ), + migrations.AlterField( + model_name='guild', + name='new_patron_message', + field=models.TextField(blank=True, max_length=1000, null=True), + ), + ] diff --git a/geeksbot_v2/guilds/migrations/__init__.py b/geeksbot_v2/guilds/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/geeksbot_v2/guilds/models.py b/geeksbot_v2/guilds/models.py new file mode 100644 index 0000000..b954330 --- /dev/null +++ b/geeksbot_v2/guilds/models.py @@ -0,0 +1,25 @@ +from django.db import models +from django.contrib.postgres.fields import ArrayField + +# Create your models here. + + +class Guild(models.Model): + id = models.CharField(max_length=30, primary_key=True) + admin_chat = models.CharField(max_length=30, blank=True, null=True) + new_patron_message = models.TextField(max_length=1000, blank=True, null=True) + default_channel = models.CharField(max_length=30) + new_patron_channel = models.CharField(max_length=30, blank=True, null=True) + prefixes = ArrayField(models.CharField(max_length=10)) + + def __str__(self): + return self.id + + +class Role(models.Model): + id = models.CharField(max_length=30, primary_key=True) + guild = models.ForeignKey(Guild, on_delete=models.CASCADE, null=False) + type = models.PositiveSmallIntegerField() + + def __str__(self): + return f"{self.guild.id} | {self.id}" diff --git a/geeksbot_v2/guilds/serializers.py b/geeksbot_v2/guilds/serializers.py new file mode 100644 index 0000000..8dc1548 --- /dev/null +++ b/geeksbot_v2/guilds/serializers.py @@ -0,0 +1,16 @@ +from rest_framework import serializers + +from geeksbot_v2.guilds.models import Guild +from geeksbot_v2.guilds.models import Role + + +class GuildSerializer(serializers.ModelSerializer): + class Meta: + model = Guild + fields = "__all__" + + +class RoleSerializer(serializers.ModelSerializer): + class Meta: + model = Role + fields = ["id", "guild", "type"] diff --git a/geeksbot_v2/guilds/tests.py b/geeksbot_v2/guilds/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/geeksbot_v2/guilds/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/geeksbot_v2/guilds/urls.py b/geeksbot_v2/guilds/urls.py new file mode 100644 index 0000000..e69de29 diff --git a/geeksbot_v2/guilds/views.py b/geeksbot_v2/guilds/views.py new file mode 100644 index 0000000..fc08748 --- /dev/null +++ b/geeksbot_v2/guilds/views.py @@ -0,0 +1,51 @@ +import os + +from django.shortcuts import render +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework import status + +from geeksbot_v2.utils.api_utils import PaginatedAPIView +from .models import Guild +from .serializers import GuildSerializer + +# Create your views here. + +# API Views + + +class GuildsAPI(PaginatedAPIView): + def get(self, request, format=None): + users = Guild.objects.all() + page = self.paginate_queryset(users) + if page is not None: + serialized_users = GuildSerializer(users, many=True) + return self.get_paginated_response(serialized_users.data) + + serialized_users = GuildSerializer(users, many=True) + return Response(serialized_users.data) + + def post(self, request, format=None): + data = dict(request.data) + print(data) + id = data.get('id') + default_channel = data.get('default_channel') + if not (id and default_channel): + return Response({'msg': 'id and default_channel are required'}, status=status.HTTP_400_BAD_REQUEST) + + admin_chat = data.get('admin_chat') + new_patron_message = data.get('new_patron_message') + default_prefix = os.environ['DISCORD_DEFAULT_PREFIX'] + prefixes = data.get('prefixes', [default_prefix, ]) + print(prefixes) + + guild = Guild( + id=id[0] if isinstance(id, list) else id, + default_channel=default_channel[0] if isinstance(default_channel, list) else default_channel, + prefixes=prefixes, + admin_chat=admin_chat[0] if isinstance(admin_chat, list) else admin_chat, + new_patron_message=new_patron_message[0] if isinstance(new_patron_message, list) else new_patron_message + ) + guild.save() + + return Response(GuildSerializer(guild).data, status=status.HTTP_201_CREATED) diff --git a/manage.py b/geeksbot_v2/manage.py similarity index 91% rename from manage.py rename to geeksbot_v2/manage.py index f482f42..31df531 100755 --- a/manage.py +++ b/geeksbot_v2/manage.py @@ -3,7 +3,7 @@ import os import sys if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "geeksbot_v2.config.settings.local") try: from django.core.management import execute_from_command_line diff --git a/geeksbot_v2/patreon/__init__.py b/geeksbot_v2/patreon/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/geeksbot_v2/patreon/admin.py b/geeksbot_v2/patreon/admin.py new file mode 100644 index 0000000..6e73988 --- /dev/null +++ b/geeksbot_v2/patreon/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin + + +from .models import PatreonCreator +from .models import PatreonTier + +# Register your models here. +admin.site.register(PatreonCreator) +admin.site.register(PatreonTier) \ No newline at end of file diff --git a/geeksbot_v2/patreon/apps.py b/geeksbot_v2/patreon/apps.py new file mode 100644 index 0000000..5544439 --- /dev/null +++ b/geeksbot_v2/patreon/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class PatreonConfig(AppConfig): + name = 'geeksbot_v2.patreon' + verbose_name = _("Patreon") diff --git a/geeksbot_v2/patreon/migrations/0001_initial.py b/geeksbot_v2/patreon/migrations/0001_initial.py new file mode 100644 index 0000000..b74b38f --- /dev/null +++ b/geeksbot_v2/patreon/migrations/0001_initial.py @@ -0,0 +1,37 @@ +# Generated by Django 2.2.4 on 2019-09-16 05:23 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('guilds', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='PatreonCreator', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('creator', models.CharField(max_length=50)), + ('link', models.CharField(max_length=100)), + ('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')), + ], + ), + migrations.CreateModel( + name='PatreonTier', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50)), + ('description', models.TextField()), + ('amount', models.IntegerField(null=True)), + ('creator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='patreon.PatreonCreator')), + ('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')), + ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Role')), + ], + ), + ] diff --git a/geeksbot_v2/patreon/migrations/__init__.py b/geeksbot_v2/patreon/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/geeksbot_v2/patreon/models.py b/geeksbot_v2/patreon/models.py new file mode 100644 index 0000000..1035fa8 --- /dev/null +++ b/geeksbot_v2/patreon/models.py @@ -0,0 +1,27 @@ +from django.db import models + +from geeksbot_v2.guilds.models import Guild +from geeksbot_v2.guilds.models import Role + +# Create your models here. + + +class PatreonCreator(models.Model): + guild = models.ForeignKey(Guild, on_delete=models.CASCADE, null=False) + creator = models.CharField(max_length=50, null=False) + link = models.CharField(max_length=100, null=False) + + def __str__(self): + return f"{self.guild.id} | {self.creator}" + + +class PatreonTier(models.Model): + creator = models.ForeignKey(PatreonCreator, on_delete=models.CASCADE) + guild = models.ForeignKey(Guild, on_delete=models.CASCADE) + name = models.CharField(max_length=50) + description = models.TextField() + role = models.ForeignKey(Role, on_delete=models.CASCADE) + amount = models.IntegerField(null=True) + + def __str__(self): + return f"{self.guild.id} | {self.creator.creator} | {self.name}" diff --git a/geeksbot_v2/patreon/serializers.py b/geeksbot_v2/patreon/serializers.py new file mode 100644 index 0000000..818c942 --- /dev/null +++ b/geeksbot_v2/patreon/serializers.py @@ -0,0 +1,16 @@ +from rest_framework import serializers + +from geeksbot_v2.patreon.models import PatreonCreator +from geeksbot_v2.patreon.models import PatreonTier + + +class PatreonCreatorSerializer(serializers.ModelSerializer): + class Meta: + model = PatreonCreator + fields = "__all__" + + +class PatreonTierSerializer(serializers.ModelSerializer): + class Meta: + model = PatreonTier + fields = "__all__" diff --git a/geeksbot_v2/patreon/tests.py b/geeksbot_v2/patreon/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/geeksbot_v2/patreon/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/geeksbot_v2/patreon/views.py b/geeksbot_v2/patreon/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/geeksbot_v2/patreon/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/geeksbot_v2/rcon/__init__.py b/geeksbot_v2/rcon/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/geeksbot_v2/rcon/admin.py b/geeksbot_v2/rcon/admin.py new file mode 100644 index 0000000..7209c51 --- /dev/null +++ b/geeksbot_v2/rcon/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin + +from .models import RconServer + +# Register your models here. +admin.site.register(RconServer) \ No newline at end of file diff --git a/geeksbot_v2/rcon/apps.py b/geeksbot_v2/rcon/apps.py new file mode 100644 index 0000000..c6d1290 --- /dev/null +++ b/geeksbot_v2/rcon/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class RconConfig(AppConfig): + name = 'geeksbot_v2.rcon' + verbose_name = _("Rcon") diff --git a/geeksbot_v2/rcon/migrations/0001_initial.py b/geeksbot_v2/rcon/migrations/0001_initial.py new file mode 100644 index 0000000..56e2bd6 --- /dev/null +++ b/geeksbot_v2/rcon/migrations/0001_initial.py @@ -0,0 +1,37 @@ +# Generated by Django 2.2.4 on 2019-09-16 05:23 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('guilds', '0001_initial'), + ('dmessages', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='RconServer', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50)), + ('ip', models.GenericIPAddressField()), + ('port', models.PositiveIntegerField()), + ('password', models.CharField(max_length=50)), + ('monitor_chat', models.BooleanField()), + ('monitor_chat_channel', models.CharField(blank=True, max_length=30)), + ('alerts_channel', models.CharField(blank=True, max_length=30)), + ('info_channel', models.CharField(blank=True, max_length=30)), + ('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')), + ('info_message', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='dmessages.Message')), + ('settings_message', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='dmessages.Message')), + ('whitelist', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/geeksbot_v2/rcon/migrations/__init__.py b/geeksbot_v2/rcon/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/geeksbot_v2/rcon/models.py b/geeksbot_v2/rcon/models.py new file mode 100644 index 0000000..b341b7b --- /dev/null +++ b/geeksbot_v2/rcon/models.py @@ -0,0 +1,29 @@ +from django.db import models + +from geeksbot_v2.guilds.models import Guild +from geeksbot_v2.dmessages.models import Message +from geeksbot_v2.users.models import User + +# Create your models here. + + +class RconServer(models.Model): + guild = models.ForeignKey(Guild, on_delete=models.CASCADE) + name = models.CharField(max_length=50) + ip = models.GenericIPAddressField() + port = models.PositiveIntegerField() + password = models.CharField(max_length=50) + monitor_chat = models.BooleanField() + monitor_chat_channel = models.CharField(max_length=30, blank=True) + alerts_channel = models.CharField(max_length=30, blank=True) + info_channel = models.CharField(max_length=30, blank=True) + info_message = models.ForeignKey( + Message, on_delete=models.CASCADE, related_name="+", blank=True + ) + settings_message = models.ForeignKey( + Message, on_delete=models.CASCADE, related_name="+", blank=True + ) + whitelist = models.ManyToManyField(User, blank=True) + + def __str__(self): + return f"{self.guild.id} | {self.name}" diff --git a/geeksbot_v2/rcon/serializers.py b/geeksbot_v2/rcon/serializers.py new file mode 100644 index 0000000..18184e3 --- /dev/null +++ b/geeksbot_v2/rcon/serializers.py @@ -0,0 +1,9 @@ +from rest_framework import serializers + +from geeksbot_v2.rcon.models import RconServer + + +class RconServerSerializer(serializers.ModelSerializer): + class Meta: + model = RconServer + fields = "__all__" diff --git a/geeksbot_v2/rcon/tests.py b/geeksbot_v2/rcon/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/geeksbot_v2/rcon/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/geeksbot_v2/rcon/views.py b/geeksbot_v2/rcon/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/geeksbot_v2/rcon/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/geeksbot_v2/templates/base.html b/geeksbot_v2/templates/base.html index c8ddef7..547b0c1 100644 --- a/geeksbot_v2/templates/base.html +++ b/geeksbot_v2/templates/base.html @@ -59,10 +59,6 @@ {% trans "Sign Out" %} {% else %} -