Текст сообщений вынесен в отдельный класс. Перемещены секретные файлы. Налажено развертывание контейнеров.

This commit is contained in:
serafim.urukov 2021-07-31 23:36:46 +03:00
parent 815bb0e471
commit b0bca6433f
11 changed files with 241 additions and 66 deletions

5
.gitignore vendored
View File

@ -4,4 +4,7 @@ SpreadsheetsService/utils/__pycache__
SpreadsheetsService/tests/__pycache__ SpreadsheetsService/tests/__pycache__
SpreadsheetsService/manager/__pycache__ SpreadsheetsService/manager/__pycache__
SpreadsheetsService/manager/client_secret.json SpreadsheetsService/manager/client_secret.json
SpreadsheetsService/manager/token.json SpreadsheetsService/manager/token.json
TelegramBot/.secrets
SpreadsheetsService/.secrets
TelegramBot/messages/__pycache__

View File

@ -1,9 +1,34 @@
import flask from functools import wraps
import time
import models
from manager import * from manager import *
import flask
import logging
import models
import time
LOG = logging.getLogger(__name__)
app = flask.Flask(__name__) app = flask.Flask(__name__)
app.db = None
try:
app.db = Manager()
except Exception as e:
app.db_error = e.args
LOG.exception("Cannot init manager")
def availability(func):
"""Декоратор, который проверяет доступность менеджера по работе с Google
Spreadsheets.
Если инициализация менеджера обвалилась, то сервер выдает ошибку 500 и ее
содержимое в JSON."""
@wraps(func)
def wrapper(*args, **kwargs):
if app.db is None:
return flask.jsonify(dict(error=True, message=app.db_error)), 500
func(*args, **kwargs)
return wrapper
@app.route("/") @app.route("/")
@ -12,13 +37,32 @@ def hello_world():
@app.route("/auth") @app.route("/auth")
@availability
def auth(): def auth():
# TODO: return json text instead of text/html.
token = flask.request.args.get("token") token = flask.request.args.get("token")
tg_id = flask.request.args.get("tg_id") tg_id = flask.request.args.get("tg_id")
manager = Manager() student = app.db.get_student_by_name("Дзюба")
student = manager.get_student_by_name("Дзюба") student = Student(1, "Фарид Михайлов", 5374)
return str(student) return flask.jsonify(student)
@app.route("/grades")
@availability
def grades():
"""Возвращает оценки по id пользователя из телеграма."""
tg_id = flask.request.args.get("tg_id")
student = models.Student(1, "Фарид Михайлов", 5374)
student.add_assignment(models.Assignment("ИДЗ 1", [1, 1, 0, 0, 1]))
student.add_assignment(models.Assignment("КР 1", [0.4, 0.8, 1, 0.5, 1], 5))
student.add_assignment(models.Assignment("ИДЗ 2", [1, 1, 1, 0, 1]))
return flask.jsonify(
dict(
score=student.score,
grade=student.grade,
assignments=student.assignments,
)
)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -20,8 +20,8 @@ ASSIGNMENT_SUBJECT = 3
ASSIGNMENT_WEIGHTS = 4 ASSIGNMENT_WEIGHTS = 4
ASSIGNMENT_RANGES = 5 ASSIGNMENT_RANGES = 5
ASSIGNMENT_ALLOWS = 6 ASSIGNMENT_ALLOWS = 6
TOKEN_FILE = pathlib.Path() / "manager" / "token.json" TOKEN_FILE = pathlib.Path() / ".secrets" / "token.json"
CLIENT_SECRET_FILE = pathlib.Path() / "manager" / "client_secret.json" CLIENT_SECRET_FILE = pathlib.Path() / ".secrets" / "client_secret.json"
class Manager: class Manager:

View File

@ -1,10 +1,17 @@
class Assignment: class Assignment(dict):
"""Задание.""" """Задание."""
def __init__(self, name: str, values: list, weight: float=1): def __init__(self, name: str, points: list, weight: float = 1):
self.name = name self.name = name
self.weight = weight self.weight = weight
self.values = values self.points = points
dict.__init__(
self,
name=self.name,
weight=self.weight,
points=self.points,
total=self.total,
)
@property @property
def total(self) -> float: def total(self) -> float:
@ -12,4 +19,4 @@ class Assignment:
`вес*СУММ(рез-ты)` `вес*СУММ(рез-ты)`
""" """
return sum(self.values) * self.weight return sum(self.points) * self.weight

View File

@ -1,8 +1,7 @@
from .assignment import Assignment from .assignment import Assignment
import json
class Student: class Student(dict):
"""Позволяет выполнять операции над студентами.""" """Позволяет выполнять операции над студентами."""
def __init__(self, number: int, name: str, group_id: int, tg_id: int = None): def __init__(self, number: int, name: str, group_id: int, tg_id: int = None):
@ -12,6 +11,15 @@ class Student:
self.tg_id: int = tg_id self.tg_id: int = tg_id
self.assignments = [] self.assignments = []
dict.__init__(
self,
number=self.number,
name=self.name,
group_id=self.group_id,
tg_id=self.tg_id,
assignments=self.assignments,
)
def add_assignment(self, assignment: Assignment): def add_assignment(self, assignment: Assignment):
self.assignments.append(assignment) self.assignments.append(assignment)
@ -24,13 +32,3 @@ class Student:
def score(self): def score(self):
"""Количество баллов у студента.""" """Количество баллов у студента."""
return sum([a.total for a in self.assignments]) return sum([a.total for a in self.assignments])
def __str__(self):
return json.dumps(
{
"number": self.number,
"name": self.name,
"group_id": self.group_id,
"tg_id": self.tg_id,
}
)

View File

@ -1 +1,3 @@
Flask==2.0.0 Flask==2.0.0
google-api-python-client==2.15.0
google-auth-oauthlib==0.4.5

View File

@ -12,7 +12,6 @@ COPY requirements.txt .
RUN python -m pip install -r requirements.txt RUN python -m pip install -r requirements.txt
WORKDIR /app WORKDIR /app
COPY . /app
# Creates a non-root user with an explicit UID and adds permission to access the /app folder # Creates a non-root user with an explicit UID and adds permission to access the /app folder
# For more info, please refer to https://aka.ms/vscode-docker-python-configure-containers # For more info, please refer to https://aka.ms/vscode-docker-python-configure-containers

View File

@ -1,58 +1,97 @@
import logging import logging
import os
import platform
import time
import requests import requests
from pathlib import Path
from telegram.ext import Updater
from telegram.ext import CommandHandler
from telegram.ext import MessageHandler, Filters
TOKEN = "1903048555:AAFK-d7prKvOvwwXo0IPzeRwJ1ZcuOT5ty8" # os.environ["TOKEN"] from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, Update
from telegram.ext import (
Updater,
CommandHandler,
MessageHandler,
Filters,
ConversationHandler,
CallbackContext,
)
from messages import Messages
TOKEN = Path(".secrets/bot_token.txt").read_text()
MESSAGES = Messages()
HOST = "http://carolyn-spreadsheets:5000"
logging.basicConfig( logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
) )
LOG = logging.getLogger(__name__)
LOG.debug("Token is %s", TOKEN)
updater = Updater(token=TOKEN, use_context=True) updater = Updater(token=TOKEN, use_context=True)
dispatcher = updater.dispatcher dispatcher = updater.dispatcher
# ================================================================ BOT COMMANDS # ================================================================ BOT COMMANDS
# Стартовое сообщение (к команде /start)
def start(update, context): def start(update: Update, context: CallbackContext):
"""Стартовое сообщение (к команде /start)"""
user_id = update.message.from_user.id
p = context.args[0] if len(context.args) > 0 else None p = context.args[0] if len(context.args) > 0 else None
if p is None: if p is None:
context.bot.send_message( update.message.reply_markdown_v2(text=MESSAGES.auth.NO_AUTH)
chat_id=update.effective_chat.id,
text="Нет доступа к боту",
)
return return
data = requests.get( update.message.reply_text(text=MESSAGES.auth.START)
"http://127.0.0.1:5000/auth",
params={"token": p, "tg_id": update.effective_chat.id}, LOG.info(f"Authenticanting {user_id}")
).json()
try:
data = requests.get(
f"{HOST}/auth",
params={"token": p, "tg_id": user_id},
).json()
except requests.exceptions.ConnectionError:
update.message.reply_sticker(MESSAGES.stickers.DEAD)
return
context.bot.send_message( context.bot.send_message(
chat_id=update.effective_chat.id, text=MESSAGES.auth.hello(data),
text=f"Привет, {data}",
) )
# Ответ на не командное сообщение (отвечает тем же сообщением) def grades(update: Update, context: CallbackContext):
def echo(update, context): """Команда: Получить оценки студента."""
user_id = update.message.from_user.id
try:
data = requests.get(f"{HOST}/grades", params={"tg_id": user_id}).json()
score = data["score"]
grade = data["grade"]
n_of_assignments = len(data["assignments"])
score_message = MESSAGES.score.get(grade, score, n_of_assignments)
update.message.reply_html(score_message)
update.message.reply_sticker(MESSAGES.stickers.bad())
except:
LOG.exception("Failed to get grades.")
update.message.reply_sticker(MESSAGES.stickers.DEAD)
def echo(update: Update, context: CallbackContext):
"""Ответ на не командное сообщение (отвечает тем же сообщением)"""
context.bot.send_message(chat_id=update.effective_chat.id, text=update.message.text) context.bot.send_message(chat_id=update.effective_chat.id, text=update.message.text)
# ==================================================================== HANDLERS # ==================================================================== HANDLERS
start_handler = CommandHandler("start", start) start_handler = CommandHandler("start", start)
grades_handler = CommandHandler("grades", grades)
echo_handler = MessageHandler(Filters.text & (~Filters.command), echo) echo_handler = MessageHandler(Filters.text & (~Filters.command), echo)
dispatcher.add_handler(start_handler) dispatcher.add_handler(start_handler)
dispatcher.add_handler(grades_handler)
dispatcher.add_handler(echo_handler) dispatcher.add_handler(echo_handler)
# Начало работы бота # Начало работы бота

View File

@ -0,0 +1 @@
from .messages import Messages

View File

@ -0,0 +1,84 @@
import random
class Messages:
class Auth:
def __init__(self):
self.NO_AUTH = "[Правила авторизации](https://github.com/Faridik/Carolyn/wiki/%F0%9F%A6%B8%E2%80%8D%E2%99%82%EF%B8%8F-%D0%90%D0%B2%D1%82%D0%BE%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F)"
self.START = "🔑 Авторизация..."
self.NO_ACCESS = "⛔ Нет доступа"
def hello(self, data):
return f"Привет, {data} 👋"
class Score:
def get(self, grade, score, n_of_assignments):
summary = (
f"Ранг: <b>{score:.2f}</b> {n_of_assignments} зад.\n"
+ f"Оценка: <b>{grade:.2f}</b>"
)
return summary
class Spreadsheets:
def __init__(self):
self.DEAD = "⚰ Сервис «таблицы» недоступен"
class Stickers:
def __init__(self):
# Воробей павлик с колокольчиком и надписью "позор"
self.POZOR = "CAACAgIAAxkBAAEm1RhhBZg-2D6NypUeTsnbToD8_LMFTAACuUcAAulVBRjfnofQTkeMnCAE"
# кот Сима в петле плачет и показывает палец вверх
self.ROPE = "CAACAgIAAxkBAAEm1SFhBZjLaZgFIkvyxfBmAplwUWQFwwACBS0AAulVBRiIaSQ42WLoyyAE"
# Акио смотрит на брокколи
self.BROCCOLI = "CAACAgIAAxkBAAEm1SVhBZkd24Dy2B_wYSCi6UrNuUjgwQACEUkAAulVBRiykDzrzywbmiAE"
# Ян показывает палец вниз
self.THUMB_DOWN = "CAACAgIAAxkBAAEm1SthBZlcos0_TbJo8W7mAcqGLNAh2wACJ5cAAp7OCwABDddMclD7Tf8gBA"
# Тигр закатывает глаза
self.EYES_UP = "CAACAgEAAxkBAAEm1TBhBZmIZTrwzhGSzC47rNFbewQe6QAC4gADOA6CEeTgk4YIcTeWIAQ"
# Нутри большими глазами и надписью "пипец"
self.PPTS = "CAACAgIAAxkBAAEm1TZhBZnNVwLXmrbZwvdGjxiSwefzDAACn0sAAulVBRhECmBPSGCXHSAE"
# Попугай Кент с недовольной мордахой
self.GRUMPY_KENT = "CAACAgIAAxkBAAEm1UBhBZn1gXmRfxaK_RqFTj1aFxLY-gACy0EAAulVBRhgQdlH2rd0uyAE"
# wojak
self.WOJAK = "CAACAgIAAxkBAAEm1UJhBZpfHNYVT_ZLrxUpyi8qRLWJGgACPAADB7lDCi2YrzAEyxJeIAQ"
# Лэтишный котик и надпись "ОЙ"
self.OOPS = "CAACAgIAAxkBAAEm1URhBZp2li7w2E1M6PirG7UnqLOc2QACSxEAAlTE4UoR1Olz8RANZCAE"
# Мем "оп ахах неловко вышло"
self.NELOVKO = "CAACAgIAAxkBAAEm1UZhBZqZ94SFa1e6d-Zy5I-6_SC_fQACgwgAAh8MqEiLbHl9nugoYSAE"
# Попугай смотрит в зеркало "посмотри что с тобой стало"
self.MIRROR = "CAACAgIAAxkBAAEm1VFhBZudma_FmFhybGgXikz3csc0BQAC2UEAAulVBRgkdje995ir7iAE"
# Попугай с сигаретой
self.CIG = "CAACAgIAAxkBAAEm1VZhBZv6p7GsHS6wcyt2mCXoHJCo-wAC0UEAAulVBRghLLSQAcU6rSAE"
# Попугай и песок
self.SAND = "CAACAgIAAxkBAAEm1VhhBZwbw83_UVS8A5Crsf3EVAABvWIAAshBAALpVQUYl51K7ci-ClIgBA"
# Акио падает в обморок
self.PANIC = "CAACAgIAAxkBAAEm1VxhBZxwqRUJmVXDtzmsPJUIIGJgegACB0kAAulVBRiMI-hzK3GcKiAE"
# Эникей надпись "умер"
self.DEAD = "CAACAgIAAxkBAAEm1aRhBaWXRb_tcI03O1onx0jW0bHmuAACejcAAulVBRi1oSUXeBATmiAE"
def bad(self):
"""Возвращает стикер с грустным смыслом."""
return random.choice(
[
self.POZOR,
self.ROPE,
self.BROCCOLI,
self.THUMB_DOWN,
self.EYES_UP,
self.PPTS,
self.GRUMPY_KENT,
self.WOJAK,
self.OOPS,
self.NELOVKO,
self.MIRROR,
self.CIG,
self.SAND,
self.PANIC,
]
)
def __init__(self):
self.auth = self.Auth()
self.score = self.Score()
self.spreadsheets = self.Spreadsheets()
self.stickers = self.Stickers()

View File

@ -1,19 +1,17 @@
version: "3.9" version: "3.9"
services: services:
spreadsheets: spreadsheets:
container_name: "carolyn-spreadsheets" container_name: "carolyn-spreadsheets"
build: ./SpreadsheetsService build: ./SpreadsheetsService
ports: ports:
- "5000:5000" - "5000:5000"
environment: environment:
SECRETKEY: "HELLO-WORLD-@21XX 15AF" SECRETKEY: "HELLO-WORLD-@21XX 15AF"
volumes: volumes:
- ./SpreadsheetsService:/app - ./SpreadsheetsService:/app
telegrambot: telegrambot:
container_name: "carolyn-telegrambot" container_name: "carolyn-telegrambot"
build: ./TelegramBot build: ./TelegramBot
environment: volumes:
TOKEN: "1903048555:AAFK-d7prKvOvwwXo0IPzeRwJ1ZcuOT5ty8" - ./TelegramBot:/app
volumes:
- ./TelegramBot:/app