Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
dc0b7c5
fix: remove .flake8
Marvv1ne Jul 26, 2025
eff3eb6
feat: add run_bot command
Marvv1ne Jul 26, 2025
4b7c00b
feat: add telegram bot token, celery settings and logger settings
Marvv1ne Jul 26, 2025
112f7de
feat: add celery, django-celery-beat, redis, python-telegram-bot, flower
Marvv1ne Jul 26, 2025
edae2fa
feat: add requirements.txt with command uv 'pip compile pyproject.tom…
Marvv1ne Jul 26, 2025
38d3997
feat: add logs/ directory wirh .gitignore file inside
Marvv1ne Jul 26, 2025
5102b35
feat: add celery config
Marvv1ne Jul 26, 2025
f351fe4
feat: add .dockerignore file
Marvv1ne Jul 26, 2025
af1b6aa
feat: add Dockerfile
Marvv1ne Jul 26, 2025
d12a634
feat: add sender_service app
Marvv1ne Jul 26, 2025
fc9aa89
feat: add telegram bot
Marvv1ne Jul 26, 2025
2a74afe
feat: add docker-compose file to star telegram bot with sender service
Marvv1ne Jul 26, 2025
8be06c8
fix: improve megration after start postgres
Marvv1ne Jul 26, 2025
5700f9f
fix: translate keyboards buttons to english lang
Marvv1ne Jul 27, 2025
35e0356
fix: fix setting handler with new keyboard buttons and fix back func …
Marvv1ne Jul 27, 2025
281b6a7
feat: add emodji to some buutons
Marvv1ne Jul 27, 2025
5bcd87f
fix: remove migrations from repo
Marvv1ne Jul 28, 2025
926625d
fix: add migrations to gitignore
Marvv1ne Jul 28, 2025
4fd0273
fix: improve models
Marvv1ne Jul 28, 2025
95ea5e9
fix: improve file name to state_machine.py, add save to datavase func…
Marvv1ne Jul 28, 2025
3939619
fix: add save to db func into utils.py and import them into files
Marvv1ne Jul 28, 2025
ba83976
feat: add tests to handlers
Marvv1ne Jul 28, 2025
9151075
fix: improve command into migrate service to make migrations and migrate
Marvv1ne Jul 28, 2025
302c156
fix: improve import state_machine
Marvv1ne Jul 28, 2025
31c0145
fix: improve import state_machine
Marvv1ne Jul 28, 2025
97c4ba9
fix: improve linter comments
Marvv1ne Jul 28, 2025
26d4d74
feat: add example enviroments for telegram_bot and sender service
Marvv1ne Jul 29, 2025
c8d1c4e
fix: add ENVs PYTHONDONTWRITEBYTECODE and PYTHONUNBUFFERED
Marvv1ne Jul 29, 2025
398c78e
feat: add description how to start telegram bot and sender service
Marvv1ne Jul 29, 2025
1be0776
fix: remove description
Marvv1ne Jul 29, 2025
a984a75
fix: fix task value in save method
Marvv1ne Jul 29, 2025
20b0b10
fix: improve tests
Marvv1ne Jul 29, 2025
66c8d1f
fix: remove volumes: ./code from services
Marvv1ne Jul 29, 2025
2f22927
fix: add migrations in telegram_bot app
Marvv1ne Jul 31, 2025
429410d
fix: remove migrations dir from gitignore
Marvv1ne Jul 31, 2025
d4ff354
feat: add tests to delete_settings, confirm_delete and show_settings …
Marvv1ne Aug 4, 2025
87342d7
fix: rename run_bot command to run-bot
Marvv1ne Aug 6, 2025
cc47d79
fix: improve finish_selection and back_to_previose_stage handlers to …
Marvv1ne Aug 6, 2025
132f3b6
fix: improve linter comments
Marvv1ne Aug 6, 2025
bcd8e5c
fix: impprove REDIS_URL to REDIS_URL env
Marvv1ne Aug 6, 2025
eebb6e6
feat: add in README.md description of how to launch a telegram bot
Marvv1ne Aug 12, 2025
ba353e7
empty commit
Marvv1ne Oct 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.venv/
18 changes: 17 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,20 @@ EMAIL_PORT=587
EMAIL_USE_TLS=True
[email protected]
EMAIL_HOST_PASSWORD=secret123
EMAIL_TIMEOUT=10
EMAIL_TIMEOUT=10

# telegram bot env

# Database settings
DATABASE_ENGINE=postgresql
DATABASE_NAME=your_database_name
DATABASE_USERNAME=your_postgres_username
POSTGRES_PASSWORD=your_postgres_password
DATABASE_HOST=localhost
DATABASE_PORT=5432

# Telegram bot
TELEGRAM_BOT_TOKEN=telegram_bot_token

# Redis
REDIS_URL=redis://redis:6379/0
6 changes: 0 additions & 6 deletions .flake8

This file was deleted.

2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ postgres
key_word

test_fixtures.sh
error.html
error.html
25 changes: 25 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

RUN apt-get update && \
apt-get install -y build-essential libpq-dev && \
apt-get install -y --no-install-recommends curl ca-certificates && \
rm -rf /var/lib/apt/lists/*

ADD https://astral.sh/uv/install.sh /uv-installer.sh
RUN sh /uv-installer.sh && rm /uv-installer.sh
ENV PATH="/root/.local/bin/:$PATH"

WORKDIR /code

COPY . .

COPY pyproject.toml ./

RUN make install

RUN pip install --no-cache-dir -r requirements.txt

EXPOSE 8000
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,15 @@ migrate:
run:
uv run manage.py runserver

run-bot:
uv run manage.py run_bot

run-telegram:
uv run manage.py run_listener

shell:
uv run manage.py shell

test:
uv run manage.py test
uv run manage.py test

12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,15 @@ make create-superuser
```bash
make run
```

## Run telegram bot

```bash
make run-bot
```

## Local run telegram bot

```bash
docker compose -f docker-compose-bot-sender.yml up
```
9 changes: 9 additions & 0 deletions app/celery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import os

from celery import Celery

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings')

app = Celery('app')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
Empty file.
2 changes: 2 additions & 0 deletions app/services/sender_service/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

# Register your models here.
6 changes: 6 additions & 0 deletions app/services/sender_service/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class SenderServiceConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'app.services.sender_service'
Empty file.
2 changes: 2 additions & 0 deletions app/services/sender_service/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

# Create your models here.
23 changes: 23 additions & 0 deletions app/services/sender_service/sender.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import asyncio

from django.conf import settings
from django.core.mail import send_mail
from telegram import Bot

from app.settings import TELEGRAM_BOT_TOKEN


def send_email_message(to_email, subject, message):
send_mail(
subject,
message,
settings.DEFAULT_FROM_EMAIL,
[to_email],
fail_silently=False,
)


def send_telegram_message(chat_id, message):
token = TELEGRAM_BOT_TOKEN
bot = Bot(token=token)
asyncio.run(bot.send_message(chat_id=chat_id, text=message))
13 changes: 13 additions & 0 deletions app/services/sender_service/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from celery import shared_task

from .sender import send_email_message, send_telegram_message


@shared_task
def send_email_message_task(to_email, subject, message):
send_email_message(to_email, subject, message)


@shared_task
def send_telegram_message_task(chat_id, message):
send_telegram_message(chat_id, message)
2 changes: 2 additions & 0 deletions app/services/sender_service/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

# Create your tests here.
2 changes: 2 additions & 0 deletions app/services/sender_service/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

# Create your views here.
44 changes: 44 additions & 0 deletions app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,13 @@
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_celery_beat',
'app.services.hh.hh_parser',
'app.services.telegram.telegram_parser',
'app.services.telegram.telegram_channels',
'app.services.superjob.superjob_parser',
'app.services.sender_service',
'app.telegram_bot',
]

AUTH_USER_MODEL = 'users.User'
Expand Down Expand Up @@ -168,3 +171,44 @@
EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER", "")
EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD", "")
EMAIL_TIMEOUT = int(os.environ.get("EMAIL_TIMEOUT", 10))

#Telegram bot settings

TELEGRAM_BOT_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN')

# Celery settings
CELERY_BROKER_URL = os.getenv('REDIS_URL', 'redis://redis:6379/0')
CELERY_RESULT_BACKEND = os.getenv('REDIS_URL', 'redis://redis:6379/0')
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = TIME_ZONE
CELERY_LOG_FILE = BASE_DIR / 'logs/celery.log'
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers.DatabaseScheduler'

# Logger settings
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '[{asctime}] {levelname} {name}: {message}',
'style': '{',
},
},
'handlers': {
'file': {
'level': 'INFO',
'class': 'logging.FileHandler',
'filename': BASE_DIR / 'logs/app.log',
'formatter': 'verbose',
},
},
'loggers': {
'': {
'handlers': ['file'],
'level': 'INFO',
'propagate': True,
},
},
}
Empty file added app/telegram_bot/__init__.py
Empty file.
2 changes: 2 additions & 0 deletions app/telegram_bot/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

# Register your models here.
6 changes: 6 additions & 0 deletions app/telegram_bot/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class TelegramBotConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'app.telegram_bot'
32 changes: 32 additions & 0 deletions app/telegram_bot/handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from telegram import Update
from telegram.ext import (
Application,
CommandHandler,
ContextTypes,
)

from app.telegram_bot.state_machine import settings_handler
from app.telegram_bot.utils import get_or_create_user


async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(
f'Привет {update.effective_user.username}!\n'
'Введите /subscribe чтобы подписаться на рассылку вакансий')


async def subscribe(update: Update, context: ContextTypes.DEFAULT_TYPE):
username = update.effective_user.username
user_id = update.effective_user.id
await get_or_create_user(username=username, user_id=user_id, is_subscribed=True)
await update.message.reply_text(
'Вы подписаны на рассылку вакансий, введете /settings для настройки фильтра'
)


def setup_handlers(application: Application):
application.add_handler(CommandHandler('start', start))
application.add_handler(CommandHandler('subscribe', subscribe))
application.add_handler(settings_handler)


30 changes: 30 additions & 0 deletions app/telegram_bot/keyboards.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from telegram import ReplyKeyboardMarkup

markup_filters = ReplyKeyboardMarkup([
["frontend", "backend"],
["🔙 back"]
], resize_keyboard=True)

markup_front = ReplyKeyboardMarkup([
["React", "Vue.js", "Angular"],
["JS", "HTML", "CSS"],
["👌apply", "❌cancel", "🔙 back"]
], resize_keyboard=True)

markup_backend = ReplyKeyboardMarkup([
["Python", "Java", "Nodejs"],
["Go", "PHP", "C++"],
["👌apply", "❌cancel", "🔙 back"]
], resize_keyboard=True)

markup_interval = ReplyKeyboardMarkup([
["minute", "day", "week"],
["❌cancel", "🔙 back"]
], resize_keyboard=True)

markup_settings = ReplyKeyboardMarkup([
["create/update"],
["show"],
["delete"],
["exit"]
], resize_keyboard=True)
Empty file.
Empty file.
10 changes: 10 additions & 0 deletions app/telegram_bot/management/commands/run_bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.core.management.base import BaseCommand

from app.telegram_bot.telegram_bot import run_bot


class Command(BaseCommand):
help = "Run telegram bot"

def handle(self, *args, **options):
run_bot()
36 changes: 36 additions & 0 deletions app/telegram_bot/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Generated by Django 5.2.4 on 2025-07-29 14:16

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
('django_celery_beat', '0019_alter_periodictasks_options'),
]

operations = [
migrations.CreateModel(
name='TgUser',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('username', models.CharField(max_length=50, unique=True)),
('user_id', models.CharField(blank=True, max_length=50, null=True)),
('email', models.EmailField(blank=True, max_length=254, null=True)),
('is_subscribed', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now=True)),
],
),
migrations.CreateModel(
name='UserSubscriptionSettings',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('filters', models.JSONField(default=dict, verbose_name='Настройки фильтра')),
('crontab', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='subscriptions', to='django_celery_beat.crontabschedule')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='Пользователь', to='telegram_bot.tguser')),
],
),
]
Empty file.
45 changes: 45 additions & 0 deletions app/telegram_bot/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import json

from django.db import models
from django.utils.translation import gettext_lazy as _
from django_celery_beat.models import CrontabSchedule, PeriodicTask


class TgUser(models.Model):
username = models.CharField(max_length=50, unique=True)
user_id = models.CharField(max_length=50, blank=True, null=True)
email = models.EmailField(blank=True, null=True)
is_subscribed = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now=True)


class UserSubscriptionSettings(models.Model):
user = models.ForeignKey(TgUser,
on_delete=models.CASCADE,
related_name=_('Пользователь')
)
filters = models.JSONField(default=dict,
verbose_name=_('Настройки фильтра'))
crontab = models.ForeignKey(
CrontabSchedule,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='subscriptions'
)

def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if self.crontab:
PeriodicTask.objects.update_or_create(
name=self.user.username,
defaults={
'crontab': self.crontab,
'task': (
'app.services.sender_service.tasks.'
'send_telegram_message_task'
),
'args': json.dumps([self.user.user_id, self.filters]),
'enabled': self.user.is_subscribed,
}
)
Loading