mirror of
https://github.com/Steffo99/greed.git
synced 2024-11-25 07:14:18 +00:00
Add and edit products
This commit is contained in:
parent
b7bdae4678
commit
c97dcaeef4
3 changed files with 111 additions and 26 deletions
29
database.py
29
database.py
|
@ -1,10 +1,11 @@
|
||||||
from sqlalchemy import create_engine, Column, ForeignKey, UniqueConstraint, CheckConstraint
|
from sqlalchemy import create_engine, Column, ForeignKey, UniqueConstraint, CheckConstraint
|
||||||
from sqlalchemy import Integer, BigInteger, String, Numeric, Text
|
from sqlalchemy import Integer, BigInteger, String, Text, LargeBinary
|
||||||
from sqlalchemy.orm import sessionmaker, relationship
|
from sqlalchemy.orm import sessionmaker, relationship
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
import configloader
|
import configloader
|
||||||
import telegram
|
import telegram
|
||||||
import strings
|
import strings
|
||||||
|
import requests
|
||||||
from html import escape
|
from html import escape
|
||||||
|
|
||||||
# Create a (lazy) database engine
|
# Create a (lazy) database engine
|
||||||
|
@ -67,8 +68,8 @@ class Product(TableDeclarativeBase):
|
||||||
description = Column(Text)
|
description = Column(Text)
|
||||||
# Product price, if null product is not for sale
|
# Product price, if null product is not for sale
|
||||||
price = Column(Integer)
|
price = Column(Integer)
|
||||||
# Image filename
|
# Image data
|
||||||
image = Column(String)
|
image = Column(LargeBinary)
|
||||||
# Stock quantity, if null product has infinite stock
|
# Stock quantity, if null product has infinite stock
|
||||||
stock = Column(Integer)
|
stock = Column(Integer)
|
||||||
|
|
||||||
|
@ -79,15 +80,29 @@ class Product(TableDeclarativeBase):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Return the product details formatted with Telegram HTML. The image is omitted."""
|
"""Return the product details formatted with Telegram HTML. The image is omitted."""
|
||||||
return f"<b>{escape(self.name)}</b>\n" \
|
return f"{escape(self.name)}\n" \
|
||||||
f"{escape(self.description)}\n" \
|
f"{escape(self.description)}\n\n" \
|
||||||
f"<i>{strings.in_stock_format_string.format(quantity=self.stock) if self.stock is not None else ''}</i>\n" \
|
f"{strings.in_stock_format_string.format(quantity=self.stock) if self.stock is not None else ''}\n" \
|
||||||
f"{strings.currency_format_string.format(symbol=strings.currency_symbol, value=self.price)}"
|
f"{strings.currency_format_string.format(symbol=strings.currency_symbol, value=self.price)}"
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<Product {self.name}>"
|
return f"<Product {self.name}>"
|
||||||
|
|
||||||
# TODO: add get image (and set image?) method(s)
|
def send_as_message(self, chat_id: int) -> dict:
|
||||||
|
"""Send a message containing the product data."""
|
||||||
|
r = requests.post(f"https://api.telegram.org/bot{configloader.config['Telegram']['token']}/sendPhoto",
|
||||||
|
files={"photo": self.image},
|
||||||
|
params={"chat_id": chat_id,
|
||||||
|
"caption": str(self)})
|
||||||
|
return r
|
||||||
|
|
||||||
|
def set_image(self, file: telegram.File):
|
||||||
|
"""Download an image from Telegram and store it in the image column.
|
||||||
|
This is a slow blocking function. Try to avoid calling it directly, use a thread if possible."""
|
||||||
|
# Download the photo through a get request
|
||||||
|
r = requests.get(file.file_path)
|
||||||
|
# Store the photo in the database record
|
||||||
|
self.image = r.content
|
||||||
|
|
||||||
|
|
||||||
class Transaction(TableDeclarativeBase):
|
class Transaction(TableDeclarativeBase):
|
||||||
|
|
18
strings.py
18
strings.py
|
@ -75,7 +75,20 @@ ask_product_name = "Come si deve chiamare il prodotto?"
|
||||||
ask_product_description = "Quale deve essere la descrizione del prodotto?"
|
ask_product_description = "Quale deve essere la descrizione del prodotto?"
|
||||||
|
|
||||||
# Add product: price?
|
# Add product: price?
|
||||||
ask_product_price = "Quanto deve costare il prodotto?"
|
ask_product_price = "Quanto deve costare il prodotto?\n" \
|
||||||
|
"Scrivi <code>skip</code> se vuoi che il prodotto non sia ancora in vendita."
|
||||||
|
|
||||||
|
# Add product: image?
|
||||||
|
ask_product_image = "Che immagine vuoi che abbia il prodotto?"
|
||||||
|
|
||||||
|
# Thread has started downloading an image and might be unresponsive
|
||||||
|
downloading_image = "Sto scaricando la tua foto!\n" \
|
||||||
|
"Potrei metterci un po'... Abbi pazienza!\n" \
|
||||||
|
"Non sarò in grado di risponderti durante il download."
|
||||||
|
|
||||||
|
# Edit product: current value
|
||||||
|
edit_current_value = "Il valore attuale è:\n" \
|
||||||
|
"<pre>{value}</pre>"
|
||||||
|
|
||||||
# Payment: cash payment info
|
# Payment: cash payment info
|
||||||
payment_cash = "Puoi pagare in contanti alla sede fisica del negozio.\n" \
|
payment_cash = "Puoi pagare in contanti alla sede fisica del negozio.\n" \
|
||||||
|
@ -105,6 +118,9 @@ bot_info = 'Questo bot utilizza <a href="https://github.com/Steffo99/greed">gree
|
||||||
# Success: product has been added to the database
|
# Success: product has been added to the database
|
||||||
success_product_added = "✅ Il prodotto è stato aggiunto con successo!"
|
success_product_added = "✅ Il prodotto è stato aggiunto con successo!"
|
||||||
|
|
||||||
|
# Success: product has been added to the database
|
||||||
|
success_product_edited = "✅ Il prodotto è stato modificato con successo!"
|
||||||
|
|
||||||
# Error: message received not in a private chat
|
# Error: message received not in a private chat
|
||||||
error_nonprivate_chat = "⚠️ Questo bot funziona solo in chat private."
|
error_nonprivate_chat = "⚠️ Questo bot funziona solo in chat private."
|
||||||
|
|
||||||
|
|
90
worker.py
90
worker.py
|
@ -8,6 +8,8 @@ import sys
|
||||||
import queue as queuem
|
import queue as queuem
|
||||||
import database as db
|
import database as db
|
||||||
import re
|
import re
|
||||||
|
import requests
|
||||||
|
from html import escape
|
||||||
|
|
||||||
class StopSignal:
|
class StopSignal:
|
||||||
"""A data class that should be sent to the worker when the conversation has to be stopped abnormally."""
|
"""A data class that should be sent to the worker when the conversation has to be stopped abnormally."""
|
||||||
|
@ -144,6 +146,20 @@ class ChatWorker(threading.Thread):
|
||||||
# Return the successfulpayment
|
# Return the successfulpayment
|
||||||
return update.message.successful_payment
|
return update.message.successful_payment
|
||||||
|
|
||||||
|
def __wait_for_photo(self) -> typing.List[telegram.PhotoSize]:
|
||||||
|
"""Continue getting updates until a photo is received, then download and return it."""
|
||||||
|
while True:
|
||||||
|
# Get the next update
|
||||||
|
update = self.__receive_next_update()
|
||||||
|
# Ensure the update contains a message
|
||||||
|
if update.message is None:
|
||||||
|
continue
|
||||||
|
# Ensure the message contains a photo
|
||||||
|
if update.message.photo is None:
|
||||||
|
continue
|
||||||
|
# Return the photo array
|
||||||
|
return update.message.photo
|
||||||
|
|
||||||
def __user_menu(self):
|
def __user_menu(self):
|
||||||
"""Function called from the run method when the user is not an administrator.
|
"""Function called from the run method when the user is not an administrator.
|
||||||
Normal bot actions should be placed here."""
|
Normal bot actions should be placed here."""
|
||||||
|
@ -339,47 +355,85 @@ class ChatWorker(threading.Thread):
|
||||||
# If the user has selected the Add Product option...
|
# If the user has selected the Add Product option...
|
||||||
if selection == strings.menu_add_product:
|
if selection == strings.menu_add_product:
|
||||||
# Open the add product menu
|
# Open the add product menu
|
||||||
self.__add_product_menu()
|
self.__edit_product_menu()
|
||||||
# If the user has selected a product
|
# If the user has selected a product
|
||||||
else:
|
else:
|
||||||
|
# Find the selected product
|
||||||
|
product = self.session.query(db.Product).filter_by(name=selection).one()
|
||||||
# Open the edit menu for that specific product
|
# Open the edit menu for that specific product
|
||||||
self.__edit_product_menu(selection)
|
self.__edit_product_menu(product=product)
|
||||||
|
|
||||||
def __add_product_menu(self):
|
def __edit_product_menu(self, product: typing.Optional[db.Product]=None):
|
||||||
"""Add a product to the database."""
|
"""Add a product to the database or edit an existing one."""
|
||||||
# Ask for the product name until a valid product name is specified
|
# Ask for the product name until a valid product name is specified
|
||||||
while True:
|
while True:
|
||||||
# Ask the question to the user
|
# Ask the question to the user
|
||||||
self.bot.send_message(self.chat.id, strings.ask_product_name)
|
self.bot.send_message(self.chat.id, strings.ask_product_name)
|
||||||
|
# Display the current name if you're editing an existing product
|
||||||
|
if product:
|
||||||
|
self.bot.send_message(self.chat.id, strings.edit_current_value.format(value=escape(product.name)), parse_mode="HTML")
|
||||||
# Wait for an answer
|
# Wait for an answer
|
||||||
name = self.__wait_for_regex(r"(.*)")
|
name = self.__wait_for_regex(r"(.*)")
|
||||||
# Ensure a product with that name doesn't already exist
|
# Ensure a product with that name doesn't already exist
|
||||||
if self.session.query(db.Product).filter_by(name=name).one_or_none() is None:
|
if self.session.query(db.Product).filter_by(name=name).one_or_none() in [None, product]:
|
||||||
# Exit the loop
|
# Exit the loop
|
||||||
break
|
break
|
||||||
self.bot.send_message(self.chat.id, strings.error_duplicate_name)
|
self.bot.send_message(self.chat.id, strings.error_duplicate_name)
|
||||||
# Ask for the product description
|
# Ask for the product description
|
||||||
self.bot.send_message(self.chat.id, strings.ask_product_description)
|
self.bot.send_message(self.chat.id, strings.ask_product_description)
|
||||||
|
# Display the current description if you're editing an existing product
|
||||||
|
if product:
|
||||||
|
self.bot.send_message(self.chat.id, strings.edit_current_value.format(value=escape(product.description)), parse_mode="HTML")
|
||||||
# Wait for an answer
|
# Wait for an answer
|
||||||
description = self.__wait_for_regex(r"(.*)")
|
description = self.__wait_for_regex(r"(.*)")
|
||||||
# Ask for the product price
|
# Ask for the product price
|
||||||
self.bot.send_message(self.chat.id, strings.ask_product_price)
|
self.bot.send_message(self.chat.id, strings.ask_product_price)
|
||||||
|
# Display the current name if you're editing an existing product
|
||||||
|
if product:
|
||||||
|
self.bot.send_message(self.chat.id, strings.edit_current_value.format(value=(strings.currency_format_string.format(symbol=strings.currency_symbol, value=(product.price / (10 ** int(configloader.config["Payments"]["currency_exp"]))))) if product.price is not None else 'Non in vendita'), parse_mode="HTML")
|
||||||
# Wait for an answer
|
# Wait for an answer
|
||||||
price = int(self.__wait_for_regex(r"([0-9]{1,3}(?:[.,][0-9]{1,2})?)").replace(".", "").replace(",", "")) * (
|
price = self.__wait_for_regex(r"([0-9]{1,3}(?:[.,][0-9]{1,2})?|[Ss][Kk][Ii][Pp])")
|
||||||
10 ** int(configloader.config["Payments"]["currency_exp"]))
|
# If the price is skipped
|
||||||
# TODO: ask for product image
|
if price.lower() == "skip":
|
||||||
# Create the db record for the product
|
price = None
|
||||||
product = db.Product(name=name,
|
else:
|
||||||
description=description,
|
price = int(price.replace(".", "").replace(",", "")) * (10 ** int(configloader.config["Payments"]["currency_exp"]))
|
||||||
price=price)
|
# Ask for the product image
|
||||||
# Add the record to the session, then commit
|
self.bot.send_message(self.chat.id, strings.ask_product_image)
|
||||||
self.session.add(product)
|
# Wait for an answer
|
||||||
|
photo_list = self.__wait_for_photo()
|
||||||
|
# If a new product is being added...
|
||||||
|
if not product:
|
||||||
|
# Create the db record for the product
|
||||||
|
product = db.Product(name=name,
|
||||||
|
description=description,
|
||||||
|
price=price)
|
||||||
|
# Add the record to the database
|
||||||
|
self.session.add(product)
|
||||||
|
# If a product is being edited...
|
||||||
|
else:
|
||||||
|
# Edit the record with the new values
|
||||||
|
product.name = name
|
||||||
|
product.description = description
|
||||||
|
product.price = price
|
||||||
|
# Find the largest photo id
|
||||||
|
largest_photo = photo_list[0]
|
||||||
|
for photo in photo_list[1:]:
|
||||||
|
if photo.width > largest_photo.width:
|
||||||
|
largest_photo = photo
|
||||||
|
# Get the file object associated with the photo
|
||||||
|
photo_file = self.bot.get_file(largest_photo.file_id)
|
||||||
|
# Notify the user that the bot is downloading the image and might be inactive for a while
|
||||||
|
self.bot.send_message(self.chat.id, strings.downloading_image)
|
||||||
|
self.bot.send_chat_action(self.chat.id, action="upload_photo")
|
||||||
|
# Set the image for that product
|
||||||
|
product.set_image(photo_file)
|
||||||
self.session.commit()
|
self.session.commit()
|
||||||
# Notify the user
|
# Notify the user
|
||||||
self.bot.send_message(self.chat.id, strings.success_product_added)
|
if product:
|
||||||
|
self.bot.send_message(self.chat.id, strings.success_product_edited)
|
||||||
def __edit_product_menu(self, name: str):
|
else:
|
||||||
raise NotImplementedError()
|
self.bot.send_message(self.chat.id, strings.success_product_added)
|
||||||
|
|
||||||
def __orders_menu(self):
|
def __orders_menu(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
Loading…
Reference in a new issue