Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b3dcccb
Merge pull request #1 from brandonzorn/develop
brandonzorn Jun 30, 2024
9893f26
flake8 and black configs in pyproject.toml
brandonzorn Jul 5, 2024
8beea52
Merge remote-tracking branch 'origin/main'
brandonzorn Jul 5, 2024
2c404c6
moved dirs and added .env
brandonzorn Aug 2, 2024
1eacbd5
added deploy
brandonzorn Aug 2, 2024
9d432bb
removed commnets
brandonzorn Aug 2, 2024
7847a6c
fixed files
brandonzorn Aug 2, 2024
dcb2452
.
brandonzorn Aug 3, 2024
76550fc
updated deploy
brandonzorn Aug 3, 2024
4ea920f
using pathlib
brandonzorn Aug 3, 2024
c57efc8
fixed paths
brandonzorn Aug 3, 2024
0700f73
.
brandonzorn Aug 3, 2024
75ed944
docker support
brandonzorn Aug 4, 2024
6d53478
updated docker files
brandonzorn Aug 12, 2024
0b52a5d
Update Docker config
brandonzorn Mar 4, 2025
9c2a349
Update python image
brandonzorn Mar 7, 2025
83b0e8d
Moved some consts to config
brandonzorn Mar 7, 2025
e5b9e13
Add database name param
brandonzorn Mar 7, 2025
36762d5
Add docker compose
brandonzorn Mar 7, 2025
6a5dd18
Change jobs python image
brandonzorn Mar 7, 2025
f0662a4
Fix bugs
brandonzorn Mar 7, 2025
e0015f4
add .env.example
brandonzorn Apr 14, 2025
f115e5a
refactor: replace pytz with zoneinfo for timezone handling
brandonzorn May 5, 2025
b6ac85d
chore: replace CMD with ENTRYPOINT in Dockerfile & add --no-cache-dir…
brandonzorn May 5, 2025
f5bcf7e
feat: save logs to file with timestamp in logs folder
brandonzorn May 5, 2025
051710a
style: fix PEP8 violations in code formatting
brandonzorn May 5, 2025
ec82f37
feat: add flake8-encodings & flake8-eradicate for testing
brandonzorn May 5, 2025
0f2b1e4
build: remove unnecessary dependencies from requirements.txt
brandonzorn May 5, 2025
123b3b1
fix: change logging level to INFO
brandonzorn May 5, 2025
5436e45
style: wrap lines to 79 characters to comply with PEP8
brandonzorn May 5, 2025
eb54332
refactor: move game_handler to game.py
brandonzorn May 5, 2025
3904124
chore: add __all__ for explicit module exports
brandonzorn May 5, 2025
f2bc14a
style: move 'for' to a new line in comprehension for PEP8 compliance
brandonzorn May 5, 2025
0f57c22
build: add flake8-all-not-strings and flake8-dunder-all plugins
brandonzorn May 5, 2025
9792e7f
docs: fix typos in README
brandonzorn May 5, 2025
0db4654
Merge branch 'develop' into develop
brandonzorn May 9, 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
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
**/*.db
**/logs
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
BOT_TOKEN=your_telegram_bot_token
DATABASE_NAME=database
29 changes: 25 additions & 4 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,41 @@ permissions:
jobs:
black-test:
runs-on: ubuntu-latest
container: python:3.11
container: python:3.13-alpine
steps:
- uses: actions/checkout@v3
- name: Install dependencies
run: pip install black
- name: Check code formatting with black
run: black ./ --check --verbose --diff
run: black ./tfinance --check --verbose --diff

flake8-test:
runs-on: ubuntu-latest
container: python:3.11
container: python:3.13-alpine
steps:
- uses: actions/checkout@v3
- name: Install dependencies
run: pip install -r requirements/test.txt
- name: Check code formatting with flake8
run: flake8 ./
run: flake8 ./tfinance

prod-deploy:
if: github.ref == 'refs/heads/main'
needs: [ black-test, flake8-test ]
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
cd ~/TFinance
git reset --hard origin/main
git pull origin main
docker compose down -v
docker compose up --build -d
9 changes: 5 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/.idea
/logs
/data.db
/safety_key.py
stocks.json
logs
sqlite
*.db
stocks.json
.env
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM python:3.13-alpine

COPY ./requirements /requirements
RUN pip install --no-cache-dir -r requirements/prod.txt
RUN rm -rf requirements

COPY ./tfinance /tfinance/
WORKDIR /tfinance

ENTRYPOINT ["python", "main.py"]
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
Проект: Telegram bot TFinace

TFinance - телеграм бот, позволяющий получить доступ к курсу
акций. На понравившиеся вам акции вы можете подписаться и включить ежедненую
рассылку их курса. Также вы можете попытать удачу и сыграть в игру, предугодав
акций. На понравившиеся вам акции вы можете подписаться и включить ежедневную
рассылку их курса. Также вы можете попытать удачу и сыграть в игру, предугадав
будущий курс, в случае удачи вы заработаете 1 очко в криптоигре.

<h2>Задачи, которые мы выполнили:</h2>
Expand All @@ -23,7 +23,7 @@ TFinance - телеграм бот, позволяющий получить до
<li>Использовали команду /unfollow для отписки от акции.</li>
<li>Использовали команду /stock для вывода курса акции.</li>
<li>Использовали команду /stocks для вывода акций с биржи.</li>
<li>Использовали команду /daily для подписки на ежедненую расслылку курсов
<li>Использовали команду /daily для подписки на ежедневную рассылку курсов
избранных акций.</li>
<li>Получение списка акций с биржи с помощью http-запросов.</li>
</ul>
Expand Down
10 changes: 10 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
services:
tfinance_bot:
build: .
container_name: tfinance_bot
env_file:
- ./.env
restart: unless-stopped
volumes:
- ./etc_tfinance/logs:/tfinance/logs
- ./etc_tfinance/sqlite:/tfinance/sqlite
11 changes: 2 additions & 9 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
[tool.flake8]
max-line-length = 89
max-line-length = 79
inline-quotes="double"
import-order-style="google"
extend-ignore = [
"R503",
"R502",
"F401",
"N802",
]
exclude = [
".git",
"__pycache__",
"docs/source/conf.py",
"venv",
]

[tool.black]
line-length = 89
line-length = 79
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
matplotlib>=3.5.3
python-dotenv>=1.0.1
python-telegram-bot>=21.1.1
python-telegram-bot[job-queue]
pytz>=2024.1
requests>=2.31.0
yfinance>=0.2.38
2 changes: 1 addition & 1 deletion requirements/prod.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
matplotlib>=3.5.3
python-dotenv>=1.0.1
python-telegram-bot>=21.1.1
python-telegram-bot[job-queue]
pytz>=2024.1
requests>=2.31.0
yfinance>=0.2.38
4 changes: 4 additions & 0 deletions requirements/test.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
flake8>=6.1.0
flake8-all-not-strings>=0.0.2
flake8-commas>=2.1.0
flake8-comments>=0.1.2
flake8-dunder-all>=0.4.1
flake8-encodings>=0.5.1
flake8-eradicate>=1.5.0
flake8-print>=5.0.0
Flake8-pyproject>=1.2.3
flake8-quotes>=3.3.2
Expand Down
9 changes: 8 additions & 1 deletion blast.py → tfinance/blast.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@

async def notify_assignees(context: CallbackContext):
db = Database()
# Перебираем всех пользователей и рассылаем каждому курсы их избранных акций.
# Перебираем всех пользователей и
# рассылаем каждому курсы их избранных акций.
for user in db.get_users():
if db.check_user_daily_notify(user):
if user.favourite_stocks:
Expand All @@ -35,3 +36,9 @@ async def daily(update: Update, _):
else:
await update.message.reply_text("Ежедневная рассылка включена")
db.user_daily_notify(user)


__all__ = [
"daily",
"notify_assignees",
]
12 changes: 12 additions & 0 deletions tfinance/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import os
from zoneinfo import ZoneInfo

from dotenv import load_dotenv

load_dotenv()


BOT_TOKEN = os.getenv("BOT_TOKEN")
DATABASE_NAME = os.getenv("DATABASE_NAME")

TIMEZONE = ZoneInfo("Europe/Moscow")
35 changes: 27 additions & 8 deletions database.py → tfinance/database.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import sqlite3
from pathlib import Path

from models import User
from config import DATABASE_NAME


def singleton(cls):
Expand All @@ -22,7 +24,11 @@ class Database:

def __init__(self):
# Подключение к БД с отключенной проверкой потока.
self.con = sqlite3.connect("data.db", check_same_thread=False)
Path("sqlite").mkdir(exist_ok=True)
self.con = sqlite3.connect(
f"sqlite/{DATABASE_NAME}.db",
check_same_thread=False,
)
self.cur = self.con.cursor()
self.setup()

Expand Down Expand Up @@ -111,7 +117,8 @@ def add_prediction(self, user: User, stock_name: str, updown: str):
if predictions[0]:
prediction = f"{predictions[0]} {prediction}"
self.cur.execute(
f"UPDATE users SET prediction = ' {prediction}' WHERE id = {user.id}",
f"UPDATE users SET prediction = ' {prediction}' "
f"WHERE id = {user.id}",
)
self.con.commit()

Expand Down Expand Up @@ -145,7 +152,9 @@ def delete_predictions(self, user: User):
:param user: Экземпляр класса User с данными об этом пользователе.
:return: None
"""
self.cur.execute(f"UPDATE users SET prediction = '' WHERE id = {user.id}")
self.cur.execute(
f"UPDATE users SET prediction = '' WHERE id = {user.id}",
)
self.con.commit()

def select_stock(self, user: User, stock_name: str):
Expand All @@ -161,11 +170,12 @@ def select_stock(self, user: User, stock_name: str):
if selected_stocks:
stock_name = f"{selected_stocks} {stock_name}"
self.cur.execute(
f"UPDATE users SET selected_stock = '{stock_name}' WHERE id = {user.id}",
f"UPDATE users SET selected_stock = '{stock_name}' "
f"WHERE id = {user.id}",
)
self.con.commit()

def get_selected_stock_byid(self, user: User, message_id) -> str:
def get_selected_stock_byid(self, user: User, message_id) -> str | None:
"""
Получить название акции по id сообщения с игрой.
:param user: Экземпляр класса User с данными об этом пользователе.
Expand All @@ -179,6 +189,7 @@ def get_selected_stock_byid(self, user: User, message_id) -> str:
for i in data.split():
if str(message_id) == i.split(":")[-1]:
return i.split(":")[0]
return None

def get_selected_stocks(self, user: User) -> list:
"""
Expand Down Expand Up @@ -253,7 +264,8 @@ def add_favourites_stocks(self, user: User, stock_name: str):
if stocks and stocks[0]:
stock_name = f"{stocks[0]} {stock_name}"
self.cur.execute(
f"UPDATE users SET favourites_stocks = '{stock_name}' WHERE id = {user.id}",
f"UPDATE users SET favourites_stocks = '{stock_name}' "
f"WHERE id = {user.id}",
)
self.con.commit()

Expand Down Expand Up @@ -299,7 +311,8 @@ def remove_favourites_stock(self, user: User, stock_name: str):
else:
a = f"'{' '.join(a)}'"
self.cur.execute(
f"UPDATE users SET favourites_stocks = {a} WHERE id = {user.id}",
f"UPDATE users SET favourites_stocks = {a} "
f"WHERE id = {user.id}",
)
self.con.commit()

Expand All @@ -310,7 +323,8 @@ def user_daily_notify(self, user: User):
:return: None
"""
self.cur.execute(
f"UPDATE users SET daily_notify = NOT daily_notify WHERE id = {user.id}",
f"UPDATE users SET daily_notify = NOT daily_notify "
f"WHERE id = {user.id}",
)
self.con.commit()

Expand Down Expand Up @@ -339,3 +353,8 @@ def add_point(self, user: User):
)
user.points += 1
self.con.commit()


__all__ = [
"Database",
]
8 changes: 8 additions & 0 deletions exceptions.py → tfinance/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,11 @@ class EmptyDataFrameError(Exception): # Пустой дата фрейм

class WrongPeriodError(Exception): # Неверный период
pass


__all__ = [
"StockSelectedAlready",
"PredictionAlreadySet",
"EmptyDataFrameError",
"WrongPeriodError",
]
5 changes: 5 additions & 0 deletions functions.py → tfinance/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ def create_user(update: Update) -> User:
user_data["language_code"],
user_data["is_bot"],
)


__all__ = [
"create_user",
]
47 changes: 41 additions & 6 deletions game.py → tfinance/game.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.ext import ContextTypes, CallbackContext
from telegram import (
InlineKeyboardButton,
InlineKeyboardMarkup,
Update,
)
from telegram.ext import (
ContextTypes,
CallbackContext,
ConversationHandler,
CommandHandler,
CallbackQueryHandler,
)

from database import Database
from exceptions import EmptyDataFrameError, PredictionAlreadySet, StockSelectedAlready
from exceptions import (
EmptyDataFrameError,
PredictionAlreadySet,
StockSelectedAlready,
)
from functions import create_user
from graphics.visualize import check_stock_prices, do_stock_image
from models import User
Expand All @@ -17,12 +31,14 @@ async def game_menu(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Сохраняем id сообщения для возможности одновременной
message_id = str(int(update.message.message_id) + 2)
# Игры на многих акциях.
# Прибавляем 2 т.к. отправляем 2 сообщения: фото и приписку к нему с клавиатурой.
# Прибавляем 2 т.к. отправляем 2 сообщения:
# фото и приписку к нему с клавиатурой.
try:
# Проверка на наличие аргументов.
if not context.args:
await update.message.reply_text(
"Неправильно введена команда! Попробуйте: /game [индекс акции]",
"Неправильно введена команда! "
"Попробуйте: /game [индекс акции]",
)
# Проверка: была ли выбрана акция до этого? Избегаем читерства.
if db.check_selected_stocks(user):
Expand Down Expand Up @@ -80,7 +96,8 @@ async def game_menu(update: Update, context: ContextTypes.DEFAULT_TYPE):
)
db.remove_selected_stock(user, message_id)

# Возвращаем 1, чтобы показать ConversationHandler'у состояние, в котором находимся.
# Возвращаем 1, чтобы показать ConversationHandler'у состояние,
# в котором находимся.
return 1


Expand Down Expand Up @@ -194,3 +211,21 @@ async def game_results(context: CallbackContext):
# Удаляем пройденные прогнозы
db.delete_predictions(user)
user.prediction = db.get_predictions(user)


game_handler = ConversationHandler(
entry_points=[CommandHandler("game", game_menu)],
states={
1: [
CallbackQueryHandler(higher_game, pattern="^1$"),
CallbackQueryHandler(lower_game, pattern="^2$"),
],
},
fallbacks=[CommandHandler("game", game_menu)],
)


__all__ = [
"game_handler",
"game_results",
]
Loading