readme and docker-compose
This commit is contained in:
parent
4d85b32efd
commit
34f375989c
13
Dockerfile
13
Dockerfile
@ -0,0 +1,13 @@
|
|||||||
|
FROM python:3.10
|
||||||
|
|
||||||
|
RUN apt-get install libpq-dev -y
|
||||||
|
|
||||||
|
ADD requirements.txt requirements.txt
|
||||||
|
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
ADD src /app
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENTRYPOINT [ "gunicorn", "--chdir=/app/src", "--bond=127.0.0.1:8080", "main:app" ]
|
45
README.md
45
README.md
@ -0,0 +1,45 @@
|
|||||||
|
# JeDoIst клиент
|
||||||
|
|
||||||
|
## Что это?
|
||||||
|
|
||||||
|
- это клиент для телеграм-бота [JeDoIst](https://t.me/jedoist)
|
||||||
|
|
||||||
|
## Зачем вам это?
|
||||||
|
|
||||||
|
- [JeDoIst](https://t.me/jedoist) - это автоматизатор входящей очереди задач
|
||||||
|
|
||||||
|
Не так давно я прочитал книгу Максима Дорофеева "[Джедайские техники](https://t.me/bvn13_blog/108)", данный проект основан на идеях, описанных в данной книге.
|
||||||
|
|
||||||
|
## Какие идеи?
|
||||||
|
|
||||||
|
- каждый человек работает с задачами
|
||||||
|
- чтобы быть успешным, нужно **правильно** работать со своими задачами
|
||||||
|
- очередь входящий задач должна быть одна: должно быть одно место, куда складываются все входящие задачи
|
||||||
|
- **работа** заключается в постоянной проработке и декомпозиции этой очереди по правилам, описанным в книге, по принципу "для того, чтобы обезьянке стало проще выполнять"
|
||||||
|
|
||||||
|
### Улучшения
|
||||||
|
|
||||||
|
- часто идеи приходят в совершенно неподходящий для записывания "от руки" время
|
||||||
|
- хочется иметь инструмент, который послужит входящей очередью, в которую можно буквально **надиктовать** задачу
|
||||||
|
- но очередь задач должна быть одна, поэтому...
|
||||||
|
- голосовые записи должны быть расшифрованы и сложены в единую очередь
|
||||||
|
|
||||||
|
## Поэтому
|
||||||
|
|
||||||
|
- [JeDoIst](https://t.me/jedoist) является этой входящей очередью, в которую можно "надиктовать" свою идею или задачу
|
||||||
|
- он бережно расшифрует голосовое сообщение и передаст его в callback-е на любой удаленный сервер
|
||||||
|
- с одним ограничением: протокол диктует [JeDoIst](https://t.me/jedoist), и должна быть авторизация
|
||||||
|
|
||||||
|
### И поэтому...
|
||||||
|
|
||||||
|
- этот репозиторий предназначен для быстрого развертывания веб-сервера, в который будут отправляться ваши задачи
|
||||||
|
|
||||||
|
## Но!
|
||||||
|
|
||||||
|
- Вопрос: но где же опубликовать этот веб-сервер?
|
||||||
|
- Ответ: на любом VPS
|
||||||
|
- Вопрос: как сохраняются задачи?
|
||||||
|
- Ответ: в одном файле в формате Markdown
|
||||||
|
- Вопрос: файл на сервере, я работаю на ноутбуке - где логика?
|
||||||
|
- Ответ: есть Synkthing, который позволит синхронизировать файл с задачми на сервере и на ноутбуке. И на смартфоне.
|
||||||
|
|
@ -0,0 +1,19 @@
|
|||||||
|
version: '3.3'
|
||||||
|
|
||||||
|
networks:
|
||||||
|
jedoist:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
bot:
|
||||||
|
# build: ./
|
||||||
|
image: localhost:5001/jedoist-client:${APP_VERSION}
|
||||||
|
environment:
|
||||||
|
- TODO_PATH=/mnt/todo
|
||||||
|
- TODO_FILE=${TODO_FILE}
|
||||||
|
volumes:
|
||||||
|
- ${TODO_PATH}:/mnt/todo
|
||||||
|
restart: on-failure
|
||||||
|
networks:
|
||||||
|
- jedoist
|
69
src/main.py
69
src/main.py
@ -1,5 +1,41 @@
|
|||||||
import falcon
|
import falcon
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
# https://falcon.readthedocs.io/en/stable/user/quickstart.html#learning-by-example
|
||||||
|
|
||||||
|
class AuthMiddleware:
|
||||||
|
def process_request(self, req, resp):
|
||||||
|
token = req.get_header('Authorization')
|
||||||
|
account_id = req.get_header('Account-ID')
|
||||||
|
|
||||||
|
challenges = ['Token type="Fernet"']
|
||||||
|
|
||||||
|
if token is None:
|
||||||
|
description = 'Please provide an auth token as part of the request.'
|
||||||
|
|
||||||
|
raise falcon.HTTPUnauthorized(
|
||||||
|
title='Auth token required',
|
||||||
|
description=description,
|
||||||
|
challenges=challenges,
|
||||||
|
href='http://docs.example.com/auth',
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self._token_is_valid(token, account_id):
|
||||||
|
description = (
|
||||||
|
'The provided auth token is not valid. '
|
||||||
|
'Please request a new token and try again.'
|
||||||
|
)
|
||||||
|
|
||||||
|
raise falcon.HTTPUnauthorized(
|
||||||
|
title='Authentication required',
|
||||||
|
description=description,
|
||||||
|
challenges=challenges,
|
||||||
|
href='http://docs.example.com/auth',
|
||||||
|
)
|
||||||
|
|
||||||
|
def _token_is_valid(self, token, account_id):
|
||||||
|
return os.environ["TOKEN"] # Suuuuuure it's valid...
|
||||||
|
|
||||||
|
|
||||||
class RequireJSON:
|
class RequireJSON:
|
||||||
@ -17,6 +53,7 @@ class RequireJSON:
|
|||||||
href='http://docs.examples.com/api/json',
|
href='http://docs.examples.com/api/json',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class JSONTranslator:
|
class JSONTranslator:
|
||||||
# NOTE: Normally you would simply use req.media and resp.media for
|
# NOTE: Normally you would simply use req.media and resp.media for
|
||||||
# this particular use case; this example serves only to illustrate
|
# this particular use case; this example serves only to illustrate
|
||||||
@ -72,18 +109,46 @@ def max_body(limit):
|
|||||||
|
|
||||||
return hook
|
return hook
|
||||||
|
|
||||||
|
########################################################################################################
|
||||||
|
|
||||||
class CreateTaskResource:
|
class CreateTaskResource:
|
||||||
|
|
||||||
|
TODO_HEADER = '# ToDo'
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.__todo_path = "{0}/{1}".format(os.environ['TODO_PATH'], os.environ['TODO_FILE'])
|
||||||
|
|
||||||
def on_post(self, req, resp):
|
def on_post(self, req, resp):
|
||||||
|
task = req.context.doc['task']
|
||||||
|
self.__create_task(task)
|
||||||
quote = {
|
quote = {
|
||||||
'title': 'Получена задача',
|
'title': 'Получена задача',
|
||||||
'description': req.context.doc['task']
|
'description': task
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.media = quote
|
resp.media = quote
|
||||||
|
|
||||||
|
def __create_task(self, text: str) -> None:
|
||||||
|
content = []
|
||||||
|
section_found = False
|
||||||
|
is_waiting_empty_line = False
|
||||||
|
with open(self.__todo_path, 'r', encoding='UTF-8') as file:
|
||||||
|
while line := file.readline():
|
||||||
|
if section_found == False:
|
||||||
|
if line.strip() == CreateTaskResource.TODO_HEADER:
|
||||||
|
section_found = True
|
||||||
|
is_waiting_empty_line = True
|
||||||
|
content.append(line)
|
||||||
|
if section_found and is_waiting_empty_line == True and line.strip() == '':
|
||||||
|
content.append("- [ ] {0}\n".format(text))
|
||||||
|
is_waiting_empty_line = False
|
||||||
|
with open(self.__todo_path, 'w', encoding='utf-8') as file:
|
||||||
|
file.writelines(content)
|
||||||
|
|
||||||
|
########################################################################################################
|
||||||
|
|
||||||
app = falcon.App(
|
app = falcon.App(
|
||||||
middleware=[
|
middleware=[
|
||||||
|
AuthMiddleware(),
|
||||||
RequireJSON(),
|
RequireJSON(),
|
||||||
JSONTranslator()
|
JSONTranslator()
|
||||||
]
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user