10 Commits

750 changed files with 22460 additions and 39912 deletions

View File

@@ -1,65 +0,0 @@
name: Deploy dev on push
run-name: Deploy dev on push
on:
push:
branches:
- master
jobs:
on-push-deploy_sh-edraft:
runs-on: [ dobby.sh-edraft.de, ubuntu-latest ]
container: catthehacker/ubuntu:act-latest
steps:
- name: Setup Python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10.12"
- run: python -v
- name: Setup docker
uses: https://github.com/papodaca/install-docker-action@main
- run: docker -v
- name: Clone Repository
uses: https://github.com/actions/checkout@v3
- name: Shutdown stack
run: docker stack rm kdb_prod
- name: Prepare bot build
run: |
cd kdb-bot
pip install --extra-index-url https://pip.sh-edraft.de cpl-cli
cpl i
- name: Build docker bot
run: |
cd kdb-bot
docker image prune -f
cpl docker-build
- name: Setup node
uses: https://github.com/actions/setup-node@v3
- name: Prepare web build
run: |
cd kdb-web
npm install -g ts-node
npm i
- name: Build docker web
run: |
cd kdb-web
docker image prune -f
npm run docker-build
- name: Deploy Stack to sh-edraft.de
uses: https://github.com/kgierke/portainer-stack-deployment@v1
with:
portainer-url: "https://docker.sh-edraft.de"
portainer-username: "gitea_job"
portainer-password: "${{ secrets.docker_job }}"
portainer-endpoint: 2
name: kdb_prod
file: ./docker-compose.yml
variables: '{}'

View File

@@ -1,65 +0,0 @@
name: Deploy dev on push
run-name: Deploy dev on push
on:
push:
branches:
- support
jobs:
on-push-deploy_sh-edraft:
runs-on: [ dobby.sh-edraft.de, ubuntu-latest ]
container: catthehacker/ubuntu:act-latest
steps:
- name: Setup Python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10.12"
- run: python -v
- name: Setup docker
uses: https://github.com/papodaca/install-docker-action@main
- run: docker -v
- name: Clone Repository
uses: https://github.com/actions/checkout@v3
- name: Shutdown stack
run: docker stack rm kdb_staging
- name: Prepare bot build
run: |
cd kdb-bot
pip install --extra-index-url https://pip.sh-edraft.de cpl-cli
cpl i
- name: Build docker bot
run: |
cd kdb-bot
docker image prune -f
cpl docker-build
- name: Setup node
uses: https://github.com/actions/setup-node@v3
- name: Prepare web build
run: |
cd kdb-web
npm install -g ts-node
npm i
- name: Build docker web
run: |
cd kdb-web
docker image prune -f
npm run docker-build
- name: Deploy Stack to sh-edraft.de
uses: https://github.com/kgierke/portainer-stack-deployment@v1
with:
portainer-url: "https://docker.sh-edraft.de"
portainer-username: "gitea_job"
portainer-password: "${{ secrets.docker_job }}"
portainer-endpoint: 2
name: kdb_staging
file: ./docker-compose.staging.yml
variables: '{}'

3
.gitignore vendored
View File

@@ -143,4 +143,5 @@ PythonImportHelper-v2-Completion.json
deploy/ deploy/
# idea # idea
.idea/ .idea/
selenium-data/

View File

@@ -1,9 +0,0 @@
MIT License
Copyright (c) 2022-2023 sh-edraft.de
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,89 +0,0 @@
version: "3.9"
volumes:
kdb_db_staging_1:
services:
kdb_bot_staging_1:
image: sh-edraft.de/kdb-bot:1.1.6
container_name: kdb_bot_staging_1
depends_on:
- kdb_db_staging_1
networks:
- kdb_test
- reverse_proxy
volumes:
- /opt/kdb/staging/bot/config:/app/bot/config
- /opt/kdb/staging/bot/api_config:/app/bot_api/config
- /opt/kdb/staging/bot/logs:/app/bot/logs
environment:
KDB_ENVIRONMENT: "staging"
KDB_TOKEN: "OTk4MTU5ODAyMzkzOTY0NTk0.G-csct.b2Y-HxvLz0SfFLl5HpukROv2GaiWhcMABbMzYE"
KDB_PREFIX: "!kt "
command: bash /app/bot/bot -stage
deploy:
mode: replicated
replicas: 1
placement:
constraints: [ node.role == manager ]
resources:
reservations:
cpus: "0.5"
memory: 1024M
kdb_web_staging_1:
image: sh-edraft.de/kdb-web:1.1.6
container_name: kdb_web_staging_1
depends_on:
- kdb_bot_staging_1
networks:
- kdb_test
- reverse_proxy
volumes:
- /opt/kdb/staging/web/config.json:/usr/share/nginx/html/assets/config.json
environment:
BOT_CONTAINER_NAME: "kdb_bot_staging_1"
deploy:
mode: replicated
replicas: 1
placement:
constraints: [ node.role == manager ]
resources:
limits:
cpus: "0.4"
memory: 400M
reservations:
cpus: "0.1"
memory: 20M
kdb_db_staging_1:
image: mysql:latest
container_name: kdb_db_staging_1
command: mysqld --default-authentication-plugin=mysql_native_password --log_bin_trust_function_creators=1
networks:
- kdb_test
environment:
MYSQL_ROOT_PASSWORD: "kd_kdb"
MYSQL_USER: "kd_kdb"
MYSQL_PASSWORD: "~qELxjvtjJ3r7yg4PZr5!,V}d.{TC4rg"
MYSQL_DATABASE: "kd_kdb"
ports:
- "3308:3306"
volumes:
- kdb_db_staging_1:/var/lib/mysql
deploy:
mode: replicated
replicas: 1
placement:
constraints: [ node.role == manager ]
resources:
reservations:
cpus: "0.1"
memory: 150M
networks:
reverse_proxy:
external: true
kdb_test:
driver: overlay
attachable: true

View File

@@ -1,86 +0,0 @@
version: "3.9"
volumes:
kdb_db_prod_1:
services:
kdb_bot_prod_1:
image: sh-edraft.de/kdb-bot:1.1.6
depends_on:
- kdb_db_prod_1
networks:
- kdb_prod
- reverse_proxy
volumes:
- /opt/kdb/production/bot/config:/app/bot/config
- /opt/kdb/production/bot/api_config:/app/bot_api/config
- /opt/kdb/production/bot/logs:/app/bot/logs
environment:
KDB_ENVIRONMENT: "production"
KDB_TOKEN: "OTk4MTU5NTEyNDYyNzA4Nzg2.Gx0hSB.Ouq2dfRKxLBJvHfEq8OrFBHVUF24AQrVf55coM"
KDB_PREFIX: "!k "
deploy:
mode: replicated
replicas: 1
placement:
constraints: [ node.role == manager ]
resources:
reservations:
cpus: "0.5"
memory: 1024M
kdb_web_prod_1:
image: sh-edraft.de/kdb-web:1.1.6
depends_on:
- kdb_bot_prod_1
networks:
- kdb_prod
- reverse_proxy
volumes:
- /opt/kdb/production/web/config.json:/usr/share/nginx/html/assets/config.json
environment:
BOT_CONTAINER_NAME: "kdb_bot_prod_1"
deploy:
mode: replicated
replicas: 1
placement:
constraints: [ node.role == manager ]
resources:
limits:
cpus: "0.4"
memory: 400M
reservations:
cpus: "0.1"
memory: 20M
kdb_db_prod_1:
image: mysql:latest
command: mysqld --default-authentication-plugin=mysql_native_password --log_bin_trust_function_creators=1
networks:
- kdb_prod
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: "kd_kdb"
MYSQL_USER: "kd_kdb"
MYSQL_PASSWORD: ",2#MzfN4J=7r(q,Tz3npDkCR§>VE&}7T"
MYSQL_DATABASE: "kd_kdb"
ports:
- "3307:3306"
volumes:
- kdb_db_prod_1:/var/lib/mysql
deploy:
mode: replicated
replicas: 1
placement:
constraints: [ node.role == manager ]
resources:
reservations:
cpus: "0.1"
memory: 150M
networks:
reverse_proxy:
external: true
kdb_prod:
driver: overlay
attachable: true

View File

@@ -1,100 +0,0 @@
from cpl_cli.abc.generate_schematic_abc import GenerateSchematicABC
class DBTable(GenerateSchematicABC):
def __init__(self, *args: str):
GenerateSchematicABC.__init__(self, *args)
self._name = self._name.replace("_db_table", "")
self._class_name = self._class_name.split("Db_table")[0]
def get_code(self) -> str:
import textwrap
code = textwrap.dedent(
"""\
from datetime import datetime
from cpl_core.database import TableABC
class $ClassName(TableABC):
def __init__(
self,
value: str,
created_at: datetime = None,
modified_at: datetime = None,
id=0,
):
self._id = id
self._value = value
TableABC.__init__(self)
self._created_at = created_at if created_at is not None else self._created_at
self._modified_at = modified_at if modified_at is not None else self._modified_at
@property
def value(self) -> str:
return self._value
@value.setter
def value(self, value: str):
self._value = value
@staticmethod
def get_select_all_string() -> str:
return str(
f\"""
SELECT * FROM `$TableName`;
\"""
)
@staticmethod
def get_select_by_id_string(id: int) -> str:
return str(
f\"""
SELECT * FROM `$TableName`
WHERE `Id` = {id};
\"""
)
@property
def insert_string(self) -> str:
return str(
f\"""
INSERT INTO `$TableName` (
`Value`
) VALUES (
{self._value}
);
\"""
)
@property
def udpate_string(self) -> str:
return str(
f\"""
UPDATE `$TableName`
SET `Value` = {self._value}
WHERE `Id` = {self._id};
\"""
)
@property
def delete_string(self) -> str:
return str(
f\"""
DELETE FROM `$TableName`
WHERE `Id` = {self._id};
\"""
)
"""
)
return self.build_code_str(
code,
ClassName=self._class_name,
TableName=self._class_name,
)
@classmethod
def register(cls):
GenerateSchematicABC.register(cls, "db-table", [])

View File

@@ -1,55 +0,0 @@
from cpl_cli.abc.generate_schematic_abc import GenerateSchematicABC
class Migration(GenerateSchematicABC):
def __init__(self, *args: str):
GenerateSchematicABC.__init__(self, *args)
def get_code(self) -> str:
import textwrap
code = textwrap.dedent(
"""\
from bot_core.logging.database_logger import DatabaseLogger
from bot_data.abc.migration_abc import MigrationABC
from bot_data.db_context import DBContext
class $ClassName(MigrationABC):
name = "1.0_$ClassName"
def __init__(self, logger: DatabaseLogger, db: DBContext):
MigrationABC.__init__(self)
self._logger = logger
self._db = db
self._cursor = db.cursor
def upgrade(self):
self._logger.debug(__name__, "Running upgrade")
self._cursor.execute(
str(
f\"""
CREATE TABLE IF NOT EXISTS `$TableName` (
`Id` BIGINT NOT NULL AUTO_INCREMENT,
`CreatedAt` DATETIME(6),
`LastModifiedAt` DATETIME(6),
PRIMARY KEY(`Id`)
);
\"""
)
)
def downgrade(self):
self._cursor.execute("DROP TABLE `$TableName`;")
"""
)
return self.build_code_str(
code,
ClassName=self._class_name,
TableName=self._class_name.split("Migration")[0],
)
@classmethod
def register(cls):
GenerateSchematicABC.register(cls, "migration", [])

View File

@@ -1,34 +0,0 @@
from cpl_cli.abc.generate_schematic_abc import GenerateSchematicABC
class Query(GenerateSchematicABC):
def __init__(self, *args: str):
GenerateSchematicABC.__init__(self, *args)
def get_code(self) -> str:
import textwrap
code = textwrap.dedent(
"""\
from bot_graphql.abc.data_query_abc import DataQueryABC
class $ClassName(DataQueryABC):
def __init__(self):
DataQueryABC.__init__(self, "Name")
self.set_field("id", self.resolve_id)
@staticmethod
def resolve_id(x, *_):
return x.id
"""
)
return self.build_code_str(
code,
ClassName=self._class_name,
)
@classmethod
def register(cls):
GenerateSchematicABC.register(cls, "query", [])

View File

@@ -1,2 +1,14 @@
# kd_discord_bot # kd_discord_bot
## Test Bot
To test the bot run unittests or call ```cpl test```.
Configure test instance by creating the file ./test/ui_tests/.env and set following environment variables:
```sh
KDB_TEST_DB_PASSWORD=
KDB_TEST_NAME=
KDB_TEST_TOKEN=
KDB_TEST_DISCORD_MAIL=
KDB_TEST_DISCORD_PASSWORD=
```

View File

@@ -6,37 +6,36 @@
"bot-api": "src/bot_api/bot-api.json", "bot-api": "src/bot_api/bot-api.json",
"bot-core": "src/bot_core/bot-core.json", "bot-core": "src/bot_core/bot-core.json",
"bot-data": "src/bot_data/bot-data.json", "bot-data": "src/bot_data/bot-data.json",
"bot-graphql": "src/bot_graphql/bot-graphql.json",
"achievements": "src/modules/achievements/achievements.json",
"auto-role": "src/modules/auto_role/auto-role.json", "auto-role": "src/modules/auto_role/auto-role.json",
"base": "src/modules/base/base.json", "base": "src/modules/base/base.json",
"boot-log": "src/modules/boot_log/boot-log.json", "boot-log": "src/modules/boot_log/boot-log.json",
"config": "src/modules/config/config.json",
"database": "src/modules/database/database.json", "database": "src/modules/database/database.json",
"level": "src/modules/level/level.json", "level": "src/modules/level/level.json",
"permission": "src/modules/permission/permission.json", "permission": "src/modules/permission/permission.json",
"stats": "src/modules/stats/stats.json",
"technician": "src/modules/technician/technician.json", "technician": "src/modules/technician/technician.json",
"short-role-name": "src/modules/_short_role_name/modules/short-role-name.json", "ui-tests": "test/ui_tests/ui-tests.json",
"checks": "tools/checks/checks.json", "ui-tests-shared": "test/ui_tests_shared/ui-tests-shared.json",
"ui-tests-tests": "test/ui_tests_tests/ui-tests-tests.json",
"get-version": "tools/get_version/get-version.json", "get-version": "tools/get_version/get-version.json",
"post-build": "tools/post_build/post-build.json", "post-build": "tools/post_build/post-build.json",
"set-version": "tools/set_version/set-version.json" "set-version": "tools/set_version/set-version.json"
}, },
"Scripts": { "Scripts": {
"format": "black ./", "test": "export $(cat test/ui_tests/.env); export PYTHONPATH=$PWD/src:$PYTHONPATH; cpl run ui-tests",
"sv": "cpl set-version $ARGS", "sv": "cpl set-version $ARGS",
"set-version": "cpl run set-version $ARGS --dev; echo '';", "set-version": "cpl run set-version $ARGS; echo '';",
"gv": "cpl get-version", "gv": "cpl get-version",
"get-version": "export VERSION=$(cpl run get-version --dev); echo $VERSION;", "get-version": "export VERSION=$(cpl run get-version); echo $VERSION;",
"pre-build": "cpl set-version $ARGS; black ./;", "pre-build": "cpl set-version $ARGS",
"post-build": "cpl run post-build --dev; black ./;", "post-build": "cpl run post-build",
"pre-prod": "cpl build", "pre-prod": "cpl build",
"prod": "export KDB_ENVIRONMENT=production; export KDB_NAME=KDB-Prod; cpl start;", "prod": "export KDB_ENVIRONMENT=production; export KDB_NAME=KDB-Prod; cpl start;",
"pre-stage": "cpl build", "pre-stage": "cpl build",
"stage": "export KDB_ENVIRONMENT=staging; export KDB_NAME=KDB-Stage; cpl start;", "stage": "export KDB_ENVIRONMENT=staging; export KDB_NAME=KDB-Stage; cpl start;",
"pre-dev": "cpl build", "pre-dev": "cpl build",
"dev": "export KDB_ENVIRONMENT=development; export KDB_NAME=KDB-Dev; cpl start;", "dev": "export KDB_ENVIRONMENT=development; export KDB_NAME=KDB-Dev; cpl start;",
"docker-build": "cpl build $ARGS; docker build -t sh-edraft.de/kdb-bot:$(cpl gv) .;", "docker-build": "cpl build $ARGS; docker build -t kdb-bot/kdb-bot:$(cpl gv) .;",
"dc-up": "docker-compose up -d", "dc-up": "docker-compose up -d",
"dc-down": "docker-compose down", "dc-down": "docker-compose down",
"docker": "cpl dc-down; cpl docker-build; cpl dc-up;" "docker": "cpl dc-down; cpl docker-build; cpl dc-up;"

View File

@@ -2,8 +2,7 @@
FROM python:3.10.4-alpine FROM python:3.10.4-alpine
WORKDIR /app WORKDIR /app
COPY ./dist/bot/build/kdb-bot/ . COPY ./dist/bot/build/ .
COPY ./dist/bot/build/requirements.txt .
RUN python -m pip install --upgrade pip RUN python -m pip install --upgrade pip
@@ -15,7 +14,4 @@ RUN apk add nano
RUN pip install -r requirements.txt --extra-index-url https://pip.sh-edraft.de RUN pip install -r requirements.txt --extra-index-url https://pip.sh-edraft.de
RUN pip install flask[async] RUN pip install flask[async]
# RUN pip install dnspython==2.2.1 # https://stackoverflow.com/questions/75137717/eventlet-dns-python-attribute-error-module-dns-rdtypes-has-no-attribute-any
# ^ probably fixed py package updates
CMD [ "bash", "/app/bot/bot"] CMD [ "bash", "/app/bot/bot"]

View File

@@ -1,2 +0,0 @@
[tool.black]
line-length = 120

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
bot sh-edraft.de Discord bot bot Keksdose bot
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Discord bot for some customers of sh-edraft.de Discord bot for the Keksdose discord Server
:copyright: (c) 2022 - 2023 sh-edraft.de :copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details. :license: MIT, see LICENSE for more details.
""" """
@@ -14,13 +14,13 @@ Discord bot for some customers of sh-edraft.de
__title__ = 'bot' __title__ = 'bot'
__author__ = 'Sven Heidemann' __author__ = 'Sven Heidemann'
__license__ = 'MIT' __license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 - 2023 sh-edraft.de' __copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.1.7' __version__ = '0.3.dev25'
from collections import namedtuple from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro') VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='1', micro='7') version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -10,10 +10,10 @@ from cpl_translation import TranslatePipe, TranslationServiceABC, TranslationSet
from bot_api.api_thread import ApiThread from bot_api.api_thread import ApiThread
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
from bot_core.service.data_integrity_service import DataIntegrityService
class Application(DiscordBotApplicationABC): class Application(DiscordBotApplicationABC):
def __init__(self, config: ConfigurationABC, services: ServiceProviderABC): def __init__(self, config: ConfigurationABC, services: ServiceProviderABC):
DiscordBotApplicationABC.__init__(self, config, services) DiscordBotApplicationABC.__init__(self, config, services)
@@ -22,7 +22,6 @@ class Application(DiscordBotApplicationABC):
# cpl-core # cpl-core
self._logger: LoggerABC = services.get_service(LoggerABC) self._logger: LoggerABC = services.get_service(LoggerABC)
self._data_integrity: DataIntegrityService = services.get_service(DataIntegrityService)
# cpl-discord # cpl-discord
self._bot: DiscordBotServiceABC = services.get_service(DiscordBotServiceABC) self._bot: DiscordBotServiceABC = services.get_service(DiscordBotServiceABC)
self._bot_settings: DiscordBotSettings = config.get_configuration(DiscordBotSettings) self._bot_settings: DiscordBotSettings = config.get_configuration(DiscordBotSettings)
@@ -43,22 +42,18 @@ class Application(DiscordBotApplicationABC):
async def main(self): async def main(self):
try: try:
self._logger.debug(__name__, f"Starting...") self._logger.debug(__name__, f'Starting...')
if ( if self._feature_flags.get_flag(FeatureFlagsEnum.api_module) and self._feature_flags.get_flag(FeatureFlagsEnum.api_only) and self._environment.environment_name == 'development':
self._feature_flags.get_flag(FeatureFlagsEnum.api_module)
and self._feature_flags.get_flag(FeatureFlagsEnum.api_only)
and self._environment.environment_name == "development"
):
self._api.start() self._api.start()
self._api.join() self._api.join()
return return
self._logger.info(__name__, f"Try to start {DiscordBotService.__name__}") self._logger.trace(__name__, f'Try to start {DiscordBotService.__name__}')
await self._bot.start_async() await self._bot.start_async()
await self._bot.stop_async() await self._bot.stop_async()
except Exception as e: except Exception as e:
self._logger.error(__name__, "Start failed", e) self._logger.error(__name__, 'Start failed', e)
async def stop_async(self): async def stop_async(self):
if self._is_stopping: if self._is_stopping:
@@ -66,17 +61,13 @@ class Application(DiscordBotApplicationABC):
self._is_stopping = True self._is_stopping = True
try: try:
self._logger.info(__name__, f"Try to stop {DiscordBotService.__name__}") self._logger.trace(__name__, f'Try to stop {DiscordBotService.__name__}')
if self._feature_flags.get_flag(FeatureFlagsEnum.api_module):
self._api.stop()
await self._bot.close() await self._bot.close()
await self._data_integrity.check_data_integrity(is_for_shutdown=True) self._logger.trace(__name__, f'Stopped {DiscordBotService.__name__}')
self._logger.info(__name__, f"Stopped {DiscordBotService.__name__}")
except Exception as e: except Exception as e:
self._logger.error(__name__, "stop failed", e) self._logger.error(__name__, 'stop failed', e)
Console.write_line() Console.write_line()
def is_restart(self): def is_restart(self):
return True if self._configuration.get_configuration("IS_RESTART") == "true" else False return True if self._configuration.get_configuration('IS_RESTART') == 'true' else False #

View File

@@ -2,40 +2,36 @@
"ProjectSettings": { "ProjectSettings": {
"Name": "bot", "Name": "bot",
"Version": { "Version": {
"Major": "1", "Major": "0",
"Minor": "1", "Minor": "3",
"Micro": "7" "Micro": "dev25"
}, },
"Author": "Sven Heidemann", "Author": "Sven Heidemann",
"AuthorEmail": "sven.heidemann@sh-edraft.de", "AuthorEmail": "sven.heidemann@sh-edraft.de",
"Description": "sh-edraft.de Discord bot", "Description": "Keksdose bot",
"LongDescription": "Discord bot for some customers of sh-edraft.de", "LongDescription": "Discord bot for the Keksdose discord Server",
"URL": "https://www.sh-edraft.de", "URL": "https://www.sh-edraft.de",
"CopyrightDate": "2022 - 2023", "CopyrightDate": "2022",
"CopyrightName": "sh-edraft.de", "CopyrightName": "sh-edraft.de",
"LicenseName": "MIT", "LicenseName": "MIT",
"LicenseDescription": "MIT, see LICENSE for more details.", "LicenseDescription": "MIT, see LICENSE for more details.",
"Dependencies": [ "Dependencies": [
"cpl-core==2023.4.0.post5", "cpl-core==2022.10.0.post9",
"cpl-translation==2023.4.0.post1", "cpl-translation==2022.10.0.post2",
"cpl-query==2023.4.0.post1", "cpl-query==2022.10.0.post2",
"cpl-discord==2023.4.0.post3", "cpl-discord==2022.10.0.post6",
"Flask==2.3.2", "Flask==2.2.2",
"Flask-Classful==0.14.2", "Flask-Classful==0.14.2",
"Flask-Cors==4.0.0", "Flask-Cors==3.0.10",
"PyJWT==2.8.0", "PyJWT==2.6.0",
"waitress==2.1.2", "waitress==2.1.2",
"Flask-SocketIO==5.3.4", "Flask-SocketIO==5.3.1",
"eventlet==0.33.3", "eventlet==0.33.1",
"requests-oauthlib==1.3.1", "requests-oauthlib==1.3.1",
"icmplib==3.0.3", "icmplib==3.0.3"
"ariadne==0.20.1",
"cryptography==41.0.2",
"discord>=2.3.2"
], ],
"DevDependencies": [ "DevDependencies": [
"cpl-cli==2023.4.0.post3", "cpl-cli==2022.10.0"
"pygount==1.6.1"
], ],
"PythonVersion": ">=3.10.4", "PythonVersion": ">=3.10.4",
"PythonPath": {}, "PythonPath": {},
@@ -59,16 +55,13 @@
"../bot_api/bot-api.json", "../bot_api/bot-api.json",
"../bot_core/bot-core.json", "../bot_core/bot-core.json",
"../bot_data/bot-data.json", "../bot_data/bot-data.json",
"../bot_graphql/bot-graphql.json",
"../modules/achievements/achievements.json",
"../modules/auto_role/auto-role.json", "../modules/auto_role/auto-role.json",
"../modules/base/base.json", "../modules/base/base.json",
"../modules/boot_log/boot-log.json", "../modules/boot_log/boot-log.json",
"../modules/config/config.json",
"../modules/database/database.json", "../modules/database/database.json",
"../modules/level/level.json", "../modules/level/level.json",
"../modules/permission/permission.json", "../modules/permission/permission.json",
"../modules/short_role_name/short-role-name.json", "../modules/stats/stats.json",
"../modules/technician/technician.json" "../modules/technician/technician.json"
] ]
} }

View File

@@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
"""
bot sh-edraft.de Discord bot
~~~~~~~~~~~~~~~~~~~
Discord bot for some customers of sh-edraft.de
:copyright: (c) 2022 - 2023 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'bot.extension'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 - 2023 sh-edraft.de'
__version__ = '1.1.7'
from collections import namedtuple
# imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='1', micro='7')

View File

@@ -1,16 +0,0 @@
from cpl_core.application import ApplicationExtensionABC
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceProviderABC
from cpl_discord.service import DiscordBotServiceABC
from bot_data.model.technician_config import TechnicianConfig
class InitBotExtension(ApplicationExtensionABC):
def __init__(self):
ApplicationExtensionABC.__init__(self)
async def run(self, config: ConfigurationABC, services: ServiceProviderABC):
settings = config.get_configuration(TechnicianConfig)
bot: DiscordBotServiceABC = services.get_service(DiscordBotServiceABC, max_messages=settings.cache_max_messages)

View File

@@ -6,7 +6,6 @@ from cpl_core.application import ApplicationBuilder
from cpl_core.console import Console from cpl_core.console import Console
from bot.application import Application from bot.application import Application
from bot.extension.init_bot_extension import InitBotExtension
from bot.startup import Startup from bot.startup import Startup
from bot.startup_discord_extension import StartupDiscordExtension from bot.startup_discord_extension import StartupDiscordExtension
from bot.startup_migration_extension import StartupMigrationExtension from bot.startup_migration_extension import StartupMigrationExtension
@@ -15,33 +14,28 @@ from bot.startup_settings_extension import StartupSettingsExtension
from bot_api.app_api_extension import AppApiExtension from bot_api.app_api_extension import AppApiExtension
from bot_core.core_extension.core_extension import CoreExtension from bot_core.core_extension.core_extension import CoreExtension
from modules.boot_log.boot_log_extension import BootLogExtension from modules.boot_log.boot_log_extension import BootLogExtension
from modules.config.config_extension import ConfigExtension
from modules.database.database_extension import DatabaseExtension from modules.database.database_extension import DatabaseExtension
class Program: class Program:
def __init__(self): def __init__(self):
self.app: Optional[Application] = None self.app: Optional[Application] = None
async def start(self): async def start(self):
# discord extension has to be loaded before modules (modules depends on discord stuff) # discord extension has to be loaded before modules (modules depends on discord stuff)
app_builder = ( app_builder = ApplicationBuilder(Application) \
ApplicationBuilder(Application) .use_extension(StartupSettingsExtension) \
.use_extension(StartupSettingsExtension) .use_extension(StartupDiscordExtension) \
.use_extension(StartupDiscordExtension) .use_extension(StartupModuleExtension) \
.use_extension(StartupModuleExtension) .use_extension(StartupMigrationExtension) \
.use_extension(StartupMigrationExtension) .use_extension(BootLogExtension) \
.use_extension(DatabaseExtension) .use_extension(DatabaseExtension) \
.use_extension(ConfigExtension) .use_extension(AppApiExtension) \
.use_extension(InitBotExtension) .use_extension(CoreExtension) \
.use_extension(BootLogExtension)
.use_extension(AppApiExtension)
.use_extension(CoreExtension)
.use_startup(Startup) .use_startup(Startup)
)
self.app: Application = await app_builder.build_async() self.app: Application = await app_builder.build_async()
await self.app.run_async() await self.app.run_async()
Console.write_line(f"[ INFO ] [ {__name__} ]: Finished app.run_async")
async def stop(self): async def stop(self):
if self.app is None: if self.app is None:
@@ -56,25 +50,19 @@ def main():
except KeyboardInterrupt: except KeyboardInterrupt:
asyncio.run(program.stop()) asyncio.run(program.stop())
except Exception as e: except Exception as e:
Console.error( Console.error(f'[ ERROR ] [ {__name__} ]: Cannot start the bot', f'{e} -> {traceback.format_exc()}')
f"[ ERROR ] [ {__name__} ]: Cannot start the bot",
f"{e} -> {traceback.format_exc()}",
)
finally: finally:
try: try:
asyncio.run(program.stop()) asyncio.run(program.stop())
except Exception as e: except Exception as e:
Console.error( Console.error(f'[ ERROR ] [ {__name__} ]: Cannot stop the bot', f'{e} -> {traceback.format_exc()}')
f"[ ERROR ] [ {__name__} ]: Cannot stop the bot",
f"{e} -> {traceback.format_exc()}",
)
if program.app is not None and program.app.is_restart(): if program.app is not None and program.app.is_restart():
del program del program
main() main()
if __name__ == "__main__": if __name__ == '__main__':
main() main()
# (( # ((

View File

@@ -4,41 +4,33 @@ from bot_api.api_module import ApiModule
from bot_core.core_extension.core_extension_module import CoreExtensionModule from bot_core.core_extension.core_extension_module import CoreExtensionModule
from bot_core.core_module import CoreModule from bot_core.core_module import CoreModule
from bot_data.data_module import DataModule from bot_data.data_module import DataModule
from bot_graphql.graphql_module import GraphQLModule
from modules.achievements.achievements_module import AchievementsModule
from modules.auto_role.auto_role_module import AutoRoleModule from modules.auto_role.auto_role_module import AutoRoleModule
from modules.base.base_module import BaseModule from modules.base.base_module import BaseModule
from modules.boot_log.boot_log_module import BootLogModule from modules.boot_log.boot_log_module import BootLogModule
from modules.config.config_module import ConfigModule
from modules.database.database_module import DatabaseModule from modules.database.database_module import DatabaseModule
from modules.level.level_module import LevelModule from modules.level.level_module import LevelModule
from modules.permission.permission_module import PermissionModule from modules.permission.permission_module import PermissionModule
from modules.short_role_name.short_role_name_module import ShortRoleNameModule from modules.stats.stats_module import StatsModule
from modules.technician.technician_module import TechnicianModule from modules.technician.technician_module import TechnicianModule
class ModuleList: class ModuleList:
@staticmethod @staticmethod
def get_modules(): def get_modules():
# core modules (modules out of modules folder) should be loaded first! # core modules (modules out of modules folder) should be loaded first!
return List( return List(type, [
type, CoreModule, # has to be first!
[ DataModule,
CoreModule, # has to be first! PermissionModule,
DataModule, DatabaseModule,
ConfigModule, # has to be before db check AutoRoleModule,
DatabaseModule, BaseModule,
GraphQLModule, LevelModule,
PermissionModule, ApiModule,
AutoRoleModule, StatsModule,
BaseModule, TechnicianModule,
LevelModule, # has to be last!
ApiModule, BootLogModule,
TechnicianModule, CoreExtensionModule,
AchievementsModule, ])
ShortRoleNameModule,
# has to be last!
BootLogModule,
CoreExtensionModule,
],
)

View File

@@ -20,6 +20,7 @@ from bot_data.db_context import DBContext
class Startup(StartupABC): class Startup(StartupABC):
def __init__(self): def __init__(self):
StartupABC.__init__(self) StartupABC.__init__(self)
self._start_time = datetime.now() self._start_time = datetime.now()
@@ -27,16 +28,12 @@ class Startup(StartupABC):
self._config: Optional[ConfigurationABC] = None self._config: Optional[ConfigurationABC] = None
self._feature_flags: Optional[FeatureFlagsSettings] = None self._feature_flags: Optional[FeatureFlagsSettings] = None
def configure_configuration( def configure_configuration(self, configuration: ConfigurationABC, environment: ApplicationEnvironment) -> ConfigurationABC:
self, configuration: ConfigurationABC, environment: ApplicationEnvironment
) -> ConfigurationABC:
self._config = configuration self._config = configuration
self._feature_flags = configuration.get_configuration(FeatureFlagsSettings) self._feature_flags = configuration.get_configuration(FeatureFlagsSettings)
return configuration return configuration
def configure_services( def configure_services(self, services: ServiceCollectionABC, environment: ApplicationEnvironment) -> ServiceProviderABC:
self, services: ServiceCollectionABC, environment: ApplicationEnvironment
) -> ServiceProviderABC:
services.add_logging() services.add_logging()
if self._feature_flags.get_flag(FeatureFlagsEnum.core_module): if self._feature_flags.get_flag(FeatureFlagsEnum.core_module):
# custom logging # custom logging
@@ -55,11 +52,9 @@ class Startup(StartupABC):
for c in CustomFileLoggerABC.__subclasses__(): for c in CustomFileLoggerABC.__subclasses__():
i: LoggerABC = provider.get_service(c) i: LoggerABC = provider.get_service(c)
logger: LoggerABC = provider.get_service(LoggerABC) logger: LoggerABC = provider.get_service(LoggerABC)
for flag in [f for f in FeatureFlagsEnum]: for flag in [f for f in FeatureFlagsEnum]:
logger.debug( logger.debug(__name__, f'Loaded feature-flag: {flag} = {self._feature_flags.get_flag(flag)}')
__name__,
f"Loaded feature-flag: {flag} = {self._feature_flags.get_flag(flag)}",
)
return provider return provider

View File

@@ -6,6 +6,7 @@ from cpl_discord import get_discord_collection
class StartupDiscordExtension(StartupExtensionABC): class StartupDiscordExtension(StartupExtensionABC):
def __init__(self): def __init__(self):
pass pass

View File

@@ -4,30 +4,16 @@ from cpl_core.dependency_injection import ServiceCollectionABC
from cpl_core.environment import ApplicationEnvironmentABC from cpl_core.environment import ApplicationEnvironmentABC
from bot_data.abc.migration_abc import MigrationABC from bot_data.abc.migration_abc import MigrationABC
from bot_data.migration.achievements_migration import AchievementsMigration
from bot_data.migration.api_key_migration import ApiKeyMigration
from bot_data.migration.api_migration import ApiMigration from bot_data.migration.api_migration import ApiMigration
from bot_data.migration.auto_role_fix1_migration import AutoRoleFix1Migration
from bot_data.migration.auto_role_migration import AutoRoleMigration from bot_data.migration.auto_role_migration import AutoRoleMigration
from bot_data.migration.config_feature_flags_migration import ConfigFeatureFlagsMigration
from bot_data.migration.config_migration import ConfigMigration
from bot_data.migration.db_history_migration import DBHistoryMigration
from bot_data.migration.default_role_migration import DefaultRoleMigration
from bot_data.migration.fix_updates_migration import FixUpdatesMigration
from bot_data.migration.initial_migration import InitialMigration from bot_data.migration.initial_migration import InitialMigration
from bot_data.migration.level_migration import LevelMigration from bot_data.migration.level_migration import LevelMigration
from bot_data.migration.remove_stats_migration import RemoveStatsMigration
from bot_data.migration.short_role_name_migration import ShortRoleNameMigration
from bot_data.migration.stats_migration import StatsMigration from bot_data.migration.stats_migration import StatsMigration
from bot_data.migration.user_joined_game_server_migration import UserJoinedGameServerMigration
from bot_data.migration.user_message_count_per_hour_migration import (
UserMessageCountPerHourMigration,
)
from bot_data.migration.user_warning_migration import UserWarningMigration
from bot_data.service.migration_service import MigrationService from bot_data.service.migration_service import MigrationService
class StartupMigrationExtension(StartupExtensionABC): class StartupMigrationExtension(StartupExtensionABC):
def __init__(self): def __init__(self):
pass pass
@@ -41,16 +27,3 @@ class StartupMigrationExtension(StartupExtensionABC):
services.add_transient(MigrationABC, ApiMigration) # 15.10.2022 #70 - 0.3.0 services.add_transient(MigrationABC, ApiMigration) # 15.10.2022 #70 - 0.3.0
services.add_transient(MigrationABC, LevelMigration) # 06.11.2022 #25 - 0.3.0 services.add_transient(MigrationABC, LevelMigration) # 06.11.2022 #25 - 0.3.0
services.add_transient(MigrationABC, StatsMigration) # 09.11.2022 #46 - 0.3.0 services.add_transient(MigrationABC, StatsMigration) # 09.11.2022 #46 - 0.3.0
services.add_transient(MigrationABC, AutoRoleFix1Migration) # 30.12.2022 #151 - 0.3.0
services.add_transient(MigrationABC, UserMessageCountPerHourMigration) # 11.01.2023 #168 - 0.3.1
services.add_transient(MigrationABC, ApiKeyMigration) # 09.02.2023 #162 - 1.0.0
services.add_transient(MigrationABC, UserJoinedGameServerMigration) # 12.02.2023 #181 - 1.0.0
services.add_transient(MigrationABC, RemoveStatsMigration) # 19.02.2023 #190 - 1.0.0
services.add_transient(MigrationABC, UserWarningMigration) # 21.02.2023 #35 - 1.0.0
services.add_transient(MigrationABC, DBHistoryMigration) # 06.03.2023 #246 - 1.0.0
services.add_transient(MigrationABC, AchievementsMigration) # 14.06.2023 #268 - 1.1.0
services.add_transient(MigrationABC, ConfigMigration) # 19.07.2023 #127 - 1.1.0
services.add_transient(MigrationABC, ConfigFeatureFlagsMigration) # 15.08.2023 #334 - 1.1.0
services.add_transient(MigrationABC, DefaultRoleMigration) # 24.09.2023 #360 - 1.1.3
services.add_transient(MigrationABC, ShortRoleNameMigration) # 28.09.2023 #378 - 1.1.7
services.add_transient(MigrationABC, FixUpdatesMigration) # 28.09.2023 #378 - 1.1.7

View File

@@ -12,6 +12,7 @@ from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
class StartupModuleExtension(StartupExtensionABC): class StartupModuleExtension(StartupExtensionABC):
def __init__(self): def __init__(self):
self._config: Optional[ConfigurationABC] = None self._config: Optional[ConfigurationABC] = None
self._feature_flags: Optional[FeatureFlagsSettings] = None self._feature_flags: Optional[FeatureFlagsSettings] = None
@@ -32,7 +33,7 @@ class StartupModuleExtension(StartupExtensionABC):
continue continue
Console.set_foreground_color(ForegroundColorEnum.green) Console.set_foreground_color(ForegroundColorEnum.green)
Console.write_line(f"[{__name__}] Loaded module: {module_type}") Console.write_line(f'[{__name__}] Loaded module: {module_type}')
Console.color_reset() Console.color_reset()
module.configure_configuration(self._config, env) module.configure_configuration(self._config, env)
module.configure_services(services, env) module.configure_services(services, env)

View File

@@ -1,6 +1,6 @@
import os import os
from datetime import datetime from datetime import datetime
from typing import Optional, Type, Callable from typing import Callable, Type, Optional
from cpl_core.application import StartupExtensionABC from cpl_core.application import StartupExtensionABC
from cpl_core.configuration import ConfigurationABC from cpl_core.configuration import ConfigurationABC
@@ -8,41 +8,46 @@ from cpl_core.dependency_injection import ServiceCollectionABC
from cpl_core.environment import ApplicationEnvironmentABC from cpl_core.environment import ApplicationEnvironmentABC
from bot_core.configuration.bot_logging_settings import BotLoggingSettings from bot_core.configuration.bot_logging_settings import BotLoggingSettings
from bot_core.configuration.bot_settings import BotSettings
from modules.base.configuration.base_settings import BaseSettings
from modules.boot_log.configuration.boot_log_settings import BootLogSettings
from modules.level.configuration.level_settings import LevelSettings
from modules.permission.configuration.permission_settings import PermissionSettings
class StartupSettingsExtension(StartupExtensionABC): class StartupSettingsExtension(StartupExtensionABC):
def __init__(self): def __init__(self):
self._start_time = datetime.now() self._start_time = datetime.now()
def configure_configuration(self, configuration: ConfigurationABC, environment: ApplicationEnvironmentABC): def configure_configuration(self, configuration: ConfigurationABC, environment: ApplicationEnvironmentABC):
# this shit has to be done here because we need settings in subsequent startup extensions # this shit has to be done here because we need settings in subsequent startup extensions
environment.set_working_directory(os.path.dirname(os.path.realpath(__file__))) environment.set_working_directory(os.path.dirname(os.path.realpath(__file__)))
configuration.add_environment_variables("KDB_") configuration.add_environment_variables('KDB_')
configuration.add_environment_variables("DISCORD_") configuration.add_environment_variables('DISCORD_')
configuration.add_json_file(f"config/appsettings.json", optional=False) configuration.add_json_file(f'config/appsettings.json', optional=False)
configuration.add_json_file(f"config/appsettings.{environment.environment_name}.json", optional=True) configuration.add_json_file(f'config/appsettings.{environment.environment_name}.json', optional=True)
configuration.add_json_file(f"config/appsettings.{environment.host_name}.json", optional=True) configuration.add_json_file(f'config/appsettings.{environment.host_name}.json', optional=True)
# load feature-flags # load feature-flags
configuration.add_json_file(f"config/feature-flags.json", optional=False) configuration.add_json_file(f'config/feature-flags.json', optional=False)
configuration.add_json_file(f"config/feature-flags.{environment.environment_name}.json", optional=True)
configuration.add_json_file(f"config/feature-flags.{environment.host_name}.json", optional=True)
configuration.add_configuration("Startup_StartTime", str(self._start_time)) configuration.add_configuration('Startup_StartTime', str(self._start_time))
self._configure_settings_with_sub_settings( self._configure_settings_with_sub_settings(configuration, BotSettings, lambda x: x.servers, lambda x: x.id)
configuration, BotLoggingSettings, lambda x: x.files, lambda x: x.key self._configure_settings_with_sub_settings(configuration, BaseSettings, lambda x: x.servers, lambda x: x.id)
) self._configure_settings_with_sub_settings(configuration, BootLogSettings, lambda x: x.servers, lambda x: x.id)
self._configure_settings_with_sub_settings(configuration, LevelSettings, lambda x: x.servers, lambda x: x.id)
self._configure_settings_with_sub_settings(configuration, PermissionSettings, lambda x: x.servers, lambda x: x.id)
self._configure_settings_with_sub_settings(configuration, BotLoggingSettings, lambda x: x.files, lambda x: x.key)
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
pass pass
@staticmethod @staticmethod
def _configure_settings_with_sub_settings( def _configure_settings_with_sub_settings(config: ConfigurationABC, settings: Type, list_atr: Callable, atr: Callable):
config: ConfigurationABC, settings_type: Type, list_atr: Callable, atr: Callable settings: Optional[settings] = config.get_configuration(settings)
):
settings: Optional[settings_type] = config.get_configuration(settings_type)
if settings is None: if settings is None:
return return
for sub_settings in list_atr(settings): for sub_settings in list_atr(settings):
config.add_configuration(f"{type(sub_settings).__name__}_{atr(sub_settings)}", sub_settings) config.add_configuration(f'{type(sub_settings).__name__}_{atr(sub_settings)}', sub_settings)

View File

@@ -1,28 +1,51 @@
{ {
"api": {
"api": {
"test_mail": {
"message": "Dies ist eine Test-Mail vom Krümelmonster Web Interface\nGesendet von {}-{}",
"subject": "Krümelmonster Web Interface Test-Mail"
}
},
"auth": {
"confirmation": {
"message": "Öffne den Link, um die E-Mail zu bestätigen:\n{}auth/register/{}",
"subject": "E-Mail für {} {} bestätigen"
},
"forgot_password": {
"message": "Öffne den Link, um das Passwort zu ändern:\n{}auth/forgot-password/{}",
"subject": "Passwort für {} {} zurücksetzen"
}
},
"mail": {
"automatic_mail": "\n\nDies ist eine automatische E-Mail.\nGesendet von {}-{}@{}"
}
},
"common": { "common": {
"feature_not_activated": "Diese Funktion ist deaktiviert", "hello_world": "Hallo Welt",
"bot_has_no_permission_message": "Ey!!!\nWas soll das?\nIch habe keine Berechtigungen :(\nScheiß System...", "bot_has_no_permission_message": "Ey!!!\nWas soll das?\nIch habe keine Berechtigungen :(\nScheiß System...",
"no_permission_message": "Nein!\nIch höre nicht auf dich ¯\\_(ツ)_/¯",
"not_implemented_yet": "Ey Alter, das kann ich noch nicht...",
"presence": {
"booting": "{} Ich fahre gerade hoch...",
"running": "{} Behalte Ruhe und iss Kekse :D",
"restart": "{} Muss neue Kekse holen...",
"shutdown": "{} Ich werde bestimmt wieder kommen..."
},
"errors": {
"error": "Es gab einen Fehler. Meld dich bitte bei einem Admin.",
"command_error": "Es gab einen Fehler beim bearbeiten des Befehls. Meld dich bitte bei einem Admin.",
"missing_required_argument": "Fehler: Ein benötigter Parameter fehlt!",
"argument_parsing_error": "Fehler: Parameter konnte nicht gelesen werden!",
"unexpected_quote_error": "Fehler: Unerwarteter Zitat Fehler!",
"invalid_end_of_quoted_string_error": "Fehler: Ungültiges Zitatende!",
"expected_closing_quote_error": "Fehler: Erwarte Zitatende!",
"bad_argument": "Fehler: Ungültiger Parameter!",
"bad_union_argument": "Fehler: Ungültiger Union Parameter!",
"private_message_only": "Fehler: Nur private Nachrichten sind erlaubt!",
"no_private_message": "Fehler: Private Nachrichten sind nicht erlaubt!",
"check_failure": "Fehler: Du hast nicht die benötigte Berechtigung!",
"check_any_failure": "Fehler: Alle checks sind Fehlgeschlagen!",
"command_not_found": "Fehler: Befehl konnte nicht gefunden werden!",
"disabled_command": "Fehler: Befehl wurde deaktiviert!",
"command_invoke_error": "Fehler: Befehl konnte nicht aufgerufen werden!",
"too_many_arguments": "Fehler: Zu viele Parameter!",
"user_input_error": "Fehler: Eingabefehler!",
"command_on_cooldown": "Fehler: Befehl befindet sich im cooldown!",
"max_concurrency_reached": "Fehler: Maximale Parallelität erreicht!",
"not_owner": "Fehler: Du bist nicht mein besitzer!",
"missing_permissions": "Fehler: Berechtigungen fehlen!",
"bot_missing_permissions": "Fehler: Mir fehlen Berechtigungen!",
"missing_role": "Fehler: Benötigte Rolle fehlt!",
"bot_missing_role": "Fehler: Mir fehlt eine benötigte Rolle!",
"missing_any_role": "Fehler: Alle benötigten Rollen fehlen!",
"bot_missing_any_role": "Fehler: Mir fehlen alle benötigten Rollen!",
"nsfw_channel_required": "Fehler: NSFW Kanal benötigt!",
"extension_error": "Fehler: Erweiterungsfehler!",
"extension_already_loaded": "Fehler: Erweiterung wurde bereits geladen!",
"extension_not_loaded": "Fehler: Erweiterung wurde nicht geladen!",
"no_entry_point_error": "Fehler: Kein Eintrittspunkt!",
"extension_failed": "Fehler: Erweiterung ist fehlgeschlagen!",
"bot_not_ready_yet": "Ey Alter! Gedulde dich doch mal! ..."
},
"colors": { "colors": {
"blue": "Blau", "blue": "Blau",
"dark_blue": "Dunkelblau", "dark_blue": "Dunkelblau",
@@ -46,321 +69,216 @@
"red": "Rot", "red": "Rot",
"teal": "Blaugrün", "teal": "Blaugrün",
"yellow": "Gelb" "yellow": "Gelb"
},
"errors": {
"argument_parsing_error": "Fehler: Parameter konnte nicht gelesen werden!",
"bad_argument": "Fehler: Ungültiger Parameter!",
"bad_union_argument": "Fehler: Ungültiger Union Parameter!",
"bot_missing_any_role": "Fehler: Mir fehlen alle benötigten Rollen!",
"bot_missing_permissions": "Fehler: Mir fehlen Berechtigungen!",
"bot_missing_role": "Fehler: Mir fehlt eine benötigte Rolle!",
"bot_not_ready_yet": "Ey Alter! Gedulde dich doch mal! ...",
"check_any_failure": "Fehler: Alle Checks sind Fehlgeschlagen!",
"check_failure": "Fehler: Du hast nicht die benötigte Berechtigung!",
"command_error": "Es gab einen Fehler beim Bearbeiten des Befehls. Melde dich bitte bei einem Admin.",
"command_invoke_error": "Fehler: Befehl konnte nicht aufgerufen werden!",
"command_not_found": "Fehler: Befehl konnte nicht gefunden werden!",
"command_on_cooldown": "Fehler: Befehl befindet sich im Cooldown!",
"disabled_command": "Fehler: Befehl wurde deaktiviert!",
"error": "Es gab einen Fehler. Melde dich bitte bei einem Admin.",
"expected_closing_quote_error": "Fehler: Erwarte Zitatende!",
"extension_already_loaded": "Fehler: Erweiterung wurde bereits geladen!",
"extension_error": "Fehler: Erweiterungsfehler!",
"extension_failed": "Fehler: Erweiterung ist fehlgeschlagen!",
"extension_not_loaded": "Fehler: Erweiterung wurde nicht geladen!",
"invalid_end_of_quoted_string_error": "Fehler: Ungültiges Zitatende!",
"max_concurrency_reached": "Fehler: Maximale Parallelität erreicht!",
"missing_any_role": "Fehler: Alle benötigten Rollen fehlen!",
"missing_permissions": "Fehler: Berechtigungen fehlen!",
"missing_required_argument": "Fehler: Ein benötigter Parameter fehlt!",
"missing_role": "Fehler: Benötigte Rolle fehlt!",
"no_entry_point_error": "Fehler: Kein Eintrittspunkt!",
"no_private_message": "Fehler: Private Nachrichten sind nicht erlaubt!",
"not_owner": "Fehler: Du bist nicht mein besitzer!",
"nsfw_channel_required": "Fehler: NSFW Kanal benötigt!",
"private_message_only": "Fehler: Nur private Nachrichten sind erlaubt!",
"too_many_arguments": "Fehler: Zu viele Parameter!",
"unexpected_quote_error": "Fehler: Unerwarteter Fehler beim Anführungszeichen!",
"user_input_error": "Fehler: Eingabefehler!"
},
"hello_world": "Hallo Welt",
"no_permission_message": "Nein!\nIch höre nicht auf dich ¯\\_(ツ)_/¯",
"not_implemented_yet": "Ey Alter, das kann ich noch nicht...",
"presence": {
"booting": "Ich fahre gerade hoch...",
"restart": "Muss neue Kekse holen...",
"running": "Ich esse Kekse :D",
"shutdown": "Ich werde bestimmt wieder kommen..."
} }
}, },
"modules": { "modules": {
"achievements": {
"got_new_achievement": "{} hat die Errungenschaft {} freigeschaltet :D",
"commands": {
"check": "Alles klar, ich schaue eben nach... nom nom"
}
},
"auto_role": { "auto_role": {
"list": {
"title": "Beobachtete Nachrichten:",
"description": "Von auto-role beobachtete Nachrichten:",
"auto_role_id": "auto-role Id",
"message_id": "Nachricht-Id"
},
"add": { "add": {
"success": "auto-role für die Nachricht {} wurde hinzugefügt :D",
"error": { "error": {
"already_exists": "auto-role für die Nachricht {} existiert bereits!", "not_found": "Nachricht {} in {} nicht gefunden!",
"not_found": "Nachricht {} in {} nicht gefunden!" "already_exists": "auto-role für die Nachricht {} existiert bereits!"
}, }
"success": "auto-role für die Nachricht {} wurde hinzugefügt :D" },
"remove": {
"success": "auto-role {} wurde entfernt :D",
"error": {
"not_found": "auto-role {} nicht gefunden!"
}
}, },
"error": { "error": {
"nothing_found": "Keine auto-role Einträge gefunden." "nothing_found": "Keine auto-role Einträge gefunden."
}, },
"list": {
"auto_role_id": "auto-role Id",
"description": "Von auto-role beobachtete Nachrichten:",
"message_id": "Nachricht-Id",
"title": "Beobachtete Nachrichten:"
},
"remove": {
"error": {
"not_found": "auto-role {} nicht gefunden!"
},
"success": "auto-role {} wurde entfernt :D"
},
"rule": { "rule": {
"list": {
"title": "auto-role Regeln:",
"description": "Von auto-role angewendete Regeln:",
"auto_role_rule_id": "auto-role Regel Id",
"emoji": "Emoji",
"role": "Rolle"
},
"add": { "add": {
"success": "Regel {} -> {} für auto-role {} wurde hinzugefügt :D",
"error": { "error": {
"already_exists": "Regel für auto-role {} existiert bereits!",
"emoji_not_found": "Emoji {} für auto-role Regel {} nicht gefunden!",
"not_found": "Regel für auto-role {} nicht gefunden!", "not_found": "Regel für auto-role {} nicht gefunden!",
"role_not_found": "Rolle {} für auto-role Regel {} nicht gefunden!" "emoji_not_found": "Emoji {} für auto-role Regel {} nicht gefunden!",
}, "rule_not_found": "Rolle {} für auto-role Regel {} nicht gefunden!",
"success": "Regel {} -> {} für auto-role {} wurde hinzugefügt :D" "already_exists": "Regel für auto-role {} existiert bereits!"
}
},
"remove": {
"success": "Regel für auto-role {} wurde entfernt :D",
"error": {
"not_found": "Regel für auto-role {} nicht gefunden!"
}
}, },
"error": { "error": {
"id_not_found": "Kein auto-role Eintrag mit der Id gefunden!" "id_not_found": "Kein auto-role Eintrag mit der Id gefunden!"
},
"list": {
"auto_role_rule_id": "auto-role Regel Id",
"description": "Von auto-role angewendete Regeln:",
"emoji": "Emoji",
"role": "Rolle",
"title": "auto-role Regeln:"
},
"remove": {
"error": {
"not_found": "Regel für auto-role {} nicht gefunden!"
},
"success": "Regel für auto-role {} wurde entfernt :D"
} }
} }
}, },
"base": {
"member_left_message": "{} hat uns leider verlassen :(",
"complaints": {
"title": "Beschwerde einreichen",
"label": "Beschwerde",
"message": "{} hat eine Beschwerde eingereicht:\n{}",
"response": "Danke für deine Beschwerde"
},
"bug": {
"title": "Bug melden",
"label": "Bug",
"message": "{} meldet einen Bug:\n{}",
"response": "Danke für dein Feedback :D"
},
"afk_command_channel_missing_message": "Zu unfähig einem Sprachkanal beizutreten?",
"afk_command_move_message": "Ich verschiebe dich ja schon... (◔_◔)",
"game_server": {
"error": {
"nothing_found": "Keine Gameserver gefunden."
},
"list_members": {
"title": "Mitglieder",
"description": "Konfigurierte Mitglieder:",
"users": "Mitglieder"
},
"list": {
"title": "Gameserver",
"description": "Konfigurierte Gameserver:",
"name": "Name",
"api_key": "API Key"
},
"add": {
"success": "Gameserver {} wurde hinzugefügt :)"
},
"remove": {
"success": "Gameserver wurde entfernt :D"
}
},
"goodbye_message": "Schade, dass du uns so schnell verlässt :(",
"info": {
"description": "Informationen über mich",
"fields": {
"deleted_message_count": "Gelöschte Nachrichten",
"modules": "Module",
"moved_users_count": "Verschobene Benutzer",
"ontime": "Ontime",
"received_command_count": "Empfangene Befehle",
"received_message_count": "Empfangene Nachrichten",
"sent_message_count": "Gesendete Nachrichten",
"version": "Version"
},
"footer": "",
"title": "Krümelmonster"
},
"mass_move": {
"channel_from_error": "Du musst dich in einem Voicechannel befinden oder die Option \"channel_from\" mit angeben.",
"moved": "Alle Personen aus {} wurden nach {} verschoben."
},
"member_joined_help_voice_channel": "{} braucht Hilfe, bitte kümmere dich drum :D",
"pong": "Pong",
"presence": {
"changed": "Presence wurde geändert.",
"max_char_count_exceeded": "Der Text darf nicht mehr als 128 Zeichen lang sein!",
"removed": "Presence wurde entfernt."
},
"register": {
"not_found": "Benutzer konnte nicht gefunden werden!",
"success": "Spieler wurde mit dem Mitglied verlinkt :D"
},
"technician_command_error_message": "Es gab ein Fehler mit dem Befehl: {} ausgelöst von {} -> {}\nDatum und Zeit: {}\nSchau bitte ins Log für Details.\nUUID: {}",
"technician_error_message": "Es gab ein Fehler mit dem Event: {}\nDatum und Zeit: {}\nSchau bitte ins Log für Details.\nUUID: {}",
"unregister": {
"success": "Verlinkung wurde entfernt :D"
},
"user": {
"add": {
"xp": "Die {} von {} wurden um {} erhöht"
},
"atr": {
"discord_join": "Discord beigetreten am",
"id": "Id",
"joins": "Beitritte",
"last_join": "Server beigetreten am",
"lefts": "Abgänge",
"name": "Name",
"ontime": "Ontime",
"roles": "Rollen",
"warnings": "Verwarnungen",
"xp": "XP"
},
"error": {
"atr_not_found": "Das Attribut {} konnte nicht gefunden werden :("
},
"get": {
"ontime": "{} war insgesamt {} Stunden aktiv in einem Sprachkanal",
"xp": "{} hat {} xp"
},
"info": {
"footer": ""
},
"remove": {
"xp": "Die {} von {} wurden um {} verringert"
},
"reset": {
"ontime": "Die {} von {} wurden entfernt",
"xp": "Die {} von {} wurden entfernt"
},
"set": {
"error": {
"type_error": "Der angegebene Wert ist keine Zahl! :(",
"value_type_not_numeric": "Der angegebende Wert ist keine Ganzzahl! :("
},
"xp": "{} hat nun {} xp"
}
},
"warnings": {
"add": {
"failed": "Verwarnung konnte nicht hinzugefügt werden :(",
"success": "Verwarnung wurde hinzugefügt :)"
},
"first": "Bei der nächsten Verwarnung wirst du auf das vorherige Level zurückgesetzt!",
"kick": "Ich musste {} aufgrund zu vieler Verwarnungen kicken",
"remove": {
"failed": "Verwarnung konnte nicht entfernt werden :(",
"success": "Verwarnung wurde entfernt :)"
},
"removed": "Die Verwarnung '{}' wurde entfernt.",
"second": "Bei der nächsten verwarnung wirst du auf das erste Level zurückgesetzt!",
"show": {
"description": "Beschreibung",
"id": "Id"
},
"team_removed": "Die Verwarnung '{}' an {} wurde entfernt.",
"team_warned": "{} wurde verwarnt. Der Grund ist: {}",
"third": "Bei der nächsten verwarnung wirst du gekickt und zurückgesetzt!",
"warned": "Du wurdest verwarnt. Der Grund ist: {}"
},
"welcome_message": "Hello There!\nIch heiße dich bei {} herzlichst Willkommen!",
"welcome_message_for_team": "{} hat gerade das Irrenhaus betreten."
},
"boot_log": {
"login_message": "Ich bin on the line :D\nDer Scheiß hat {} Sekunden gedauert"
},
"level": {
"create": {
"created": "Level {} mit Berechtigungen {} wurde erstellt :D"
},
"down": {
"already_first": "{} hat bereits das erste Level.",
"failed": "{} konnte nicht runtergesetzt werden :(",
"success": "{} wurde auf Level {} runtergesetzt :)"
},
"edit": {
"color_invalid": "Die Farbe {} ist ungültig!",
"edited": "Level {} wurde bearbeitet :D",
"not_found": "Level {} nicht gefunden!",
"permission_invalid": "Der Berechtigungswert {} ist ungültig!"
},
"error": {
"level_with_name_already_exists": "Ein Level mit dem Namen {} existiert bereits!",
"level_with_xp_already_exists": "Das Level {} hat bereits die Mindest-XP {}!",
"nothing_found": "Keine Einträge gefunden."
},
"list": {
"description": "Konfigurierte Level:",
"min_xp": "Mindest-XP",
"name": "Name",
"permission_int": "Berechtigungen",
"title": "Level:"
},
"new_level_message": "{} ist nun Level {}",
"remove": {
"error": {
"not_found": "Level {} nicht gefunden!"
},
"success": "Level {} wurde entfernt :D"
},
"seeding_failed": "Levelsystem konnte nicht neu geladen werden :(",
"seeding_finished": "Levelsystem wurde erfolgreich neu geladen :)",
"seeding_started": "Levelsystem wird neu geladen...",
"set": {
"already_level": "{} hat bereits das Level {} :/",
"failed": "Das Level von {} konnte nicht auf {} gesetzt werden :(",
"not_found": "Das Level {} konnte nicht gefunden werden :(",
"success": "{} ist nun Level {} :)"
},
"up": {
"already_last": "{} hat bereits das höchste Level.",
"failed": "{} konnte nicht hochgesetzt werden :(",
"success": "{} wurde auf Level {} hochgesetzt :)"
}
},
"moderator": { "moderator": {
"purge_message": "Na gut..., ich lösche alle Nachrichten wenns sein muss." "purge_message": "Na gut..., ich lösche alle Nachrichten wenns sein muss."
}, },
"technician": { "base": {
"api_key": { "technician_error_message": "Es gab ein Fehler mit dem Event: {}\nDatum und Zeit: {}\nSchau bitte ins log für Details.UUID: {}",
"add": { "technician_command_error_message": "Es gab ein Fehler mit dem Befehl: {} ausgelöst von {} -> {}\nDatum und Zeit: {}\nSchau bitte ins log für Details.UUID: {}",
"success": "API-Schlüssel für {} wurde erstellt: {}" "welcome_message": "Hello There!\nIch heiße dich bei {} herzlichst willkommen!",
"welcome_message_for_team": "{} hat gerade das Irrenhaus betreten.",
"goodbye_message": "Schade das du uns so schnell verlässt :(",
"afk_command_channel_missing_message": "Zu unfähig einem Sprachkanal beizutreten?",
"afk_command_move_message": "Ich verschiebe dich ja schon... (◔_◔)",
"member_joined_help_voice_channel": "{} braucht hilfe, bitte kümmer dich drum :D",
"pong": "Pong",
"info": {
"title": "Gismo",
"description": "Informationen über mich",
"fields": {
"version": "Version",
"ontime": "Ontime",
"sent_message_count": "Gesendete Nachrichten",
"received_message_count": "Empfangene Nachrichten",
"deleted_message_count": "Gelöschte Nachrichten",
"received_command_count": "Empfangene Befehle",
"moved_users_count": "Verschobene Benutzer",
"modules": "Module"
}, },
"get": "API-Schlüssel für {}: {}", "footer": ""
"remove": { },
"not_found": "API-Schlüssel konnte nicht gefunden werden!", "mass_move": {
"success": "API-Schlüssel wurde entfernt :D" "moved": "Alle Personen aus {} wurden nach {} verschoben.",
"channel_from_error": "Du musst dich in einem Voicechannel befinden oder die Option \"channel_from\" mit angeben."
},
"presence": {
"changed": "Presence wurde geändert.",
"removed": "Presence wurde entfernt.",
"max_char_count_exceeded": "Der Text darf nicht mehr als 128 Zeichen lang sein!"
},
"user_info": {
"fields": {
"id": "Id",
"name": "Name",
"discord_join": "Discord beigetreten am",
"last_join": "Server beigetreten am",
"xp": "XP",
"ontime": "Ontime",
"roles": "Rollen",
"joins": "Beitritte",
"lefts": "Abgänge",
"warnings": "Verwarnungen"
},
"footer": ""
}
},
"boot_log": {
"login_message": "Ich bin on the line :D\nDer Scheiß hat {} Sekunden gedauert"
},
"level": {
"new_level_message": "<@{}> ist nun Level {}",
"seeding_started": "Levelsystem wird neu geladen.",
"seeding_failed": "Levelsystem konnte nicht neu geladen werden.",
"seeding_finished": "Levelsystem wurde Erfolgreich neu geladen.",
"error": {
"nothing_found": "Keine Level Einträge gefunden.",
"level_with_name_already_exists": "Ein Level mit dem Namen {} existiert bereits!",
"level_with_xp_already_exists": "Das Level {} hat bereits die Mindest XP {}!"
},
"list": {
"title": "Level:",
"description": "Konfigurierte Level:",
"name": "Name",
"min_xp": "Mindest XP",
"permission_int": "Berechtigungen"
},
"create": {
"created": "Level {} mit Berechtigungen {} wurde erstellt :D"
},
"edit": {
"edited": "Level {} wurde bearbeitet :D",
"color_invalid": "Die Farbe {} ist ungültig!",
"permission_invalid": "Der Berechtigungswert {} ist ungültig!",
"not_found": "Level {} nicht gefunden!"
},
"remove": {
"success": "Level {} wurde entfernt :D",
"error": {
"not_found": "Level {} nicht gefunden!"
} }
}, },
"synced_message": "Der Sync wurde abgeschlossen.", "down": {
"log_message": "Hier sind deine Logdateien! :)", "already_first": "{} hat bereits das erste Level.",
"success": "{} wurde auf Level {} runtergesetzt :)",
"failed": "{} konnte nicht runtergesetzt werden :("
},
"up": {
"already_last": "{} hat bereits das höchste Level.",
"success": "{} wurde auf Level {} hochgesetzt :)",
"failed": "{} konnte nicht hochgesetzt werden :("
},
"set": {
"already_level": "{} hat bereits das Level {} :/",
"success": "{} ist nun Level {} :)",
"failed": "Das Level von {} konnte nicht auf {} gesetzt werden :(",
"not_found": "Das Level {} konnte nicht gefunden werden :("
}
},
"database": {},
"permission": {},
"stats": {
"list": {
"statistic": "Statistik",
"description": "Beschreibung",
"nothing_found": "Keine Statistiken gefunden."
},
"view": {
"statistic": "Statistik",
"description": "Beschreibung",
"failed": "Statistik kann nicht gezeigt werden :("
},
"add": {
"failed": "Statistik kann nicht hinzugefügt werden :(",
"success": "Statistik wurde hinzugefügt :D"
},
"edit": {
"failed": "Statistik kann nicht bearbeitet werden :(",
"success": "Statistik wurde gespeichert :D"
},
"remove": {
"failed": "Statistik kann nicht gelöscht werden :(",
"success": "Statistik wurde gelöscht :D"
}
},
"technician": {
"restart_message": "Bin gleich wieder da :D", "restart_message": "Bin gleich wieder da :D",
"shutdown_message": "Trauert nicht um mich, es war eine logische Entscheidung. Das Wohl von Vielen, es wiegt schwerer als das Wohl von Wenigen oder eines Einzelnen. Ich war es und ich werde es immer sein, euer Freund. Lebt lange und in Frieden :)" "shutdown_message": "Trauert nicht um mich, es war eine logische Entscheidung. Das Wohl von Vielen, es wiegt schwerer als das Wohl von Wenigen oder eines Einzelnen. Ich war es und ich werde es immer sein, Euer Freund. Lebt lange und in Frieden :)",
"log_message": "Hier sind deine Logdateien! :)"
}
},
"api": {
"mail": {
"automatic_mail": "\n\nDies ist eine automatische E-Mail.\nGesendet von {}-{}@{}"
},
"api": {
"test_mail": {
"subject": "Krümmelmonster Web Interface Test-Mail",
"message": "Dies ist eine Test-Mail vom Krümmelmonster Web Interface\nGesendet von {}-{}"
}
},
"auth": {
"confirmation": {
"subject": "E-Mail für {} {} bestätigen",
"message": "Öffne den Link um die E-Mail zu bestätigen:\n{}auth/register/{}"
},
"forgot_password": {
"subject": "Passwort für {} {} zurücksetzen",
"message": "Öffne den Link um das Passwort zu ändern:\n{}auth/forgot-password/{}"
}
} }
} }
} }

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
bot sh-edraft.de Discord bot bot Keksdose bot
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Discord bot for some customers of sh-edraft.de Discord bot for the Keksdose discord Server
:copyright: (c) 2022 - 2023 sh-edraft.de :copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details. :license: MIT, see LICENSE for more details.
""" """
@@ -14,13 +14,13 @@ Discord bot for some customers of sh-edraft.de
__title__ = 'bot_api' __title__ = 'bot_api'
__author__ = 'Sven Heidemann' __author__ = 'Sven Heidemann'
__license__ = 'MIT' __license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 - 2023 sh-edraft.de' __copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.1.7' __version__ = '0.3.dev25'
from collections import namedtuple from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro') VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='1', micro='7') version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -1,21 +1,21 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
bot sh-edraft.de Discord bot bot Keksdose bot
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Discord bot for some customers of sh-edraft.de Discord bot for the Keksdose discord Server
:copyright: (c) 2022 - 2023 sh-edraft.de :copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details. :license: MIT, see LICENSE for more details.
""" """
__title__ = 'bot_api.abc' __title__ = 'bot_api.service'
__author__ = 'Sven Heidemann' __author__ = 'Sven Heidemann'
__license__ = 'MIT' __license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 - 2023 sh-edraft.de' __copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.1.7' __version__ = '0.3.dev25'
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro') VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='1', micro='7') version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -15,102 +15,78 @@ from bot_data.model.auth_user import AuthUser
class AuthServiceABC(ABC): class AuthServiceABC(ABC):
@abstractmethod
def __init__(self):
pass
@abstractmethod @abstractmethod
def generate_token(self, user: AuthUser) -> str: def __init__(self): pass
pass
@abstractmethod @abstractmethod
def decode_token(self, token: str) -> dict: def generate_token(self, user: AuthUser) -> str: pass
pass
@abstractmethod @abstractmethod
def get_decoded_token_from_request(self) -> dict: def decode_token(self, token: str) -> dict: pass
pass
@abstractmethod @abstractmethod
def find_decoded_token_from_request(self) -> Optional[dict]: def get_decoded_token_from_request(self) -> dict: pass
pass
@abstractmethod @abstractmethod
async def get_all_auth_users_async(self) -> List[AuthUserDTO]: def find_decoded_token_from_request(self) -> Optional[dict]: pass
pass
@abstractmethod @abstractmethod
async def get_filtered_auth_users_async(self, criteria: AuthUserSelectCriteria) -> AuthUserFilteredResultDTO: async def get_all_auth_users_async(self) -> List[AuthUserDTO]: pass
pass
@abstractmethod @abstractmethod
async def get_auth_user_by_email_async(self, email: str, with_password: bool = False) -> AuthUserDTO: async def get_filtered_auth_users_async(self, criteria: AuthUserSelectCriteria) -> AuthUserFilteredResultDTO: pass
pass
@abstractmethod @abstractmethod
async def find_auth_user_by_email_async(self, email: str) -> AuthUserDTO: async def get_auth_user_by_email_async(self, email: str, with_password: bool = False) -> AuthUserDTO: pass
pass
@abstractmethod @abstractmethod
def add_auth_user(self, user_dto: AuthUserDTO): async def find_auth_user_by_email_async(self, email: str) -> AuthUserDTO: pass
pass
@abstractmethod @abstractmethod
async def add_auth_user_by_oauth_async(self, dto: OAuthDTO): async def add_auth_user_async(self, user_dto: AuthUserDTO): pass
pass
@abstractmethod @abstractmethod
async def update_user_async(self, update_user_dto: UpdateAuthUserDTO): async def add_auth_user_by_oauth_async(self, dto: OAuthDTO): pass
pass
@abstractmethod @abstractmethod
async def update_user_as_admin_async(self, update_user_dto: UpdateAuthUserDTO): async def add_auth_user_by_discord_async(self, user_dto: AuthUserDTO, dc_id: int) -> OAuthDTO: pass
pass
@abstractmethod @abstractmethod
async def delete_auth_user_by_email_async(self, email: str): async def update_user_async(self, update_user_dto: UpdateAuthUserDTO): pass
pass
@abstractmethod @abstractmethod
async def delete_auth_user_async(self, user_dto: AuthUserDTO): async def update_user_as_admin_async(self, update_user_dto: UpdateAuthUserDTO): pass
pass
@abstractmethod @abstractmethod
async def verify_login(self, token_str: str) -> bool: async def delete_auth_user_by_email_async(self, email: str): pass
pass
@abstractmethod @abstractmethod
def verify_api_key(self, api_key: str) -> bool: async def delete_auth_user_async(self, user_dto: AuthUserDTO): pass
pass
@abstractmethod @abstractmethod
async def login_async(self, user_dto: AuthUserDTO) -> TokenDTO: async def verify_login(self, token_str: str) -> bool: pass
pass
@abstractmethod @abstractmethod
async def login_discord_async(self, oauth_dto: AuthUserDTO, dc_id: int) -> TokenDTO: async def login_async(self, user_dto: AuthUserDTO) -> TokenDTO: pass
pass
@abstractmethod @abstractmethod
async def refresh_async(self, token_dto: TokenDTO) -> TokenDTO: async def login_discord_async(self, oauth_dto: AuthUserDTO) -> TokenDTO: pass
pass
@abstractmethod @abstractmethod
async def revoke_async(self, token_dto: TokenDTO): async def refresh_async(self, token_dto: TokenDTO) -> TokenDTO: pass
pass
@abstractmethod @abstractmethod
async def confirm_email_async(self, id: str) -> bool: async def revoke_async(self, token_dto: TokenDTO): pass
pass
@abstractmethod @abstractmethod
async def forgot_password_async(self, email: str): async def confirm_email_async(self, id: str) -> bool: pass
pass
@abstractmethod @abstractmethod
async def confirm_forgot_password_async(self, id: str) -> EMailStringDTO: async def forgot_password_async(self, email: str): pass
pass
@abstractmethod @abstractmethod
async def reset_password_async(self, rp_dto: ResetPasswordDTO): async def confirm_forgot_password_async(self, id: str) -> EMailStringDTO: pass
pass
@abstractmethod
async def reset_password_async(self, rp_dto: ResetPasswordDTO): pass

View File

@@ -2,14 +2,12 @@ from abc import ABC, abstractmethod
class DtoABC(ABC): class DtoABC(ABC):
@abstractmethod
def __init__(self):
pass
@abstractmethod @abstractmethod
def from_dict(self, values: dict): def __init__(self): pass
pass
@abstractmethod @abstractmethod
def to_dict(self) -> dict: def from_dict(self, values: dict): pass
pass
@abstractmethod
def to_dict(self) -> dict: pass

View File

@@ -2,8 +2,15 @@ from abc import ABC, abstractmethod
class SelectCriteriaABC(ABC): class SelectCriteriaABC(ABC):
@abstractmethod @abstractmethod
def __init__(self, page_index: int, page_size: int, sort_direction: str, sort_column: str): def __init__(
self,
page_index: int,
page_size: int,
sort_direction: str,
sort_column: str
):
self.page_index = page_index self.page_index = page_index
self.page_size = page_size self.page_size = page_size
self.sort_direction = sort_direction self.sort_direction = sort_direction

View File

@@ -6,12 +6,11 @@ from bot_api.abc.dto_abc import DtoABC
class TransformerABC: class TransformerABC:
@staticmethod
@abstractmethod
def to_db(dto: DtoABC) -> TableABC:
pass
@staticmethod @staticmethod
@abstractmethod @abstractmethod
def to_dto(db: TableABC) -> DtoABC: def to_db(dto: DtoABC) -> TableABC: pass
pass
@staticmethod
@abstractmethod
def to_dto(db: TableABC) -> DtoABC: pass

View File

@@ -1,9 +1,9 @@
import socket import re
import sys import sys
import textwrap import textwrap
import uuid import uuid
from functools import partial from functools import partial
from typing import Union, Optional from typing import Union
import eventlet import eventlet
from cpl_core.dependency_injection import ServiceProviderABC from cpl_core.dependency_injection import ServiceProviderABC
@@ -16,6 +16,7 @@ from werkzeug.exceptions import NotFound
from bot_api.configuration.api_settings import ApiSettings from bot_api.configuration.api_settings import ApiSettings
from bot_api.configuration.authentication_settings import AuthenticationSettings from bot_api.configuration.authentication_settings import AuthenticationSettings
from bot_api.configuration.frontend_settings import FrontendSettings
from bot_api.exception.service_error_code_enum import ServiceErrorCode from bot_api.exception.service_error_code_enum import ServiceErrorCode
from bot_api.exception.service_exception import ServiceException from bot_api.exception.service_exception import ServiceException
from bot_api.logging.api_logger import ApiLogger from bot_api.logging.api_logger import ApiLogger
@@ -24,17 +25,18 @@ from bot_api.route.route import Route
class Api(Flask): class Api(Flask):
def __init__( def __init__(
self, self,
logger: ApiLogger, logger: ApiLogger,
services: ServiceProviderABC, services: ServiceProviderABC,
api_settings: ApiSettings, api_settings: ApiSettings,
auth_settings: AuthenticationSettings, frontend_settings: FrontendSettings,
*args, auth_settings: AuthenticationSettings,
**kwargs, *args, **kwargs
): ):
if not args: if not args:
kwargs.setdefault("import_name", __name__) kwargs.setdefault('import_name', __name__)
Flask.__init__(self, *args, **kwargs) Flask.__init__(self, *args, **kwargs)
@@ -54,26 +56,17 @@ class Api(Flask):
self.register_error_handler(exc_class, self.handle_exception) self.register_error_handler(exc_class, self.handle_exception)
# websockets # websockets
# Added async_mode see link below self._socketio = SocketIO(self, cors_allowed_origins='*', path='/api/socket.io')
# https://github.com/miguelgrinberg/Flask-SocketIO/discussions/1849 self._socketio.on_event('connect', self.on_connect)
# https://stackoverflow.com/questions/39370848/flask-socket-io-sometimes-client-calls-freeze-the-server self._socketio.on_event('disconnect', self.on_disconnect)
self._socketio = SocketIO(self, cors_allowed_origins="*", path="/api/socket.io", async_mode="eventlet")
self._socketio.on_event("connect", self.on_connect)
self._socketio.on_event("disconnect", self.on_disconnect)
self._socket: Optional[socket] = None
self._requests = {} self._requests = {}
@staticmethod @staticmethod
def _get_methods_from_registered_route() -> Union[list[str], str]: def _get_methods_from_registered_route() -> Union[list[str], str]:
methods = ["Unknown"] methods = ['Unknown']
if ( if request.path in Route.registered_routes and len(Route.registered_routes[request.path]) >= 1 and 'methods' in Route.registered_routes[request.path][1]:
request.path in Route.registered_routes methods = Route.registered_routes[request.path][1]['methods']
and len(Route.registered_routes[request.path]) >= 1
and "methods" in Route.registered_routes[request.path][1]
):
methods = Route.registered_routes[request.path][1]["methods"]
if len(methods) == 1: if len(methods) == 1:
return methods[0] return methods[0]
@@ -84,7 +77,7 @@ class Api(Flask):
route = f[0] route = f[0]
kwargs = f[1] kwargs = f[1]
cls = None cls = None
qual_name_split = route.__qualname__.split(".") qual_name_split = route.__qualname__.split('.')
if len(qual_name_split) > 0: if len(qual_name_split) > 0:
cls_type = vars(sys.modules[route.__module__])[qual_name_split[0]] cls_type = vars(sys.modules[route.__module__])[qual_name_split[0]]
cls = self._services.get_service(cls_type) cls = self._services.get_service(cls_type)
@@ -94,7 +87,7 @@ class Api(Flask):
self.route(path, **kwargs)(partial_f) self.route(path, **kwargs)(partial_f)
def handle_exception(self, e: Exception): def handle_exception(self, e: Exception):
self._logger.error(__name__, f"Caught error", e) self._logger.error(__name__, f'Caught error', e)
if isinstance(e, ServiceException): if isinstance(e, ServiceException):
ex: ServiceException = e ex: ServiceException = e
@@ -107,7 +100,7 @@ class Api(Flask):
return jsonify(error.to_dict()), 404 return jsonify(error.to_dict()), 404
else: else:
tracking_id = uuid.uuid4() tracking_id = uuid.uuid4()
user_message = f"Tracking Id: {tracking_id}" user_message = f'Tracking Id: {tracking_id}'
self._logger.error(__name__, user_message, e) self._logger.error(__name__, user_message, e)
error = ErrorDTO(None, user_message) error = ErrorDTO(None, user_message)
return jsonify(error.to_dict()), 400 return jsonify(error.to_dict()), 400
@@ -117,55 +110,47 @@ class Api(Flask):
self._requests[request] = request_id self._requests[request] = request_id
method = request.access_control_request_method method = request.access_control_request_method
self._logger.info( self._logger.info(__name__, f'Received {request_id} @ {self._get_methods_from_registered_route() if method is None else method} {request.url} from {request.remote_addr}')
__name__,
f"Received {request_id} @ {self._get_methods_from_registered_route() if method is None else method} {request.url} from {request.remote_addr}",
)
headers = str(request.headers).replace("\n", "\n\t\t") headers = str(request.headers).replace('\n', '\n\t\t')
data = request.get_data() data = request.get_data()
data = "" if len(data) == 0 else str(data.decode(encoding="utf-8")) data = '' if len(data) == 0 else str(data.decode(encoding="utf-8"))
text = textwrap.dedent( text = textwrap.dedent(f'Request: {request_id}:\n\tHeader:\n\t\t{headers}\n\tUser-Agent: {request.user_agent.string}\n\tBody: {data}')
f"Request: {request_id}:\n\tHeader:\n\t\t{headers}\n\tUser-Agent: {request.user_agent.string}\n\tBody: {data}"
)
self._logger.trace(__name__, text) self._logger.trace(__name__, text)
def after_request_hook(self, response: Response): def after_request_hook(self, response: Response):
method = request.access_control_request_method method = request.access_control_request_method
request_id = f"{self._get_methods_from_registered_route() if method is None else method} {request.url} from {request.remote_addr}" request_id = f'{self._get_methods_from_registered_route() if method is None else method} {request.url} from {request.remote_addr}'
if request in self._requests: if request in self._requests:
request_id = self._requests[request] request_id = self._requests[request]
self._logger.info(__name__, f"Answered {request_id}") self._logger.info(__name__, f'Answered {request_id}')
headers = str(request.headers).replace("\n", "\n\t\t") headers = str(request.headers).replace('\n', '\n\t\t')
data = request.get_data() data = request.get_data()
data = "" if len(data) == 0 else str(data.decode(encoding="utf-8")) data = '' if len(data) == 0 else str(data.decode(encoding="utf-8"))
text = textwrap.dedent(f"Request: {request_id}:\n\tHeader:\n\t\t{headers}\n\tResponse: {data}") text = textwrap.dedent(f'Request: {request_id}:\n\tHeader:\n\t\t{headers}\n\tResponse: {data}')
self._logger.trace(__name__, text) self._logger.trace(__name__, text)
return response return response
def start(self): def start(self):
self._logger.info(__name__, f"Starting API {self._api_settings.host}:{self._api_settings.port}") self._logger.info(__name__, f'Starting API {self._api_settings.host}:{self._api_settings.port}')
self._register_routes() self._register_routes()
self.secret_key = CredentialManager.decrypt(self._auth_settings.secret_key) self.secret_key = CredentialManager.decrypt(self._auth_settings.secret_key)
# from waitress import serve # from waitress import serve
# https://docs.pylonsproject.org/projects/waitress/en/stable/arguments.html # https://docs.pylonsproject.org/projects/waitress/en/stable/arguments.html
# serve(self, host=self._apt_settings.host, port=self._apt_settings.port, threads=10, connection_limit=1000, channel_timeout=10) # serve(self, host=self._apt_settings.host, port=self._apt_settings.port, threads=10, connection_limit=1000, channel_timeout=10)
self._socket = eventlet.listen((self._api_settings.host, self._api_settings.port)) wsgi.server(
wsgi.server(self._socket, self, log_output=False) eventlet.listen((self._api_settings.host, self._api_settings.port)),
self,
def stop(self): log_output=False
if self._socket is None: )
return
self._socket.shutdown(socket.SHUT_RDWR)
self._socket.close()
def on_connect(self): def on_connect(self):
self._logger.info(__name__, f"Client connected") self._logger.info(__name__, f'Client connected')
def on_disconnect(self): def on_disconnect(self):
self._logger.info(__name__, f"Client disconnected") self._logger.info(__name__, f'Client disconnected')

View File

@@ -13,7 +13,7 @@ from bot_api.api import Api
from bot_api.api_thread import ApiThread from bot_api.api_thread import ApiThread
from bot_api.controller.auth_controller import AuthController from bot_api.controller.auth_controller import AuthController
from bot_api.controller.auth_discord_controller import AuthDiscordController from bot_api.controller.auth_discord_controller import AuthDiscordController
from bot_api.controller.graphql_controller import GraphQLController from bot_api.controller.discord.server_controller import ServerController
from bot_api.controller.gui_controller import GuiController from bot_api.controller.gui_controller import GuiController
from bot_api.event.bot_api_on_ready_event import BotApiOnReadyEvent from bot_api.event.bot_api_on_ready_event import BotApiOnReadyEvent
from bot_api.service.auth_service import AuthService from bot_api.service.auth_service import AuthService
@@ -23,15 +23,16 @@ from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
class ApiModule(ModuleABC): class ApiModule(ModuleABC):
def __init__(self, dc: DiscordCollectionABC): def __init__(self, dc: DiscordCollectionABC):
ModuleABC.__init__(self, dc, FeatureFlagsEnum.api_module) ModuleABC.__init__(self, dc, FeatureFlagsEnum.api_module)
def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC): def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC):
cwd = env.working_directory cwd = env.working_directory
env.set_working_directory(os.path.dirname(os.path.realpath(__file__))) env.set_working_directory(os.path.dirname(os.path.realpath(__file__)))
config.add_json_file(f"config/apisettings.json", optional=False) config.add_json_file(f'config/apisettings.json', optional=False)
config.add_json_file(f"config/apisettings.{env.environment_name}.json", optional=True) config.add_json_file(f'config/apisettings.{env.environment_name}.json', optional=True)
config.add_json_file(f"config/apisettings.{env.host_name}.json", optional=True) config.add_json_file(f'config/apisettings.{env.host_name}.json', optional=True)
env.set_working_directory(cwd) env.set_working_directory(cwd)
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
@@ -45,7 +46,7 @@ class ApiModule(ModuleABC):
services.add_transient(AuthDiscordController) services.add_transient(AuthDiscordController)
services.add_transient(GuiController) services.add_transient(GuiController)
services.add_transient(DiscordService) services.add_transient(DiscordService)
services.add_transient(GraphQLController) services.add_transient(ServerController)
# cpl-discord # cpl-discord
self._dc.add_event(DiscordEventTypesEnum.on_ready.value, BotApiOnReadyEvent) self._dc.add_event(DiscordEventTypesEnum.on_ready.value, BotApiOnReadyEvent)

View File

@@ -5,7 +5,12 @@ from bot_api.logging.api_logger import ApiLogger
class ApiThread(threading.Thread): class ApiThread(threading.Thread):
def __init__(self, logger: ApiLogger, api: Api):
def __init__(
self,
logger: ApiLogger,
api: Api
):
threading.Thread.__init__(self, daemon=True) threading.Thread.__init__(self, daemon=True)
self._logger = logger self._logger = logger
@@ -13,14 +18,7 @@ class ApiThread(threading.Thread):
def run(self) -> None: def run(self) -> None:
try: try:
self._logger.trace(__name__, f"Try to start {type(self._api).__name__}") self._logger.trace(__name__, f'Try to start {type(self._api).__name__}')
self._api.start() self._api.start()
except Exception as e: except Exception as e:
self._logger.error(__name__, "Start failed", e) self._logger.error(__name__, 'Start failed', e)
def stop(self):
try:
self._logger.trace(__name__, f"Try to stop {type(self._api).__name__}")
self._api.stop()
except Exception as e:
self._logger.error(__name__, "Stop failed", e)

View File

@@ -2,12 +2,16 @@ from cpl_core.application import ApplicationExtensionABC
from cpl_core.configuration import ConfigurationABC from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceProviderABC from cpl_core.dependency_injection import ServiceProviderABC
from bot_api.abc.auth_service_abc import AuthServiceABC
from bot_api.configuration.authentication_settings import AuthenticationSettings
from bot_api.route.route import Route from bot_api.route.route import Route
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC
class AppApiExtension(ApplicationExtensionABC): class AppApiExtension(ApplicationExtensionABC):
def __init__(self): def __init__(self):
ApplicationExtensionABC.__init__(self) ApplicationExtensionABC.__init__(self)
@@ -16,4 +20,7 @@ class AppApiExtension(ApplicationExtensionABC):
if not feature_flags.get_flag(FeatureFlagsEnum.api_module): if not feature_flags.get_flag(FeatureFlagsEnum.api_module):
return return
Route.init_authorize() auth_settings: AuthenticationSettings = config.get_configuration(AuthenticationSettings)
auth_users: AuthUserRepositoryABC = services.get_service(AuthUserRepositoryABC)
auth: AuthServiceABC = services.get_service(AuthServiceABC)
Route.init_authorize(auth_users, auth)

View File

@@ -2,9 +2,9 @@
"ProjectSettings": { "ProjectSettings": {
"Name": "bot-api", "Name": "bot-api",
"Version": { "Version": {
"Major": "1", "Major": "0",
"Minor": "1", "Minor": "3",
"Micro": "7" "Micro": "dev70"
}, },
"Author": "", "Author": "",
"AuthorEmail": "", "AuthorEmail": "",
@@ -16,10 +16,10 @@
"LicenseName": "", "LicenseName": "",
"LicenseDescription": "", "LicenseDescription": "",
"Dependencies": [ "Dependencies": [
"cpl-core==2022.12.0" "cpl-core==2022.10.0.post7"
], ],
"DevDependencies": [ "DevDependencies": [
"cpl-cli==2022.12.0" "cpl-cli==2022.10.0"
], ],
"PythonVersion": ">=3.10.4", "PythonVersion": ">=3.10.4",
"PythonPath": {}, "PythonPath": {},

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
bot sh-edraft.de Discord bot bot Keksdose bot
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Discord bot for some customers of sh-edraft.de Discord bot for the Keksdose discord Server
:copyright: (c) 2022 - 2023 sh-edraft.de :copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details. :license: MIT, see LICENSE for more details.
""" """
@@ -14,8 +14,8 @@ Discord bot for some customers of sh-edraft.de
__title__ = 'bot_api.configuration' __title__ = 'bot_api.configuration'
__author__ = 'Sven Heidemann' __author__ = 'Sven Heidemann'
__license__ = 'MIT' __license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 - 2023 sh-edraft.de' __copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.1.7' __version__ = '0.3.dev25'
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports # imports
VersionInfo = namedtuple('VersionInfo', 'major minor micro') VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='1', micro='7') version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -1,13 +1,17 @@
import traceback
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl_core.console import Console
class ApiSettings(ConfigurationModelABC): class ApiSettings(ConfigurationModelABC):
def __init__(self, port: int = None, host: str = None, redirect_uri: bool = None):
def __init__(self):
ConfigurationModelABC.__init__(self) ConfigurationModelABC.__init__(self)
self._port = 80 if port is None else port self._port = 80
self._host = "" if host is None else host self._host = ''
self._redirect_to_https = False if redirect_uri is None else redirect_uri self._redirect_to_https = False
@property @property
def port(self) -> int: def port(self) -> int:
@@ -20,3 +24,12 @@ class ApiSettings(ConfigurationModelABC):
@property @property
def redirect_to_https(self) -> bool: def redirect_to_https(self) -> bool:
return self._redirect_to_https return self._redirect_to_https
def from_dict(self, settings: dict):
try:
self._port = int(settings['Port'])
self._host = settings['Host']
self._redirect_to_https = bool(settings['RedirectToHTTPS'])
except Exception as e:
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings')
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')

View File

@@ -1,22 +1,20 @@
import traceback
from datetime import datetime
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl_core.console import Console
class AuthenticationSettings(ConfigurationModelABC): class AuthenticationSettings(ConfigurationModelABC):
def __init__(
self, def __init__(self):
secret_key: str = None,
issuer: str = None,
audience: str = None,
token_expire_time: int = None,
refresh_token_expire_time: int = None,
):
ConfigurationModelABC.__init__(self) ConfigurationModelABC.__init__(self)
self._secret_key = "" if secret_key is None else secret_key self._secret_key = ''
self._issuer = "" if issuer is None else issuer self._issuer = ''
self._audience = "" if audience is None else audience self._audience = ''
self._token_expire_time = 0 if token_expire_time is None else token_expire_time self._token_expire_time = 0
self._refresh_token_expire_time = 0 if refresh_token_expire_time is None else refresh_token_expire_time self._refresh_token_expire_time = 0
@property @property
def secret_key(self) -> str: def secret_key(self) -> str:
@@ -37,3 +35,14 @@ class AuthenticationSettings(ConfigurationModelABC):
@property @property
def refresh_token_expire_time(self) -> int: def refresh_token_expire_time(self) -> int:
return self._refresh_token_expire_time return self._refresh_token_expire_time
def from_dict(self, settings: dict):
try:
self._secret_key = settings['SecretKey']
self._issuer = settings['Issuer']
self._audience = settings['Audience']
self._token_expire_time = int(settings['TokenExpireTime'])
self._refresh_token_expire_time = int(settings['RefreshTokenExpireTime'])
except Exception as e:
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings')
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')

View File

@@ -1,23 +1,20 @@
import traceback
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl_core.console import Console
from cpl_query.extension import List from cpl_query.extension import List
class DiscordAuthenticationSettings(ConfigurationModelABC): class DiscordAuthenticationSettings(ConfigurationModelABC):
def __init__(
self, def __init__(self):
client_secret: str = None,
redirect_uri: str = None,
scope: list = None,
token_url: str = None,
auth_url: str = None,
):
ConfigurationModelABC.__init__(self) ConfigurationModelABC.__init__(self)
self._client_secret = "" if client_secret is None else client_secret self._client_secret = ''
self._redirect_url = "" if redirect_uri is None else redirect_uri self._redirect_url = ''
self._scope = List() if scope is None else List(str, scope) self._scope = List()
self._token_url = "" if token_url is None else token_url self._token_url = ''
self._auth_url = "" if auth_url is None else auth_url self._auth_url = ''
@property @property
def client_secret(self) -> str: def client_secret(self) -> str:
@@ -38,3 +35,14 @@ class DiscordAuthenticationSettings(ConfigurationModelABC):
@property @property
def auth_url(self) -> str: def auth_url(self) -> str:
return self._auth_url return self._auth_url
def from_dict(self, settings: dict):
try:
self._client_secret = settings['ClientSecret']
self._redirect_url = settings['RedirectURL']
self._scope = List(str, settings['Scope'])
self._token_url = settings['TokenURL']
self._auth_url = settings['AuthURL']
except Exception as e:
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings')
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')

View File

@@ -1,12 +1,23 @@
import traceback
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl_core.console import Console
class FrontendSettings(ConfigurationModelABC): class FrontendSettings(ConfigurationModelABC):
def __init__(self, url: str = None):
def __init__(self):
ConfigurationModelABC.__init__(self) ConfigurationModelABC.__init__(self)
self._url = "" if url is None else url self._url = ''
@property @property
def url(self) -> str: def url(self) -> str:
return self._url return self._url
def from_dict(self, settings: dict):
try:
self._url = settings['URL']
except Exception as e:
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings')
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')

View File

@@ -0,0 +1,55 @@
from typing import Optional
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl_cli.configuration.version_settings_name_enum import VersionSettingsNameEnum
class VersionSettings(ConfigurationModelABC):
def __init__(
self,
major: str = None,
minor: str = None,
micro: str = None
):
ConfigurationModelABC.__init__(self)
self._major: Optional[str] = major
self._minor: Optional[str] = minor
self._micro: Optional[str] = micro
@property
def major(self) -> str:
return self._major
@property
def minor(self) -> str:
return self._minor
@property
def micro(self) -> str:
return self._micro
def to_str(self) -> str:
if self._micro is None:
return f'{self._major}.{self._minor}'
else:
return f'{self._major}.{self._minor}.{self._micro}'
def from_dict(self, settings: dict):
self._major = settings[VersionSettingsNameEnum.major.value]
self._minor = settings[VersionSettingsNameEnum.minor.value]
micro = settings[VersionSettingsNameEnum.micro.value]
if micro != '':
self._micro = micro
def to_dict(self) -> dict:
version = {
VersionSettingsNameEnum.major.value: self._major,
VersionSettingsNameEnum.minor.value: self._minor,
}
if self._micro is not None:
version[VersionSettingsNameEnum.micro.value] = self._micro
return version

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
bot sh-edraft.de Discord bot bot Keksdose bot
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Discord bot for some customers of sh-edraft.de Discord bot for the Keksdose discord Server
:copyright: (c) 2022 - 2023 sh-edraft.de :copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details. :license: MIT, see LICENSE for more details.
""" """
@@ -14,8 +14,8 @@ Discord bot for some customers of sh-edraft.de
__title__ = 'bot_api.controller' __title__ = 'bot_api.controller'
__author__ = 'Sven Heidemann' __author__ = 'Sven Heidemann'
__license__ = 'MIT' __license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 - 2023 sh-edraft.de' __copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.1.7' __version__ = '0.3.dev25'
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro') VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='1', micro='7') version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -6,6 +6,8 @@ from flask import request, jsonify, Response
from bot_api.abc.auth_service_abc import AuthServiceABC from bot_api.abc.auth_service_abc import AuthServiceABC
from bot_api.api import Api from bot_api.api import Api
from bot_api.exception.service_error_code_enum import ServiceErrorCode
from bot_api.exception.service_exception import ServiceException
from bot_api.filter.auth_user_select_criteria import AuthUserSelectCriteria from bot_api.filter.auth_user_select_criteria import AuthUserSelectCriteria
from bot_api.json_processor import JSONProcessor from bot_api.json_processor import JSONProcessor
from bot_api.logging.api_logger import ApiLogger from bot_api.logging.api_logger import ApiLogger
@@ -18,18 +20,18 @@ from bot_data.model.auth_role_enum import AuthRoleEnum
class AuthController: class AuthController:
BasePath = "/api/auth" BasePath = '/api/auth'
def __init__( def __init__(
self, self,
config: ConfigurationABC, config: ConfigurationABC,
env: ApplicationEnvironmentABC, env: ApplicationEnvironmentABC,
logger: ApiLogger, logger: ApiLogger,
t: TranslatePipe, t: TranslatePipe,
api: Api, api: Api,
mail_settings: EMailClientSettings, mail_settings: EMailClientSettings,
mailer: EMailClientABC, mailer: EMailClientABC,
auth_service: AuthServiceABC, auth_service: AuthServiceABC
): ):
self._config = config self._config = config
self._env = env self._env = env
@@ -40,57 +42,55 @@ class AuthController:
self._mailer = mailer self._mailer = mailer
self._auth_service = auth_service self._auth_service = auth_service
@Route.get(f"{BasePath}/users") @Route.get(f'{BasePath}/users')
@Route.authorize(role=AuthRoleEnum.admin) @Route.authorize(role=AuthRoleEnum.admin)
async def get_all_users(self) -> Response: async def get_all_users(self) -> Response:
result = await self._auth_service.get_all_auth_users_async() result = await self._auth_service.get_all_auth_users_async()
return jsonify(result.select(lambda x: x.to_dict()).to_list()) return jsonify(result.select(lambda x: x.to_dict()))
@Route.post(f"{BasePath}/users/get/filtered") @Route.post(f'{BasePath}/users/get/filtered')
@Route.authorize(role=AuthRoleEnum.admin) @Route.authorize(role=AuthRoleEnum.admin)
async def get_filtered_users(self) -> Response: async def get_filtered_users(self) -> Response:
dto: AuthUserSelectCriteria = JSONProcessor.process( dto: AuthUserSelectCriteria = JSONProcessor.process(AuthUserSelectCriteria, request.get_json(force=True, silent=True))
AuthUserSelectCriteria, request.get_json(force=True, silent=True)
)
result = await self._auth_service.get_filtered_auth_users_async(dto) result = await self._auth_service.get_filtered_auth_users_async(dto)
result.result = result.result.select(lambda x: x.to_dict()).to_list() result.result = result.result.select(lambda x: x.to_dict())
return jsonify(result.to_dict()) return jsonify(result.to_dict())
@Route.get(f"{BasePath}/users/get/<email>") @Route.get(f'{BasePath}/users/get/<email>')
@Route.authorize @Route.authorize
async def get_user_from_email(self, email: str) -> Response: async def get_user_from_email(self, email: str) -> Response:
result = await self._auth_service.get_auth_user_by_email_async(email) result = await self._auth_service.get_auth_user_by_email_async(email)
return jsonify(result.to_dict()) return jsonify(result.to_dict())
@Route.get(f"{BasePath}/users/find/<email>") @Route.get(f'{BasePath}/users/find/<email>')
@Route.authorize @Route.authorize
async def find_user_from_email(self, email: str) -> Response: async def find_user_from_email(self, email: str) -> Response:
result = await self._auth_service.find_auth_user_by_email_async(email) result = await self._auth_service.find_auth_user_by_email_async(email)
return jsonify(result.to_dict()) return jsonify(result.to_dict())
@Route.post(f"{BasePath}/register") @Route.post(f'{BasePath}/register')
async def register(self): async def register(self):
dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True)) dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True))
self._auth_service.add_auth_user(dto) await self._auth_service.add_auth_user_async(dto)
return "", 200 return '', 200
@Route.post(f"{BasePath}/register-by-id/<id>") @Route.post(f'{BasePath}/register-by-id/<id>')
async def register_id(self, id: str): async def register_id(self, id: str):
result = await self._auth_service.confirm_email_async(id) result = await self._auth_service.confirm_email_async(id)
return jsonify(result) return jsonify(result)
@Route.post(f"{BasePath}/login") @Route.post(f'{BasePath}/login')
async def login(self) -> Response: async def login(self) -> Response:
dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True)) dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True))
result = await self._auth_service.login_async(dto) result = await self._auth_service.login_async(dto)
return jsonify(result.to_dict()) return jsonify(result.to_dict())
@Route.get(f"{BasePath}/verify-login") @Route.get(f'{BasePath}/verify-login')
async def verify_login(self): async def verify_login(self):
token = None token = None
result = False result = False
if "Authorization" in request.headers: if 'Authorization' in request.headers:
bearer = request.headers.get("Authorization") bearer = request.headers.get('Authorization')
token = bearer.split()[1] token = bearer.split()[1]
if token is not None: if token is not None:
@@ -98,57 +98,58 @@ class AuthController:
return jsonify(result) return jsonify(result)
@Route.post(f"{BasePath}/forgot-password/<email>") @Route.post(f'{BasePath}/forgot-password/<email>')
async def forgot_password(self, email: str): async def forgot_password(self, email: str):
await self._auth_service.forgot_password_async(email) await self._auth_service.forgot_password_async(email)
return "", 200 return '', 200
@Route.post(f"{BasePath}/confirm-forgot-password/<id>") @Route.post(f'{BasePath}/confirm-forgot-password/<id>')
async def confirm_forgot_password(self, id: str): async def confirm_forgot_password(self, id: str):
result = await self._auth_service.confirm_forgot_password_async(id) result = await self._auth_service.confirm_forgot_password_async(id)
return jsonify(result.to_dict()) return jsonify(result.to_dict())
@Route.post(f"{BasePath}/reset-password") @Route.post(f'{BasePath}/reset-password')
async def reset_password(self): async def reset_password(self):
dto: ResetPasswordDTO = JSONProcessor.process(ResetPasswordDTO, request.get_json(force=True, silent=True)) dto: ResetPasswordDTO = JSONProcessor.process(ResetPasswordDTO, request.get_json(force=True, silent=True))
await self._auth_service.reset_password_async(dto) await self._auth_service.reset_password_async(dto)
return "", 200 return '', 200
@Route.post(f"{BasePath}/update-user") @Route.post(f'{BasePath}/update-user')
@Route.authorize @Route.authorize
async def update_user(self): async def update_user(self):
dto: UpdateAuthUserDTO = JSONProcessor.process(UpdateAuthUserDTO, request.get_json(force=True, silent=True)) dto: UpdateAuthUserDTO = JSONProcessor.process(UpdateAuthUserDTO, request.get_json(force=True, silent=True))
await self._auth_service.update_user_async(dto) await self._auth_service.update_user_async(dto)
return "", 200 return '', 200
@Route.post(f"{BasePath}/update-user-as-admin") @Route.post(f'{BasePath}/update-user-as-admin')
@Route.authorize(role=AuthRoleEnum.admin) @Route.authorize(role=AuthRoleEnum.admin)
async def update_user_as_admin(self): async def update_user_as_admin(self):
dto: UpdateAuthUserDTO = JSONProcessor.process(UpdateAuthUserDTO, request.get_json(force=True, silent=True)) dto: UpdateAuthUserDTO = JSONProcessor.process(UpdateAuthUserDTO, request.get_json(force=True, silent=True))
await self._auth_service.update_user_as_admin_async(dto) await self._auth_service.update_user_as_admin_async(dto)
return "", 200 return '', 200
@Route.post(f"{BasePath}/refresh") @Route.post(f'{BasePath}/refresh')
@Route.authorize
async def refresh(self) -> Response: async def refresh(self) -> Response:
dto: TokenDTO = JSONProcessor.process(TokenDTO, request.get_json(force=True, silent=True)) dto: TokenDTO = JSONProcessor.process(TokenDTO, request.get_json(force=True, silent=True))
result = await self._auth_service.refresh_async(dto) result = await self._auth_service.refresh_async(dto)
return jsonify(result.to_dict()) return jsonify(result.to_dict())
@Route.post(f"{BasePath}/revoke") @Route.post(f'{BasePath}/revoke')
async def revoke(self): async def revoke(self):
dto: TokenDTO = JSONProcessor.process(TokenDTO, request.get_json(force=True, silent=True)) dto: TokenDTO = JSONProcessor.process(TokenDTO, request.get_json(force=True, silent=True))
await self._auth_service.revoke_async(dto) await self._auth_service.revoke_async(dto)
return "", 200 return '', 200
@Route.post(f"{BasePath}/delete-user") @Route.post(f'{BasePath}/delete-user')
@Route.authorize(role=AuthRoleEnum.admin) @Route.authorize(role=AuthRoleEnum.admin)
async def delete_user(self): async def delete_user(self):
dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True)) dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True))
await self._auth_service.delete_auth_user_async(dto) await self._auth_service.delete_auth_user_async(dto)
return "", 200 return '', 200
@Route.post(f"{BasePath}/delete-user-by-mail/<email>") @Route.post(f'{BasePath}/delete-user-by-mail/<email>')
@Route.authorize(role=AuthRoleEnum.admin) @Route.authorize(role=AuthRoleEnum.admin)
async def delete_user_by_mail(self, email: str): async def delete_user_by_mail(self, email: str):
await self._auth_service.delete_auth_user_by_email_async(email) await self._auth_service.delete_auth_user_by_email_async(email)
return "", 200 return '', 200

View File

@@ -8,38 +8,38 @@ from cpl_core.utils import CredentialManager
from cpl_discord.service import DiscordBotServiceABC from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslatePipe from cpl_translation import TranslatePipe
from flask import jsonify, Response from flask import jsonify, Response
from flask import request from flask import request, session
from requests_oauthlib import OAuth2Session from requests_oauthlib import OAuth2Session
from bot_api.abc.auth_service_abc import AuthServiceABC from bot_api.abc.auth_service_abc import AuthServiceABC
from bot_api.api import Api from bot_api.api import Api
from bot_api.configuration.discord_authentication_settings import ( from bot_api.configuration.discord_authentication_settings import DiscordAuthenticationSettings
DiscordAuthenticationSettings, from bot_api.json_processor import JSONProcessor
)
from bot_api.logging.api_logger import ApiLogger from bot_api.logging.api_logger import ApiLogger
from bot_api.model.auth_user_dto import AuthUserDTO from bot_api.model.auth_user_dto import AuthUserDTO
from bot_api.model.o_auth_dto import OAuthDTO
from bot_api.route.route import Route from bot_api.route.route import Route
from bot_data.model.auth_role_enum import AuthRoleEnum from bot_data.model.auth_role_enum import AuthRoleEnum
# Disable SSL requirement # Disable SSL requirement
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1" os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
class AuthDiscordController: class AuthDiscordController:
BasePath = "/api/auth/discord" BasePath = '/api/auth/discord'
def __init__( def __init__(
self, self,
auth_settings: DiscordAuthenticationSettings, auth_settings: DiscordAuthenticationSettings,
config: ConfigurationABC, config: ConfigurationABC,
env: ApplicationEnvironmentABC, env: ApplicationEnvironmentABC,
logger: ApiLogger, logger: ApiLogger,
bot: DiscordBotServiceABC, bot: DiscordBotServiceABC,
t: TranslatePipe, t: TranslatePipe,
api: Api, api: Api,
mail_settings: EMailClientSettings, mail_settings: EMailClientSettings,
mailer: EMailClientABC, mailer: EMailClientABC,
auth_service: AuthServiceABC, auth_service: AuthServiceABC
): ):
self._auth_settings = auth_settings self._auth_settings = auth_settings
self._config = config self._config = config
@@ -53,42 +53,47 @@ class AuthDiscordController:
self._auth_service = auth_service self._auth_service = auth_service
def _get_user_from_discord_response(self) -> dict: def _get_user_from_discord_response(self) -> dict:
discord = OAuth2Session( discord = OAuth2Session(self._bot.user.id, redirect_uri=self._auth_settings.redirect_url, state=request.args.get('state'), scope=self._auth_settings.scope)
self._bot.user.id,
redirect_uri=self._auth_settings.redirect_url,
state=request.args.get("state"),
scope=self._auth_settings.scope.to_list(),
)
token = discord.fetch_token( token = discord.fetch_token(
self._auth_settings.token_url, self._auth_settings.token_url,
client_secret=CredentialManager.decrypt(self._auth_settings.client_secret), client_secret=CredentialManager.decrypt(self._auth_settings.client_secret),
authorization_response=request.url, authorization_response=request.url,
) )
discord = OAuth2Session(self._bot.user.id, token=token) discord = OAuth2Session(self._bot.user.id, token=token)
return discord.get("https://discordapp.com/api" + "/users/@me").json() return discord.get('https://discordapp.com/api' + '/users/@me').json()
@Route.get(f"{BasePath}/get-url") @Route.get(f'{BasePath}/get-url')
async def get_url(self): async def get_url(self):
oauth = OAuth2Session( oauth = OAuth2Session(self._bot.user.id, redirect_uri=self._auth_settings.redirect_url, scope=self._auth_settings.scope)
self._bot.user.id,
redirect_uri=self._auth_settings.redirect_url,
scope=self._auth_settings.scope.to_list(),
)
login_url, state = oauth.authorization_url(self._auth_settings.auth_url) login_url, state = oauth.authorization_url(self._auth_settings.auth_url)
return jsonify({"loginUrl": login_url}) return jsonify({'loginUrl': login_url})
@Route.get(f"{BasePath}/login") @Route.get(f'{BasePath}/create-user')
async def discord_create_user(self) -> Response:
response = self._get_user_from_discord_response()
result = await self._auth_service.add_auth_user_by_discord_async(AuthUserDTO(
0,
response['username'],
response['discriminator'],
response['email'],
str(uuid.uuid4()),
None,
AuthRoleEnum.normal
), response['id'])
return jsonify(result.to_dict())
@Route.get(f'{BasePath}/login')
async def discord_login(self) -> Response: async def discord_login(self) -> Response:
response = self._get_user_from_discord_response() response = self._get_user_from_discord_response()
dto = AuthUserDTO( dto = AuthUserDTO(
0, 0,
response["username"], response['username'],
response["discriminator"], response['discriminator'],
response["email"], response['email'],
str(uuid.uuid4()), str(uuid.uuid4()),
None, None,
AuthRoleEnum.normal, AuthRoleEnum.normal
) )
result = await self._auth_service.login_discord_async(dto, response["id"]) result = await self._auth_service.login_discord_async(dto)
return jsonify(result.to_dict()) return jsonify(result.to_dict())

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
bot Keksdose bot
~~~~~~~~~~~~~~~~~~~
Discord bot for the Keksdose discord Server
:copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = 'bot_api.controller.discord'
__author__ = 'Sven Heidemann'
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '0.3.dev25'
from collections import namedtuple
# imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -0,0 +1,65 @@
from cpl_core.configuration import ConfigurationABC
from cpl_core.environment import ApplicationEnvironmentABC
from cpl_core.mailing import EMailClientABC, EMailClientSettings
from cpl_translation import TranslatePipe
from flask import Response, jsonify, request
from bot_api.api import Api
from bot_api.filter.discord.server_select_criteria import ServerSelectCriteria
from bot_api.json_processor import JSONProcessor
from bot_api.logging.api_logger import ApiLogger
from bot_api.route.route import Route
from bot_api.service.discord_service import DiscordService
from bot_data.model.auth_role_enum import AuthRoleEnum
class ServerController:
BasePath = f'/api/discord/server'
def __init__(
self,
config: ConfigurationABC,
env: ApplicationEnvironmentABC,
logger: ApiLogger,
t: TranslatePipe,
api: Api,
mail_settings: EMailClientSettings,
mailer: EMailClientABC,
discord_service: DiscordService
):
self._config = config
self._env = env
self._logger = logger
self._t = t
self._api = api
self._mail_settings = mail_settings
self._mailer = mailer
self._discord_service = discord_service
@Route.get(f'{BasePath}/get/servers')
@Route.authorize(role=AuthRoleEnum.admin)
async def get_all_servers(self) -> Response:
result = await self._discord_service.get_all_servers()
result = result.select(lambda x: x.to_dict())
return jsonify(result)
@Route.get(f'{BasePath}/get/servers-by-user')
@Route.authorize
async def get_all_servers_by_user(self) -> Response:
result = await self._discord_service.get_all_servers_by_user()
result = result.select(lambda x: x.to_dict())
return jsonify(result)
@Route.post(f'{BasePath}/get/filtered')
@Route.authorize
async def get_filtered_servers(self) -> Response:
dto: ServerSelectCriteria = JSONProcessor.process(ServerSelectCriteria, request.get_json(force=True, silent=True))
result = await self._discord_service.get_filtered_servers_async(dto)
result.result = result.result.select(lambda x: x.to_dict())
return jsonify(result.to_dict())
@Route.get(f'{BasePath}/get/<id>')
@Route.authorize
async def get_server_by_id(self, id: int) -> Response:
result = await self._discord_service.get_server_by_id_async(id)
return jsonify(result.to_dict())

View File

@@ -1,44 +0,0 @@
from ariadne import graphql_sync
from ariadne.explorer import ExplorerPlayground
from cpl_core.configuration import ConfigurationABC
from cpl_core.environment import ApplicationEnvironmentABC
from flask import request, jsonify
from bot_api.logging.api_logger import ApiLogger
from bot_api.route.route import Route
from bot_graphql.schema import Schema
class GraphQLController:
BasePath = f"/api/graphql"
def __init__(
self,
config: ConfigurationABC,
env: ApplicationEnvironmentABC,
logger: ApiLogger,
schema: Schema,
):
self._config = config
self._env = env
self._logger = logger
self._schema = schema
@Route.get(f"{BasePath}/playground")
@Route.authorize(skip_in_dev=True)
async def playground(self):
if self._env.environment_name != "development":
return "", 403
return ExplorerPlayground().html(None), 200
@Route.post(f"{BasePath}")
@Route.authorize(by_api_key=True)
async def graphql(self):
data = request.get_json()
# Note: Passing the request to the context is optional.
# In Flask, the current request is always accessible as flask.request
success, result = graphql_sync(self._schema.schema, data, context_value=request)
return jsonify(result), 200 if success else 400

View File

@@ -15,18 +15,18 @@ from bot_api.route.route import Route
class GuiController: class GuiController:
BasePath = f"/api/gui" BasePath = f'/api/gui'
def __init__( def __init__(
self, self,
config: ConfigurationABC, config: ConfigurationABC,
env: ApplicationEnvironmentABC, env: ApplicationEnvironmentABC,
logger: ApiLogger, logger: ApiLogger,
t: TranslatePipe, t: TranslatePipe,
api: Api, api: Api,
mail_settings: EMailClientSettings, mail_settings: EMailClientSettings,
mailer: EMailClientABC, mailer: EMailClientABC,
auth_settings: AuthenticationSettings, auth_settings: AuthenticationSettings
): ):
self._config = config self._config = config
self._env = env self._env = env
@@ -37,48 +37,42 @@ class GuiController:
self._mailer = mailer self._mailer = mailer
self._auth_settings = auth_settings self._auth_settings = auth_settings
@Route.get(f"{BasePath}/api-version") @Route.get(f'{BasePath}/api-version')
async def api_version(self): async def api_version(self):
import bot_api import bot_api
version = bot_api.version_info version = bot_api.version_info
return VersionDTO(version.major, version.minor, version.micro).to_dict() return VersionDTO(version.major, version.minor, version.micro).to_dict()
@Route.get(f"{BasePath}/settings") @Route.get(f'{BasePath}/settings')
@Route.authorize @Route.authorize
async def settings(self): async def settings(self):
import bot_api import bot_api
version = bot_api.version_info version = bot_api.version_info
return jsonify( return jsonify(SettingsDTO(
SettingsDTO( '',
"", VersionDTO(version.major, version.minor, version.micro),
VersionDTO(version.major, version.minor, version.micro), os.path.abspath(os.path.join(self._env.working_directory, 'config')),
os.path.abspath(os.path.join(self._env.working_directory, "config")), '/',
"/", '/',
"/", self._auth_settings.token_expire_time,
self._auth_settings.token_expire_time, self._auth_settings.refresh_token_expire_time,
self._auth_settings.refresh_token_expire_time, self._mail_settings.user_name,
self._mail_settings.user_name, self._mail_settings.port,
self._mail_settings.port, self._mail_settings.host,
self._mail_settings.host, self._mail_settings.user_name,
self._mail_settings.user_name, self._mail_settings.user_name,
self._mail_settings.user_name, ).to_dict())
).to_dict()
)
@Route.post(f"{BasePath}/send-test-mail/<email>") @Route.post(f'{BasePath}/send-test-mail/<email>')
@Route.authorize @Route.authorize
async def send_test_mail(self, email: str): async def send_test_mail(self, email: str):
mail = EMail() mail = EMail()
mail.add_header("Mime-Version: 1.0") mail.add_header('Mime-Version: 1.0')
mail.add_header("Content-Type: text/plain; charset=utf-8") mail.add_header('Content-Type: text/plain; charset=utf-8')
mail.add_header("Content-Transfer-Encoding: quoted-printable") mail.add_header('Content-Transfer-Encoding: quoted-printable')
mail.add_receiver(email) mail.add_receiver(email)
mail.subject = self._t.transform("api.api.test_mail.subject") mail.subject = self._t.transform('api.api.test_mail.subject')
mail.body = self._t.transform("api.api.test_mail.message").format( mail.body = self._t.transform('api.api.test_mail.message').format(self._env.host_name, self._env.environment_name)
self._env.host_name, self._env.environment_name
)
self._mailer.send_mail(mail) self._mailer.send_mail(mail)
return "", 200 return '', 200

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
bot sh-edraft.de Discord bot bot Keksdose bot
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Discord bot for some customers of sh-edraft.de Discord bot for the Keksdose discord Server
:copyright: (c) 2022 - 2023 sh-edraft.de :copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details. :license: MIT, see LICENSE for more details.
""" """
@@ -14,8 +14,8 @@ Discord bot for some customers of sh-edraft.de
__title__ = 'bot_api.event' __title__ = 'bot_api.event'
__author__ = 'Sven Heidemann' __author__ = 'Sven Heidemann'
__license__ = 'MIT' __license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 - 2023 sh-edraft.de' __copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.1.7' __version__ = '0.3.dev25'
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro') VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='1', micro='7') version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -4,6 +4,7 @@ from bot_api.api_thread import ApiThread
class BotApiOnReadyEvent(OnReadyABC): class BotApiOnReadyEvent(OnReadyABC):
def __init__(self, api: ApiThread): def __init__(self, api: ApiThread):
OnReadyABC.__init__(self) OnReadyABC.__init__(self)
self._api = api self._api = api

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
bot sh-edraft.de Discord bot bot Keksdose bot
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Discord bot for some customers of sh-edraft.de Discord bot for the Keksdose discord Server
:copyright: (c) 2022 - 2023 sh-edraft.de :copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details. :license: MIT, see LICENSE for more details.
""" """
@@ -14,8 +14,8 @@ Discord bot for some customers of sh-edraft.de
__title__ = 'bot_api.exception' __title__ = 'bot_api.exception'
__author__ = 'Sven Heidemann' __author__ = 'Sven Heidemann'
__license__ = 'MIT' __license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 - 2023 sh-edraft.de' __copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.1.7' __version__ = '0.3.dev25'
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro') VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='1', micro='7') version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -4,6 +4,7 @@ from werkzeug.exceptions import Unauthorized
class ServiceErrorCode(Enum): class ServiceErrorCode(Enum):
Unknown = 0 Unknown = 0
InvalidDependencies = 1 InvalidDependencies = 1

View File

@@ -2,6 +2,7 @@ from bot_api.exception.service_error_code_enum import ServiceErrorCode
class ServiceException(Exception): class ServiceException(Exception):
def __init__(self, error_code: ServiceErrorCode, message: str, *args): def __init__(self, error_code: ServiceErrorCode, message: str, *args):
Exception.__init__(self, *args) Exception.__init__(self, *args)
@@ -9,4 +10,4 @@ class ServiceException(Exception):
self.message = message self.message = message
def get_detailed_message(self) -> str: def get_detailed_message(self) -> str:
return f"ServiceException - ErrorCode: {self.error_code} - ErrorMessage: {self.message}" return f'ServiceException - ErrorCode: {self.error_code} - ErrorMessage: {self.message}'

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
bot sh-edraft.de Discord bot bot Keksdose bot
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Discord bot for some customers of sh-edraft.de Discord bot for the Keksdose discord Server
:copyright: (c) 2022 - 2023 sh-edraft.de :copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details. :license: MIT, see LICENSE for more details.
""" """
@@ -14,8 +14,8 @@ Discord bot for some customers of sh-edraft.de
__title__ = 'bot_api.filter' __title__ = 'bot_api.filter'
__author__ = 'Sven Heidemann' __author__ = 'Sven Heidemann'
__license__ = 'MIT' __license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 - 2023 sh-edraft.de' __copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.1.7' __version__ = '0.3.dev25'
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro') VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='1', micro='7') version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -2,16 +2,18 @@ from bot_api.abc.select_criteria_abc import SelectCriteriaABC
class AuthUserSelectCriteria(SelectCriteriaABC): class AuthUserSelectCriteria(SelectCriteriaABC):
def __init__( def __init__(
self, self,
page_index: int, page_index: int,
page_size: int, page_size: int,
sort_direction: str, sort_direction: str,
sort_column: str, sort_column: str,
first_name: str,
last_name: str, first_name: str,
email: str, last_name: str,
auth_role: int, email: str,
auth_role: int
): ):
SelectCriteriaABC.__init__(self, page_index, page_size, sort_direction, sort_column) SelectCriteriaABC.__init__(self, page_index, page_size, sort_direction, sort_column)

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
bot sh-edraft.de Discord bot bot Keksdose bot
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Discord bot for some customers of sh-edraft.de Discord bot for the Keksdose discord Server
:copyright: (c) 2022 - 2023 sh-edraft.de :copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details. :license: MIT, see LICENSE for more details.
""" """
@@ -14,8 +14,8 @@ Discord bot for some customers of sh-edraft.de
__title__ = 'bot_api.filter.discord' __title__ = 'bot_api.filter.discord'
__author__ = 'Sven Heidemann' __author__ = 'Sven Heidemann'
__license__ = 'MIT' __license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 - 2023 sh-edraft.de' __copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.1.7' __version__ = '0.3.dev25'
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro') VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='1', micro='7') version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -2,13 +2,15 @@ from bot_api.abc.select_criteria_abc import SelectCriteriaABC
class ServerSelectCriteria(SelectCriteriaABC): class ServerSelectCriteria(SelectCriteriaABC):
def __init__( def __init__(
self, self,
page_index: int, page_index: int,
page_size: int, page_size: int,
sort_direction: str, sort_direction: str,
sort_column: str, sort_column: str,
name: str,
name: str,
): ):
SelectCriteriaABC.__init__(self, page_index, page_size, sort_direction, sort_column) SelectCriteriaABC.__init__(self, page_index, page_size, sort_direction, sort_column)

View File

@@ -5,6 +5,7 @@ from cpl_core.utils import String
class JSONProcessor: class JSONProcessor:
@staticmethod @staticmethod
def process(_t: type, values: dict) -> object: def process(_t: type, values: dict) -> object:
args = [] args = []
@@ -12,14 +13,14 @@ class JSONProcessor:
sig = signature(_t.__init__) sig = signature(_t.__init__)
for param in sig.parameters.items(): for param in sig.parameters.items():
parameter = param[1] parameter = param[1]
if parameter.name == "self" or parameter.annotation == Parameter.empty: if parameter.name == 'self' or parameter.annotation == Parameter.empty:
continue continue
name = String.convert_to_camel_case(parameter.name) name = String.convert_to_camel_case(parameter.name)
name = name.replace("Dto", "DTO") name = name.replace('Dto', 'DTO')
name_first_lower = String.first_to_lower(name) name_first_lower = String.first_to_lower(name)
if name in values or name_first_lower in values: if name in values or name_first_lower in values:
value = "" value = ''
if name in values: if name in values:
value = values[name] value = values[name]
else: else:

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
bot sh-edraft.de Discord bot bot Keksdose bot
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Discord bot for some customers of sh-edraft.de Discord bot for the Keksdose discord Server
:copyright: (c) 2022 - 2023 sh-edraft.de :copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details. :license: MIT, see LICENSE for more details.
""" """
@@ -14,8 +14,8 @@ Discord bot for some customers of sh-edraft.de
__title__ = 'bot_api.logging' __title__ = 'bot_api.logging'
__author__ = 'Sven Heidemann' __author__ = 'Sven Heidemann'
__license__ = 'MIT' __license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 - 2023 sh-edraft.de' __copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.1.7' __version__ = '0.3.dev25'
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro') VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='1', micro='7') version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -6,10 +6,6 @@ from bot_core.abc.custom_file_logger_abc import CustomFileLoggerABC
class ApiLogger(CustomFileLoggerABC): class ApiLogger(CustomFileLoggerABC):
def __init__(
self, def __init__(self, config: ConfigurationABC, time_format: TimeFormatSettings, env: ApplicationEnvironmentABC):
config: ConfigurationABC, CustomFileLoggerABC.__init__(self, 'Api', config, time_format, env)
time_format: TimeFormatSettings,
env: ApplicationEnvironmentABC,
):
CustomFileLoggerABC.__init__(self, "Api", config, time_format, env)

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
bot sh-edraft.de Discord bot bot Keksdose bot
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Discord bot for some customers of sh-edraft.de Discord bot for the Keksdose discord Server
:copyright: (c) 2022 - 2023 sh-edraft.de :copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details. :license: MIT, see LICENSE for more details.
""" """
@@ -14,8 +14,8 @@ Discord bot for some customers of sh-edraft.de
__title__ = 'bot_api.model' __title__ = 'bot_api.model'
__author__ = 'Sven Heidemann' __author__ = 'Sven Heidemann'
__license__ = 'MIT' __license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 - 2023 sh-edraft.de' __copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.1.7' __version__ = '0.3.dev25'
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro') VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='1', micro='7') version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -1,26 +1,20 @@
from datetime import datetime
from typing import Optional from typing import Optional
from cpl_query.extension import List
from bot_api.abc.dto_abc import DtoABC from bot_api.abc.dto_abc import DtoABC
from bot_api.model.user_dto import UserDTO
from bot_data.model.auth_role_enum import AuthRoleEnum from bot_data.model.auth_role_enum import AuthRoleEnum
class AuthUserDTO(DtoABC): class AuthUserDTO(DtoABC):
def __init__( def __init__(
self, self,
id: int = None, id: int = None,
first_name: str = None, first_name: str = None,
last_name: str = None, last_name: str = None,
email: str = None, email: str = None,
password: str = None, password: str = None,
confirmation_id: Optional[str] = None, confirmation_id: Optional[str] = None,
auth_role: AuthRoleEnum = None, auth_role: AuthRoleEnum = None,
users: List[UserDTO] = None,
created_at: datetime = None,
modified_at: datetime = None,
): ):
DtoABC.__init__(self) DtoABC.__init__(self)
@@ -31,13 +25,6 @@ class AuthUserDTO(DtoABC):
self._password = password self._password = password
self._is_confirmed = confirmation_id is None self._is_confirmed = confirmation_id is None
self._auth_role = auth_role self._auth_role = auth_role
self._created_at = created_at
self._modified_at = modified_at
if users is None:
self._users = List(UserDTO)
else:
self._users = users
@property @property
def id(self) -> int: def id(self) -> int:
@@ -91,46 +78,22 @@ class AuthUserDTO(DtoABC):
def auth_role(self, value: AuthRoleEnum): def auth_role(self, value: AuthRoleEnum):
self._auth_role = value self._auth_role = value
@property
def users(self) -> List[UserDTO]:
return self._users
@property
def created_at(self) -> datetime:
return self._created_at
@property
def modified_at(self) -> datetime:
return self._modified_at
def from_dict(self, values: dict): def from_dict(self, values: dict):
self._id = values["id"] self._id = values['id']
self._first_name = values["firstName"] self._first_name = values['firstName']
self._last_name = values["lastName"] self._last_name = values['lastName']
self._email = values["email"] self._email = values['email']
self._password = values["password"] self._password = values['password']
self._is_confirmed = values["isConfirmed"] self._is_confirmed = values['isConfirmed']
self._auth_role = AuthRoleEnum(values["authRole"]) self._auth_role = AuthRoleEnum(values['authRole'])
if "users" in values:
self._users = List(UserDTO)
for u in values["users"]:
user = UserDTO()
user.from_dict(u)
self._users.add(user)
self._created_at = values["createdAt"]
self._modified_at = values["modifiedAt"]
def to_dict(self) -> dict: def to_dict(self) -> dict:
return { return {
"id": self._id, 'id': self._id,
"firstName": self._first_name, 'firstName': self._first_name,
"lastName": self._last_name, 'lastName': self._last_name,
"email": self._email, 'email': self._email,
"password": self._password, 'password': self._password,
"isConfirmed": self._is_confirmed, 'isConfirmed': self._is_confirmed,
"authRole": self._auth_role.value, 'authRole': self._auth_role.value,
"users": self._users.select(lambda u: u.to_dict()).to_list(),
"createdAt": self._created_at,
"modifiedAt": self._modified_at,
} }

View File

@@ -5,13 +5,17 @@ from bot_data.filtered_result import FilteredResult
class AuthUserFilteredResultDTO(DtoABC, FilteredResult): class AuthUserFilteredResultDTO(DtoABC, FilteredResult):
def __init__(self, result: List = None, total_count: int = 0): def __init__(self, result: List = None, total_count: int = 0):
DtoABC.__init__(self) DtoABC.__init__(self)
FilteredResult.__init__(self, result, total_count) FilteredResult.__init__(self, result, total_count)
def from_dict(self, values: dict): def from_dict(self, values: dict):
self._result = values["users"] self._result = values['users']
self._total_count = values["totalCount"] self._total_count = values['totalCount']
def to_dict(self) -> dict: def to_dict(self) -> dict:
return {"users": self.result, "totalCount": self.total_count} return {
'users': self.result,
'totalCount': self.total_count
}

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
bot sh-edraft.de Discord bot bot Keksdose bot
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Discord bot for some customers of sh-edraft.de Discord bot for the Keksdose discord Server
:copyright: (c) 2022 - 2023 sh-edraft.de :copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details. :license: MIT, see LICENSE for more details.
""" """
@@ -14,8 +14,8 @@ Discord bot for some customers of sh-edraft.de
__title__ = 'bot_api.model.discord' __title__ = 'bot_api.model.discord'
__author__ = 'Sven Heidemann' __author__ = 'Sven Heidemann'
__license__ = 'MIT' __license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 - 2023 sh-edraft.de' __copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.1.7' __version__ = '0.3.dev25'
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro') VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='1', micro='7') version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -4,13 +4,15 @@ from bot_api.abc.dto_abc import DtoABC
class ServerDTO(DtoABC): class ServerDTO(DtoABC):
def __init__( def __init__(
self, self,
server_id: int, server_id: int,
discord_id: int, discord_id: int,
name: str, name: str,
member_count: int, member_count: int,
icon_url: Optional[str], icon_url: Optional[str]
): ):
DtoABC.__init__(self) DtoABC.__init__(self)
@@ -19,19 +21,19 @@ class ServerDTO(DtoABC):
self._name = name self._name = name
self._member_count = member_count self._member_count = member_count
self._icon_url = icon_url self._icon_url = icon_url
@property @property
def server_id(self) -> int: def server_id(self) -> int:
return self._server_id return self._server_id
@property @property
def discord_id(self) -> int: def discord_id(self) -> int:
return self._discord_id return self._discord_id
@property @property
def name(self) -> str: def name(self) -> str:
return self._name return self._name
@property @property
def member_count(self) -> int: def member_count(self) -> int:
return self._member_count return self._member_count
@@ -41,16 +43,16 @@ class ServerDTO(DtoABC):
return self._icon_url return self._icon_url
def from_dict(self, values: dict): def from_dict(self, values: dict):
self._server_id = int(values["serverId"]) self._server_id = int(values['serverId'])
self._discord_id = int(values["discordId"]) self._discord_id = int(values['discordId'])
self._name = values["name"] self._name = values['name']
self._icon_url = values["iconURL"] self._icon_url = values['iconURL']
def to_dict(self) -> dict: def to_dict(self) -> dict:
return { return {
"serverId": self._server_id, 'serverId': self._server_id,
"discordId": self._discord_id, 'discordId': self._discord_id,
"name": self._name, 'name': self._name,
"memberCount": self._member_count, 'memberCount': self._member_count,
"iconURL": self._icon_url, 'iconURL': self._icon_url,
} }

View File

@@ -5,13 +5,17 @@ from bot_data.filtered_result import FilteredResult
class ServerFilteredResultDTO(DtoABC, FilteredResult): class ServerFilteredResultDTO(DtoABC, FilteredResult):
def __init__(self, result: List = None, total_count: int = 0): def __init__(self, result: List = None, total_count: int = 0):
DtoABC.__init__(self) DtoABC.__init__(self)
FilteredResult.__init__(self, result, total_count) FilteredResult.__init__(self, result, total_count)
def from_dict(self, values: dict): def from_dict(self, values: dict):
self._result = values["servers"] self._result = values['servers']
self._total_count = values["totalCount"] self._total_count = values['totalCount']
def to_dict(self) -> dict: def to_dict(self) -> dict:
return {"servers": self.result, "totalCount": self.total_count} return {
'servers': self.result,
'totalCount': self.total_count
}

View File

@@ -6,13 +6,16 @@ from bot_api.abc.dto_abc import DtoABC
class EMailStringDTO(DtoABC): class EMailStringDTO(DtoABC):
def __init__(self, email: str): def __init__(self, email: str):
DtoABC.__init__(self) DtoABC.__init__(self)
self._email = email self._email = email
def from_dict(self, values: dict): def from_dict(self, values: dict):
self._email = values["email"] self._email = values['email']
def to_dict(self) -> dict: def to_dict(self) -> dict:
return {"email": self._email} return {
'email': self._email
}

View File

@@ -8,6 +8,7 @@ from bot_api.exception.service_error_code_enum import ServiceErrorCode
class ErrorDTO(DtoABC): class ErrorDTO(DtoABC):
def __init__(self, error_code: Optional[ServiceErrorCode], message: str): def __init__(self, error_code: Optional[ServiceErrorCode], message: str):
DtoABC.__init__(self) DtoABC.__init__(self)
@@ -23,8 +24,11 @@ class ErrorDTO(DtoABC):
return self._message return self._message
def from_dict(self, values: dict): def from_dict(self, values: dict):
self._error_code = values["ErrorCode"] self._error_code = values['ErrorCode']
self._message = values["Message"] self._message = values['Message']
def to_dict(self) -> dict: def to_dict(self) -> dict:
return {"errorCode": int(self._error_code.value), "message": self._message} return {
'errorCode': int(self._error_code.value),
'message': self._message
}

View File

@@ -6,10 +6,11 @@ from bot_data.model.auth_role_enum import AuthRoleEnum
class OAuthDTO(DtoABC): class OAuthDTO(DtoABC):
def __init__( def __init__(
self, self,
user: AuthUserDTO, user: AuthUserDTO,
o_auth_id: Optional[str], o_auth_id: Optional[str],
): ):
DtoABC.__init__(self) DtoABC.__init__(self)
@@ -33,8 +34,11 @@ class OAuthDTO(DtoABC):
self._oauth_id = value self._oauth_id = value
def from_dict(self, values: dict): def from_dict(self, values: dict):
self._user = AuthUserDTO().from_dict(values["user"]) self._user = AuthUserDTO().from_dict(values['user'])
self._oauth_id = values["oAuthId"] self._oauth_id = values['oAuthId']
def to_dict(self) -> dict: def to_dict(self) -> dict:
return {"user": self._user.to_dict(), "oAuthId": self._oauth_id} return {
'user': self._user.to_dict(),
'oAuthId': self._oauth_id
}

View File

@@ -6,6 +6,7 @@ from bot_api.abc.dto_abc import DtoABC
class ResetPasswordDTO(DtoABC): class ResetPasswordDTO(DtoABC):
def __init__(self, id: str, password: str): def __init__(self, id: str, password: str):
DtoABC.__init__(self) DtoABC.__init__(self)
@@ -21,8 +22,11 @@ class ResetPasswordDTO(DtoABC):
return self._password return self._password
def from_dict(self, values: dict): def from_dict(self, values: dict):
self._id = values["id"] self._id = values['id']
self._password = values["password"] self._password = values['password']
def to_dict(self) -> dict: def to_dict(self) -> dict:
return {"id": self._id, "password": self._password} return {
'id': self._id,
'password': self._password
}

View File

@@ -3,20 +3,21 @@ from bot_api.model.version_dto import VersionDTO
class SettingsDTO(DtoABC): class SettingsDTO(DtoABC):
def __init__( def __init__(
self, self,
web_version: str, web_version: str,
api_version: VersionDTO, api_version: VersionDTO,
config_path: str, config_path: str,
web_base_url: str, web_base_url: str,
api_base_url: str, api_base_url: str,
token_expire_time: int, token_expire_time: int,
refresh_token_expire_time: int, refresh_token_expire_time: int,
mail_user: str, mail_user: str,
mail_port: int, mail_port: int,
mail_host: str, mail_host: str,
mail_transceiver: str, mail_transceiver: str,
mail_transceiver_address: str, mail_transceiver_address: str,
): ):
DtoABC.__init__(self) DtoABC.__init__(self)
@@ -36,31 +37,31 @@ class SettingsDTO(DtoABC):
self._mail_transceiver_address = mail_transceiver_address self._mail_transceiver_address = mail_transceiver_address
def from_dict(self, values: dict): def from_dict(self, values: dict):
self._web_version = values["webVersion"] self._web_version = values['webVersion']
self._api_version.from_dict(values["apiVersion"]) self._api_version.from_dict(values['apiVersion'])
self._config_path = values["configPath"] self._config_path = values['configPath']
self._web_base_url = values["webBaseURL"] self._web_base_url = values['webBaseURL']
self._api_base_url = values["apiBaseURL"] self._api_base_url = values['apiBaseURL']
self._token_expire_time = values["tokenExpireTime"] self._token_expire_time = values['tokenExpireTime']
self._refresh_token_expire_time = values["refreshTokenExpireTime"] self._refresh_token_expire_time = values['refreshTokenExpireTime']
self._mail_user = values["mailUser"] self._mail_user = values['mailUser']
self._mail_port = values["mailPort"] self._mail_port = values['mailPort']
self._mail_host = values["mailHost"] self._mail_host = values['mailHost']
self._mail_transceiver = values["mailTransceiver"] self._mail_transceiver = values['mailTransceiver']
self._mail_transceiver_address = values["mailTransceiverAddress"] self._mail_transceiver_address = values['mailTransceiverAddress']
def to_dict(self) -> dict: def to_dict(self) -> dict:
return { return {
"webVersion": self._web_version, 'webVersion': self._web_version,
"apiVersion": self._api_version.str, 'apiVersion': self._api_version.str,
"configPath": self._config_path, 'configPath': self._config_path,
"webBaseURL": self._web_base_url, 'webBaseURL': self._web_base_url,
"apiBaseURL": self._api_base_url, 'apiBaseURL': self._api_base_url,
"tokenExpireTime": self._token_expire_time, 'tokenExpireTime': self._token_expire_time,
"refreshTokenExpireTime": self._refresh_token_expire_time, 'refreshTokenExpireTime': self._refresh_token_expire_time,
"mailUser": self._mail_user, 'mailUser': self._mail_user,
"mailPort": self._mail_port, 'mailPort': self._mail_port,
"mailHost": self._mail_host, 'mailHost': self._mail_host,
"mailTransceiver": self._mail_transceiver, 'mailTransceiver': self._mail_transceiver,
"mailTransceiverAddress": self._mail_transceiver_address, 'mailTransceiverAddress': self._mail_transceiver_address,
} }

View File

@@ -1,30 +1,32 @@
import traceback
from cpl_core.console import Console
from bot_api.abc.dto_abc import DtoABC from bot_api.abc.dto_abc import DtoABC
class TokenDTO(DtoABC): class TokenDTO(DtoABC):
def __init__(self, token: str, refresh_token: str, first_login: bool = False):
def __init__(self, token: str, refresh_token: str):
DtoABC.__init__(self) DtoABC.__init__(self)
self._token = token self._token = token
self._refresh_token = refresh_token self._refresh_token = refresh_token
self._first_login = first_login
@property @property
def token(self) -> str: def token(self) -> str:
return self._token return self._token
@property @property
def refresh_token(self) -> str: def refresh_token(self) -> str:
return self._refresh_token return self._refresh_token
@property
def first_login(self) -> bool:
return self._first_login
def from_dict(self, values: dict): def from_dict(self, values: dict):
self._token = values["token"] self._token = values['token']
self._refresh_token = values["refreshToken"] self._refresh_token = values['refreshToken']
self._first_login = values["firstLogin"]
def to_dict(self) -> dict: def to_dict(self) -> dict:
return {"token": self._token, "refreshToken": self._refresh_token, "firstLogin": self._first_login} return {
'token': self._token,
'refreshToken': self._refresh_token
}

View File

@@ -7,11 +7,12 @@ from bot_api.model.auth_user_dto import AuthUserDTO
class UpdateAuthUserDTO(DtoABC): class UpdateAuthUserDTO(DtoABC):
def __init__( def __init__(
self, self,
auth_user_dto: AuthUserDTO, auth_user_dto: AuthUserDTO,
new_auth_user_dto: AuthUserDTO, new_auth_user_dto: AuthUserDTO,
change_password: bool = False, change_password: bool = False
): ):
DtoABC.__init__(self) DtoABC.__init__(self)
@@ -32,13 +33,13 @@ class UpdateAuthUserDTO(DtoABC):
return self._change_password return self._change_password
def from_dict(self, values: dict): def from_dict(self, values: dict):
self._auth_user = AuthUserDTO().from_dict(values["authUser"]) self._auth_user = AuthUserDTO().from_dict(values['authUser'])
self._new_auth_user = AuthUserDTO().from_dict(values["newAuthUser"]) self._new_auth_user = AuthUserDTO().from_dict(values['newAuthUser'])
self._change_password = False if "changePassword" not in values else bool(values["changePassword"]) self._change_password = False if 'changePassword' not in values else bool(values['changePassword'])
def to_dict(self) -> dict: def to_dict(self) -> dict:
return { return {
"authUser": self._auth_user, 'authUser': self._auth_user,
"newAuthUser": self._new_auth_user, 'newAuthUser': self._new_auth_user,
"changePassword": self._change_password, 'changePassword': self._change_password
} }

View File

@@ -1,76 +0,0 @@
from typing import Optional
from bot_api.abc.dto_abc import DtoABC
from bot_data.model.server import Server
class UserDTO(DtoABC):
def __init__(
self,
id: int = None,
dc_id: int = None,
xp: int = None,
server: Optional[Server] = None,
is_technician: Optional[bool] = None,
is_admin: Optional[bool] = None,
is_moderator: Optional[bool] = None,
):
DtoABC.__init__(self)
self._user_id = id
self._discord_id = dc_id
self._xp = xp
self._server = server
self._is_technician = is_technician
self._is_admin = is_admin
self._is_moderator = is_moderator
@property
def user_id(self) -> int:
return self._user_id
@property
def discord_id(self) -> int:
return self._discord_id
@property
def xp(self) -> int:
return self._xp
@xp.setter
def xp(self, value: int):
self._xp = value
@property
def server(self) -> Optional[Server]:
return self._server
@property
def is_technician(self) -> bool:
return self._is_technician if self._is_technician is not None else False
@property
def is_admin(self) -> bool:
return self._is_admin if self._is_admin is not None else False
@property
def is_moderator(self) -> bool:
return self._is_moderator if self._is_moderator is not None else False
def from_dict(self, values: dict):
self._user_id = values["id"]
self._discord_id = values["dcId"]
self._xp = values["xp"]
self._server = values["server"]
def to_dict(self) -> dict:
return {
"id": self._user_id,
"dcId": self._discord_id,
"xp": self._xp,
"server": self._server.id,
"isTechnician": self.is_technician,
"isAdmin": self.is_admin,
"isModerator": self.is_moderator,
}

View File

@@ -6,6 +6,7 @@ from bot_api.abc.dto_abc import DtoABC
class VersionDTO(DtoABC): class VersionDTO(DtoABC):
def __init__(self, major: str = None, minor: str = None, micro: str = None): def __init__(self, major: str = None, minor: str = None, micro: str = None):
DtoABC.__init__(self) DtoABC.__init__(self)
@@ -27,16 +28,16 @@ class VersionDTO(DtoABC):
@property @property
def str(self) -> str: def str(self) -> str:
return f"{self._major}.{self._minor}.{self._micro}" return f'{self._major}.{self._minor}.{self._micro}'
def from_dict(self, values: dict): def from_dict(self, values: dict):
self._major = values["major"] self._major = values['major']
self._minor = values["minor"] self._minor = values['minor']
self._micro = values["micro"] self._micro = values['micro']
def to_dict(self) -> dict: def to_dict(self) -> dict:
return { return {
"major": self._major, 'major': self._major,
"minor": self._minor, 'minor': self._minor,
"micro": self._micro, 'micro': self._micro,
} }

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
bot sh-edraft.de Discord bot bot Keksdose bot
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Discord bot for some customers of sh-edraft.de Discord bot for the Keksdose discord Server
:copyright: (c) 2022 - 2023 sh-edraft.de :copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details. :license: MIT, see LICENSE for more details.
""" """
@@ -14,8 +14,8 @@ Discord bot for some customers of sh-edraft.de
__title__ = 'bot_api.route' __title__ = 'bot_api.route'
__author__ = 'Sven Heidemann' __author__ = 'Sven Heidemann'
__license__ = 'MIT' __license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 - 2023 sh-edraft.de' __copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.1.7' __version__ = '0.3.dev25'
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro') VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='1', micro='7') version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -1,9 +1,7 @@
import functools import functools
from functools import wraps from functools import wraps
from typing import Optional, Callable, Union from typing import Optional, Callable
from cpl_core.dependency_injection import ServiceProviderABC
from cpl_core.environment import ApplicationEnvironmentABC
from flask import request, jsonify from flask import request, jsonify
from flask_cors import cross_origin from flask_cors import cross_origin
@@ -13,7 +11,6 @@ from bot_api.exception.service_exception import ServiceException
from bot_api.model.error_dto import ErrorDTO from bot_api.model.error_dto import ErrorDTO
from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC
from bot_data.model.auth_role_enum import AuthRoleEnum from bot_data.model.auth_role_enum import AuthRoleEnum
from bot_data.model.auth_user import AuthUser
class Route: class Route:
@@ -21,107 +18,53 @@ class Route:
_auth_users: Optional[AuthUserRepositoryABC] = None _auth_users: Optional[AuthUserRepositoryABC] = None
_auth: Optional[AuthServiceABC] = None _auth: Optional[AuthServiceABC] = None
_env = "production"
@classmethod @classmethod
@ServiceProviderABC.inject def init_authorize(cls, auth_users: AuthUserRepositoryABC, auth: AuthServiceABC):
def init_authorize(cls, env: ApplicationEnvironmentABC, auth_users: AuthUserRepositoryABC, auth: AuthServiceABC):
cls._auth_users = auth_users cls._auth_users = auth_users
cls._auth = auth cls._auth = auth
cls._env = env.environment_name
@classmethod @classmethod
def get_user(cls) -> Optional[Union[str, AuthUser]]: def authorize(cls, f: Callable = None, role: AuthRoleEnum = None):
token = None
api_key = None
authorization = request.headers.get("Authorization").split()
match authorization[0]:
case "Bearer":
token = authorization[1]
case "API-Key":
api_key = authorization[1]
if api_key is not None:
return "system"
if token is None:
return None
jwt = cls._auth.decode_token(token)
user = cls._auth_users.get_auth_user_by_email(jwt["email"])
return user
@classmethod
def authorize(cls, f: Callable = None, role: AuthRoleEnum = None, skip_in_dev=False, by_api_key=False):
if f is None: if f is None:
return functools.partial(cls.authorize, role=role, skip_in_dev=skip_in_dev, by_api_key=by_api_key) return functools.partial(cls.authorize, role=role)
@wraps(f) @wraps(f)
async def decorator(*args, **kwargs): async def decorator(*args, **kwargs):
if skip_in_dev and cls._env == "development":
return await f(*args, **kwargs)
token = None token = None
api_key = None if 'Authorization' in request.headers:
if "Authorization" in request.headers: bearer = request.headers.get('Authorization')
if " " not in request.headers.get("Authorization"): token = bearer.split()[1]
ex = ServiceException(ServiceErrorCode.Unauthorized, f"Token not set")
error = ErrorDTO(ex.error_code, ex.message)
return jsonify(error.to_dict()), 401
authorization = request.headers.get("Authorization").split()
match authorization[0]:
case "Bearer":
token = authorization[1]
case "API-Key":
api_key = authorization[1]
if api_key is not None:
valid = False
try:
valid = cls._auth.verify_api_key(api_key)
except ServiceException as e:
error = ErrorDTO(e.error_code, e.message)
return jsonify(error.to_dict()), 403
except Exception as e:
return jsonify(e), 500
if not valid:
ex = ServiceException(ServiceErrorCode.Unauthorized, f"API-Key invalid")
error = ErrorDTO(ex.error_code, ex.message)
return jsonify(error.to_dict()), 401
return await f(*args, **kwargs)
if token is None: if token is None:
ex = ServiceException(ServiceErrorCode.Unauthorized, f"Token not set") ex = ServiceException(ServiceErrorCode.Unauthorized, f'Token not set')
error = ErrorDTO(ex.error_code, ex.message) error = ErrorDTO(ex.error_code, ex.message)
return jsonify(error.to_dict()), 401 return jsonify(error.to_dict()), 401
if cls._auth_users is None or cls._auth is None: if cls._auth_users is None or cls._auth is None:
ex = ServiceException(ServiceErrorCode.Unauthorized, f"Authorize is not initialized") ex = ServiceException(ServiceErrorCode.Unauthorized, f'Authorize is not initialized')
error = ErrorDTO(ex.error_code, ex.message) error = ErrorDTO(ex.error_code, ex.message)
return jsonify(error.to_dict()), 401 return jsonify(error.to_dict()), 401
if not cls._auth.verify_login(token): if not cls._auth.verify_login(token):
ex = ServiceException(ServiceErrorCode.Unauthorized, f"Token expired") ex = ServiceException(ServiceErrorCode.Unauthorized, f'Token expired')
error = ErrorDTO(ex.error_code, ex.message) error = ErrorDTO(ex.error_code, ex.message)
return jsonify(error.to_dict()), 401 return jsonify(error.to_dict()), 401
token = cls._auth.decode_token(token) token = cls._auth.decode_token(token)
if token is None or "email" not in token: if token is None or 'email' not in token:
ex = ServiceException(ServiceErrorCode.Unauthorized, f"Token invalid") ex = ServiceException(ServiceErrorCode.Unauthorized, f'Token invalid')
error = ErrorDTO(ex.error_code, ex.message) error = ErrorDTO(ex.error_code, ex.message)
return jsonify(error.to_dict()), 401 return jsonify(error.to_dict()), 401
user = cls._auth_users.get_auth_user_by_email(token["email"]) user = cls._auth_users.get_auth_user_by_email(token['email'])
if user is None: if user is None:
ex = ServiceException(ServiceErrorCode.Unauthorized, f"Token invalid") ex = ServiceException(ServiceErrorCode.Unauthorized, f'Token invalid')
error = ErrorDTO(ex.error_code, ex.message) error = ErrorDTO(ex.error_code, ex.message)
return jsonify(error.to_dict()), 401 return jsonify(error.to_dict()), 401
if role is not None and user.auth_role.value < role.value: if role is not None and user.auth_role.value < role.value:
ex = ServiceException(ServiceErrorCode.Unauthorized, f"Role {role} required") ex = ServiceException(ServiceErrorCode.Unauthorized, f'Role {role} required')
error = ErrorDTO(ex.error_code, ex.message) error = ErrorDTO(ex.error_code, ex.message)
return jsonify(error.to_dict()), 403 return jsonify(error.to_dict()), 403
@@ -141,20 +84,20 @@ class Route:
@classmethod @classmethod
def get(cls, path=None, **kwargs): def get(cls, path=None, **kwargs):
return cls.route(path, methods=["GET"], **kwargs) return cls.route(path, methods=['GET'], **kwargs)
@classmethod @classmethod
def post(cls, path=None, **kwargs): def post(cls, path=None, **kwargs):
return cls.route(path, methods=["POST"], **kwargs) return cls.route(path, methods=['POST'], **kwargs)
@classmethod @classmethod
def head(cls, path=None, **kwargs): def head(cls, path=None, **kwargs):
return cls.route(path, methods=["HEAD"], **kwargs) return cls.route(path, methods=['HEAD'], **kwargs)
@classmethod @classmethod
def put(cls, path=None, **kwargs): def put(cls, path=None, **kwargs):
return cls.route(path, methods=["PUT"], **kwargs) return cls.route(path, methods=['PUT'], **kwargs)
@classmethod @classmethod
def delete(cls, path=None, **kwargs): def delete(cls, path=None, **kwargs):
return cls.route(path, methods=["DELETE"], **kwargs) return cls.route(path, methods=['DELETE'], **kwargs)

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
bot sh-edraft.de Discord bot bot Keksdose bot
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Discord bot for some customers of sh-edraft.de Discord bot for the Keksdose discord Server
:copyright: (c) 2022 - 2023 sh-edraft.de :copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details. :license: MIT, see LICENSE for more details.
""" """
@@ -14,8 +14,8 @@ Discord bot for some customers of sh-edraft.de
__title__ = 'bot_api.service' __title__ = 'bot_api.service'
__author__ = 'Sven Heidemann' __author__ = 'Sven Heidemann'
__license__ = 'MIT' __license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 - 2023 sh-edraft.de' __copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.1.7' __version__ = '0.3.dev25'
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports # imports
VersionInfo = namedtuple('VersionInfo', 'major minor micro') VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='1', micro='7') version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -31,33 +31,33 @@ from bot_api.model.reset_password_dto import ResetPasswordDTO
from bot_api.model.token_dto import TokenDTO from bot_api.model.token_dto import TokenDTO
from bot_api.model.update_auth_user_dto import UpdateAuthUserDTO from bot_api.model.update_auth_user_dto import UpdateAuthUserDTO
from bot_api.transformer.auth_user_transformer import AuthUserTransformer as AUT from bot_api.transformer.auth_user_transformer import AuthUserTransformer as AUT
from bot_data.abc.api_key_repository_abc import ApiKeyRepositoryABC
from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC
from bot_data.abc.server_repository_abc import ServerRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.abc.user_repository_abc import UserRepositoryABC from bot_data.abc.user_repository_abc import UserRepositoryABC
from bot_data.model.api_key import ApiKey
from bot_data.model.auth_role_enum import AuthRoleEnum from bot_data.model.auth_role_enum import AuthRoleEnum
from bot_data.model.auth_user import AuthUser from bot_data.model.auth_user import AuthUser
from bot_data.model.auth_user_users_relation import AuthUserUsersRelation from bot_data.model.auth_user_users_relation import AuthUserUsersRelation
_email_regex = r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b" _email_regex = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
class AuthService(AuthServiceABC): class AuthService(AuthServiceABC):
def __init__( def __init__(
self, self,
env: ApplicationEnvironmentABC, env: ApplicationEnvironmentABC,
logger: ApiLogger, logger: ApiLogger,
bot: DiscordBotServiceABC, bot: DiscordBotServiceABC,
db: DatabaseContextABC, db: DatabaseContextABC,
auth_users: AuthUserRepositoryABC, auth_users: AuthUserRepositoryABC,
api_keys: ApiKeyRepositoryABC, users: UserRepositoryABC,
users: UserRepositoryABC, servers: ServerRepositoryABC,
servers: ServerRepositoryABC, # mailer: MailThread,
mailer: EMailClientABC, mailer: EMailClientABC,
t: TranslatePipe, t: TranslatePipe,
auth_settings: AuthenticationSettings, auth_settings: AuthenticationSettings,
frontend_settings: FrontendSettings, frontend_settings: FrontendSettings,
): ):
AuthServiceABC.__init__(self) AuthServiceABC.__init__(self)
@@ -66,7 +66,6 @@ class AuthService(AuthServiceABC):
self._bot = bot self._bot = bot
self._db = db self._db = db
self._auth_users = auth_users self._auth_users = auth_users
self._api_keys = api_keys
self._users = users self._users = users
self._servers = servers self._servers = servers
self._mailer = mailer self._mailer = mailer
@@ -76,34 +75,26 @@ class AuthService(AuthServiceABC):
@staticmethod @staticmethod
def _hash_sha256(password: str, salt: str) -> str: def _hash_sha256(password: str, salt: str) -> str:
return hashlib.sha256(f"{password}{salt}".encode("utf-8")).hexdigest() return hashlib.sha256(f'{password}{salt}'.encode('utf-8')).hexdigest()
@staticmethod @staticmethod
def _is_email_valid(email: str) -> bool: def _is_email_valid(email: str) -> bool:
if email is None:
raise False
if re.fullmatch(_email_regex, email) is not None: if re.fullmatch(_email_regex, email) is not None:
return True return True
return False return False
def _get_api_key_str(self, api_key: ApiKey) -> str:
return hashlib.sha256(
f"{api_key.identifier}:{api_key.key}+{self._auth_settings.secret_key}".encode("utf-8")
).hexdigest()
def generate_token(self, user: AuthUser) -> str: def generate_token(self, user: AuthUser) -> str:
token = jwt.encode( token = jwt.encode(
payload={ payload={
"user_id": user.id, 'user_id': user.id,
"email": user.email, 'email': user.email,
"role": user.auth_role.value, 'role': user.auth_role.value,
"exp": datetime.now(tz=timezone.utc) + timedelta(days=self._auth_settings.token_expire_time), 'exp': datetime.now(tz=timezone.utc) + timedelta(days=self._auth_settings.token_expire_time),
"iss": self._auth_settings.issuer, 'iss': self._auth_settings.issuer,
"aud": self._auth_settings.audience, 'aud': self._auth_settings.audience
}, },
key=CredentialManager.decrypt(self._auth_settings.secret_key), key=CredentialManager.decrypt(self._auth_settings.secret_key)
) )
return token return token
@@ -114,43 +105,39 @@ class AuthService(AuthServiceABC):
key=CredentialManager.decrypt(self._auth_settings.secret_key), key=CredentialManager.decrypt(self._auth_settings.secret_key),
issuer=self._auth_settings.issuer, issuer=self._auth_settings.issuer,
audience=self._auth_settings.audience, audience=self._auth_settings.audience,
algorithms=["HS256"], algorithms=['HS256']
) )
def get_decoded_token_from_request(self) -> dict: def get_decoded_token_from_request(self) -> dict:
token = None token = None
if "Authorization" in request.headers: if 'Authorization' in request.headers:
bearer = request.headers.get("Authorization") bearer = request.headers.get('Authorization')
token = bearer.split()[1] token = bearer.split()[1]
if token is None: if token is None:
raise ServiceException(ServiceErrorCode.Unauthorized, f"Token not set") raise ServiceException(ServiceErrorCode.Unauthorized, f'Token not set')
return jwt.decode( return jwt.decode(
token, token,
key=CredentialManager.decrypt(self._auth_settings.secret_key), key=CredentialManager.decrypt(self._auth_settings.secret_key),
issuer=self._auth_settings.issuer, issuer=self._auth_settings.issuer,
audience=self._auth_settings.audience, audience=self._auth_settings.audience,
algorithms=["HS256"], algorithms=['HS256']
) )
def find_decoded_token_from_request(self) -> Optional[dict]: def find_decoded_token_from_request(self) -> Optional[dict]:
token = None token = None
if "Authorization" in request.headers: if 'Authorization' in request.headers:
bearer = request.headers.get("Authorization") bearer = request.headers.get('Authorization')
token = bearer.split()[1] token = bearer.split()[1]
return ( return jwt.decode(
jwt.decode( token,
token, key=CredentialManager.decrypt(self._auth_settings.secret_key),
key=CredentialManager.decrypt(self._auth_settings.secret_key), issuer=self._auth_settings.issuer,
issuer=self._auth_settings.issuer, audience=self._auth_settings.audience,
audience=self._auth_settings.audience, algorithms=['HS256']
algorithms=["HS256"], ) if token is not None else None
)
if token is not None
else None
)
def _create_and_save_refresh_token(self, user: AuthUser) -> str: def _create_and_save_refresh_token(self, user: AuthUser) -> str:
token = str(uuid.uuid4()) token = str(uuid.uuid4())
@@ -162,56 +149,58 @@ class AuthService(AuthServiceABC):
def _send_link_mail(self, email: str, subject: str, message: str): def _send_link_mail(self, email: str, subject: str, message: str):
url = self._frontend_settings.url url = self._frontend_settings.url
if not url.endswith("/"): if not url.endswith('/'):
url = f"{url}/" url = f'{url}/'
self._mailer.connect() self._mailer.connect()
mail = EMail() mail = EMail()
mail.add_header("Mime-Version: 1.0") mail.add_header('Mime-Version: 1.0')
mail.add_header("Content-Type: text/plain; charset=utf-8") mail.add_header('Content-Type: text/plain; charset=utf-8')
mail.add_header("Content-Transfer-Encoding: quoted-printable") mail.add_header('Content-Transfer-Encoding: quoted-printable')
mail.add_receiver(str(email)) mail.add_receiver(str(email))
mail.subject = subject mail.subject = subject
mail.body = textwrap.dedent( mail.body = textwrap.dedent(f"""{message}
f"""{message}
{self._t.transform('api.mail.automatic_mail').format(self._environment.application_name, self._environment.environment_name, self._environment.host_name)} {self._t.transform('api.mail.automatic_mail').format(self._environment.application_name, self._environment.environment_name, self._environment.host_name)}
""" """)
)
thr = Thread(target=self._mailer.send_mail, args=[mail]) thr = Thread(target=self._mailer.send_mail, args=[mail])
thr.start() thr.start()
def _send_confirmation_id_to_user(self, user: AuthUser): def _send_confirmation_id_to_user(self, user: AuthUser):
url = self._frontend_settings.url url = self._frontend_settings.url
if not url.endswith("/"): if not url.endswith('/'):
url = f"{url}/" url = f'{url}/'
self._send_link_mail( self._send_link_mail(
user.email, user.email,
self._t.transform("api.auth.confirmation.subject").format(user.first_name, user.last_name), self._t.transform('api.auth.confirmation.subject').format(user.first_name, user.last_name),
self._t.transform("api.auth.confirmation.message").format(url, user.confirmation_id), self._t.transform('api.auth.confirmation.message').format(url, user.confirmation_id)
) )
def _send_forgot_password_id_to_user(self, user: AuthUser): def _send_forgot_password_id_to_user(self, user: AuthUser):
url = self._frontend_settings.url url = self._frontend_settings.url
if not url.endswith("/"): if not url.endswith('/'):
url = f"{url}/" url = f'{url}/'
self._send_link_mail( self._send_link_mail(
user.email, user.email,
self._t.transform("api.auth.forgot_password.subject").format(user.first_name, user.last_name), self._t.transform('api.auth.forgot_password.subject').format(user.first_name, user.last_name),
self._t.transform("api.auth.forgot_password.message").format(url, user.forgot_password_id), self._t.transform('api.auth.forgot_password.message').format(url, user.forgot_password_id)
) )
async def get_all_auth_users_async(self) -> List[AuthUserDTO]: async def get_all_auth_users_async(self) -> List[AuthUserDTO]:
result = self._auth_users.get_all_auth_users().select(lambda x: AUT.to_dto(x)) result = self._auth_users.get_all_auth_users() \
.select(lambda x: AUT.to_dto(x))
return List(AuthUserDTO, result) return List(AuthUserDTO, result)
async def get_filtered_auth_users_async(self, criteria: AuthUserSelectCriteria) -> AuthUserFilteredResultDTO: async def get_filtered_auth_users_async(self, criteria: AuthUserSelectCriteria) -> AuthUserFilteredResultDTO:
users = self._auth_users.get_filtered_auth_users(criteria) users = self._auth_users.get_filtered_auth_users(criteria)
result = users.result.select(lambda x: AUT.to_dto(x)) result = users.result.select(lambda x: AUT.to_dto(x))
return AuthUserFilteredResultDTO(List(AuthUserDTO, result), users.total_count) return AuthUserFilteredResultDTO(
List(AuthUserDTO, result),
users.total_count
)
async def get_auth_user_by_email_async(self, email: str, with_password: bool = False) -> AuthUserDTO: async def get_auth_user_by_email_async(self, email: str, with_password: bool = False) -> AuthUserDTO:
try: try:
@@ -219,17 +208,17 @@ class AuthService(AuthServiceABC):
user = self._auth_users.get_auth_user_by_email(email) user = self._auth_users.get_auth_user_by_email(email)
return AUT.to_dto(user, password=user.password if with_password else None) return AUT.to_dto(user, password=user.password if with_password else None)
except Exception as e: except Exception as e:
self._logger.error(__name__, f"AuthUser not found", e) self._logger.error(__name__, f'AuthUser not found', e)
raise ServiceException(ServiceErrorCode.InvalidData, f"User not found {email}") raise ServiceException(ServiceErrorCode.InvalidData, f'User not found {email}')
async def find_auth_user_by_email_async(self, email: str) -> Optional[AuthUser]: async def find_auth_user_by_email_async(self, email: str) -> Optional[AuthUser]:
user = self._auth_users.find_auth_user_by_email(email) user = self._auth_users.find_auth_user_by_email(email)
return AUT.to_dto(user) if user is not None else None return AUT.to_dto(user) if user is not None else None
def add_auth_user(self, user_dto: AuthUserDTO): async def add_auth_user_async(self, user_dto: AuthUserDTO):
db_user = self._auth_users.find_auth_user_by_email(user_dto.email) db_user = self._auth_users.find_auth_user_by_email(user_dto.email)
if db_user is not None: if db_user is not None:
raise ServiceException(ServiceErrorCode.InvalidUser, "User already exists") raise ServiceException(ServiceErrorCode.InvalidUser, 'User already exists')
user = AUT.to_db(user_dto) user = AUT.to_db(user_dto)
if self._auth_users.get_all_auth_users().count() == 0: if self._auth_users.get_all_auth_users().count() == 0:
@@ -238,26 +227,26 @@ class AuthService(AuthServiceABC):
user.password_salt = uuid.uuid4() user.password_salt = uuid.uuid4()
user.password = self._hash_sha256(user_dto.password, user.password_salt) user.password = self._hash_sha256(user_dto.password, user.password_salt)
if not self._is_email_valid(user.email): if not self._is_email_valid(user.email):
raise ServiceException(ServiceErrorCode.InvalidData, "Invalid E-Mail address") raise ServiceException(ServiceErrorCode.InvalidData, 'Invalid E-Mail address')
try: try:
user.confirmation_id = uuid.uuid4() user.confirmation_id = uuid.uuid4()
self._auth_users.add_auth_user(user) self._auth_users.add_auth_user(user)
self._send_confirmation_id_to_user(user) self._send_confirmation_id_to_user(user)
self._db.save_changes() self._db.save_changes()
self._logger.info(__name__, f"Added auth user with E-Mail: {user_dto.email}") self._logger.info(__name__, f'Added auth user with E-Mail: {user_dto.email}')
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Cannot add user with E-Mail {user_dto.email}", e) self._logger.error(__name__, f'Cannot add user with E-Mail {user_dto.email}', e)
raise ServiceException(ServiceErrorCode.UnableToAdd, "Invalid E-Mail") raise ServiceException(ServiceErrorCode.UnableToAdd, "Invalid E-Mail")
async def add_auth_user_by_oauth_async(self, dto: OAuthDTO): async def add_auth_user_by_oauth_async(self, dto: OAuthDTO):
db_user = self._auth_users.find_auth_user_by_email(dto.user.email) db_user = self._auth_users.find_auth_user_by_email(dto.user.email)
if db_user is None: if db_user is None:
raise ServiceException(ServiceErrorCode.InvalidUser, "User not found") raise ServiceException(ServiceErrorCode.InvalidUser, 'User not found')
if db_user.oauth_id != dto.oauth_id: if db_user.oauth_id != dto.oauth_id:
raise ServiceException(ServiceErrorCode.InvalidUser, "Wrong OAuthId") raise ServiceException(ServiceErrorCode.InvalidUser, 'Wrong OAuthId')
try: try:
db_user.first_name = dto.user.first_name db_user.first_name = dto.user.first_name
@@ -268,70 +257,100 @@ class AuthService(AuthServiceABC):
db_user.confirmation_id = uuid.uuid4() db_user.confirmation_id = uuid.uuid4()
self._send_confirmation_id_to_user(db_user) self._send_confirmation_id_to_user(db_user)
self._auth_users.update_auth_user(db_user) self._auth_users.update_auth_user(db_user)
self._logger.info(__name__, f"Added auth user with E-Mail: {dto.user.email}") self._logger.info(__name__, f'Added auth user with E-Mail: {dto.user.email}')
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Cannot add user with E-Mail {dto.user.email}", e) self._logger.error(__name__, f'Cannot add user with E-Mail {dto.user.email}', e)
raise ServiceException(ServiceErrorCode.UnableToAdd, "Invalid E-Mail") raise ServiceException(ServiceErrorCode.UnableToAdd, "Invalid E-Mail")
self._db.save_changes() self._db.save_changes()
async def add_auth_user_by_discord_async(self, user_dto: AuthUserDTO, dc_id: int) -> OAuthDTO:
db_auth_user = self._auth_users.find_auth_user_by_email(user_dto.email)
# user exists
if db_auth_user is not None and db_auth_user.users.count() > 0:
# raise ServiceException(ServiceErrorCode.InvalidUser, 'User already exists')
self._logger.debug(__name__, f'Discord user already exists')
return OAuthDTO(AUT.to_dto(db_auth_user), None)
# user exists but discord user id not set
elif db_auth_user is not None and db_auth_user.users.count() == 0:
self._logger.debug(__name__, f'Auth user exists but not linked with discord')
# users = self._users.get_users_by_discord_id(user_dto.user_id)
# add auth_user to user refs
db_auth_user.oauth_id = None
else:
# user does not exists
self._logger.debug(__name__, f'Auth user does not exist')
try:
user_dto.user_id = self._users.get_users_by_discord_id(user_dto.user_id).single().user_id
except Exception as e:
self._logger.error(__name__, f'User not found')
user_dto.user_id = None
await self.add_auth_user_async(user_dto)
db_auth_user = self._auth_users.get_auth_user_by_email(user_dto.email)
db_auth_user.oauth_id = uuid.uuid4()
for g in self._bot.guilds:
member = g.get_member(int(dc_id))
if member is None:
continue
server = self._servers.get_server_by_discord_id(g.id)
users = self._users.get_users_by_discord_id(dc_id)
for user in users:
if user.server.server_id != server.server_id:
continue
self._auth_users.add_auth_user_user_rel(AuthUserUsersRelation(db_auth_user, user))
self._auth_users.update_auth_user(db_auth_user)
self._db.save_changes()
return OAuthDTO(AUT.to_dto(db_auth_user), db_auth_user.oauth_id)
async def update_user_async(self, update_user_dto: UpdateAuthUserDTO): async def update_user_async(self, update_user_dto: UpdateAuthUserDTO):
if update_user_dto is None: if update_user_dto is None:
raise ServiceException(ServiceErrorCode.InvalidData, f"User is empty") raise ServiceException(ServiceErrorCode.InvalidData, f'User is empty')
if update_user_dto.auth_user is None: if update_user_dto.auth_user is None:
raise ServiceException(ServiceErrorCode.InvalidData, f"Existing user is empty") raise ServiceException(ServiceErrorCode.InvalidData, f'Existing user is empty')
if update_user_dto.new_auth_user is None: if update_user_dto.new_auth_user is None:
raise ServiceException(ServiceErrorCode.InvalidData, f"New user is empty") raise ServiceException(ServiceErrorCode.InvalidData, f'New user is empty')
if not self._is_email_valid(update_user_dto.auth_user.email) or not self._is_email_valid( if not self._is_email_valid(update_user_dto.auth_user.email) or not self._is_email_valid(update_user_dto.new_auth_user.email):
update_user_dto.new_auth_user.email raise ServiceException(ServiceErrorCode.InvalidData, f'Invalid E-Mail')
):
raise ServiceException(ServiceErrorCode.InvalidData, f"Invalid E-Mail")
user = self._auth_users.find_auth_user_by_email(update_user_dto.auth_user.email) user = self._auth_users.find_auth_user_by_email(update_user_dto.auth_user.email)
if user is None: if user is None:
raise ServiceException(ServiceErrorCode.InvalidUser, "User not found") raise ServiceException(ServiceErrorCode.InvalidUser, 'User not found')
if user.confirmation_id is not None: if user.confirmation_id is not None:
raise ServiceException(ServiceErrorCode.InvalidUser, "E-Mail not confirmed") raise ServiceException(ServiceErrorCode.InvalidUser, 'E-Mail not confirmed')
# update first name # update first name
if ( if update_user_dto.new_auth_user.first_name is not None and update_user_dto.auth_user.first_name != update_user_dto.new_auth_user.first_name:
update_user_dto.new_auth_user.first_name is not None
and update_user_dto.auth_user.first_name != update_user_dto.new_auth_user.first_name
):
user.first_name = update_user_dto.new_auth_user.first_name user.first_name = update_user_dto.new_auth_user.first_name
# update last name # update last name
if ( if update_user_dto.new_auth_user.last_name is not None and update_user_dto.new_auth_user.last_name != '' and \
update_user_dto.new_auth_user.last_name is not None update_user_dto.auth_user.last_name != update_user_dto.new_auth_user.last_name:
and update_user_dto.new_auth_user.last_name != ""
and update_user_dto.auth_user.last_name != update_user_dto.new_auth_user.last_name
):
user.last_name = update_user_dto.new_auth_user.last_name user.last_name = update_user_dto.new_auth_user.last_name
# update E-Mail # update E-Mail
if ( if update_user_dto.new_auth_user.email is not None and update_user_dto.new_auth_user.email != '' and update_user_dto.auth_user.email != update_user_dto.new_auth_user.email:
update_user_dto.new_auth_user.email is not None
and update_user_dto.new_auth_user.email != ""
and update_user_dto.auth_user.email != update_user_dto.new_auth_user.email
):
user_by_new_e_mail = self._auth_users.find_auth_user_by_email(update_user_dto.new_auth_user.email) user_by_new_e_mail = self._auth_users.find_auth_user_by_email(update_user_dto.new_auth_user.email)
if user_by_new_e_mail is not None: if user_by_new_e_mail is not None:
raise ServiceException(ServiceErrorCode.InvalidUser, "User already exists") raise ServiceException(ServiceErrorCode.InvalidUser, 'User already exists')
user.email = update_user_dto.new_auth_user.email user.email = update_user_dto.new_auth_user.email
update_user_dto.auth_user.password = self._hash_sha256(update_user_dto.auth_user.password, user.password_salt) update_user_dto.auth_user.password = self._hash_sha256(update_user_dto.auth_user.password, user.password_salt)
if update_user_dto.auth_user.password != user.password: if update_user_dto.auth_user.password != user.password:
raise ServiceException(ServiceErrorCode.InvalidUser, "Wrong password") raise ServiceException(ServiceErrorCode.InvalidUser, 'Wrong password')
# update password # update password
if ( if update_user_dto.new_auth_user.password is not None and self._hash_sha256(update_user_dto.new_auth_user.password, user.password_salt) != user.password:
update_user_dto.new_auth_user.password is not None
and self._hash_sha256(update_user_dto.new_auth_user.password, user.password_salt) != user.password
):
user.password_salt = uuid.uuid4() user.password_salt = uuid.uuid4()
user.password = self._hash_sha256(update_user_dto.new_auth_user.password, user.password_salt) user.password = self._hash_sha256(update_user_dto.new_auth_user.password, user.password_salt)
@@ -340,22 +359,20 @@ class AuthService(AuthServiceABC):
async def update_user_as_admin_async(self, update_user_dto: UpdateAuthUserDTO): async def update_user_as_admin_async(self, update_user_dto: UpdateAuthUserDTO):
if update_user_dto is None: if update_user_dto is None:
raise ServiceException(ServiceErrorCode.InvalidData, f"User is empty") raise ServiceException(ServiceErrorCode.InvalidData, f'User is empty')
if update_user_dto.auth_user is None: if update_user_dto.auth_user is None:
raise ServiceException(ServiceErrorCode.InvalidData, f"Existing user is empty") raise ServiceException(ServiceErrorCode.InvalidData, f'Existing user is empty')
if update_user_dto.new_auth_user is None: if update_user_dto.new_auth_user is None:
raise ServiceException(ServiceErrorCode.InvalidData, f"New user is empty") raise ServiceException(ServiceErrorCode.InvalidData, f'New user is empty')
if not self._is_email_valid(update_user_dto.auth_user.email) or not self._is_email_valid( if not self._is_email_valid(update_user_dto.auth_user.email) or not self._is_email_valid(update_user_dto.new_auth_user.email):
update_user_dto.new_auth_user.email raise ServiceException(ServiceErrorCode.InvalidData, f'Invalid E-Mail')
):
raise ServiceException(ServiceErrorCode.InvalidData, f"Invalid E-Mail")
user = self._auth_users.find_auth_user_by_email(update_user_dto.auth_user.email) user = self._auth_users.find_auth_user_by_email(update_user_dto.auth_user.email)
if user is None: if user is None:
raise ServiceException(ServiceErrorCode.InvalidUser, "User not found") raise ServiceException(ServiceErrorCode.InvalidUser, 'User not found')
if user.confirmation_id is not None and update_user_dto.new_auth_user.is_confirmed: if user.confirmation_id is not None and update_user_dto.new_auth_user.is_confirmed:
user.confirmation_id = None user.confirmation_id = None
@@ -365,45 +382,27 @@ class AuthService(AuthServiceABC):
# raise ServiceException(ServiceErrorCode.InvalidUser, 'E-Mail not confirmed') # raise ServiceException(ServiceErrorCode.InvalidUser, 'E-Mail not confirmed')
# update first name # update first name
if ( if update_user_dto.new_auth_user.first_name is not None and update_user_dto.auth_user.first_name != update_user_dto.new_auth_user.first_name:
update_user_dto.new_auth_user.first_name is not None
and update_user_dto.auth_user.first_name != update_user_dto.new_auth_user.first_name
):
user.first_name = update_user_dto.new_auth_user.first_name user.first_name = update_user_dto.new_auth_user.first_name
# update last name # update last name
if ( if update_user_dto.new_auth_user.last_name is not None and update_user_dto.new_auth_user.last_name != '' and update_user_dto.auth_user.last_name != update_user_dto.new_auth_user.last_name:
update_user_dto.new_auth_user.last_name is not None
and update_user_dto.new_auth_user.last_name != ""
and update_user_dto.auth_user.last_name != update_user_dto.new_auth_user.last_name
):
user.last_name = update_user_dto.new_auth_user.last_name user.last_name = update_user_dto.new_auth_user.last_name
# update E-Mail # update E-Mail
if ( if update_user_dto.new_auth_user.email is not None and update_user_dto.new_auth_user.email != '' and update_user_dto.auth_user.email != update_user_dto.new_auth_user.email:
update_user_dto.new_auth_user.email is not None
and update_user_dto.new_auth_user.email != ""
and update_user_dto.auth_user.email != update_user_dto.new_auth_user.email
):
user_by_new_e_mail = self._auth_users.find_auth_user_by_email(update_user_dto.new_auth_user.email) user_by_new_e_mail = self._auth_users.find_auth_user_by_email(update_user_dto.new_auth_user.email)
if user_by_new_e_mail is not None: if user_by_new_e_mail is not None:
raise ServiceException(ServiceErrorCode.InvalidUser, "User already exists") raise ServiceException(ServiceErrorCode.InvalidUser, 'User already exists')
user.email = update_user_dto.new_auth_user.email user.email = update_user_dto.new_auth_user.email
# update password # update password
if ( if update_user_dto.new_auth_user.password is not None and update_user_dto.change_password and user.password != self._hash_sha256(update_user_dto.new_auth_user.password, user.password_salt):
update_user_dto.new_auth_user.password is not None
and update_user_dto.change_password
and user.password != self._hash_sha256(update_user_dto.new_auth_user.password, user.password_salt)
):
user.password_salt = uuid.uuid4() user.password_salt = uuid.uuid4()
user.password = self._hash_sha256(update_user_dto.new_auth_user.password, user.password_salt) user.password = self._hash_sha256(update_user_dto.new_auth_user.password, user.password_salt)
# update role # update role
if ( if user.auth_role == update_user_dto.auth_user.auth_role and user.auth_role != update_user_dto.new_auth_user.auth_role:
user.auth_role == update_user_dto.auth_user.auth_role
and user.auth_role != update_user_dto.new_auth_user.auth_role
):
user.auth_role = update_user_dto.new_auth_user.auth_role user.auth_role = update_user_dto.new_auth_user.auth_role
self._auth_users.update_auth_user(user) self._auth_users.update_auth_user(user)
@@ -415,61 +414,43 @@ class AuthService(AuthServiceABC):
self._auth_users.delete_auth_user(user) self._auth_users.delete_auth_user(user)
self._db.save_changes() self._db.save_changes()
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Cannot delete user", e) self._logger.error(__name__, f'Cannot delete user', e)
raise ServiceException(ServiceErrorCode.UnableToDelete, f"Cannot delete user by mail {email}") raise ServiceException(ServiceErrorCode.UnableToDelete, f'Cannot delete user by mail {email}')
async def delete_auth_user_async(self, user_dto: AuthUser): async def delete_auth_user_async(self, user_dto: AuthUser):
try: try:
self._auth_users.delete_auth_user(AUT.to_db(user_dto)) self._auth_users.delete_auth_user(AUT.to_db(user_dto))
self._db.save_changes() self._db.save_changes()
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Cannot delete user", e) self._logger.error(__name__, f'Cannot delete user', e)
raise ServiceException( raise ServiceException(ServiceErrorCode.UnableToDelete, f'Cannot delete user by mail {user_dto.email}')
ServiceErrorCode.UnableToDelete,
f"Cannot delete user by mail {user_dto.email}",
)
def verify_login(self, token_str: str) -> bool: def verify_login(self, token_str: str) -> bool:
try: try:
token = self.decode_token(token_str) token = self.decode_token(token_str)
if token is None or "email" not in token: if token is None or 'email' not in token:
raise ServiceException(ServiceErrorCode.InvalidData, "Token invalid") raise ServiceException(ServiceErrorCode.InvalidData, 'Token invalid')
user = self._auth_users.find_auth_user_by_email(token["email"]) user = self._auth_users.find_auth_user_by_email(token['email'])
if user is None: if user is None:
raise ServiceException(ServiceErrorCode.InvalidData, "Token expired") raise ServiceException(ServiceErrorCode.InvalidData, 'Token expired')
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Token invalid", e) self._logger.error(__name__, f'Token invalid', e)
return False
return True
def verify_api_key(self, api_key: str) -> bool:
try:
keys = self._api_keys.get_api_keys().select(self._get_api_key_str)
if not keys.contains(api_key):
raise ServiceException(ServiceErrorCode.InvalidData, "API-Key invalid")
except Exception as e:
self._logger.error(__name__, f"API-Key invalid", e)
return False return False
return True return True
async def login_async(self, user_dto: AuthUser) -> TokenDTO: async def login_async(self, user_dto: AuthUser) -> TokenDTO:
if user_dto is None: if user_dto is None:
raise ServiceException(ServiceErrorCode.InvalidData, "User not set") raise ServiceException(ServiceErrorCode.InvalidData, 'User not set')
db_user = self._auth_users.find_auth_user_by_email(user_dto.email) db_user = self._auth_users.find_auth_user_by_email(user_dto.email)
if db_user is None: if db_user is None:
raise ServiceException(ServiceErrorCode.InvalidUser, f"User not found") raise ServiceException(ServiceErrorCode.InvalidUser, f'User not found')
user_dto.password = self._hash_sha256(user_dto.password, db_user.password_salt) user_dto.password = self._hash_sha256(user_dto.password, db_user.password_salt)
if db_user.password != user_dto.password: if db_user.password != user_dto.password:
raise ServiceException(ServiceErrorCode.InvalidUser, "Wrong password") raise ServiceException(ServiceErrorCode.InvalidUser, 'Wrong password')
if db_user.confirmation_id is not None:
raise ServiceException(ServiceErrorCode.Forbidden, "E-Mail not verified")
token = self.generate_token(db_user) token = self.generate_token(db_user)
refresh_token = self._create_and_save_refresh_token(db_user) refresh_token = self._create_and_save_refresh_token(db_user)
@@ -479,82 +460,58 @@ class AuthService(AuthServiceABC):
self._db.save_changes() self._db.save_changes()
return TokenDTO(token, refresh_token) return TokenDTO(token, refresh_token)
async def login_discord_async(self, user_dto: AuthUserDTO, dc_id: int) -> TokenDTO: async def login_discord_async(self, user_dto: AuthUserDTO) -> TokenDTO:
if user_dto is None: if user_dto is None:
raise ServiceException(ServiceErrorCode.InvalidData, "User not set") raise ServiceException(ServiceErrorCode.InvalidData, 'User not set')
members = self._users.get_users_by_discord_id(dc_id)
if members.count() == 0:
raise ServiceException(ServiceErrorCode.InvalidUser, f"Member not found")
added_user = False
db_user = self._auth_users.find_auth_user_by_email(user_dto.email) db_user = self._auth_users.find_auth_user_by_email(user_dto.email)
if db_user is None: if db_user is None:
self.add_auth_user(user_dto) await self.add_auth_user_async(user_dto)
added_user = True # raise ServiceException(ServiceErrorCode.InvalidUser, f'User not found')
db_user = self._auth_users.get_auth_user_by_email(user_dto.email) db_user = self._auth_users.get_auth_user_by_email(user_dto.email)
user_ids = db_user.users.select(lambda x: x.id)
for user in self._users.get_users_by_discord_id(dc_id):
if user.id in user_ids:
continue
self._auth_users.add_auth_user_user_rel(AuthUserUsersRelation(db_user, user))
if db_user.confirmation_id is not None and not added_user:
raise ServiceException(ServiceErrorCode.Forbidden, "E-Mail not verified")
token = self.generate_token(db_user) token = self.generate_token(db_user)
refresh_token = self._create_and_save_refresh_token(db_user) refresh_token = self._create_and_save_refresh_token(db_user)
if db_user.forgot_password_id is not None: if db_user.forgot_password_id is not None:
db_user.forgot_password_id = None db_user.forgot_password_id = None
self._db.save_changes() self._db.save_changes()
return TokenDTO(token, refresh_token, first_login=added_user) return TokenDTO(token, refresh_token)
async def refresh_async(self, token_dto: TokenDTO) -> TokenDTO: async def refresh_async(self, token_dto: TokenDTO) -> TokenDTO:
if token_dto is None: if token_dto is None:
raise ServiceException(ServiceErrorCode.InvalidData, f"Token not set") raise ServiceException(ServiceErrorCode.InvalidData, f'Token not set')
try: try:
token = self.decode_token(token_dto.token) token = self.decode_token(token_dto.token)
if token is None or "email" not in token: if token is None or 'email' not in token:
raise ServiceException(ServiceErrorCode.InvalidData, "Token invalid") raise ServiceException(ServiceErrorCode.InvalidData, 'Token invalid')
user = self._auth_users.get_auth_user_by_email(token["email"]) user = self._auth_users.get_auth_user_by_email(token['email'])
if ( if user is None or user.refresh_token != token_dto.refresh_token or user.refresh_token_expire_time <= datetime.now():
user is None raise ServiceException(ServiceErrorCode.InvalidData, 'Token expired')
or user.refresh_token != token_dto.refresh_token
or user.refresh_token_expire_time <= datetime.now()
):
raise ServiceException(ServiceErrorCode.InvalidData, "Token expired")
return TokenDTO(self.generate_token(user), self._create_and_save_refresh_token(user)) return TokenDTO(self.generate_token(user), self._create_and_save_refresh_token(user))
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Refreshing token failed", e) self._logger.error(__name__, f'Refreshing token failed', e)
return TokenDTO("", "") return TokenDTO('', '')
async def revoke_async(self, token_dto: TokenDTO): async def revoke_async(self, token_dto: TokenDTO):
if token_dto is None or token_dto.token is None or token_dto.refresh_token is None: if token_dto is None or token_dto.token is None or token_dto.refresh_token is None:
raise ServiceException(ServiceErrorCode.InvalidData, "Token not set") raise ServiceException(ServiceErrorCode.InvalidData, 'Token not set')
try: try:
token = self.decode_token(token_dto.token) token = self.decode_token(token_dto.token)
user = self._auth_users.get_auth_user_by_email(token["email"]) user = self._auth_users.get_auth_user_by_email(token['email'])
if ( if user is None or user.refresh_token != token_dto.refresh_token or user.refresh_token_expire_time <= datetime.now():
user is None raise ServiceException(ServiceErrorCode.InvalidData, 'Token expired')
or user.refresh_token != token_dto.refresh_token
or user.refresh_token_expire_time <= datetime.now()
):
raise ServiceException(ServiceErrorCode.InvalidData, "Token expired")
user.refresh_token = None user.refresh_token = None
self._auth_users.update_auth_user(user) self._auth_users.update_auth_user(user)
self._db.save_changes() self._db.save_changes()
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Refreshing token failed", e) self._logger.error(__name__, f'Refreshing token failed', e)
async def confirm_email_async(self, id: str) -> bool: async def confirm_email_async(self, id: str) -> bool:
user = self._auth_users.find_auth_user_by_confirmation_id(id) user = self._auth_users.find_auth_user_by_confirmation_id(id)
@@ -583,16 +540,13 @@ class AuthService(AuthServiceABC):
async def reset_password_async(self, rp_dto: ResetPasswordDTO): async def reset_password_async(self, rp_dto: ResetPasswordDTO):
user = self._auth_users.find_auth_user_by_forgot_password_id(rp_dto.id) user = self._auth_users.find_auth_user_by_forgot_password_id(rp_dto.id)
if user is None: if user is None:
raise ServiceException( raise ServiceException(ServiceErrorCode.InvalidUser, f'User by forgot password id {rp_dto.id} not found')
ServiceErrorCode.InvalidUser,
f"User by forgot password id {rp_dto.id} not found",
)
if user.confirmation_id is not None: if user.confirmation_id is not None:
raise ServiceException(ServiceErrorCode.InvalidUser, f"E-Mail not confirmed") raise ServiceException(ServiceErrorCode.InvalidUser, f'E-Mail not confirmed')
if user.password is None or rp_dto.password == "": if user.password is None or rp_dto.password == '':
raise ServiceException(ServiceErrorCode.InvalidData, f"Password not set") raise ServiceException(ServiceErrorCode.InvalidData, f'Password not set')
user.password_salt = uuid.uuid4() user.password_salt = uuid.uuid4()
user.password = self._hash_sha256(rp_dto.password, user.password_salt) user.password = self._hash_sha256(rp_dto.password, user.password_salt)

View File

@@ -2,6 +2,7 @@ from typing import Optional
from cpl_discord.service import DiscordBotServiceABC from cpl_discord.service import DiscordBotServiceABC
from cpl_query.extension import List from cpl_query.extension import List
from flask import jsonify
from bot_api.abc.auth_service_abc import AuthServiceABC from bot_api.abc.auth_service_abc import AuthServiceABC
from bot_api.exception.service_error_code_enum import ServiceErrorCode from bot_api.exception.service_error_code_enum import ServiceErrorCode
@@ -9,6 +10,7 @@ from bot_api.exception.service_exception import ServiceException
from bot_api.filter.discord.server_select_criteria import ServerSelectCriteria from bot_api.filter.discord.server_select_criteria import ServerSelectCriteria
from bot_api.model.discord.server_dto import ServerDTO from bot_api.model.discord.server_dto import ServerDTO
from bot_api.model.discord.server_filtered_result_dto import ServerFilteredResultDTO from bot_api.model.discord.server_filtered_result_dto import ServerFilteredResultDTO
from bot_api.model.error_dto import ErrorDTO
from bot_api.transformer.server_transformer import ServerTransformer from bot_api.transformer.server_transformer import ServerTransformer
from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC
from bot_data.abc.server_repository_abc import ServerRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC
@@ -18,13 +20,14 @@ from bot_data.model.server import Server
class DiscordService: class DiscordService:
def __init__( def __init__(
self, self,
bot: DiscordBotServiceABC, bot: DiscordBotServiceABC,
servers: ServerRepositoryABC, servers: ServerRepositoryABC,
auth: AuthServiceABC, auth: AuthServiceABC,
auth_users: AuthUserRepositoryABC, auth_users: AuthUserRepositoryABC,
users: UserRepositoryABC, users: UserRepositoryABC,
): ):
self._bot = bot self._bot = bot
self._servers = servers self._servers = servers
@@ -33,59 +36,70 @@ class DiscordService:
self._users = users self._users = users
def _to_dto(self, x: Server) -> Optional[ServerDTO]: def _to_dto(self, x: Server) -> Optional[ServerDTO]:
guild = self._bot.get_guild(x.discord_id) guild = self._bot.get_guild(x.discord_server_id)
if guild is None: if guild is None:
return ServerTransformer.to_dto(x, "", 0, None) return ServerTransformer.to_dto(
x,
'',
0,
None
)
return ServerTransformer.to_dto(x, guild.name, guild.member_count, guild.icon) return ServerTransformer.to_dto(
x,
guild.name,
guild.member_count,
guild.icon
)
async def get_all_servers(self) -> List[ServerDTO]: async def get_all_servers(self) -> List[ServerDTO]:
servers = List(ServerDTO, self._servers.get_servers()) servers = List(ServerDTO, self._servers.get_servers())
return servers.select(self._to_dto).where(lambda x: x.name != "") return servers.select(self._to_dto).where(lambda x: x.name != '')
async def get_all_servers_by_user(self) -> List[ServerDTO]: async def get_all_servers_by_user(self) -> List[ServerDTO]:
token = self._auth.get_decoded_token_from_request() token = self._auth.get_decoded_token_from_request()
if token is None or "email" not in token or "role" not in token: if token is None or 'email' not in token or 'role' not in token:
raise ServiceException(ServiceErrorCode.InvalidData, "Token invalid") raise ServiceException(ServiceErrorCode.InvalidData, 'Token invalid')
role = AuthRoleEnum(token["role"]) role = AuthRoleEnum(token['role'])
servers = self._servers.get_servers() servers = self._servers.get_servers()
if role != AuthRoleEnum.admin: if role != AuthRoleEnum.admin:
auth_user = self._auth_users.find_auth_user_by_email(token["email"]) auth_user = self._auth_users.find_auth_user_by_email(token['email'])
if auth_user is not None: if auth_user is not None:
user_ids = auth_user.users.select(lambda x: x.server is not None and x.server.id) user_ids = auth_user.users.select(lambda x: x.server is not None and x.server.server_id)
servers = servers.where(lambda x: x.id in user_ids) servers = servers.where(lambda x: x.server_id in user_ids)
servers = List(ServerDTO, servers) servers = List(ServerDTO, servers)
return servers.select(self._to_dto).where(lambda x: x.name != "") return servers.select(self._to_dto).where(lambda x: x.name != '')
async def get_filtered_servers_async(self, criteria: ServerSelectCriteria) -> ServerFilteredResultDTO: async def get_filtered_servers_async(self, criteria: ServerSelectCriteria) -> ServerFilteredResultDTO:
token = self._auth.get_decoded_token_from_request() token = self._auth.get_decoded_token_from_request()
if token is None or "email" not in token or "role" not in token: if token is None or 'email' not in token or 'role' not in token:
raise ServiceException(ServiceErrorCode.InvalidData, "Token invalid") raise ServiceException(ServiceErrorCode.InvalidData, 'Token invalid')
role = AuthRoleEnum(token["role"]) role = AuthRoleEnum(token['role'])
filtered_result = self._servers.get_filtered_servers(criteria) filtered_result = self._servers.get_filtered_servers(criteria)
# filter out servers, where the user not exists # filter out servers, where the user not exists
if role != AuthRoleEnum.admin: if role != AuthRoleEnum.admin:
auth_user = self._auth_users.find_auth_user_by_email(token["email"]) auth_user = self._auth_users.find_auth_user_by_email(token['email'])
if auth_user is not None: if auth_user is not None:
user_ids = auth_user.users.select(lambda x: x.server is not None and x.server.id) user_ids = auth_user.users.select(lambda x: x.server is not None and x.server.server_id)
filtered_result.result = filtered_result.result.where(lambda x: x.id in user_ids) filtered_result.result = filtered_result.result.where(lambda x: x.server_id in user_ids)
servers: List = filtered_result.result.select(self._to_dto).where(lambda x: x.name != "") servers: List = filtered_result.result.select(self._to_dto).where(lambda x: x.name != '')
result = List(ServerDTO, servers) result = List(ServerDTO, servers)
if criteria.name is not None and criteria.name != "": if criteria.name is not None and criteria.name != '':
result = result.where( result = result.where(lambda x: criteria.name.lower() in x.name.lower() or x.name.lower() == criteria.name.lower())
lambda x: criteria.name.lower() in x.name.lower() or x.name.lower() == criteria.name.lower()
)
return ServerFilteredResultDTO(List(ServerDTO, result), servers.count()) return ServerFilteredResultDTO(
List(ServerDTO, result),
servers.count()
)
async def get_server_by_id_async(self, id: int) -> ServerDTO: async def get_server_by_id_async(self, id: int) -> ServerDTO:
server = self._servers.get_server_by_id(id) server = self._servers.get_server_by_id(id)
guild = self._bot.get_guild(server.discord_id) guild = self._bot.get_guild(server.discord_server_id)
server_dto = ServerTransformer.to_dto(server, guild.name, guild.member_count, guild.icon) server_dto = ServerTransformer.to_dto(server, guild.name, guild.member_count, guild.icon)
return server_dto return server_dto

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
bot sh-edraft.de Discord bot bot Keksdose bot
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Discord bot for some customers of sh-edraft.de Discord bot for the Keksdose discord Server
:copyright: (c) 2022 - 2023 sh-edraft.de :copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details. :license: MIT, see LICENSE for more details.
""" """
@@ -14,8 +14,8 @@ Discord bot for some customers of sh-edraft.de
__title__ = 'bot_api.transformer' __title__ = 'bot_api.transformer'
__author__ = 'Sven Heidemann' __author__ = 'Sven Heidemann'
__license__ = 'MIT' __license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 - 2023 sh-edraft.de' __copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.1.7' __version__ = '0.3.dev25'
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro') VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='1', micro='7') version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -1,19 +1,13 @@
from datetime import datetime from datetime import datetime, timezone
from cpl_core.dependency_injection import ServiceProviderABC
from cpl_discord.service import DiscordBotServiceABC
from cpl_query.extension import List
from bot_api.abc.transformer_abc import TransformerABC from bot_api.abc.transformer_abc import TransformerABC
from bot_api.model.auth_user_dto import AuthUserDTO from bot_api.model.auth_user_dto import AuthUserDTO
from bot_api.model.user_dto import UserDTO
from bot_data.model.auth_role_enum import AuthRoleEnum from bot_data.model.auth_role_enum import AuthRoleEnum
from bot_data.model.auth_user import AuthUser from bot_data.model.auth_user import AuthUser
from bot_data.model.user import User
from modules.permission.abc.permission_service_abc import PermissionServiceABC
class AuthUserTransformer(TransformerABC): class AuthUserTransformer(TransformerABC):
@staticmethod @staticmethod
def to_db(dto: AuthUserDTO) -> AuthUser: def to_db(dto: AuthUserDTO) -> AuthUser:
return AuthUser( return AuthUser(
@@ -28,54 +22,17 @@ class AuthUserTransformer(TransformerABC):
None, None,
datetime.now(), datetime.now(),
AuthRoleEnum.normal if dto.auth_role is None else AuthRoleEnum(dto.auth_role), AuthRoleEnum.normal if dto.auth_role is None else AuthRoleEnum(dto.auth_role),
auth_user_id=0 if dto.id is None else dto.id, auth_user_id=0 if dto.id is None else dto.id
) )
@staticmethod @staticmethod
@ServiceProviderABC.inject def to_dto(db: AuthUser, password: str = None) -> AuthUserDTO:
def _is_technician(user: User, bot: DiscordBotServiceABC, permissions: PermissionServiceABC):
guild = bot.get_guild(user.server.discord_id)
member = guild.get_member(user.discord_id)
return permissions.is_member_technician(member)
@staticmethod
@ServiceProviderABC.inject
def _is_admin(user: User, bot: DiscordBotServiceABC, permissions: PermissionServiceABC):
guild = bot.get_guild(user.server.discord_id)
member = guild.get_member(user.discord_id)
return permissions.is_member_admin(member)
@staticmethod
@ServiceProviderABC.inject
def _is_moderator(user: User, bot: DiscordBotServiceABC, permissions: PermissionServiceABC):
guild = bot.get_guild(user.server.discord_id)
member = guild.get_member(user.discord_id)
return permissions.is_member_moderator(member)
@classmethod
def to_dto(cls, db: AuthUser, password: str = None) -> AuthUserDTO:
return AuthUserDTO( return AuthUserDTO(
db.id, db.id,
db.first_name, db.first_name,
db.last_name, db.last_name,
db.email, db.email,
"" if password is None else password, '' if password is None else password,
db.confirmation_id, db.confirmation_id,
db.auth_role, db.auth_role
List(
UserDTO,
db.users.select(
lambda u: UserDTO(
u.id,
u.discord_id,
u.xp,
u.server,
cls._is_technician(u),
cls._is_admin(u),
cls._is_moderator(u),
)
),
),
db.created_at,
db.modified_at,
) )

View File

@@ -8,6 +8,7 @@ from bot_data.model.server import Server
class ServerTransformer(TransformerABC): class ServerTransformer(TransformerABC):
@staticmethod @staticmethod
def to_db(dto: ServerDTO) -> Server: def to_db(dto: ServerDTO) -> Server:
return Server(dto.discord_id) return Server(dto.discord_id)
@@ -15,8 +16,8 @@ class ServerTransformer(TransformerABC):
@staticmethod @staticmethod
def to_dto(db: Server, name: str, member_count: int, icon_url: Optional[discord.Asset]) -> ServerDTO: def to_dto(db: Server, name: str, member_count: int, icon_url: Optional[discord.Asset]) -> ServerDTO:
return ServerDTO( return ServerDTO(
db.id, db.server_id,
db.discord_id, db.discord_server_id,
name, name,
member_count, member_count,
icon_url.url if icon_url is not None else None, icon_url.url if icon_url is not None else None,

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
bot sh-edraft.de Discord bot bot Keksdose bot
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Discord bot for some customers of sh-edraft.de Discord bot for the Keksdose discord Server
:copyright: (c) 2022 - 2023 sh-edraft.de :copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details. :license: MIT, see LICENSE for more details.
""" """
@@ -14,8 +14,8 @@ Discord bot for some customers of sh-edraft.de
__title__ = 'bot_core' __title__ = 'bot_core'
__author__ = 'Sven Heidemann' __author__ = 'Sven Heidemann'
__license__ = 'MIT' __license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 - 2023 sh-edraft.de' __copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.1.7' __version__ = '0.3.dev25'
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports # imports
VersionInfo = namedtuple('VersionInfo', 'major minor micro') VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='1', micro='7') version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -1,21 +1,21 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
bot sh-edraft.de Discord bot bot Keksdose bot
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Discord bot for some customers of sh-edraft.de Discord bot for the Keksdose discord Server
:copyright: (c) 2022 - 2023 sh-edraft.de :copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details. :license: MIT, see LICENSE for more details.
""" """
__title__ = 'bot_core.abc' __title__ = 'bot_core.service'
__author__ = 'Sven Heidemann' __author__ = 'Sven Heidemann'
__license__ = 'MIT' __license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 - 2023 sh-edraft.de' __copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.1.7' __version__ = '0.3.dev25'
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro') VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='1', micro='7') version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -1,73 +0,0 @@
from abc import ABC, abstractmethod
from datetime import datetime
from typing import Callable, Union
import discord
from cpl_query.extension import List
from discord.ext.commands import Context
from bot_data.model.auto_role_rule import AutoRoleRule
from bot_data.model.server_config import ServerConfig
from bot_data.model.user import User
class ClientUtilsABC(ABC):
@abstractmethod
def __init__(self):
pass
@abstractmethod
def received_command(self, guild_id: int):
pass
@abstractmethod
def moved_user(self, guild_id: int):
pass
@abstractmethod
def moved_users(self, guild_id: int, count: int):
pass
@abstractmethod
def get_client(self, dc_ic: int, guild_id: int):
pass
@abstractmethod
async def check_if_bot_is_ready_yet(self) -> bool:
pass
@abstractmethod
async def check_if_bot_is_ready_yet_and_respond(self, ctx: Context) -> bool:
pass
@abstractmethod
async def presence_game(self, t_key: str):
pass
@abstractmethod
def get_auto_complete_list(self, _l: List, current: str, select: Callable = None) -> List:
pass
@abstractmethod
def is_message_xp_count_by_hour_higher_that_max_message_count_per_hour(
self,
created_at: datetime,
user: User,
settings: ServerConfig,
is_reaction: bool = False,
) -> bool:
pass
@abstractmethod
def get_ontime_for_user(self, user: User) -> float:
pass
@abstractmethod
async def react_to_message_by_auto_role_rule(
self, discord_channel_id: int, discord_message_id: int, rule: AutoRoleRule, guild: discord.Guild
):
pass
@abstractmethod
async def check_default_role(self, member: Union[discord.User, discord.Member]):
pass

View File

@@ -0,0 +1,27 @@
from abc import ABC, abstractmethod
from discord.ext.commands import Context
class ClientUtilsServiceABC(ABC):
@abstractmethod
def __init__(self): pass
@abstractmethod
def received_command(self, guild_id: int): pass
@abstractmethod
def moved_user(self, guild_id: int): pass
@abstractmethod
def get_client(self, dc_ic: int, guild_id: int): pass
@abstractmethod
async def check_if_bot_is_ready_yet(self) -> bool: pass
@abstractmethod
async def check_if_bot_is_ready_yet_and_respond(self, ctx: Context) -> bool: pass
@abstractmethod
async def presence_game(self, t_key: str): pass

View File

@@ -9,16 +9,11 @@ from bot_core.configuration.file_logging_settings import FileLoggingSettings
class CustomFileLoggerABC(Logger, ABC): class CustomFileLoggerABC(Logger, ABC):
@abstractmethod @abstractmethod
def __init__( def __init__(self, key: str, config: ConfigurationABC, time_format: TimeFormatSettings, env: ApplicationEnvironmentABC):
self,
key: str,
config: ConfigurationABC,
time_format: TimeFormatSettings,
env: ApplicationEnvironmentABC,
):
self._key = key self._key = key
self._settings: LoggingSettings = config.get_configuration(f"{FileLoggingSettings.__name__}_{key}") self._settings: LoggingSettings = config.get_configuration(f'{FileLoggingSettings.__name__}_{key}')
Logger.__init__(self, self._settings, time_format, env) Logger.__init__(self, self._settings, time_format, env)
self._begin_log() self._begin_log()
@@ -29,41 +24,41 @@ class CustomFileLoggerABC(Logger, ABC):
def _begin_log(self): def _begin_log(self):
console_level = self._console.value console_level = self._console.value
self._console = LoggingLevelEnum.OFF self._console = LoggingLevelEnum.OFF
self.info(__name__, f"Starting...") self.info(__name__, f'Starting...')
self._console = LoggingLevelEnum(console_level) self._console = LoggingLevelEnum(console_level)
def _get_string(self, name_list_as_str: str, level: LoggingLevelEnum, message: str) -> str: def _get_string(self, name_list_as_str: str, level: LoggingLevelEnum, message: str) -> str:
names = name_list_as_str.split(" ") names = name_list_as_str.split(' ')
log_level = level.name log_level = level.name
string = f"<{self._get_datetime_now()}> [ {log_level} ]" string = f'<{self._get_datetime_now()}> [ {log_level} ]'
for name in names: for name in names:
string += f" [ {name} ]" string += f' [ {name} ]'
string += f": {message}" string += f': {message}'
return string return string
def header(self, string: str): def header(self, string: str):
super().header(string) super().header(string)
def trace(self, name: str, message: str): def trace(self, name: str, message: str):
name = f"{name} {self._key}" name = f'{name} {self._key}'
super().trace(name, message) super().trace(name, message)
def debug(self, name: str, message: str): def debug(self, name: str, message: str):
name = f"{name} {self._key}" name = f'{name} {self._key}'
super().debug(name, message) super().debug(name, message)
def info(self, name: str, message: str): def info(self, name: str, message: str):
name = f"{name} {self._key}" name = f'{name} {self._key}'
super().info(name, message) super().info(name, message)
def warn(self, name: str, message: str): def warn(self, name: str, message: str):
name = f"{name} {self._key}" name = f'{name} {self._key}'
super().warn(name, message) super().warn(name, message)
def error(self, name: str, message: str, ex: Exception = None): def error(self, name: str, message: str, ex: Exception = None):
name = f"{name} {self._key}" name = f'{name} {self._key}'
super().error(name, message, ex) super().error(name, message, ex)
def fatal(self, name: str, message: str, ex: Exception = None): def fatal(self, name: str, message: str, ex: Exception = None):
name = f"{name} {self._key}" name = f'{name} {self._key}'
super().fatal(name, message, ex) super().fatal(name, message, ex)

View File

@@ -1,5 +1,5 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Union, Optional from typing import Union
import discord import discord
from cpl_query.extension import List from cpl_query.extension import List
@@ -8,60 +8,24 @@ from discord.ext.commands import Context
class MessageServiceABC(ABC): class MessageServiceABC(ABC):
@abstractmethod
def __init__(self):
pass
@abstractmethod @abstractmethod
async def delete_messages(self, messages: List[discord.Message], guild_id: int, without_tracking=False): def __init__(self): pass
pass
@abstractmethod @abstractmethod
async def delete_message(self, message: discord.Message, without_tracking=False): async def delete_messages(self, messages: List[discord.Message], guild_id: int, without_tracking=False): pass
pass
@abstractmethod @abstractmethod
async def send_channel_message( async def delete_message(self, message: discord.Message, without_tracking=False): pass
self,
channel: discord.TextChannel,
message: Union[str, discord.Embed],
is_persistent: bool = False,
wait_before_delete: int = None,
without_tracking=False,
):
pass
@abstractmethod @abstractmethod
async def send_dm_message( async def send_channel_message(self, channel: discord.TextChannel, message: Union[str, discord.Embed], without_tracking=True): pass
self,
message: Union[str, discord.Embed],
receiver: Union[discord.User, discord.Member],
without_tracking=False,
):
pass
@abstractmethod @abstractmethod
async def send_ctx_msg( async def send_dm_message(self, message: Union[str, discord.Embed], receiver: Union[discord.User, discord.Member], without_tracking=False): pass
self,
ctx: Context,
message: Union[str, discord.Embed],
file: discord.File = None,
is_persistent: bool = False,
is_public: bool = False,
wait_before_delete: int = None,
without_tracking=True,
) -> Optional[discord.Message]:
pass
@abstractmethod @abstractmethod
async def send_interaction_msg( async def send_ctx_msg(self, ctx: Context, message: Union[str, discord.Embed], file: discord.File = None, is_persistent: bool = False, wait_before_delete: int = None, without_tracking=True): pass
self,
interaction: Interaction, @abstractmethod
message: Union[str, discord.Embed], async def send_interaction_msg(self, interaction: Interaction, message: Union[str, discord.Embed], is_persistent: bool = False, wait_before_delete: int = None, without_tracking=True, **kwargs): pass
is_persistent: bool = False,
is_public: bool = False,
wait_before_delete: int = None,
without_tracking=True,
**kwargs
):
pass

View File

@@ -7,6 +7,7 @@ from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
class ModuleABC(StartupExtensionABC): class ModuleABC(StartupExtensionABC):
@abstractmethod @abstractmethod
def __init__(self, dc: DiscordCollectionABC, feature_flag: FeatureFlagsEnum): def __init__(self, dc: DiscordCollectionABC, feature_flag: FeatureFlagsEnum):
StartupExtensionABC.__init__(self) StartupExtensionABC.__init__(self)

View File

@@ -2,9 +2,9 @@
"ProjectSettings": { "ProjectSettings": {
"Name": "bot-core", "Name": "bot-core",
"Version": { "Version": {
"Major": "1", "Major": "0",
"Minor": "1", "Minor": "3",
"Micro": "7" "Micro": "dev70"
}, },
"Author": "Sven Heidemann", "Author": "Sven Heidemann",
"AuthorEmail": "sven.heidemann@sh-edraft.de", "AuthorEmail": "sven.heidemann@sh-edraft.de",
@@ -16,10 +16,10 @@
"LicenseName": "MIT", "LicenseName": "MIT",
"LicenseDescription": "MIT, see LICENSE for more details.", "LicenseDescription": "MIT, see LICENSE for more details.",
"Dependencies": [ "Dependencies": [
"cpl-core==2022.12.0" "cpl-core>=0.3.dev70"
], ],
"DevDependencies": [ "DevDependencies": [
"cpl-cli==2022.12.0" "cpl-cli==2022.10.0"
], ],
"PythonVersion": ">=3.10.4", "PythonVersion": ">=3.10.4",
"PythonPath": {}, "PythonPath": {},

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
bot sh-edraft.de Discord bot bot Keksdose bot
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
Discord bot for some customers of sh-edraft.de Discord bot for the Keksdose discord Server
:copyright: (c) 2022 - 2023 sh-edraft.de :copyright: (c) 2022 sh-edraft.de
:license: MIT, see LICENSE for more details. :license: MIT, see LICENSE for more details.
""" """
@@ -14,8 +14,8 @@ Discord bot for some customers of sh-edraft.de
__title__ = 'bot_core.configuration' __title__ = 'bot_core.configuration'
__author__ = 'Sven Heidemann' __author__ = 'Sven Heidemann'
__license__ = 'MIT' __license__ = 'MIT'
__copyright__ = 'Copyright (c) 2022 - 2023 sh-edraft.de' __copyright__ = 'Copyright (c) 2022 sh-edraft.de'
__version__ = '1.1.7' __version__ = '0.3.dev25'
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple('VersionInfo', 'major minor micro') VersionInfo = namedtuple('VersionInfo', 'major minor micro')
version_info = VersionInfo(major='1', minor='1', micro='7') version_info = VersionInfo(major='0', minor='3', micro='dev25')

View File

@@ -1,25 +1,33 @@
import traceback
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl_core.utils.json_processor import JSONProcessor from cpl_core.console import Console, ForegroundColorEnum
from cpl_query.extension import List from cpl_query.extension import List
from bot_core.configuration.file_logging_settings import FileLoggingSettings from bot_core.configuration.file_logging_settings import FileLoggingSettings
class BotLoggingSettings(ConfigurationModelABC): class BotLoggingSettings(ConfigurationModelABC):
def __init__(self, **kwargs: dict):
def __init__(self):
ConfigurationModelABC.__init__(self) ConfigurationModelABC.__init__(self)
self._files: List[FileLoggingSettings] = List(FileLoggingSettings) self._files: List[FileLoggingSettings] = List(FileLoggingSettings)
if kwargs is not None:
self._files_from_dict(kwargs)
@property @property
def files(self) -> List[FileLoggingSettings]: def files(self) -> List[FileLoggingSettings]:
return self._files return self._files
def _files_from_dict(self, settings: dict): def from_dict(self, settings: dict):
files = List(FileLoggingSettings) try:
for s in settings: files = List(FileLoggingSettings)
settings[s]["Key"] = s for s in settings:
files.append(JSONProcessor.process(FileLoggingSettings, settings[s])) st = FileLoggingSettings()
self._files = files settings[s]['Key'] = s
st.from_dict(settings[s])
files.append(st)
self._files = files
except Exception as e:
Console.set_foreground_color(ForegroundColorEnum.red)
Console.write_line(f'[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings')
Console.write_line(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')
Console.set_foreground_color(ForegroundColorEnum.default)

View File

@@ -0,0 +1,53 @@
import traceback
from cpl_core.configuration import ConfigurationModelABC
from cpl_core.console import Console
from cpl_query.extension import List
from bot_core.configuration.server_settings import ServerSettings
class BotSettings(ConfigurationModelABC):
def __init__(self):
ConfigurationModelABC.__init__(self)
self._servers: List[ServerSettings] = List(ServerSettings)
self._technicians: List[int] = List(int)
self._wait_for_restart = 2
self._wait_for_shutdown = 2
@property
def servers(self) -> List[ServerSettings]:
return self._servers
@property
def technicians(self) -> List[int]:
return self._technicians
@property
def wait_for_restart(self) -> int:
return self._wait_for_restart
@property
def wait_for_shutdown(self) -> int:
return self._wait_for_shutdown
def from_dict(self, settings: dict):
try:
self._technicians = settings["Technicians"]
self._wait_for_restart = settings["WaitForRestart"]
self._wait_for_shutdown = settings["WaitForShutdown"]
settings.pop("Technicians")
settings.pop("WaitForRestart")
settings.pop("WaitForShutdown")
servers = List(ServerSettings)
for s in settings:
st = ServerSettings()
settings[s]["Id"] = s
st.from_dict(settings[s])
servers.append(st)
self._servers = servers
except Exception as e:
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings')
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')

View File

@@ -3,24 +3,19 @@ from enum import Enum
class FeatureFlagsEnum(Enum): class FeatureFlagsEnum(Enum):
# modules # modules
achievements_module = "AchievementsModule" api_module = 'ApiModule'
api_module = "ApiModule" admin_module = 'AdminModule'
auto_role_module = "AutoRoleModule" auto_role_module = 'AutoRoleModule'
base_module = "BaseModule" base_module = 'BaseModule'
boot_log_module = "BootLogModule" boot_log_module = 'BootLogModule'
core_module = "CoreModule" core_module = 'CoreModule'
core_extension_module = "CoreExtensionModule" core_extension_module = 'CoreExtensionModule'
config_module = "ConfigModule" data_module = 'DataModule',
data_module = "DataModule" database_module = 'DatabaseModule',
database_module = "DatabaseModule" level_module = 'LevelModule'
level_module = "LevelModule" moderator_module = 'ModeratorModule'
moderator_module = "ModeratorModule" permission_module = 'PermissionModule'
permission_module = "PermissionModule" stats_module = 'StatsModule'
short_role_name_module = "ShortRoleNameModule"
# features # features
api_only = "ApiOnly" api_only = 'ApiOnly'
presence = "Presence" presence = 'Presence'
version_in_presence = "VersionInPresence"
game_server = "GameServer"
sync_xp = "SyncXp"
short_role_name = "ShortRoleName"

Some files were not shown because too many files have changed in this diff Show More