Skip to content

Commit

Permalink
Merge pull request #12 from ptrvsrg/dev
Browse files Browse the repository at this point in the history
v1.0.0
  • Loading branch information
ptrvsrg committed Jun 30, 2024
2 parents d4f4eb9 + a2cb795 commit 6b73287
Show file tree
Hide file tree
Showing 56 changed files with 342 additions and 574 deletions.
15 changes: 15 additions & 0 deletions .env.docker.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
TELEGRAM_TOKEN=
OWNER_USERNAME=
WEBHOOK_URL=

POSTGRES_PORT=5432
POSTGRES_USER=user
POSTGRES_PASSWORD=password
POSTGRES_DB=hotdog_or_not_bot

REDIS_PORT=6379
REDIS_PASSWORD=password

YANDEX_DISK_API_KEY=

BOT_PORT=8080
6 changes: 3 additions & 3 deletions .env.example → .env.local.example
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
MAJOR_VERSION=0
MINOR_VERSION=1
MAJOR_VERSION=1
MINOR_VERSION=0
PATCH_VERSION=0

LOCALE_DIR=locales
DETECT_MODEL_PATH=models/yolov8n.pt
PREDICT_MODEL_PATH=models/yolov8n-hotdog-cls.pt
MIGRATIONS_DIR=migrations

TELEGRAM_TOKEN=
OWNER_USERNAME=
WEBHOOK_URL=
DAILY_LIMIT=100

POSTGRES_HOST=localhost
POSTGRES_PORT=5432
Expand Down
49 changes: 37 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<h1 align="center">Hotdog or Not Bot</h1>

<p align="center">
<img alt="License" src="https://img.shields.io/github/license/ptrvsrg/hotdog-or-not-bot?color=56BEB8">
<img alt="Github issues" src="https://img.shields.io/github/issues/ptrvsrg/hotdog-or-not-bot?color=56BEB8" />
<img alt="Github forks" src="https://img.shields.io/github/forks/ptrvsrg/hotdog-or-not-bot?color=56BEB8" />
<img alt="Github stars" src="https://img.shields.io/github/stars/ptrvsrg/hotdog-or-not-bot?color=56BEB8" />
<img alt="License" src="https://img.shields.io/github/license/ptrvsrg/hotdog-or-not-bot?color=56BEB8&style=flat">
<img alt="Github issues" src="https://img.shields.io/github/issues/ptrvsrg/hotdog-or-not-bot?color=56BEB8&style=flat" />
<img alt="Github forks" src="https://img.shields.io/github/forks/ptrvsrg/hotdog-or-not-bot?color=56BEB8&style=flat" />
<img alt="Github stars" src="https://img.shields.io/github/stars/ptrvsrg/hotdog-or-not-bot?color=56BEB8&style=flat" />
</p>

## About
Expand Down Expand Up @@ -40,6 +40,8 @@ Before starting, you need to have:

## Starting

### Local

1. Clone this project

```shell
Expand All @@ -60,7 +62,7 @@ source .venv/bin/activate
pip install -r requirements.txt
```

4. Run http tunnel
4. (Optional) Run http tunnel

> **_NOTE:_** Run in a separate terminal.
Expand All @@ -73,30 +75,53 @@ ngrok http 8080

5. Set up environment variables

> **_NOTE:_** Initialize environment variable **WEBHOOK_URL** with the value \<public URL from ngrok\>/webhook
> **_NOTE:_** Initialize environment variable **WEBHOOK_URL** with the value \<public URL\>/webhook
```shell
cp .env.exmaple .env
cp .env.local.exmaple .env
nano .env
export $(cat .env | xargs)
```

6. Start database and cache

```shell
docker compose --env-file .env up -d
docker compose --file docker-compose.local.yml --env-file .env up -d
```

7. Migrate database
7. Run application:

```shell
python -m yoyo apply --database postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} ${MIGRATIONS_DIR}
python app/main.py
```

8. Run application:
### Docker

1. Install `docker-compose.yml`:

```shell
python app/main.py
wget https://github.com/ptrvsrg/hotdog-or-not-bot/blob/<release_version>/docker-compose.yml
```

2. Install example environment file:

```shell
wget https://github.com/ptrvsrg/hotdog-or-not-bot/blob/<release_version>/.env.docker.example
```

3. Set up environment variables

> **_NOTE:_** Initialize environment variable **WEBHOOK_URL** with the value \<public URL\>/webhook
```shell
cp .env.docker.exmaple .env
nano .env
```

4. Run containers:

```shell
docker compose --file docker-compose.yml --env-file .env up -d
```

## Contribution to the project
Expand Down
18 changes: 8 additions & 10 deletions app/bot/callback_data/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
from app.bot.callback_data.cancel_callback_factory import CancelCallbackFactory
from app.bot.callback_data.main_menu_callback_factory import MainMenuCallbackFactory
from app.bot.callback_data.result_callback_factory import ResultFeedbackCallbackFactory
from app.bot.callback_data.select_image_callback_factory import (
SelectImageCallbackFactory,
from app.bot.callback_data.main_menu_callback_factory import (
MainMenuCallbackFactory,
MainMenuCallbackAction,
)
from app.bot.callback_data.subscription_buy_callback_factory import (
SubscriptionBuyCallbackFactory,
from app.bot.callback_data.result_callback_factory import (
ResultFeedbackCallbackFactory,
)
from app.bot.callback_data.sudscription_callback_factory import (
SubscriptionCallbackFactory,
from app.bot.callback_data.select_image_callback_factory import (
SelectImageCallbackFactory,
)

__all__ = [
"MainMenuCallbackAction",
"MainMenuCallbackFactory",
"SubscriptionBuyCallbackFactory",
"SubscriptionCallbackFactory",
"CancelCallbackFactory",
"SelectImageCallbackFactory",
"ResultFeedbackCallbackFactory",
Expand Down
14 changes: 13 additions & 1 deletion app/bot/callback_data/main_menu_callback_factory.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
from enum import Enum

from aiogram.filters.callback_data import CallbackData


class MainMenuCallbackAction(Enum):
SHOW_PROFILE = "show_profile"
BAN_USER = "ban_user"
UNBAN_USER = "unban_user"
SHOW_BANNED_USERS = "show_banned_users"
SHOW_ADMINS = "show_admins"
ADD_ADMIN = "add_admin"
REMOVE_ADMIN = "remove_admin"


class MainMenuCallbackFactory(CallbackData, prefix="main_menu"):
action: str
action: MainMenuCallbackAction
3 changes: 2 additions & 1 deletion app/bot/callback_data/result_callback_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@


class ResultFeedbackCallbackFactory(CallbackData, prefix="result"):
action: str
is_hotdog: bool
is_success: bool
message_id: int
2 changes: 1 addition & 1 deletion app/bot/callback_data/select_image_callback_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@


class SelectImageCallbackFactory(CallbackData, prefix="select_image"):
index: int
photo_index: int
message_id: int
5 changes: 0 additions & 5 deletions app/bot/callback_data/subscription_buy_callback_factory.py

This file was deleted.

5 changes: 0 additions & 5 deletions app/bot/callback_data/sudscription_callback_factory.py

This file was deleted.

4 changes: 0 additions & 4 deletions app/bot/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,13 @@
from app.bot.handlers.predict_images_handler import router as predict_router
from app.bot.handlers.profile_callback_handler import router as profile_router
from app.bot.handlers.start_command_handler import router as start_router
from app.bot.handlers.subscription_callbacks_handler import (
router as subscription_router,
)

__all__ = [
"start_router",
"menu_router",
"admin_router",
"owner_router",
"profile_router",
"subscription_router",
"cancel_router",
"predict_router",
]
32 changes: 22 additions & 10 deletions app/bot/handlers/admin_callbacks_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
from aiogram.types import Message, CallbackQuery
from i18next import trans as t

from app.bot.callback_data import MainMenuCallbackFactory
from app.bot.callback_data import MainMenuCallbackFactory, MainMenuCallbackAction
from app.bot.keyboards import create_cancel_menu
from app.bot.states import AdminStates
from app.service import user_service, UserAlreadyBannedException, UserNotBannedException

router = Router(name="admin")


@router.callback_query(MainMenuCallbackFactory.filter(F.action == "ban_user"))
@router.callback_query(
MainMenuCallbackFactory.filter(F.action == MainMenuCallbackAction.BAN_USER)
)
async def ban_user(callback: CallbackQuery, state: FSMContext):
await callback.message.answer(
t("message.enter_username"), reply_markup=create_cancel_menu()
Expand Down Expand Up @@ -40,7 +42,9 @@ async def process_ban(message: Message, state: FSMContext):
await state.clear()


@router.callback_query(MainMenuCallbackFactory.filter(F.action == "unban_user"))
@router.callback_query(
MainMenuCallbackFactory.filter(F.action == MainMenuCallbackAction.UNBAN_USER)
)
async def unban_user(callback: CallbackQuery, state: FSMContext):
await callback.message.answer(
t("message.enter_username"), reply_markup=create_cancel_menu()
Expand All @@ -49,23 +53,29 @@ async def unban_user(callback: CallbackQuery, state: FSMContext):


@router.message(AdminStates.awaiting_unbanned_username)
async def process_ban(message: Message, state: FSMContext):
if not username.startswith("@"):
async def process_unban(message: Message, state: FSMContext):
unbanned_username = message.text

if not unbanned_username.startswith("@"):
await message.answer(t("error.incorrect_username"))
await state.clear()
return

try:
user_service.unban_user(username[1:])
user_service.unban_user(unbanned_username[1:])
await message.answer(
t("message.user_is_unbanned", params={"username": username})
t("message.user_is_unbanned", params={"username": unbanned_username})
)
except UserNotBannedException as e:
await message.answer(t("error.user_not_banned", params={"username": username}))
await message.answer(
t("error.user_not_banned", params={"username": unbanned_username})
)
await state.clear()


@router.callback_query(MainMenuCallbackFactory.filter(F.action == "list_banned_users"))
@router.callback_query(
MainMenuCallbackFactory.filter(F.action == MainMenuCallbackAction.SHOW_BANNED_USERS)
)
async def list_banned_users(callback: CallbackQuery):
banned_users = user_service.get_banned_users()
if len(banned_users) > 0:
Expand All @@ -77,7 +87,9 @@ async def list_banned_users(callback: CallbackQuery):
await callback.message.answer(content)


@router.callback_query(MainMenuCallbackFactory.filter(F.action == "list_admins"))
@router.callback_query(
MainMenuCallbackFactory.filter(F.action == MainMenuCallbackAction.SHOW_ADMINS)
)
async def list_admins(callback: CallbackQuery):
admins = user_service.get_admins()
if len(admins) > 0:
Expand Down
10 changes: 7 additions & 3 deletions app/bot/handlers/owner_callbacks_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
from aiogram.types import Message, CallbackQuery
from i18next import trans as t

from app.bot.callback_data import MainMenuCallbackFactory
from app.bot.callback_data import MainMenuCallbackFactory, MainMenuCallbackAction
from app.bot.keyboards import create_cancel_menu
from app.bot.states import OwnerStates
from app.service import user_service, UserNotAdminException, UserAlreadyAdminException

router = Router(name="owner")


@router.callback_query(MainMenuCallbackFactory.filter(F.action == "add_admin"))
@router.callback_query(
MainMenuCallbackFactory.filter(F.action == MainMenuCallbackAction.ADD_ADMIN)
)
async def add_admin(callback: CallbackQuery, state: FSMContext):
await callback.message.answer(
t("message.enter_username"), reply_markup=create_cancel_menu()
Expand All @@ -37,7 +39,9 @@ async def process_add(message: Message, state: FSMContext):
await state.clear()


@router.callback_query(MainMenuCallbackFactory.filter(F.action == "remove_admin"))
@router.callback_query(
MainMenuCallbackFactory.filter(F.action == MainMenuCallbackAction.REMOVE_ADMIN)
)
async def remove_admin(callback: CallbackQuery, state: FSMContext):
await callback.message.answer(
t("message.enter_username"), reply_markup=create_cancel_menu()
Expand Down
15 changes: 9 additions & 6 deletions app/bot/handlers/predict_images_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,10 @@ async def process_select_image(
await callback.message.answer(
t(
"message.probability_with_index",
params={"probability": round(prob * 100, 4), "index": callback_data.index},
params={
"probability": round(prob * 100, 4),
"index": callback_data.photo_index,
},
),
reply_markup=create_result_feedback_menu(
callback_data.message_id, prob >= PredictService.CLASSIFICATION_THRESHOLD
Expand Down Expand Up @@ -164,16 +167,16 @@ async def feedback_prediction(

# Add statistics
username = get_username_from_callback(callback)
if callback_data.action.startswith("success"):
if callback_data.is_success:
statistics_service.add_successful_prediction(username)
elif callback_data.action.startswith("fail"):
else:
statistics_service.add_failed_predictions(username)

# Upload image to Yandex Disk
if callback_data.action in ["success_hotdog", "fail_not_hotdog"]:
await yandex_disk_service.upload_file(file_bytes, "hotdog")
if callback_data.action in ["success_not_hotdog", "fail_hotdog"]:
if callback_data.is_success ^ callback_data.is_hotdog:
await yandex_disk_service.upload_file(file_bytes, "not_hotdog")
else:
await yandex_disk_service.upload_file(file_bytes, "hotdog")

# Show message
await callback.message.edit_reply_markup(reply_markup=None)
Expand Down
Loading

0 comments on commit 6b73287

Please sign in to comment.