├── .gitignore
├── README.md
├── bot
├── __init__.py
├── admin.py
├── bot.py
├── management
│ ├── __init__.py
│ └── commands
│ │ ├── __init__.py
│ │ └── runbot.py
├── migrations
│ ├── 0001_initial.py
│ ├── 0002_auto_20191118_0825.py
│ ├── 0003_auto_20191118_0839.py
│ └── __init__.py
├── models.py
└── views.py
├── manage.py
├── requirements.txt
├── screen.png
└── survey
├── __init__.py
├── questions.py
├── settings.py
├── urls.py
└── wsgi.py
/.gitignore:
--------------------------------------------------------------------------------
1 | venv
2 | .idea/
3 | db.sqlite3
4 | __pycache__
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SurveyBot
2 | ## Telegram bot for Surveys built with Django and Telebot
3 |
4 |
5 |
6 |
7 |
8 | ### First get the repo on your machine:
9 | ```bash
10 | $ git clone https://github.com/azimjohn/survey-bot.git
11 | ```
12 |
13 |
14 | ### Then install requirements:
15 | ```bash
16 | $ pip install -r requirements.txt
17 | ```
18 |
19 | ### Make the migrations and apply them:
20 | ```bash
21 | $ python manage.py makemigrations
22 | $ python manage.py migrate
23 | ```
24 |
25 | ### Export the bot token, replace `REPLACE_ME` with your bot's token
26 | ```bash
27 | $ export BOT_TOKEN=REPLACE_ME
28 | ```
29 |
30 | ### Finally, run the server 🎉
31 | ```bash
32 | $ python manage.py runbot
33 | ```
34 |
35 |
--------------------------------------------------------------------------------
/bot/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azimjohn/survey-bot/fa5c275ac3d415bdaeccba361d850f4b9e66ba7a/bot/__init__.py
--------------------------------------------------------------------------------
/bot/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from .models import Respondent, Response
3 |
4 | admin.site.site_header = 'SurveyBot Admin'
5 |
6 |
7 | class ResponseInline(admin.StackedInline):
8 | model = Response
9 | max_num = 0
10 | can_delete = False
11 | fields = ("html",)
12 | readonly_fields = ("html",)
13 |
14 |
15 | @admin.register(Respondent)
16 | class RespondentAdmin(admin.ModelAdmin):
17 | search_fields = ("first_name", "last_name", "username")
18 | fields = ("first_name", "last_name", "username", "completed_responses_count")
19 | list_display = ("first_name", "last_name", "username", "completed_responses_count")
20 | readonly_fields = ("first_name", "last_name", "username", "completed_responses_count")
21 | list_select_related = ("responses",)
22 | inlines = (ResponseInline,)
23 |
--------------------------------------------------------------------------------
/bot/bot.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import telebot
3 | from django.conf import settings
4 |
5 | from survey.questions import QUESTIONS
6 | from .models import Respondent
7 |
8 | bot = telebot.TeleBot(settings.TOKEN, num_threads=5)
9 | number_of_questions = len(QUESTIONS)
10 | start_command = "/start"
11 | restart_command = "Отправить еще один отзыв"
12 |
13 |
14 | def markup_choices(choices):
15 | if not choices:
16 | return telebot.types.ReplyKeyboardRemove(selective=False)
17 |
18 | markup = telebot.types.ReplyKeyboardMarkup(True, False)
19 | for choice in choices:
20 | markup.add(telebot.types.KeyboardButton(choice))
21 |
22 | return markup
23 |
24 |
25 | @bot.message_handler()
26 | def handler(message):
27 | registrant, _ = Respondent.objects.get_or_create(
28 | user_id=message.from_user.id,
29 | defaults={
30 | "first_name": message.from_user.first_name or "",
31 | "last_name": message.from_user.last_name or "",
32 | "username": message.from_user.username or "",
33 | }
34 | )
35 |
36 | response = registrant.responses.filter(completed=False).last()
37 | if not response or message.text in [start_command, restart_command]:
38 | response = registrant.responses.create()
39 |
40 | if number_of_questions >= response.step >= 1:
41 | prev_question = QUESTIONS[response.step - 1]["text"]
42 | response.details[prev_question] = message.text
43 | response.save(update_fields=["details"])
44 |
45 | if response.step >= number_of_questions:
46 | response.completed = True
47 | response.save(update_fields=["completed"])
48 | bot.send_message(
49 | registrant.user_id,
50 | "Готово. Спасибо за участие.",
51 | reply_markup=markup_choices([restart_command])
52 | )
53 | return
54 |
55 | question = QUESTIONS[response.step]
56 | text = question["text"]
57 | choices = question.get("choices") or []
58 |
59 | bot.send_message(registrant.user_id, text, reply_markup=markup_choices(choices))
60 |
61 | response.step += 1
62 | response.save(update_fields=["step"])
63 |
--------------------------------------------------------------------------------
/bot/management/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azimjohn/survey-bot/fa5c275ac3d415bdaeccba361d850f4b9e66ba7a/bot/management/__init__.py
--------------------------------------------------------------------------------
/bot/management/commands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azimjohn/survey-bot/fa5c275ac3d415bdaeccba361d850f4b9e66ba7a/bot/management/commands/__init__.py
--------------------------------------------------------------------------------
/bot/management/commands/runbot.py:
--------------------------------------------------------------------------------
1 | import time
2 |
3 | from django.core.management import BaseCommand
4 |
5 | from bot.bot import bot
6 |
7 |
8 | class Command(BaseCommand):
9 | def handle(self, *args, **options):
10 | print("Started Development Bot Client")
11 | try:
12 | bot.polling()
13 | except Exception as e:
14 | print(e)
15 | time.sleep(5)
16 | self.handle(*args, **options)
17 |
--------------------------------------------------------------------------------
/bot/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.1.9 on 2019-11-13 10:59
2 |
3 | from django.db import migrations, models
4 | import jsonfield.fields
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | initial = True
10 |
11 | dependencies = [
12 | ]
13 |
14 | operations = [
15 | migrations.CreateModel(
16 | name='Respondent',
17 | fields=[
18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19 | ('user_id', models.IntegerField(null=True)),
20 | ('first_name', models.CharField(blank=True, max_length=128)),
21 | ('last_name', models.CharField(blank=True, max_length=128)),
22 | ('username', models.CharField(blank=True, max_length=128)),
23 | ('step', models.SmallIntegerField(default=0)),
24 | ('details', jsonfield.fields.JSONField(default=dict, max_length=8192)),
25 | ],
26 | ),
27 | ]
28 |
--------------------------------------------------------------------------------
/bot/migrations/0002_auto_20191118_0825.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2 on 2019-11-18 08:25
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 | import jsonfield.fields
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('bot', '0001_initial'),
12 | ]
13 |
14 | operations = [
15 | migrations.RemoveField(
16 | model_name='respondent',
17 | name='details',
18 | ),
19 | migrations.RemoveField(
20 | model_name='respondent',
21 | name='step',
22 | ),
23 | migrations.CreateModel(
24 | name='Response',
25 | fields=[
26 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
27 | ('step', models.SmallIntegerField(default=0)),
28 | ('details', jsonfield.fields.JSONField(default=dict, max_length=8192)),
29 | ('completed', models.BooleanField(default=False)),
30 | ('respondent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='bot.Respondent')),
31 | ],
32 | ),
33 | ]
34 |
--------------------------------------------------------------------------------
/bot/migrations/0003_auto_20191118_0839.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2 on 2019-11-18 08:39
2 |
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('bot', '0002_auto_20191118_0825'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='response',
16 | name='respondent',
17 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='responses', to='bot.Respondent'),
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/bot/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azimjohn/survey-bot/fa5c275ac3d415bdaeccba361d850f4b9e66ba7a/bot/migrations/__init__.py
--------------------------------------------------------------------------------
/bot/models.py:
--------------------------------------------------------------------------------
1 | import jsonfield
2 | from django.db import models
3 | from django.utils.html import format_html
4 |
5 |
6 | class Respondent(models.Model):
7 | user_id = models.IntegerField(null=True)
8 | first_name = models.CharField(max_length=128, blank=True)
9 | last_name = models.CharField(max_length=128, blank=True)
10 | username = models.CharField(max_length=128, blank=True)
11 |
12 | @property
13 | def completed_responses_count(self):
14 | return self.responses.filter(completed=True).count()
15 |
16 |
17 | class Response(models.Model):
18 | step = models.SmallIntegerField(default=0)
19 | details = jsonfield.JSONField(max_length=8192, default=dict)
20 | respondent = models.ForeignKey(Respondent, related_name="responses", on_delete=models.CASCADE)
21 |
22 | completed = models.BooleanField(default=False)
23 |
24 | @property
25 | def html(self):
26 | table = ""
27 |
28 | for question in self.details:
29 | answer = self.details[question]
30 | table += f"""
31 |
32 | {question} |
33 | {answer} |
34 |
35 | """
36 |
37 | table += "
"
38 | return format_html(table)
39 |
--------------------------------------------------------------------------------
/bot/views.py:
--------------------------------------------------------------------------------
1 | import telebot
2 | from django.http import HttpResponse
3 | from django.views.decorators.csrf import csrf_exempt
4 | from .bot import bot
5 |
6 |
7 | @csrf_exempt
8 | def get_updates(request):
9 | json_string = request.body.decode('utf-8')
10 | update = telebot.types.Update.de_json(json_string)
11 | bot.process_new_updates([update])
12 |
13 | return HttpResponse('')
14 |
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == '__main__':
6 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'survey.settings')
7 | try:
8 | from django.core.management import execute_from_command_line
9 | except ImportError as exc:
10 | raise ImportError(
11 | "Couldn't import Django. Are you sure it's installed and "
12 | "available on your PYTHONPATH environment variable? Did you "
13 | "forget to activate a virtual environment?"
14 | ) from exc
15 | execute_from_command_line(sys.argv)
16 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Django==3.2.24
2 | pyTelegramBotAPI==3.6.6
3 | django-jsonfield==1.3.1
4 |
--------------------------------------------------------------------------------
/screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azimjohn/survey-bot/fa5c275ac3d415bdaeccba361d850f4b9e66ba7a/screen.png
--------------------------------------------------------------------------------
/survey/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/azimjohn/survey-bot/fa5c275ac3d415bdaeccba361d850f4b9e66ba7a/survey/__init__.py
--------------------------------------------------------------------------------
/survey/questions.py:
--------------------------------------------------------------------------------
1 | QUESTIONS = [
2 | {
3 | "text": "What's your name?",
4 | },
5 | {
6 | "text": "How old are you?",
7 | "choices": ["-18", "18-25", "25-30", "30+"]
8 | },
9 | {
10 | "text": "How do you rate our app?",
11 | "choices": [1, 2, 3, 4, 5],
12 | },
13 | {
14 | "text": "How can we make it better?"
15 | },
16 | {
17 | "text": "Where did you hear about us?",
18 | "choices": [
19 | "Telegram",
20 | "Facebook",
21 | "Instagram",
22 | "Other",
23 | ]
24 | },
25 | ]
26 |
--------------------------------------------------------------------------------
/survey/settings.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
4 |
5 | SECRET_KEY = '#)tr$f!pycw8u+-u3@wba$sp*qi_qt*f0@if8!z*+ny%^7^bo-'
6 |
7 | DEBUG = True
8 |
9 | ALLOWED_HOSTS = ['*']
10 |
11 | INSTALLED_APPS = [
12 | 'django.contrib.admin',
13 | 'django.contrib.auth',
14 | 'django.contrib.contenttypes',
15 | 'django.contrib.sessions',
16 | 'django.contrib.messages',
17 | 'django.contrib.staticfiles',
18 | 'bot',
19 | ]
20 |
21 | MIDDLEWARE = [
22 | 'django.middleware.security.SecurityMiddleware',
23 | 'django.contrib.sessions.middleware.SessionMiddleware',
24 | 'django.middleware.common.CommonMiddleware',
25 | 'django.middleware.csrf.CsrfViewMiddleware',
26 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
27 | 'django.contrib.messages.middleware.MessageMiddleware',
28 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
29 | ]
30 |
31 | ROOT_URLCONF = 'survey.urls'
32 |
33 | TEMPLATES = [
34 | {
35 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
36 | 'DIRS': [],
37 | 'APP_DIRS': True,
38 | 'OPTIONS': {
39 | 'context_processors': [
40 | 'django.template.context_processors.debug',
41 | 'django.template.context_processors.request',
42 | 'django.contrib.auth.context_processors.auth',
43 | 'django.contrib.messages.context_processors.messages',
44 | ],
45 | },
46 | },
47 | ]
48 |
49 | WSGI_APPLICATION = 'survey.wsgi.application'
50 |
51 | DATABASES = {
52 | 'default': {
53 | 'ENGINE': 'django.db.backends.sqlite3',
54 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
55 | }
56 | }
57 | STATIC_URL = '/static/'
58 | STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
59 |
60 | TOKEN = os.environ.get("BOT_TOKEN")
61 |
--------------------------------------------------------------------------------
/survey/urls.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.urls import path
3 | from bot.views import get_updates
4 |
5 | urlpatterns = [
6 | path('', admin.site.urls),
7 | path('bot/get_updates/', get_updates),
8 | ]
9 |
--------------------------------------------------------------------------------
/survey/wsgi.py:
--------------------------------------------------------------------------------
1 | import os
2 | from django.core.wsgi import get_wsgi_application
3 |
4 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'survey.settings')
5 | application = get_wsgi_application()
6 |
--------------------------------------------------------------------------------