initial
This commit is contained in:
commit
f82f6e821c
27
.deploy/deploy-dev.yaml
Normal file
27
.deploy/deploy-dev.yaml
Normal file
@ -0,0 +1,27 @@
|
||||
version: "3.4"
|
||||
|
||||
|
||||
services:
|
||||
|
||||
bot:
|
||||
image: mathwave/sprint-repo:roulette-bot
|
||||
command: bot
|
||||
environment:
|
||||
TELEGRAM_TOKEN: $TELEGRAM_TOKEN_DEV
|
||||
MONGO_HOST: "mongo.develop.sprinthub.ru"
|
||||
MONGO_PASSWORD: $MONGO_PASSWORD_DEV
|
||||
networks:
|
||||
- net
|
||||
deploy:
|
||||
mode: replicated
|
||||
restart_policy:
|
||||
condition: any
|
||||
update_config:
|
||||
parallelism: 1
|
||||
order: start-first
|
||||
|
||||
networks:
|
||||
net:
|
||||
driver: overlay
|
||||
common-infra-nginx:
|
||||
external: true
|
28
.deploy/deploy-prod.yaml
Normal file
28
.deploy/deploy-prod.yaml
Normal file
@ -0,0 +1,28 @@
|
||||
version: "3.4"
|
||||
|
||||
|
||||
services:
|
||||
|
||||
bot:
|
||||
image: mathwave/sprint-repo:roulette-bot
|
||||
command: bot
|
||||
networks:
|
||||
- net
|
||||
- common-infra-nginx
|
||||
environment:
|
||||
TELEGRAM_TOKEN: $TELEGRAM_TOKEN_PROD
|
||||
MONGO_HOST: "mongo.sprinthub.ru"
|
||||
MONGO_PASSWORD: $MONGO_PASSWORD_PROD
|
||||
deploy:
|
||||
mode: replicated
|
||||
restart_policy:
|
||||
condition: any
|
||||
update_config:
|
||||
parallelism: 1
|
||||
order: start-first
|
||||
|
||||
networks:
|
||||
net:
|
||||
driver: overlay
|
||||
common-infra-nginx:
|
||||
external: true
|
119
.gitignore
vendored
Normal file
119
.gitignore
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
# Django #
|
||||
*.log
|
||||
*.pot
|
||||
*.pyc
|
||||
__pycache__
|
||||
db.sqlite3
|
||||
media
|
||||
data
|
||||
*/__pycache__
|
||||
|
||||
# Backup files #
|
||||
*.bak
|
||||
|
||||
# If you are using PyCharm #
|
||||
.idea
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
*.iws /out/
|
||||
|
||||
# Python #
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# Distribution / packaging
|
||||
.Python build/
|
||||
develop-eggs/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
.pytest_cache/
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
postgres-data
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery
|
||||
celerybeat-schedule.*
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.venv
|
||||
env/
|
||||
ENV/
|
||||
venv/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
|
||||
# Sublime Text #
|
||||
*.tmlanguage.cache
|
||||
*.tmPreferences.cache
|
||||
*.stTheme.cache
|
||||
*.sublime-workspace
|
||||
*.sublime-project
|
||||
|
||||
# sftp configuration file
|
||||
sftp-config.json
|
||||
|
||||
# Package control specific files Package
|
||||
Control.last-run
|
||||
Control.ca-list
|
||||
Control.ca-bundle
|
||||
Control.system-ca-bundle
|
||||
GitHub.sublime-settings
|
||||
|
||||
# Visual Studio Code #
|
||||
.vscode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history
|
43
.gitlab-ci.yml
Normal file
43
.gitlab-ci.yml
Normal file
@ -0,0 +1,43 @@
|
||||
stages:
|
||||
- build
|
||||
- deploy-dev
|
||||
- deploy-prod
|
||||
|
||||
build:
|
||||
stage: build
|
||||
tags:
|
||||
- dev
|
||||
before_script:
|
||||
- docker login -u mathwave -p $DOCKERHUB_PASSWORD
|
||||
script:
|
||||
- docker build -t mathwave/sprint-repo:roulette-bot .
|
||||
- docker push mathwave/sprint-repo:roulette-bot
|
||||
|
||||
.deploy:
|
||||
before_script:
|
||||
- docker login -u mathwave -p $DOCKERHUB_PASSWORD
|
||||
|
||||
deploy-dev:
|
||||
extends:
|
||||
- .deploy
|
||||
stage: deploy-dev
|
||||
tags:
|
||||
- dev
|
||||
rules:
|
||||
- if: '$CI_COMMIT_BRANCH == "master"'
|
||||
when: on_success
|
||||
- when: manual
|
||||
script:
|
||||
- docker stack deploy --with-registry-auth -c ./.deploy/deploy-dev.yaml roulette-bot
|
||||
|
||||
deploy-prod:
|
||||
extends:
|
||||
- .deploy
|
||||
stage: deploy-prod
|
||||
tags:
|
||||
- prod
|
||||
only:
|
||||
- master
|
||||
when: manual
|
||||
script:
|
||||
- docker stack deploy --with-registry-auth -c ./.deploy/deploy-prod.yaml roulette-bot
|
8
Dockerfile
Normal file
8
Dockerfile
Normal file
@ -0,0 +1,8 @@
|
||||
FROM python:3.10
|
||||
RUN mkdir /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
COPY requirements.txt requirements.txt
|
||||
RUN pip install -r requirements.txt
|
||||
COPY . .
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
ENTRYPOINT ["python", "main.py"]
|
105
bot.py
Normal file
105
bot.py
Normal file
@ -0,0 +1,105 @@
|
||||
import os
|
||||
|
||||
import telebot
|
||||
from telebot.types import Message, ReplyKeyboardRemove
|
||||
|
||||
from mongo import mongo
|
||||
|
||||
bot = telebot.TeleBot(os.getenv("TELEGRAM_TOKEN"))
|
||||
|
||||
|
||||
class Core:
|
||||
def __init__(self, message: Message):
|
||||
self.message = message
|
||||
self.chat_id = message.chat.id
|
||||
self.message_text = message.text
|
||||
user = mongo.chats_collection.find_one({"chat_id": message.chat.id})
|
||||
if user is None:
|
||||
doc = {
|
||||
"state": "new",
|
||||
"chat_id": message.chat.id,
|
||||
}
|
||||
mongo.chats_collection.insert_one(doc)
|
||||
else:
|
||||
doc = user
|
||||
self.doc = doc
|
||||
self.state = doc['state']
|
||||
|
||||
def process(self):
|
||||
if self.message_text.startswith('/'):
|
||||
self.exec_command()
|
||||
return
|
||||
getattr(self, "handle_state_" + self.state, self.handle_default)()
|
||||
|
||||
def exec_command(self):
|
||||
if self.message_text == '/pause':
|
||||
self.set_state('pause')
|
||||
if self.state == 'dialog':
|
||||
current_dialog = mongo.get_current_dialog(self.chat_id)
|
||||
mongo.finish_dialog(current_dialog['_id'])
|
||||
if self.chat_id == current_dialog['chat_id_1']:
|
||||
another_chat_id = current_dialog['chat_id_2']
|
||||
else:
|
||||
another_chat_id = current_dialog['chat_id_1']
|
||||
self.send_message('🤖 Диалог окончен, жду тебя снова!')
|
||||
self.start_new_dialog([another_chat_id])
|
||||
return
|
||||
if self.state == 'pause':
|
||||
self.send_message('🤖 Сейчас твой аккаунт не активен. Активируй его с помощью команды /start')
|
||||
return
|
||||
if self.state == 'search':
|
||||
self.send_message('🤖 Поиск собеседника окончен, жду тебя снова!')
|
||||
return
|
||||
if self.message_text == '/next' or self.message_text == '/start':
|
||||
if self.state == 'dialog':
|
||||
dialog = mongo.get_current_dialog(self.chat_id)
|
||||
self.start_new_dialog([dialog['chat_id_1'], dialog['chat_id_2']])
|
||||
return
|
||||
else:
|
||||
self.start_new_dialog([self.chat_id])
|
||||
return
|
||||
|
||||
def handle_state_search(self):
|
||||
self.send_message('🤖 Поиски собеседника продолжаются')
|
||||
|
||||
def send_message(self, text, chat_id=None, reply_markup=None, remove_keyboard=True, **kwargs):
|
||||
if reply_markup is None and remove_keyboard:
|
||||
reply_markup = ReplyKeyboardRemove()
|
||||
bot.send_message(chat_id or self.chat_id, text, reply_markup=reply_markup, **kwargs)
|
||||
|
||||
def set_state(self, state, chat_ids=None):
|
||||
mongo.chats_collection.update_many({"chat_id": {"$in": chat_ids or [self.chat_id]}}, {"$set": {"state": state}})
|
||||
|
||||
def handle_default(self):
|
||||
raise NotImplementedError(f"handler for {self.state} is not implemented")
|
||||
|
||||
def handle_state_new(self):
|
||||
self.start_new_dialog([self.chat_id])
|
||||
|
||||
def handle_state_dialog(self):
|
||||
current_dialog = mongo.get_current_dialog(self.chat_id)
|
||||
mongo.create_message(self.message_text, current_dialog['_id'], self.chat_id)
|
||||
if current_dialog['chat_id_1'] == self.chat_id:
|
||||
self.send_message(self.message_text, current_dialog['chat_id_2'])
|
||||
else:
|
||||
self.send_message(self.message_text, current_dialog['chat_id_1'])
|
||||
|
||||
def start_new_dialog(self, chat_ids):
|
||||
self.set_state('search', chat_ids)
|
||||
for chat in chat_ids:
|
||||
self.send_message("🤖 Начинаю искать собеседника. Сообщу тебе, когда найду его.", chat)
|
||||
next_chat = mongo.find_searching(chat_ids)
|
||||
if not next_chat:
|
||||
continue
|
||||
self.send_message('🤖 Собеседник найден! Можешь начинать общаться', chat)
|
||||
self.send_message('🤖 Собеседник найден! Можешь начинать общаться', next_chat['chat_id'])
|
||||
mongo.create_dialog(chat, next_chat['chat_id'])
|
||||
self.set_state('dialog', [chat, next_chat['chat_id']])
|
||||
|
||||
|
||||
def run_bot():
|
||||
@bot.message_handler()
|
||||
def do_action(message: Message):
|
||||
Core(message).process()
|
||||
|
||||
bot.polling()
|
8
main.py
Normal file
8
main.py
Normal file
@ -0,0 +1,8 @@
|
||||
import sys
|
||||
|
||||
|
||||
if sys.argv[-1] == "bot":
|
||||
from bot import run_bot
|
||||
run_bot()
|
||||
else:
|
||||
raise NotImplementedError
|
67
mongo.py
Normal file
67
mongo.py
Normal file
@ -0,0 +1,67 @@
|
||||
import datetime
|
||||
import random
|
||||
|
||||
import pymongo
|
||||
|
||||
import settings
|
||||
|
||||
|
||||
class Mongo:
|
||||
def __init__(self):
|
||||
url = f"mongodb://{settings.MONGO_USER}:{settings.MONGO_PASSWORD}@{settings.MONGO_HOST}:27017/"
|
||||
self.client = pymongo.MongoClient(url)
|
||||
self.database = self.client.get_database("roulette-bot")
|
||||
self.chats_collection.create_index([
|
||||
("chat_id", 1),
|
||||
('state', 1)
|
||||
])
|
||||
self.dialogs_collection.create_index([
|
||||
("chat_id", 1),
|
||||
('finished_at', 1)
|
||||
])
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.database.get_collection(item)
|
||||
|
||||
@property
|
||||
def chats_collection(self):
|
||||
return self["chats"]
|
||||
|
||||
@property
|
||||
def dialogs_collection(self):
|
||||
return self["dialogs"]
|
||||
|
||||
@property
|
||||
def messages_collection(self):
|
||||
return self["messages"]
|
||||
|
||||
def find_searching(self, except_ids):
|
||||
chats = list(self.chats_collection.find({"state": 'search', 'chat_id': {'$nin': except_ids}}))
|
||||
if not chats:
|
||||
return None
|
||||
return random.choice(chats)
|
||||
|
||||
def get_current_dialog(self, chat_id):
|
||||
return self.dialogs_collection.find_one({'$or': [{'chat_id_1': chat_id}, {'chat_id_2': chat_id}], 'finished_at': None})
|
||||
|
||||
def create_message(self, text, dialog_id, sender):
|
||||
self.messages_collection.insert_one({
|
||||
'dialog_id': dialog_id,
|
||||
'text': text,
|
||||
'sender': sender,
|
||||
'sent_at': datetime.datetime.now(),
|
||||
})
|
||||
|
||||
def create_dialog(self, chat_id_1, chat_id_2):
|
||||
self.dialogs_collection.insert_one({
|
||||
'chat_id_1': chat_id_1,
|
||||
'chat_id_2': chat_id_2,
|
||||
'started_at': datetime.datetime.now(),
|
||||
'finished_at': None,
|
||||
})
|
||||
|
||||
def finish_dialog(self, dialog_id):
|
||||
self.dialogs_collection.update_one({'_id': dialog_id}, {'$set': {'finished_at': datetime.datetime.now()}})
|
||||
|
||||
|
||||
mongo = Mongo()
|
15
requirements.txt
Normal file
15
requirements.txt
Normal file
@ -0,0 +1,15 @@
|
||||
certifi==2022.12.7
|
||||
charset-normalizer==3.0.1
|
||||
click==8.1.3
|
||||
dnspython==2.3.0
|
||||
Flask==2.2.3
|
||||
idna==3.4
|
||||
itsdangerous==2.1.2
|
||||
Jinja2==3.1.2
|
||||
MarkupSafe==2.1.2
|
||||
minio==7.1.13
|
||||
pymongo==4.3.3
|
||||
pyTelegramBotAPI==4.1.1
|
||||
requests==2.28.2
|
||||
urllib3==1.26.14
|
||||
Werkzeug==2.2.3
|
5
settings.py
Normal file
5
settings.py
Normal file
@ -0,0 +1,5 @@
|
||||
import os
|
||||
|
||||
MONGO_USER = os.getenv("MONGO_USER", "mongo")
|
||||
MONGO_PASSWORD = os.getenv("MONGO_PASSWORD", "password")
|
||||
MONGO_HOST = os.getenv("MONGO_HOST", "localhost")
|
Loading…
Reference in New Issue
Block a user