GIGACOMMIT

Changes:
	Frontend:
		- Added and adjusted command handlers for /start, /cat,
	  	/subscribe, /unsubscribe, and /subscription_modify
		- Implemented mass send function to send images for all
	  	subscribed users
		- Configured scheduler to call function above at 12:00 every day
		- Added psycopg2 error exception for each handler

	Backend:
		- Changed PostgreSQL DB Users table structure(id,
		  chat_id, images_amount) and adjusted all functions'
		  arguments and executes to match these changes
		- Added a function to fetch selected images_amount for a
		  set chat_id
		- Slightly optimized GetObjectExtension function
		- Allowed all functions that work with currentDay to
		  accept int values
		- Added a script for batch population of MinIO bucket
		  with images from ./files/<day number>/ directory
	Src:
		- Updated requirements.txt
This commit is contained in:
2025-08-02 18:58:12 +03:00
parent 76bf8f9f26
commit 3692c0233d
6 changed files with 189 additions and 41 deletions

Binary file not shown.

View File

@ -5,10 +5,11 @@ from src import config
def get_last_id(cursor):
cursor.execute("SELECT MAX(id) FROM Users")
id = cursor.fetchall()[0][0]
if id == None:
if id is None:
return 0
return id
def set_connection():
connection = psycopg2.connect(
dbname = config.db_name,
@ -20,24 +21,35 @@ def set_connection():
cursor = connection.cursor()
return cursor, connection
def close_connection(connection, cursor):
cursor.close()
connection.close()
#Functions don't close connection automatically, it has to be closed manually
def add_user(username, chat_id, connection, cursor):
cursor.execute("INSERT INTO Users VALUES (%s, %s, %s);", (get_last_id(cursor) + 1, username, chat_id))
def add_user(chat_id, connection, cursor):
cursor.execute("INSERT INTO Users VALUES (%s, %s, %s);", (get_last_id(cursor) + 1, chat_id, 1))
connection.commit()
def delete_user(username, connection, cursor):
cursor.execute("DELETE FROM Users WHERE username = %s;", (username,))
def delete_user(chat_id, connection, cursor):
cursor.execute("DELETE FROM Users WHERE chat_id = %s;", (chat_id,))
connection.commit()
def change_name(old_username, new_username, connection, cursor):
cursor.execute("UPDATE Users SET username = %s WHERE username = %s;", (new_username, old_username))
def change_images_amount(chat_id, amount, connection, cursor):
cursor.execute('UPDATE Users SET images_amount = %s WHERE chat_id = %s;', (amount, chat_id))
connection.commit()
def get_images_amount(chat_id, connection, cursor):
cursor.execute('SELECT images_amount FROM Users WHERE chat_id = %s;', (chat_id,))
images_amount = cursor.fetchall()[0][0]
return images_amount
def get_chat_id(id, cursor):
cursor.execute("SELECT chatid FROM Users WHERE id = %s", (id,))
cursor.execute("SELECT chat_id FROM Users WHERE id = %s", (id,))
chat_id = cursor.fetchall()[0][0]
return chat_id

View File

@ -1,7 +1,6 @@
from minio import Minio
from random import randint
from datetime import timedelta
from src.Backend import DBwork
from src import config
@ -14,38 +13,36 @@ def _setClient():
)
return minio_client
def getNumberofObjects(client, currentDay):
objects = client.list_objects(config.bucket_name, prefix=currentDay+'/')
objects = client.list_objects(config.bucket_name, prefix=str(currentDay) + '/')
return sum(1 for _ in objects)
def getObjectExtension(client, currentDay, fileNumber):
objects = client.list_objects(config.bucket_name, prefix=currentDay+'/')
for counter, obj in enumerate(objects, start=1):
objects = client.list_objects(config.bucket_name, prefix=str(currentDay) + '/')
counter = 0
object_extension = None
for obj in objects:
counter += 1
if counter == fileNumber:
return obj.object_name.split('.')[-1]
object_extension = obj.object_name.split('.')[-1]
return object_extension
def getImageName(currentDay, client):
maxFiles = getNumberofObjects(client, currentDay)
fileNumber = randint(1, maxFiles)
fileExtension = '.' + getObjectExtension(client, currentDay, fileNumber)
desiredFile = currentDay + '/' + str(fileNumber) + fileExtension
desiredFile = str(currentDay) + '/' + str(fileNumber) + fileExtension
return desiredFile
def getDownloadURL(currentDay):
client = _setClient()
object_name = getImageName(currentDay, client)
url = client.presigned_get_object(
config.bucket_name,
object_name,
expires=timedelta(days=1))
expires=timedelta(days=1)
)
return url
def downloadForAll(currentDay):
cur, conn = DBwork.set_connection()
max_id = DBwork.get_last_id(cur)
for id in range(1, max_id + 1):
chat_id = DBwork.get_chat_id(id, cur)
image_URL = getDownloadURL(currentDay)
# await bot.send_photo(chat_id = chat_id, photo = image_URL
DBwork.close_connection(conn, cur)

View File

@ -0,0 +1,58 @@
from minio import Minio
from minio.error import S3Error
from src import config
import glob
import os
access_key = config.acc_key
secret_key = config.sec_key
bucket = config.bucket_name
def upload_file(access_key, secret_key, bucket_name, object_name, file_path):
try:
client = Minio(
config.IS_address,
access_key = access_key,
secret_key = secret_key,
secure = False
)
if file_path.split('.')[-1] == 'jpeg':
client.fput_object(bucket_name, object_name, file_path, 'image/jpeg')
if file_path.split('.')[-1] == 'jpg':
client.fput_object(bucket_name, object_name, file_path, 'image/jpg')
elif file_path.split('.')[-1] == 'png':
client.fput_object(bucket_name, object_name, file_path, 'image/png')
elif file_path.split('.')[-1] == 'pjpeg':
client.fput_object(bucket_name, object_name, file_path, 'image/pjpeg')
print(f'image {object_name} with local path \'{file_path}\' was successfully uploaded')
except S3Error:
print(f'Error during MinIO operation: {S3Error}')
paths = glob.glob('../../files/*/*')
shortened_paths = []
for i in range(len(paths)):
paths[i] = os.path.abspath(paths[i]).replace('\\', '/')
for i in range(len(paths)):
shortened_paths.append(paths[i][paths[i].find('files/') + 6:])
# for i in range(0, len(paths)):
# upload_file(access_key, secret_key, bucket, f'{shortened_paths[i]}', f'{paths[i]}')
# print(paths[i])
client = Minio(
config.IS_address,
access_key = access_key,
secret_key = secret_key,
secure = False
)
objects = client.list_objects(config.bucket_name, prefix='1' + '/')
for obj in objects:
print(obj.object_name)

View File

@ -1,15 +1,21 @@
import psycopg2
import Backend.ISwork, Backend.DBwork
from src import config
import asyncio
from src.Backend import DBwork
from src.Backend import ISwork
import logging
from aiogram import Bot, Dispatcher, F, Router
from aiogram.filters import Command
from aiogram.types import Message
from aiogram import Bot, Dispatcher, Router
from aiogram.filters import Command, CommandObject
from aiogram.types import Message, URLInputFile
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from aiogram.fsm.storage.memory import MemoryStorage
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from datetime import datetime
current_day = datetime.now().weekday()
start_router = Router()
scheduler = AsyncIOScheduler(timezone = 'Europe/Moscow')
@ -17,21 +23,95 @@ scheduler = AsyncIOScheduler(timezone = 'Europe/Moscow')
logging.basicConfig(level = logging.INFO, format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
bot = Bot(token = config.TG_TOKEN , default = DefaultBotProperties(parse_mode = ParseMode.HTML))
bot = Bot(token = config.TG_token , default = DefaultBotProperties(parse_mode = ParseMode.HTML))
dp = Dispatcher(storage = MemoryStorage())
@dp.message(Command('start'))
async def cmd_start(message: Message):
await message.answer('Запуск сообщения по команде /start используя фильтр CommandStart()')
await message.answer('''
This is a bot that sends images of cats.
List of available commands:
/cat - request an image of a cat from today's pool of images
/subscribe - subscribe to daily cat images sent at 12:00 UTC+3
/subscription_modify <number> - change the amount of images sent daily
/unsubscribe - cancel your subscription(but why would you want to? :3)
''')
@dp.message(F.text)
async def basic_reaction(message: Message):
if message.text != '':
await message.answer(message.text)
@dp.message(Command('cat'))
async def cmd_cat(message: Message):
image_link = URLInputFile(ISwork.getDownloadURL(current_day), filename=datetime.now().strftime('%Y_%m_%d_%H_%M_%S'))
await message.answer_photo(image_link, caption='Look, a cat :3')
@dp.message(Command('subscription_modify'))
async def subscription_modify(message: Message, command: CommandObject):
if command.args is None or command.args.isdigit() == False:
await message.answer('Please write the number of images you would like\n'
'to receive in the same message as a command.\n'
'Example:'
'/subscription_modify <number of daily images>')
return
try:
cursor, connection = DBwork.set_connection()
chat_id = message.chat.id
amount = command.args
DBwork.change_images_amount(chat_id, amount, connection, cursor)
DBwork.close_connection(connection, cursor)
except psycopg2.Error:
await message.answer('You are not yet subscribed.')
return
await message.answer('Amount of daily images was changed successfully!')
@dp.message(Command('subscribe'))
async def cmd_subscribe(message: Message):
try:
cursor, connection = DBwork.set_connection()
chat_id = message.chat.id
DBwork.add_user(chat_id, connection, cursor)
DBwork.close_connection(connection, cursor)
except psycopg2.Error as e:
await message.answer('You are already subscribed.')
print(e.pgerror)
return
await message.answer('''
You have successfully subscribed to daily cat photos!
You will get 1 photo a day by default,
use /subscription_modify to change that amount.
''')
@dp.message(Command('unsubscribe'))
async def cmd_unsubscribe(message: Message):
try:
cursor, connection = DBwork.set_connection()
chat_id = message.chat.id
DBwork.delete_user(chat_id, connection, cursor)
DBwork.close_connection(connection, cursor)
except psycopg2.Error:
await message.answer('You are not yet subscribed.')
return
await message.answer('You have successfully unsubscribed.')
async def send_daily_images():
cursor, connection = DBwork.set_connection()
max_id = DBwork.get_last_id(cursor)
for id in range(1, max_id + 1):
chat_id = DBwork.get_chat_id(id, cursor)
images_amount = DBwork.get_images_amount(chat_id, connection, cursor)
for _ in range(images_amount):
image_link = URLInputFile(ISwork.getDownloadURL(current_day), filename=datetime.now().strftime('%Y_%m_%d_%H_%M_%S'))
await bot.send_photo(chat_id = chat_id, photo = image_link)
DBwork.close_connection(connection, cursor)
scheduler.add_job(send_daily_images, 'cron', hour = 12, minute = 0)
async def main():
scheduler.start()
await dp.start_polling(bot)

View File

@ -2,8 +2,9 @@ from decouple import config
IS_address = config('IS_ADDRESS')
acc_key = config('ACC_KEY')
sec_key = config('SEC_KEY')
acc_key = config('MINIO_ACCESS_KEY')
sec_key = config('MINIO_SECRET_KEY')
root_user = config('MINIO_ROOT_USER')
db_name = config('DB_NAME')
postgres_user = config('POSTGRES_USER')
postgres_password = config('POSTGRES_PASSWORD')
@ -12,5 +13,5 @@ port = config('PORT')
bucket_name = config('BUCKET_NAME')
TG_TOKEN = config('TG_TOKEN')
TG_token = config('TG_TOKEN')
# ADMINS = [int(admin_id) for admin_id in config('ADMINS').split(',')]