A lot of rework. Added inline keyboard

This commit is contained in:
Joan
2023-03-10 18:47:09 +01:00
parent 9b02c783a4
commit 5841038d55
6 changed files with 331 additions and 80 deletions

View File

@@ -9,7 +9,6 @@ services:
- ./data:/app/data
restart: unless-stopped
environment:
- TELEGRAM_CHANNEL_ID=${TELEGRAM_CHANNEL_ID}
- TELEGRAM_TOKEN=${TELEGRAM_TOKEN}
- LATITUDE=${LATITUDE}
- LONGITUDE=${LONGITUDE}

View File

@@ -1,7 +1,8 @@
import os
#TELEGRAM_ESCAPE_CHARACTERS = ['_', '*', '[', ']', '(', ')', '~', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!']
TELEGRAM_ESCAPE_CHARACTERS = ['_', '*', '[', ']', '(', ')', '~', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!']
TELEGRAM_ESCAPE_CHARACTERS = ['_', '*', '[', ']', '(', ')', '~', '>', '+', '-', '=', '|', '{', '}', '.', '!']
TELEGRAM_REMOVE_CHARACTERS = ['#']
ADMIN_IDS = [10101691]
TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN")
DB = "/app/data/wallamanta.db"

View File

@@ -2,11 +2,11 @@ import time
import random
import requests
import logging
import os
import constants
from PIL import Image, ImageDraw, ImageFont
from datetime import datetime, timedelta
from telegram import InlineKeyboardButton
# Enable logging
logging.basicConfig(
@@ -21,13 +21,19 @@ def telegram_escape_characters(text):
text = text.replace(character, f'\\{character}')
except:
pass
for character in constants.TELEGRAM_REMOVE_CHARACTERS:
try:
text = text.replace(character, '')
except:
pass
return text
def is_user_admin(telegram_user_id):
return telegram_user_id in constants.ADMIN_IDS
def get_telegram_user_id(update):
return update.message.chat_id
#return update.message.chat_id
return update.effective_chat.id
def get_date_ahead(add_days):
date_ahead = datetime.today() + timedelta(days=add_days)
@@ -99,11 +105,69 @@ def create_image(article):
def send_article(article, product):
create_image(article)
text = f"*{telegram_escape_characters(article['title'])}*\n\n*Descripción*: {telegram_escape_characters(article['description'])}\\nn*Precio*: {telegram_escape_characters(str(article['price']))} {telegram_escape_characters(article['currency'])}\n\n[IR AL ANUNCIO](https://es\.wallapop\.com/item/{telegram_escape_characters(article['web_slug'])})"
text = f"*{telegram_escape_characters(article['title'])}*\n\n*Descripción*: {telegram_escape_characters(article['description'])}\n\n*Precio*: {telegram_escape_characters(str(article['price']))} {telegram_escape_characters(article['currency'])}\n\n[IR AL ANUNCIO](https://es\.wallapop\.com/item/{telegram_escape_characters(article['web_slug'])})"
url = f"https://api.telegram.org/bot{constants.TELEGRAM_TOKEN}/sendPhoto?chat_id={product['telegram_user_id']}&caption={text}&parse_mode=MarkdownV2"
files = {'photo':open(f"/app/data/images/products/{article['id']}_composed.png", 'rb')}
logging.info(requests.post(url, files=files).content)
def get_category_id(category):
ret = constants.CATEGORIES.get(category, '')
return ret
return ret
def create_category_keyboard():
keyboard = [
[
InlineKeyboardButton("🖥 Informática", callback_data='15000'),
InlineKeyboardButton("Motor", callback_data='12800'),
InlineKeyboardButton("Coches", callback_data='100'),
],
[
InlineKeyboardButton("Motos", callback_data='14000'),
InlineKeyboardButton("Moda", callback_data='12465'),
InlineKeyboardButton("Inmobiliaria", callback_data='200'),
],
[
InlineKeyboardButton("TV, audio y foto", callback_data='12545'),
InlineKeyboardButton("Móviles", callback_data='16000'),
InlineKeyboardButton("Deportes y ocio", callback_data='12579'),
],
[
InlineKeyboardButton("Bicicletas", callback_data='17000'),
InlineKeyboardButton("Consolas y videojuegos", callback_data='12900'),
InlineKeyboardButton("Hogar y jardín", callback_data='12467'),
],
[
InlineKeyboardButton("Electrodomésticos", callback_data='13100'),
InlineKeyboardButton("Cine, libros y música", callback_data='12463'),
InlineKeyboardButton("Niños y bebés", callback_data='12461'),
],
[
InlineKeyboardButton("Coleccionismo", callback_data='18000'),
InlineKeyboardButton("Construcción y reformas", callback_data='19000'),
InlineKeyboardButton("Industria y agricultura", callback_data='20000'),
],
[
InlineKeyboardButton("Empleo", callback_data='21000'),
InlineKeyboardButton("Servicios", callback_data='13200'),
InlineKeyboardButton("Otros", callback_data='12485'),
],
[
InlineKeyboardButton("Todos", callback_data='0'),
],
]
return keyboard
def create_continue_keyboard():
keyboard = [
[
InlineKeyboardButton("Finalizar", callback_data='add'),
InlineKeyboardButton("Descartar", callback_data='remove')
],[
InlineKeyboardButton("Exclusión de título", callback_data='title_exclude'),
InlineKeyboardButton("Exclusión de descripción", callback_data='description_exclude')
],[
InlineKeyboardButton("Coordenadas", callback_data='coords'),
InlineKeyboardButton("Distancia", callback_data='distance')
],
]
return keyboard

View File

@@ -5,7 +5,7 @@ import constants
# Enable logging
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
)
logger = logging.getLogger(__name__)
@@ -145,7 +145,7 @@ def get_all_products():
def add_product(product):
condition = 'all'
product_name = product.get('product_name')
product_name = product.get('product_name').lower()
distance = product.get('distance', 0)
latitude = product.get('latitude', constants.LATITUDE)
longitude = product.get('longitude', constants.LONGITUDE)
@@ -154,6 +154,8 @@ def add_product(product):
title_exclude = product.get('title_exclude', '')
title_description_exclude = product.get('title_description_exclude', '')
category = product.get('category', '')
if category == '0':
category = ''
telegram_user_id = product.get('telegram_user_id')
params = (product_name, \
distance, latitude, longitude, condition, min_price, \
@@ -162,8 +164,8 @@ def add_product(product):
con = sqlite3.connect(constants.DB)
cur = con.cursor()
res = cur.execute(f"SELECT * FROM products WHERE telegram_user_id={product.get('telegram_user_id')} \
AND product_name='{product.get('product_name')}'")
res = cur.execute(f"SELECT * FROM products WHERE telegram_user_id={telegram_user_id} \
AND product_name='{product_name}'")
if res.fetchone() is None:
cur.execute("INSERT INTO products VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", params)
con.commit()
@@ -175,7 +177,7 @@ def remove_product(product):
con = sqlite3.connect(constants.DB)
cur = con.cursor()
res = cur.execute(f"DELETE FROM products WHERE telegram_user_id={product.get('telegram_user_id')} \
AND product_name='{product.get('product_name')}'")
AND product_name='{product.get('product_name').lower()}'")
con.commit()
con.close()
logging.info(f"Removed product {product['product_name']}")

View File

@@ -1,5 +1,4 @@
import json
import os
import threading
import logging
import prettytable
@@ -8,8 +7,22 @@ import walladb
import constants
from worker import Worker
from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, ForceReply
from telegram.constants import ParseMode
from telegram.ext import (
Application,
CallbackQueryHandler,
CallbackContext,
CommandHandler,
ContextTypes,
ConversationHandler,
MessageHandler,
filters
)
ACTION, ADD_PRODUCT_NAME, ADD_PRODUCT_MIN_PRICE, ADD_PRODUCT_MAX_PRICE, \
ADD_PRODUCT_CATEGORY, ADD_PRODUCT_TITLE_EXCLUDE, ADD_PRODUCT_DESCRIPTION_EXCLUDE, \
ADD_PRODUCT_COORDS, ADD_PRODUCT_DISTANCE, REMOVE_PRODUCT, LIST, FINISH, CONTINUE_OR_FINISH = range(13)
# Enable logging
logging.basicConfig(
@@ -42,82 +55,229 @@ Borra un producto con `/remove nombre del producto`\n
message = """Activa tu periodo de prueba de 7 días con `/test` o contacta con @jocarduck para más información."""
await update.message.reply_markdown_v2(helpers.telegram_escape_characters(message))
async def categories_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if walladb.is_user_valid(helpers.get_telegram_user_id(update)):
message = ''
for category in constants.CATEGORIES:
message = f"{message}, {category}"
await update.message.reply_markdown_v2(f"```{helpers.telegram_escape_characters(message)}```")
async def main_menu(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
context.user_data.clear()
keyboard = [
[
InlineKeyboardButton("Añadir", callback_data='add'),
InlineKeyboardButton("Listar", callback_data='list'),
InlineKeyboardButton("Borrar", callback_data='remove')]
]
markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text('Selecciona una acción a realizar', reply_markup=markup)
return ACTION
async def add_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
async def menu_click_handler(update: Update, context: CallbackContext):
telegram_user_id = helpers.get_telegram_user_id(update)
if walladb.is_user_valid(telegram_user_id):
number_of_products = walladb.count_user_products(telegram_user_id)
message = """Tienes que pasar el número correcto de parámetros: `/add producto;precio_mínimo;precio_máximo,categoría,excluir_título(opcional, separado por comas);excluir_descripción_y_título(opciona, separado por comas);latitud(opcional);longitud(opcional),distancia(opcional)`\n
Ejemplo: `/add placa base itx;0;150`\n
Ejemplo 2: `/add cpu;10;30;;intel,core 2 duo,celeron;;;100`\n
Los campos opcionales que se dejen vacíos tomarán el valor configurado en el archivo `.env`"""
valid = False
if walladb.is_user_testing(telegram_user_id):
valid = True
if number_of_products >= 5:
message = "Ya tienes 5 productos en seguimiento. Con premium puedes tener hasta 20."
valid = False
elif walladb.is_user_premium(telegram_user_id):
valid = True
if number_of_products >= 20:
message = "Ya tienes 20 productos en seguimiento. Borra algunos para añadir más."
valid = False
if valid:
args = update.message.text.split("/add ")
if len(args) == 1:
pass
elif len(args[1].split(";")) > 2:
product = dict()
product['telegram_user_id'] = telegram_user_id
args = args[1].split(";")
product['product_name'], product['min_price'], product['max_price'] = args[0:3]
if len(args) > 3 and args[3]:
product['category'] = helpers.get_category_id(args[3])
if len(args) > 4 and args[4]:
product['title_exclude'] = args[4]
if len(args) > 5 and args[5]:
product['title_description_exclude'] = args[5]
if len(args) > 6 and args[6]:
product['latitude'] = args[6]
if len(args) > 7 and args[7]:
product['longitude'] = args[7]
if len(args) > 8 and args[8]:
product['distance'] = args[8]
logging.info(f'Adding: {product}')
if not walladb.get_product(product):
walladb.add_product(product)
p = threading.Thread(target=Worker.run, args=(walladb.get_product(product), ))
p.start()
message = f"Añadido {walladb.get_product(product)['product_name']} a seguimiento."
else:
message = f"¡{walladb.get_product(product)['product_name']} ya está en seguimiento!"
await update.message.reply_markdown_v2(helpers.telegram_escape_characters(message))
context.user_data['telegram_user_id'] = telegram_user_id
query = update.callback_query
if query.data == 'add':
valid = False
if walladb.is_user_testing(telegram_user_id):
number_of_products = walladb.count_user_products(telegram_user_id)
valid = True
if number_of_products >= 5:
message = "Ya tienes 5 productos en seguimiento. Con premium puedes tener hasta 20."
valid = False
elif walladb.is_user_premium(telegram_user_id):
valid = True
if number_of_products >= 20:
message = "Ya tienes 20 productos en seguimiento. Borra algunos para añadir más."
valid = False
if valid:
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Escribe el nombre del producto a buscar', reply_markup=ForceReply())
return ADD_PRODUCT_NAME
else:
await context.bot.send_message(chat_id=update.effective_chat.id, text=f'{message}')
return ConversationHandler.END
if query.data == 'remove':
await send_list(update, context)
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Escribe el nombre del producto a borrar, tal y como aparece en la lista', reply_markup=ForceReply())
return REMOVE_PRODUCT
if query.data == 'list':
await send_list(update, context)
#await context.bot.send_message(chat_id=update.effective_chat.id, text='Send your name', reply_markup=ForceReply())
return ConversationHandler.END
else:
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Activa tu periodo de prueba de 7 días con `/test` o contacta con @jocarduck para más información.', parse_mode=ParseMode.MARKDOWN_V2)
return ConversationHandler.END
async def add_product_name(update: Update, context: CallbackContext):
if context.user_data.get('product_name', '') == '':
answer = update.message.text
context.user_data['product_name'] = answer
if not walladb.get_product(context.user_data):
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Escribe el precio mínimo', reply_markup=ForceReply())
return ADD_PRODUCT_MIN_PRICE
else:
await context.bot.send_message(chat_id=update.effective_chat.id,
text=f"¡{walladb.get_product(context.user_data)['product_name']} ya está en seguimiento!")
return ConversationHandler.END
async def remove_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
async def add_product_min_price(update: Update, context: CallbackContext):
if context.user_data.get('min_price', '') == '':
answer = update.message.text
try:
min_price = int(answer)
except:
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Pon un precio mínimo en números', reply_markup=ForceReply())
return ADD_PRODUCT_MIN_PRICE
if min_price < 0:
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Pon un precio mínimo en números que no sean negativos', reply_markup=ForceReply())
return ADD_PRODUCT_MIN_PRICE
context.user_data['min_price'] = min_price
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Escribe el precio máximo', reply_markup=ForceReply())
return ADD_PRODUCT_MAX_PRICE
async def add_product_max_price(update: Update, context: CallbackContext):
if context.user_data.get('max_price', '') == '':
answer = update.message.text
try:
max_price = int(answer)
except:
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Pon un precio máximo en números', reply_markup=ForceReply())
return ADD_PRODUCT_MAX_PRICE
if max_price < 0:
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Pon un precio máximo en números que no sean negativos', reply_markup=ForceReply())
return ADD_PRODUCT_MAX_PRICE
context.user_data['max_price'] = max_price
keyboard = [
[
InlineKeyboardButton("Añadir", callback_data='add'),
InlineKeyboardButton("Descartar", callback_data='remove')
], [InlineKeyboardButton("Escoge una categoría", callback_data='category'),],
]
markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(f'{context.user_data["product_name"]} de {context.user_data["min_price"]}€ a {context.user_data["max_price"]}', reply_markup=markup)
return ADD_PRODUCT_CATEGORY
async def add_product_category(update: Update, context: CallbackContext):
query = update.callback_query
if query.data == 'category':
markup = InlineKeyboardMarkup(helpers.create_category_keyboard())
context.user_data['last_step'] = 'category'
await context.bot.send_message(chat_id=update.effective_chat.id, text='Escoge la categoría', reply_markup=markup)
return CONTINUE_OR_FINISH
elif query.data == 'add':
await add_to_db_and_send(update, context)
return ConversationHandler.END
elif query.data == 'remove':
context.user_data.clear()
await context.bot.send_message(chat_id=update.effective_chat.id, text=f'Borrado')
return ConversationHandler.END
async def continue_or_finish(update: Update, context: CallbackContext):
markup = InlineKeyboardMarkup(helpers.create_continue_keyboard())
last_step = context.user_data.get('last_step', '')
query = update.callback_query
try:
qd = query.data
except:
qd = ''
if qd == 'add':
await add_to_db_and_send(update, context)
return ConversationHandler.END
if qd == 'remove':
context.user_data.clear()
await context.bot.send_message(chat_id=update.effective_chat.id, text=f'Borrado')
return ConversationHandler.END
if last_step == 'category':
category = int(query.data)
context.user_data['category'] = category
context.user_data['last_step'] = ''
if qd == 'title_exclude':
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Exclusión de título', reply_markup=ForceReply())
context.user_data['last_step'] = 'title_exclude'
return CONTINUE_OR_FINISH
if last_step == 'title_exclude':
context.user_data['title_exclude'] = update.message.text
context.user_data['last_step'] = ''
if qd == 'description_exclude':
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Exclusión de descripción', reply_markup=ForceReply())
context.user_data['last_step'] = 'description_exclude'
return CONTINUE_OR_FINISH
if last_step == 'description_exclude':
context.user_data['description_exclude'] = update.message.text
context.user_data['last_step'] = ''
if qd == 'coords':
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Coordenadas', reply_markup=ForceReply())
context.user_data['last_step'] = 'coords'
return CONTINUE_OR_FINISH
if last_step == 'coords':
answer = update.message.text
try:
latitude = float(answer.split(',')[0])
longitude = float(answer.split(',')[1])
except:
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Pon las coordenadas siguiendo el ejemplo: 41.34, 0.65', reply_markup=ForceReply())
return CONTINUE_OR_FINISH
context.user_data['latitude'] = latitude
context.user_data['longitude'] = longitude
context.user_data['last_step'] = ''
if qd == 'distance':
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Distancia', reply_markup=ForceReply())
context.user_data['last_step'] = 'distance'
return CONTINUE_OR_FINISH
if last_step == 'distance':
answer = update.message.text
try:
latitude = int(answer)
except:
await context.bot.send_message(chat_id=update.effective_chat.id,
text='Pon la distancia en km, por ejemplo: 100', reply_markup=ForceReply())
return CONTINUE_OR_FINISH
context.user_data['distance'] = answer
context.user_data['last_step'] = ''
if context.user_data['last_step'] == '':
await context.bot.send_message(chat_id=update.effective_chat.id, text=f'Añadir?', reply_markup=markup)
return CONTINUE_OR_FINISH
async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
context.user_data.clear()
await context.bot.send_message(chat_id=update.effective_chat.id, text="Cancelado.")
return ConversationHandler.END
async def remove_product(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
telegram_user_id = helpers.get_telegram_user_id(update)
if walladb.is_user_valid(telegram_user_id):
if walladb.is_user_testing(telegram_user_id) or walladb.is_user_premium(telegram_user_id):
product_to_remove = update.message.text[len('/remove '):]
product_to_remove = update.message.text
message = f"¡{product_to_remove} no está en seguimiento!"
if walladb.remove_product({'product_name' : product_to_remove, \
'telegram_user_id' : telegram_user_id}):
message = f"¡{product_to_remove} borrado de la lista de seguimiento!"
await update.message.reply_text(message)
return ConversationHandler.END
async def list_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
async def send_list(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
telegram_user_id = helpers.get_telegram_user_id(update)
if walladb.is_user_valid(telegram_user_id):
if walladb.is_user_testing(telegram_user_id) or walladb.is_user_premium(telegram_user_id):
products = walladb.get_products_from_user(telegram_user_id)
args = update.message.text.split("/list ")
args = ''
found = False
if len(args) > 1:
product = walladb.get_product({'product_name':args[1],'telegram_user_id':telegram_user_id})
@@ -135,7 +295,7 @@ async def list_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
table.align['Máx'] = 'r'
for product in products:
table.add_row([helpers.telegram_escape_characters(product['product_name']), f"{helpers.telegram_escape_characters(product['min_price'])}", f"{helpers.telegram_escape_characters(product['max_price'])}"])
await update.message.reply_markdown_v2(f'```{(table)}```')
await context.bot.send_message(chat_id=update.effective_chat.id, text=f'```{(table)}```', parse_mode=ParseMode.MARKDOWN_V2)
async def admin_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if helpers.is_user_admin(update.message.chat_id):
@@ -185,6 +345,16 @@ async def test_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
message = "Ya eres premium. No puedes volver al periodo de prueba."
await update.message.reply_markdown_v2(helpers.telegram_escape_characters(message))
async def add_to_db_and_send(update, context):
logging.info(f"Adding product with context: {context.user_data}")
walladb.add_product(context.user_data)
p = threading.Thread(target=Worker.run, args=(walladb.get_product(context.user_data), ))
p.start()
await context.bot.send_message(chat_id=update.effective_chat.id, text=f"¡*{context.user_data['product_name']}* añadido correctamente\!", parse_mode=ParseMode.MARKDOWN_V2)
def error(update, context):
logging.error(f'Update ---{update}--- caused error ---{context.error}---')
def main()->None:
walladb.setup_db()
products = walladb.get_all_products()
@@ -200,16 +370,34 @@ def main()->None:
# on different commands - answer in Telegram
application.add_handler(CommandHandler("help", help_command))
application.add_handler(CommandHandler("categories", categories_command))
application.add_handler(CommandHandler("add", add_command))
application.add_handler(CommandHandler("remove", remove_command))
application.add_handler(CommandHandler("list", list_command))
application.add_handler(CommandHandler("admin", admin_command))
application.add_handler(CommandHandler("add_premium_user", add_premium_user_command))
application.add_handler(CommandHandler("remove_user", remove_user_command))
application.add_handler(CommandHandler("status", status_command))
application.add_handler(CommandHandler("test", test_command))
conv_handler = ConversationHandler(
entry_points=[CommandHandler("menu", main_menu)],
states={
ACTION: [CallbackQueryHandler(menu_click_handler)],
ADD_PRODUCT_NAME: [MessageHandler(filters.TEXT, add_product_name),
CallbackQueryHandler(add_product_name)],
ADD_PRODUCT_MIN_PRICE: [MessageHandler(filters.TEXT, add_product_min_price)],
ADD_PRODUCT_MAX_PRICE: [MessageHandler(filters.TEXT, add_product_max_price)],
ADD_PRODUCT_CATEGORY: [CallbackQueryHandler(add_product_category)],
CONTINUE_OR_FINISH: [CallbackQueryHandler(continue_or_finish),
MessageHandler(filters.TEXT, continue_or_finish)],
REMOVE_PRODUCT: [MessageHandler(filters.TEXT, remove_product)],
},
fallbacks=[CommandHandler("cancel", cancel)],
)
# Add ConversationHandler to application that will be used for handling updates
application.add_handler(conv_handler)
#application.bot.set_chat_menu_button('Menú')
application.add_error_handler(error)
# on non command i.e message - echo the message on Telegram
#application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))

View File

@@ -1,9 +1,6 @@
import time
import requests
import telegram
import os
import logging
import json
import helpers
import walladb
import constants