first
This commit is contained in:
commit
464f14fe0a
3
.env.example
Normal file
3
.env.example
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
ADMIN_ID=123321
|
||||||
|
BOT_TOKEN=12345:AADD
|
||||||
|
BOT_LINK="https://t.me/xxx"
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
venv
|
||||||
|
.env
|
||||||
|
.idea
|
||||||
|
questions.txt
|
||||||
|
user_answers.json
|
64
README.md
Normal file
64
README.md
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
### Создание виртуального окружения (venv) ###
|
||||||
|
|
||||||
|
```commandline
|
||||||
|
python3 -m venv myenv
|
||||||
|
```
|
||||||
|
|
||||||
|
```myenv``` - это имя вашего виртуального окружения, можно выбрать любое.
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
### Активация виртуального окружения ###
|
||||||
|
|
||||||
|
#### Windows (CMD): ####
|
||||||
|
|
||||||
|
```commandline
|
||||||
|
myenv\Scripts\activate
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Windows (PowerShell): ####
|
||||||
|
|
||||||
|
```commandline
|
||||||
|
.\myenv\Scripts\Activate.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Linux/macOS: ####
|
||||||
|
|
||||||
|
```commandline
|
||||||
|
source myenv/bin/activate
|
||||||
|
```
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
### Установка зависимостей ###
|
||||||
|
|
||||||
|
```commandline
|
||||||
|
python3 -m pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
После того как вы установите зависимости необходимо будет настроить конфигурационные файлы.
|
||||||
|
Для этого вам нужно переименовать файл ```.env.example``` в ```.env```.
|
||||||
|
Так же нужно изменить значение переменных в файле ```.env``` на свои.
|
||||||
|
```dotenv
|
||||||
|
ADMIN_ID=XXXXXX
|
||||||
|
BOT_TOKEN=XXX:YYY
|
||||||
|
BOT_LINK="https://t.me/xxx"
|
||||||
|
```
|
||||||
|
Где:
|
||||||
|
|
||||||
|
```ADMIN_ID``` - идентификатор администратора, нужен для использования специальных
|
||||||
|
команд получения результатов и обновления вопросов.
|
||||||
|
|
||||||
|
```BOT_TOKEN``` - токен вашего телеграм бота, получить можно при создании.
|
||||||
|
|
||||||
|
```BOT_LINK``` - ссылка на бот, получить можно при создании.
|
||||||
|
|
||||||
|
Далее необходимо создать файл с вопросами ```questions.txt```.
|
||||||
|
В нем указать вопросы (каждый вопрос с новой строки), которые бот будет задавать новым пользователям чата.
|
||||||
|
|
||||||
|
Теперь нам осталось только запустить бота:
|
||||||
|
```
|
||||||
|
python3 bot.py
|
||||||
|
```
|
251
bot.py
Normal file
251
bot.py
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
import logging
|
||||||
|
from dotenv import dotenv_values
|
||||||
|
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
|
from telegram.ext import (
|
||||||
|
CommandHandler,
|
||||||
|
MessageHandler,
|
||||||
|
filters,
|
||||||
|
CallbackContext,
|
||||||
|
CallbackQueryHandler,
|
||||||
|
ChatMemberHandler,
|
||||||
|
ApplicationBuilder,
|
||||||
|
ConversationHandler,
|
||||||
|
)
|
||||||
|
from telegram.constants import ParseMode
|
||||||
|
import os
|
||||||
|
|
||||||
|
config = dotenv_values(".env")
|
||||||
|
|
||||||
|
# Настройка логгирования (пока не записываем)
|
||||||
|
# logging.basicConfig(
|
||||||
|
# format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||||
|
# )
|
||||||
|
# logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Константы
|
||||||
|
QUESTIONS_FILE = "questions.txt" # Файл с вопросами (один вопрос на строку)
|
||||||
|
USER_DATA_FILE = "user_answers.json" # Файл для сохранения ответов
|
||||||
|
|
||||||
|
FIRST, SECOND = range(2)
|
||||||
|
|
||||||
|
|
||||||
|
class SurveyBot:
|
||||||
|
def __init__(self, token):
|
||||||
|
self.current_question_index = {}
|
||||||
|
self.questions = self.load_questions()
|
||||||
|
self.user_data = {}
|
||||||
|
self.application = ApplicationBuilder().token(token).build()
|
||||||
|
|
||||||
|
conv_handler = ConversationHandler(
|
||||||
|
entry_points=[
|
||||||
|
CommandHandler('start', self.start),
|
||||||
|
],
|
||||||
|
states={
|
||||||
|
FIRST: [
|
||||||
|
CallbackQueryHandler(self.button),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
fallbacks=[CommandHandler('start', self.start)],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.application.add_handler(conv_handler)
|
||||||
|
|
||||||
|
# Регистрируем обработчики
|
||||||
|
self.application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, self.handle_message))
|
||||||
|
self.application.add_handler(MessageHandler(filters.StatusUpdate.NEW_CHAT_MEMBERS, self.track_chat_members))
|
||||||
|
self.application.add_handler(CallbackQueryHandler(self.button))
|
||||||
|
|
||||||
|
# Для администрирования
|
||||||
|
self.application.add_handler(CommandHandler("get_answers", self.get_answers))
|
||||||
|
self.application.add_handler(CommandHandler("reload_questions", self.reload_questions))
|
||||||
|
|
||||||
|
# text_handler = MessageHandler(filters.TEXT & (~filters.COMMAND), self.text_msg)
|
||||||
|
# application.add_handler(text_handler)
|
||||||
|
|
||||||
|
|
||||||
|
# Загружаем сохраненные данные пользователей
|
||||||
|
self.user_data = self.load_user_data()
|
||||||
|
|
||||||
|
def load_questions(self):
|
||||||
|
"""Загружает вопросы из текстового файла"""
|
||||||
|
if not os.path.exists(QUESTIONS_FILE):
|
||||||
|
with open(QUESTIONS_FILE, "w", encoding="utf-8") as f:
|
||||||
|
f.write("Как вас зовут?\nСколько вам лет?\nОткуда вы узнали о нас?")
|
||||||
|
return ["Как вас зовут?", "Сколько вам лет?", "Откуда вы узнали о нас?"]
|
||||||
|
|
||||||
|
with open(QUESTIONS_FILE, "r", encoding="utf-8") as f:
|
||||||
|
questions = [line.strip() for line in f.readlines() if line.strip()]
|
||||||
|
|
||||||
|
return questions
|
||||||
|
|
||||||
|
def load_user_data(self):
|
||||||
|
"""Загружает сохраненные ответы пользователей"""
|
||||||
|
if not os.path.exists(USER_DATA_FILE):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
with open(USER_DATA_FILE, "r", encoding="utf-8") as f:
|
||||||
|
return json.load(f)
|
||||||
|
except:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def save_user_data(self, context: CallbackContext):
|
||||||
|
"""Сохраняет ответы пользователей в файл"""
|
||||||
|
import json
|
||||||
|
with open(USER_DATA_FILE, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(self.user_data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
async def start(self, update: Update, context: CallbackContext):
|
||||||
|
user_id = update.effective_user.id
|
||||||
|
if str(user_id) not in self.user_data:
|
||||||
|
context.user_data['current_user'] = {
|
||||||
|
"username": update.effective_chat.username,
|
||||||
|
"first_name": update.effective_chat.first_name,
|
||||||
|
"last_name": update.effective_chat.last_name,
|
||||||
|
"chat_id": update.effective_chat.id,
|
||||||
|
"message_id": 0,
|
||||||
|
"answers": {},
|
||||||
|
"completed": False,
|
||||||
|
"current_question_index": 0
|
||||||
|
}
|
||||||
|
self.user_data[str(user_id)] = context.user_data['current_user']
|
||||||
|
await self.ask_question(user_id, update.effective_chat.id, context)
|
||||||
|
else:
|
||||||
|
context.user_data['current_user'] = self.user_data[str(user_id)]
|
||||||
|
if self.user_data[str(user_id)]['completed'] == False:
|
||||||
|
await self.ask_question(user_id, update.effective_chat.id, context)
|
||||||
|
else:
|
||||||
|
await context.bot.send_message(
|
||||||
|
chat_id=user_id,
|
||||||
|
text="Вы уже ответили на вопросы."
|
||||||
|
)
|
||||||
|
|
||||||
|
async def track_chat_members(self, update: Update, context: CallbackContext):
|
||||||
|
"""Отслеживает новых участников чата/канала"""
|
||||||
|
for member in update.message.new_chat_members:
|
||||||
|
if member.is_bot: # Игнорируем других ботов
|
||||||
|
continue
|
||||||
|
|
||||||
|
user_id = member.id
|
||||||
|
chat_id = update.effective_chat.id
|
||||||
|
|
||||||
|
if str(user_id) not in self.user_data:
|
||||||
|
context.user_data['current_user'] = {
|
||||||
|
"username": member.username,
|
||||||
|
"first_name": member.first_name,
|
||||||
|
"last_name": member.last_name,
|
||||||
|
"chat_id": chat_id,
|
||||||
|
"message_id": 0,
|
||||||
|
"answers": {},
|
||||||
|
"completed": False,
|
||||||
|
"current_question_index": 0
|
||||||
|
}
|
||||||
|
self.user_data[str(user_id)] = context.user_data['current_user']
|
||||||
|
|
||||||
|
msg = await context.bot.send_message(
|
||||||
|
chat_id=chat_id,
|
||||||
|
text="@{username}, Пройдите пожалуйста опрос нашего бота. <a href='{bot_link}?start=start'>Пройти опрос</a>".format(
|
||||||
|
username=member.username,
|
||||||
|
bot_link=config['BOT_LINK']
|
||||||
|
),
|
||||||
|
parse_mode=ParseMode.HTML
|
||||||
|
)
|
||||||
|
|
||||||
|
self.user_data[str(user_id)]['message_id'] = msg.message_id
|
||||||
|
|
||||||
|
async def ask_question(self, user_id, chat_id, context):
|
||||||
|
"""Задает пользователю следующий вопрос"""
|
||||||
|
question_index = self.user_data[str(user_id)]['current_question_index']
|
||||||
|
|
||||||
|
if question_index >= len(self.questions):
|
||||||
|
# Все вопросы заданы
|
||||||
|
self.user_data[str(user_id)]["completed"] = True
|
||||||
|
self.save_user_data(context)
|
||||||
|
await context.bot.send_message(
|
||||||
|
chat_id=chat_id,
|
||||||
|
text="Спасибо за ответы! Теперь вы полноправный участник нашего сообщества."
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.user_data[str(user_id)]['message_id'] > 0:
|
||||||
|
await context.bot.delete_message(
|
||||||
|
chat_id=self.user_data[str(user_id)]['chat_id'],
|
||||||
|
message_id=self.user_data[str(user_id)]['message_id']
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
question = self.questions[question_index]
|
||||||
|
keyboard = [
|
||||||
|
[InlineKeyboardButton("Пропустить вопрос", callback_data=f"skip_{user_id}")]
|
||||||
|
]
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
|
||||||
|
await context.bot.send_message(
|
||||||
|
chat_id=user_id,
|
||||||
|
text=question,
|
||||||
|
reply_markup=reply_markup
|
||||||
|
)
|
||||||
|
|
||||||
|
async def handle_message(self, update: Update, context: CallbackContext):
|
||||||
|
"""Обрабатывает ответы пользователя на вопросы"""
|
||||||
|
user_id = update.effective_user.id
|
||||||
|
|
||||||
|
question_index = self.user_data[str(user_id)]['current_question_index']
|
||||||
|
if question_index >= len(self.questions):
|
||||||
|
return
|
||||||
|
|
||||||
|
answer = update.message.text
|
||||||
|
question = self.questions[question_index]
|
||||||
|
|
||||||
|
# Сохраняем ответ
|
||||||
|
self.user_data[str(user_id)]["answers"][question] = answer
|
||||||
|
self.user_data[str(user_id)]['current_question_index'] += 1
|
||||||
|
|
||||||
|
# Задаем следующий вопрос
|
||||||
|
await self.ask_question(user_id, update.effective_chat.id, context)
|
||||||
|
|
||||||
|
async def button(self, update: Update, context: CallbackContext):
|
||||||
|
"""Обрабатывает нажатия кнопок"""
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer()
|
||||||
|
|
||||||
|
if query.data.startswith("skip_"):
|
||||||
|
user_id = int(query.data.split("_")[1])
|
||||||
|
self.user_data[str(user_id)]['current_question_index'] += 1
|
||||||
|
await self.ask_question(user_id, query.message.chat_id, context)
|
||||||
|
|
||||||
|
async def get_answers(self, update: Update, context: CallbackContext):
|
||||||
|
"""Команда для получения всех ответов (только для админов)"""
|
||||||
|
admins = config['ADMIN_ID'].split(" ")
|
||||||
|
if str(update.effective_user.id) not in admins: # Замените на ваш ID
|
||||||
|
await update.message.reply_text("У вас нет прав для этой команды.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.user_data:
|
||||||
|
await update.message.reply_text("Нет данных об ответах пользователей.")
|
||||||
|
return
|
||||||
|
|
||||||
|
import json
|
||||||
|
answers_str = json.dumps(self.user_data, ensure_ascii=False, indent=2)
|
||||||
|
await update.message.reply_text(f"Ответы пользователей:\n{answers_str}")
|
||||||
|
|
||||||
|
async def reload_questions(self, update: Update, context: CallbackContext):
|
||||||
|
"""Перезагружает вопросы из файла (только для админов)"""
|
||||||
|
admins = config['ADMIN_ID'].split(" ")
|
||||||
|
if str(update.effective_user.id) not in admins:
|
||||||
|
await update.message.reply_text("У вас нет прав для этой команды.")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.questions = self.load_questions()
|
||||||
|
await update.message.reply_text(f"Вопросы перезагружены. Всего вопросов: {len(self.questions)}")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""Запускает бота"""
|
||||||
|
self.application.run_polling()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Замените 'YOUR_BOT_TOKEN' на токен вашего бота
|
||||||
|
bot = SurveyBot(token=config['BOT_TOKEN'])
|
||||||
|
bot.run()
|
12
requirements.txt
Normal file
12
requirements.txt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
anyio==4.9.0
|
||||||
|
certifi==2025.1.31
|
||||||
|
dotenv==0.9.9
|
||||||
|
exceptiongroup==1.2.2
|
||||||
|
h11==0.14.0
|
||||||
|
httpcore==1.0.8
|
||||||
|
httpx==0.28.1
|
||||||
|
idna==3.10
|
||||||
|
python-dotenv==1.1.0
|
||||||
|
python-telegram-bot==22.0
|
||||||
|
sniffio==1.3.1
|
||||||
|
typing_extensions==4.13.2
|
Loading…
x
Reference in New Issue
Block a user