diff --git a/.gitignore b/.gitignore
index fedd414..a93c9fe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,7 @@ SpreadsheetsService/utils/__pycache__
SpreadsheetsService/tests/__pycache__
SpreadsheetsService/manager/__pycache__
SpreadsheetsService/manager/client_secret.json
-SpreadsheetsService/manager/token.json
\ No newline at end of file
+SpreadsheetsService/manager/token.json
+TelegramBot/.secrets
+SpreadsheetsService/.secrets
+TelegramBot/messages/__pycache__
diff --git a/SpreadsheetsService/app.py b/SpreadsheetsService/app.py
index 7a4e52b..5263a2d 100644
--- a/SpreadsheetsService/app.py
+++ b/SpreadsheetsService/app.py
@@ -1,9 +1,34 @@
-import flask
-import time
-import models
+from functools import wraps
from manager import *
+import flask
+import logging
+import models
+import time
+LOG = logging.getLogger(__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("/")
@@ -12,13 +37,32 @@ def hello_world():
@app.route("/auth")
+@availability
def auth():
- # TODO: return json text instead of text/html.
token = flask.request.args.get("token")
tg_id = flask.request.args.get("tg_id")
- manager = Manager()
- student = manager.get_student_by_name("Дзюба")
- return str(student)
+ student = app.db.get_student_by_name("Дзюба")
+ student = Student(1, "Фарид Михайлов", 5374)
+ 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__":
diff --git a/SpreadsheetsService/manager/manager.py b/SpreadsheetsService/manager/manager.py
index abd374a..ada2d7c 100644
--- a/SpreadsheetsService/manager/manager.py
+++ b/SpreadsheetsService/manager/manager.py
@@ -20,8 +20,8 @@ ASSIGNMENT_SUBJECT = 3
ASSIGNMENT_WEIGHTS = 4
ASSIGNMENT_RANGES = 5
ASSIGNMENT_ALLOWS = 6
-TOKEN_FILE = pathlib.Path() / "manager" / "token.json"
-CLIENT_SECRET_FILE = pathlib.Path() / "manager" / "client_secret.json"
+TOKEN_FILE = pathlib.Path() / ".secrets" / "token.json"
+CLIENT_SECRET_FILE = pathlib.Path() / ".secrets" / "client_secret.json"
class Manager:
diff --git a/SpreadsheetsService/models/assignment.py b/SpreadsheetsService/models/assignment.py
index 7e85fa7..0d89206 100644
--- a/SpreadsheetsService/models/assignment.py
+++ b/SpreadsheetsService/models/assignment.py
@@ -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.weight = weight
- self.values = values
+ self.points = points
+ dict.__init__(
+ self,
+ name=self.name,
+ weight=self.weight,
+ points=self.points,
+ total=self.total,
+ )
@property
def total(self) -> float:
@@ -12,4 +19,4 @@ class Assignment:
`вес*СУММ(рез-ты)`
"""
- return sum(self.values) * self.weight
+ return sum(self.points) * self.weight
diff --git a/SpreadsheetsService/models/student.py b/SpreadsheetsService/models/student.py
index 6040665..9d39df5 100644
--- a/SpreadsheetsService/models/student.py
+++ b/SpreadsheetsService/models/student.py
@@ -1,8 +1,7 @@
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):
@@ -12,6 +11,15 @@ class Student:
self.tg_id: int = tg_id
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):
self.assignments.append(assignment)
@@ -24,13 +32,3 @@ class Student:
def score(self):
"""Количество баллов у студента."""
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,
- }
- )
diff --git a/SpreadsheetsService/requirements.txt b/SpreadsheetsService/requirements.txt
index f0b48a0..0e22ebc 100644
--- a/SpreadsheetsService/requirements.txt
+++ b/SpreadsheetsService/requirements.txt
@@ -1 +1,3 @@
-Flask==2.0.0
\ No newline at end of file
+Flask==2.0.0
+google-api-python-client==2.15.0
+google-auth-oauthlib==0.4.5
\ No newline at end of file
diff --git a/TelegramBot/Dockerfile b/TelegramBot/Dockerfile
index f5e5e93..385874e 100644
--- a/TelegramBot/Dockerfile
+++ b/TelegramBot/Dockerfile
@@ -12,7 +12,6 @@ COPY requirements.txt .
RUN python -m pip install -r requirements.txt
WORKDIR /app
-COPY . /app
# 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
diff --git a/TelegramBot/bot.py b/TelegramBot/bot.py
index 3254a81..de2967f 100644
--- a/TelegramBot/bot.py
+++ b/TelegramBot/bot.py
@@ -1,58 +1,97 @@
import logging
-import os
-import platform
-import time
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(
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)
dispatcher = updater.dispatcher
# ================================================================ 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
if p is None:
- context.bot.send_message(
- chat_id=update.effective_chat.id,
- text="Нет доступа к боту",
- )
+ update.message.reply_markdown_v2(text=MESSAGES.auth.NO_AUTH)
return
- data = requests.get(
- "http://127.0.0.1:5000/auth",
- params={"token": p, "tg_id": update.effective_chat.id},
- ).json()
+ update.message.reply_text(text=MESSAGES.auth.START)
+
+ LOG.info(f"Authenticanting {user_id}")
+
+ 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(
- chat_id=update.effective_chat.id,
- text=f"Привет, {data}",
+ text=MESSAGES.auth.hello(data),
)
-# Ответ на не командное сообщение (отвечает тем же сообщением)
-def echo(update, context):
+def grades(update: Update, context: CallbackContext):
+ """Команда: Получить оценки студента."""
+
+ 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)
# ==================================================================== HANDLERS
start_handler = CommandHandler("start", start)
+grades_handler = CommandHandler("grades", grades)
+
echo_handler = MessageHandler(Filters.text & (~Filters.command), echo)
dispatcher.add_handler(start_handler)
+dispatcher.add_handler(grades_handler)
dispatcher.add_handler(echo_handler)
# Начало работы бота
diff --git a/TelegramBot/messages/__init__.py b/TelegramBot/messages/__init__.py
new file mode 100644
index 0000000..da79b07
--- /dev/null
+++ b/TelegramBot/messages/__init__.py
@@ -0,0 +1 @@
+from .messages import Messages
diff --git a/TelegramBot/messages/messages.py b/TelegramBot/messages/messages.py
new file mode 100644
index 0000000..4eff5ba
--- /dev/null
+++ b/TelegramBot/messages/messages.py
@@ -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"Ранг: {score:.2f} {n_of_assignments} зад.\n"
+ + f"Оценка: {grade:.2f}"
+ )
+ 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()
diff --git a/docker-compose.yml b/docker-compose.yml
index 4b234a2..5ebf360 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,19 +1,17 @@
version: "3.9"
-services:
- spreadsheets:
- container_name: "carolyn-spreadsheets"
- build: ./SpreadsheetsService
- ports:
- - "5000:5000"
- environment:
- SECRETKEY: "HELLO-WORLD-@21XX 15AF"
- volumes:
- - ./SpreadsheetsService:/app
- telegrambot:
- container_name: "carolyn-telegrambot"
- build: ./TelegramBot
- environment:
- TOKEN: "1903048555:AAFK-d7prKvOvwwXo0IPzeRwJ1ZcuOT5ty8"
- volumes:
- - ./TelegramBot:/app
\ No newline at end of file
+services:
+ spreadsheets:
+ container_name: "carolyn-spreadsheets"
+ build: ./SpreadsheetsService
+ ports:
+ - "5000:5000"
+ environment:
+ SECRETKEY: "HELLO-WORLD-@21XX 15AF"
+ volumes:
+ - ./SpreadsheetsService:/app
+ telegrambot:
+ container_name: "carolyn-telegrambot"
+ build: ./TelegramBot
+ volumes:
+ - ./TelegramBot:/app