Added README, added Google Sheets URL
This commit is contained in:
54
README.md
54
README.md
@@ -1,6 +1,56 @@
|
|||||||
# conjuntasbot
|
# conjuntasbot
|
||||||
|
|
||||||
|
## Instalación
|
||||||
|
|
||||||
|
Crea un Google Spreadsheet con el nombre que quieras y añade una hoja que se llame `Socios`, aquí añadirás el nombre de los socios del canal.
|
||||||
|
|
||||||
|
Haz una copia del archivo `.env.example` y renómbrala a `.env`. Rellena los datos necesarios:
|
||||||
|
|
||||||
|
* TELEGRAM_TOKEN: El token del bot
|
||||||
|
* ADMIN_IDS: Los IDs de Telegram de quien pueda crear conjuntas, separados por comas
|
||||||
|
* GROUP_CHAT_ID: El ID del grupo donde se anclarán los mensajes de las conjuntas
|
||||||
|
* SPREADSHEET_ID: El ID del Google Spreadsheet (se obtiene de la URL)
|
||||||
|
|
||||||
|
Entra a https://console.developers.google.com/ con tu cuenta de Google y haz click en `CREAR PROYECTO`, ponle el nombre que quieras. Entra al proyecto recién creado y haz click en `+ HABILITAR APIS Y SERVICIOS`, selecciona `Google Drive API` y `Google Sheets API`, una vez añadidas vuelve al proyecto y haz click en `Credenciales`, luego en `+ CREAR CREDENCIALES` y escoge `Cuenta de servicio`, ponle un nombre y acepta todo lo siguiente sin poner nada más. Vuelve al apartado de `Credenciales` y haz click en `Administrar cuentas de servicio` (en la derecha), luego sobre la cuenta creada, al final verás tres puntos uno encima de otro (`Acciones`), haz click ahí y luego en `Administrar claves`, haz click en `AGREGAR CLAVE` y luego en `Crear clave nueva`, escoge el formato `JSON` y créala, se descargará sola. Ahora coge el contenido de esta clave y crea un archivo en la carpeta `data` que se llame `creds.json` y pega el contenido del archivo.
|
||||||
|
|
||||||
|
Abre el archivo `creds.json` y coge el mail que aparece en `"client_email":`, comparte el documento creado en Google Sheets con ese mail y dale permisos de edición.
|
||||||
|
|
||||||
|
Ahora ya está todo listo para que funcione, ejecuta `docker compose up -d` y el bot ya estará en marcha.
|
||||||
|
|
||||||
|
## Funcionamiento
|
||||||
|
|
||||||
|
El administrador usará los comandos en el apartado [#comandos](#comandos).
|
||||||
|
|
||||||
|
Los usuarios responderán al mensaje anclado en el canal con "me apunto <número>", "me uno <número>", "me desapunto", "me borro"... Esto hará que se actualice el mensaje anclado con la lista de gente apuntada y cantidades.
|
||||||
|
|
||||||
|
Las conjuntas creadas pueden tener o no límite de productos, si no hay límite, todo el mundo puede apuntarse con la cantidad que quiera. Si lo hay, también existirá un límite por persona, por lo que se avisará al usuario tanto si supera el límite individual como si no lo supera pero no quedan suficientes productos disponibles (por ejemplo si el límite es de 10 en total y de 4 por persona, si solo quedan 2 productos y alguien quiere apuntarse con 4, no podrá).
|
||||||
|
|
||||||
## Comandos
|
## Comandos
|
||||||
|
|
||||||
/start_conjunta
|
### `/start_conjunta`
|
||||||
/admin_summary
|
|
||||||
|
Empieza una nueva conjunta, el bot te pedirá por orden:
|
||||||
|
|
||||||
|
* Nombre del producto
|
||||||
|
* Descripción del producto
|
||||||
|
* Precio para socios (solo número)
|
||||||
|
* Precio para no socios (solo número)
|
||||||
|
* Una foto
|
||||||
|
* Límite (si no lo hay, pon /unlimited)
|
||||||
|
* Si hay límite, límite por persona
|
||||||
|
* Escribir `ok` para finalizar
|
||||||
|
|
||||||
|
Al crearse la conjunta se añadirá a la base de datos interna del bot y se creará una nueva hoja en el documento de Google. También se enviará un mensaje con los datos correspondientes al canal seteado en `.env` y lo anclará.
|
||||||
|
|
||||||
|
### `/admin_summary`
|
||||||
|
|
||||||
|
Este comando mostrará las conjuntas que haya ACTIVAS y un pequeño resumen, a su vez mostrará un menú para escoger si quieres ver más información de una conjunta ℹ️, o cerrarla ❌.
|
||||||
|
|
||||||
|
Si eliges mostrar más información, mostrará los detalles y la gente apuntada.
|
||||||
|
|
||||||
|
Si eliges cerrarla, la conjunta pasará al estado cerrado, el mensaje del grupo se quitará de mensajes anclados y ya no dejará apuntarse a nadie más. Tampoco se mostrará de nuevo con este comando, pero la hoja seguirá existiendo en el documento de Google Sheets.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ bot_token = os.environ.get("TELEGRAM_TOKEN")
|
|||||||
spreadsheet_id = os.environ.get("SPREADSHEET_ID")
|
spreadsheet_id = os.environ.get("SPREADSHEET_ID")
|
||||||
|
|
||||||
# Configura la base de datos SQLite
|
# Configura la base de datos SQLite
|
||||||
conn = sqlite3.connect('/app/data/db/conjuntas.db')
|
conn = sqlite3.connect('/app/data/conjuntas.db')
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute('''CREATE TABLE IF NOT EXISTS conjuntas (
|
cursor.execute('''CREATE TABLE IF NOT EXISTS conjuntas (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
@@ -65,28 +65,6 @@ creds = ServiceAccountCredentials.from_json_keyfile_name(json_keyfile, scopes)
|
|||||||
client = gspread.authorize(creds)
|
client = gspread.authorize(creds)
|
||||||
spreadsheet = client.open_by_key(spreadsheet_id)
|
spreadsheet = client.open_by_key(spreadsheet_id)
|
||||||
|
|
||||||
async def list_conjuntas(update: Update, context: CallbackContext):
|
|
||||||
chat_id = update.message.chat_id
|
|
||||||
cursor.execute("SELECT * FROM conjuntas WHERE closed=0")
|
|
||||||
conjuntas = cursor.fetchall()
|
|
||||||
|
|
||||||
if conjuntas:
|
|
||||||
for conjunta in conjuntas:
|
|
||||||
time.sleep(0.5)
|
|
||||||
conjunta_id, message_id, product_name, product_description, limit, limit_per_user, price, price_member, closed, photo_id = conjunta
|
|
||||||
caption = f"Conjunta para '{product_name}'\n"
|
|
||||||
cursor.execute("SELECT COUNT(*) FROM conjunta_users WHERE conjunta_id = ?", (conjunta[0],))
|
|
||||||
users = cursor.fetchone()
|
|
||||||
if users:
|
|
||||||
caption += f"Participantes: {users[0]}\n"
|
|
||||||
if limit is not None:
|
|
||||||
caption += f"Límite de productos: {limit} ({limit_per_user} por usuario)\n"
|
|
||||||
|
|
||||||
await context.bot.send_photo(chat_id, photo=photo_id, caption=caption)
|
|
||||||
|
|
||||||
else:
|
|
||||||
await update.message.reply_text("No hay conjuntas activas.")
|
|
||||||
|
|
||||||
# Función para iniciar una nueva conjunta
|
# Función para iniciar una nueva conjunta
|
||||||
async def start_conjunta(update: Update, context: CallbackContext):
|
async def start_conjunta(update: Update, context: CallbackContext):
|
||||||
user_id = update.effective_user.id
|
user_id = update.effective_user.id
|
||||||
@@ -390,39 +368,11 @@ async def handle_conjunta(update: Update, context: CallbackContext):
|
|||||||
else:
|
else:
|
||||||
await update.message.reply_text("La conjunta ya está cerrada y no puedes unirte.")
|
await update.message.reply_text("La conjunta ya está cerrada y no puedes unirte.")
|
||||||
|
|
||||||
|
|
||||||
# Función para consultar el estado de una conjunta
|
|
||||||
async def status_conjunta(update: Update, context: CallbackContext):
|
|
||||||
user_id = update.effective_user.id
|
|
||||||
conjunta_id = context.args[0]
|
|
||||||
|
|
||||||
cursor.execute("SELECT * FROM conjuntas WHERE id=?", (conjunta_id,))
|
|
||||||
conjunta = cursor.fetchone()
|
|
||||||
|
|
||||||
if conjunta:
|
|
||||||
conjunta_id, message_id, product_name, product_description, limit, limit_per_user, price, price_member, closed, photo_id = conjunta
|
|
||||||
|
|
||||||
cursor.execute("SELECT * FROM conjunta_users WHERE conjunta_id=?", (conjunta_id,))
|
|
||||||
users_data = cursor.fetchall()
|
|
||||||
|
|
||||||
status_text = f"Conjunta para '{product_name}':\n"
|
|
||||||
if users_data:
|
|
||||||
status_text += "Participantes y cantidades:\n"
|
|
||||||
for user_data in users_data:
|
|
||||||
user_id, user_quantity = user_data[2], user_data[3]
|
|
||||||
status_text += f"Usuario {user_id}: {user_quantity} unidades\n"
|
|
||||||
if limit is not None:
|
|
||||||
status_text += f"Límite de productos: {limit}\n"
|
|
||||||
status_text += f"Estado: {'Abierta' if closed == 0 else 'Cerrada'}"
|
|
||||||
|
|
||||||
await update.message.reply_text(status_text)
|
|
||||||
else:
|
|
||||||
await update.message.reply_text("La conjunta no existe.")
|
|
||||||
|
|
||||||
async def close_conjunta(update: Update, context: CallbackContext):
|
async def close_conjunta(update: Update, context: CallbackContext):
|
||||||
user_id = update.effective_user.id
|
user_id = update.effective_user.id
|
||||||
query = update.callback_query
|
query = update.callback_query
|
||||||
await query.answer(text="Cerrando conjunta")
|
await query.answer(text="Cerrando conjunta")
|
||||||
|
await query.edit_message_reply_markup(None)
|
||||||
|
|
||||||
conjunta_id = int(query.data.split("close ")[1])
|
conjunta_id = int(query.data.split("close ")[1])
|
||||||
|
|
||||||
@@ -484,6 +434,8 @@ async def admin_summary(update: Update, context: CallbackContext):
|
|||||||
summary_text += f"#️⃣ Límite: <b>{limit}</b>\n"
|
summary_text += f"#️⃣ Límite: <b>{limit}</b>\n"
|
||||||
summary_text += f"#️⃣ Límite por usuario: <b>{limit_per_user}</b>\n"
|
summary_text += f"#️⃣ Límite por usuario: <b>{limit_per_user}</b>\n"
|
||||||
summary_text += f"🔢 Cantidad total de productos pedidos: <b>{total_quantity}</b>\n"
|
summary_text += f"🔢 Cantidad total de productos pedidos: <b>{total_quantity}</b>\n"
|
||||||
|
url = spreadsheet.worksheet(f"{product_name} - {conjunta_id}").url
|
||||||
|
summary_text += f"<a href='{url}'>💻 URL en Google Sheets</a>\n"
|
||||||
summary_text += f"\n--------------------------------------\n\n"
|
summary_text += f"\n--------------------------------------\n\n"
|
||||||
|
|
||||||
await update.message.reply_text(summary_text, parse_mode=ParseMode.HTML)
|
await update.message.reply_text(summary_text, parse_mode=ParseMode.HTML)
|
||||||
@@ -510,6 +462,7 @@ async def admin_summary(update: Update, context: CallbackContext):
|
|||||||
async def show_conjunta_details(update: Update, context: CallbackContext):
|
async def show_conjunta_details(update: Update, context: CallbackContext):
|
||||||
query = update.callback_query
|
query = update.callback_query
|
||||||
await query.answer(text="Mostrando detalles")
|
await query.answer(text="Mostrando detalles")
|
||||||
|
await query.edit_message_reply_markup(None)
|
||||||
|
|
||||||
selected_conjunta_idx = int(query.data.split("info ")[1])
|
selected_conjunta_idx = int(query.data.split("info ")[1])
|
||||||
|
|
||||||
@@ -534,6 +487,9 @@ async def show_conjunta_details(update: Update, context: CallbackContext):
|
|||||||
selected_conjunta_details += f"@{user_name},{quantity}\n"
|
selected_conjunta_details += f"@{user_name},{quantity}\n"
|
||||||
selected_conjunta_details += "-----------------------"
|
selected_conjunta_details += "-----------------------"
|
||||||
|
|
||||||
|
url = spreadsheet.worksheet(f"{product_name} - {conjunta_id}").url
|
||||||
|
selected_conjunta_details += f"\n<a href='{url}'>💻 URL en Google Sheets</a>\n"
|
||||||
|
|
||||||
await context.bot.send_photo(chat_id=update.effective_chat.id, photo=photo_id, caption=selected_conjunta_details, parse_mode=ParseMode.HTML)
|
await context.bot.send_photo(chat_id=update.effective_chat.id, photo=photo_id, caption=selected_conjunta_details, parse_mode=ParseMode.HTML)
|
||||||
|
|
||||||
def main()->None:
|
def main()->None:
|
||||||
@@ -556,11 +512,9 @@ def main()->None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
application.add_handler(conv_handler)
|
application.add_handler(conv_handler)
|
||||||
application.add_handler(CommandHandler('status', status_conjunta))
|
|
||||||
application.add_handler(CallbackQueryHandler(close_conjunta, pattern="close \d"))
|
|
||||||
application.add_handler(CommandHandler('list', list_conjuntas))
|
|
||||||
application.add_handler(CommandHandler('admin_summary', admin_summary))
|
application.add_handler(CommandHandler('admin_summary', admin_summary))
|
||||||
application.add_handler(CallbackQueryHandler(show_conjunta_details, pattern="info \d"))
|
application.add_handler(CallbackQueryHandler(show_conjunta_details, pattern="info \d"))
|
||||||
|
application.add_handler(CallbackQueryHandler(close_conjunta, pattern="close \d"))
|
||||||
application.add_handler(MessageHandler(filters.REPLY, handle_conjunta))
|
application.add_handler(MessageHandler(filters.REPLY, handle_conjunta))
|
||||||
|
|
||||||
application.run_polling()
|
application.run_polling()
|
||||||
|
|||||||
@@ -11,11 +11,6 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- TZ="Europe/Madrid"
|
- TZ="Europe/Madrid"
|
||||||
- TELEGRAM_TOKEN=${TELEGRAM_TOKEN}
|
- TELEGRAM_TOKEN=${TELEGRAM_TOKEN}
|
||||||
- NEW_RELIC_INSERT_KEY=${NEW_RELIC_INSERT_KEY}
|
|
||||||
- NEW_RELIC_METRICS_KEY=${NEW_RELIC_METRICS_KEY}
|
|
||||||
- NR_ENV=${NR_ENV}
|
|
||||||
- NR_HOST_INSIGHTS=${NR_HOST_INSIGHTS}
|
|
||||||
- NR_HOST_METRICS=${NR_HOST_METRICS}
|
|
||||||
- ADMIN_IDS=${ADMIN_IDS}
|
- ADMIN_IDS=${ADMIN_IDS}
|
||||||
- GROUP_CHAT_ID=${GROUP_CHAT_ID}
|
- GROUP_CHAT_ID=${GROUP_CHAT_ID}
|
||||||
- SPREADSHEET_ID=${SPREADSHEET_ID}
|
- SPREADSHEET_ID=${SPREADSHEET_ID}
|
||||||
|
|||||||
Reference in New Issue
Block a user