Spaces:
Running
Running
Chandima Prabhath
Update .gitignore to include __pycache__ and remove unused compiled Python files; modify app.py for improved environment variable handling and add Supabase integration; update requirements.txt to include supabase package.
05a6e54
import os | |
import threading | |
import time | |
import random | |
import logging | |
import queue | |
import re | |
import json | |
import requests | |
from fastapi import FastAPI, Request, HTTPException | |
from fastapi.responses import PlainTextResponse, JSONResponse | |
from supabase import create_client, Client # pip install supabase | |
from FLUX import generate_image | |
from VoiceReply import generate_voice_reply | |
from polLLM import generate_llm | |
# βββββ Configuration βββββ | |
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s [%(levelname)s] %(message)s") | |
# Env vars | |
GREEN_API_URL = os.getenv("GREEN_API_URL") | |
GREEN_API_MEDIA_URL = os.getenv("GREEN_API_MEDIA_URL", "https://api.green-api.com") | |
GREEN_API_TOKEN = os.getenv("GREEN_API_TOKEN") | |
GREEN_API_ID_INSTANCE = os.getenv("GREEN_API_ID_INSTANCE") | |
WEBHOOK_AUTH_TOKEN = os.getenv("WEBHOOK_AUTH_TOKEN") | |
BOT_GROUP_CHAT = os.getenv("BOT_GROUP_CHAT") # must be a group chat ID | |
IMAGE_DIR = "/tmp/images" | |
AUDIO_DIR = "/tmp/audio" | |
# Supabase | |
SUPABASE_URL = os.getenv("SUPABASE_URL") | |
SUPABASE_KEY = os.getenv("SUPABASE_KEY") | |
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY) | |
if not all([GREEN_API_URL, GREEN_API_TOKEN, GREEN_API_ID_INSTANCE, | |
WEBHOOK_AUTH_TOKEN, BOT_GROUP_CHAT, | |
SUPABASE_URL, SUPABASE_KEY]): | |
raise ValueError("One or more environment variables are not set properly") | |
# Queues & stores | |
task_queue = queue.Queue() | |
trivia_store = {} | |
polls = {} | |
last_message_time = time.time() | |
app = FastAPI() | |
# βββββ Supabase Schema Prep βββββ | |
def prepare_tables(): | |
""" | |
Ensure 'users' and 'images' tables exist. Creates them if they don't. | |
""" | |
# Create users table | |
supabase.postgrest.client.rpc( | |
"sql", | |
{"query": """ | |
CREATE TABLE IF NOT EXISTS users ( | |
id SERIAL PRIMARY KEY, | |
chat_id TEXT UNIQUE NOT NULL, | |
created_at BIGINT NOT NULL | |
); | |
"""} | |
).execute() | |
# Create images table | |
supabase.postgrest.client.rpc( | |
"sql", | |
{"query": """ | |
CREATE TABLE IF NOT EXISTS images ( | |
id SERIAL PRIMARY KEY, | |
chat_id TEXT NOT NULL, | |
prompt TEXT NOT NULL, | |
url TEXT NOT NULL, | |
created_at BIGINT NOT NULL | |
); | |
"""} | |
).execute() | |
# βββββ Inactivity Monitor βββββ | |
def inactivity_monitor(): | |
global last_message_time | |
while True: | |
time.sleep(60) | |
if time.time() - last_message_time >= 300: | |
send_message_to_chat(BOT_GROUP_CHAT, "β° I haven't heard from you in a while! I'm still here π") | |
last_message_time = time.time() | |
# βββββ Background Worker βββββ | |
def worker(): | |
while True: | |
task = task_queue.get() | |
try: | |
if task["type"] == "image": | |
handle_image_generation(task) | |
elif task["type"] == "audio": | |
response_audio(task["message_id"], task["chat_id"], task["prompt"]) | |
except Exception as e: | |
logging.error(f"Error processing task {task}: {e}") | |
finally: | |
task_queue.task_done() | |
# βββββ Supabase Helpers βββββ | |
def ensure_user_exists(chat_id: str): | |
"""Create user record if not exists.""" | |
res = supabase.table("users").select("*").eq("chat_id", chat_id).single().execute() | |
if res.data is None: | |
supabase.table("users").insert({ | |
"chat_id": chat_id, | |
"created_at": int(time.time()) | |
}).execute() | |
def store_image_url(chat_id: str, prompt: str, url: str): | |
supabase.table("images").insert({ | |
"chat_id": chat_id, | |
"prompt": prompt, | |
"url": url, | |
"created_at": int(time.time()) | |
}).execute() | |
# βββββ Send Helpers βββββ | |
def send_message_to_chat(chat_id: str, message: str, retries=3): | |
url = f"{GREEN_API_URL}/waInstance{GREEN_API_ID_INSTANCE}/sendMessage/{GREEN_API_TOKEN}" | |
payload = {"chatId": chat_id, "message": message} | |
for i in range(retries): | |
try: | |
r = requests.post(url, json=payload) | |
r.raise_for_status() | |
return r.json() | |
except Exception as e: | |
logging.error(f"send_message_to_chat attempt {i+1} failed: {e}") | |
time.sleep(1) | |
return {"error": "Failed after retries"} | |
def send_message(message_id: str, chat_id: str, message: str, retries=3): | |
url = f"{GREEN_API_URL}/waInstance{GREEN_API_ID_INSTANCE}/sendMessage/{GREEN_API_TOKEN}" | |
payload = {"chatId": chat_id, "message": message, "quotedMessageId": message_id} | |
for i in range(retries): | |
try: | |
r = requests.post(url, json=payload) | |
r.raise_for_status() | |
return r.json() | |
except Exception as e: | |
logging.error(f"send_message attempt {i+1} failed: {e}") | |
time.sleep(1) | |
return {"error": "Failed after retries"} | |
def send_image(message_id: str, chat_id: str, image_path: str, caption="Here you go!", retries=3): | |
url = f"{GREEN_API_MEDIA_URL}/waInstance{GREEN_API_ID_INSTANCE}/sendFileByUpload/{GREEN_API_TOKEN}" | |
payload = {"chatId": chat_id, "caption": caption, "quotedMessageId": message_id} | |
for i in range(retries): | |
try: | |
with open(image_path, "rb") as f: | |
files = [("file", ("image.jpg", f, "image/jpeg"))] | |
r = requests.post(url, data=payload, files=files) | |
r.raise_for_status() | |
return r.json() | |
except Exception as e: | |
logging.error(f"send_image attempt {i+1} failed: {e}") | |
time.sleep(1) | |
return {"error": "Failed after retries"} | |
def send_audio(message_id: str, chat_id: str, audio_path: str, retries=3): | |
url = f"{GREEN_API_MEDIA_URL}/waInstance{GREEN_API_ID_INSTANCE}/sendFileByUpload/{GREEN_API_TOKEN}" | |
payload = {"chatId": chat_id, "caption": "Here is your voice reply!", "quotedMessageId": message_id} | |
for i in range(retries): | |
try: | |
with open(audio_path, "rb") as f: | |
files = [("file", ("audio.mp3", f, "audio/mpeg"))] | |
r = requests.post(url, data=payload, files=files) | |
r.raise_for_status() | |
return r.json() | |
except Exception as e: | |
logging.error(f"send_audio attempt {i+1} failed: {e}") | |
time.sleep(1) | |
return {"error": "Failed after retries"} | |
# βββββ Core Handlers βββββ | |
def response_text(message_id, chat_id, prompt): | |
try: | |
msg = generate_llm(prompt) | |
send_message(message_id, chat_id, msg) | |
except Exception: | |
send_message(message_id, chat_id, "Sorry, something went wrong π¦") | |
def response_audio(message_id, chat_id, prompt): | |
try: | |
result = generate_voice_reply(prompt, model="openai-audio", voice="coral", audio_dir=AUDIO_DIR) | |
if result and result[0]: | |
audio_path, _ = result | |
send_audio(message_id, chat_id, audio_path) | |
os.remove(audio_path) | |
else: | |
response_text(message_id, chat_id, prompt) | |
except Exception: | |
send_message(message_id, chat_id, "Error generating audio. Please try again later.") | |
def handle_image_generation(task): | |
message_id = task["message_id"] | |
chat_id = task["chat_id"] | |
prompt = task["prompt"] | |
ensure_user_exists(chat_id) | |
for attempt in range(4): | |
try: | |
img, path, ret_prompt, url = generate_image(prompt, message_id, message_id, IMAGE_DIR) | |
if img: | |
store_image_url(chat_id, prompt, url) | |
formatted = "\n\n".join(f"_{p.strip()}_" for p in ret_prompt.split("\n\n") if p.strip()) | |
send_image(message_id, chat_id, path, | |
caption=f"β¨ Image ready: {url}\n{formatted}") | |
return | |
else: | |
raise RuntimeError("generate_image returned no image") | |
except Exception as e: | |
logging.error(f"Image gen attempt {attempt+1} failed: {e}") | |
if attempt < 3: | |
time.sleep(5) | |
else: | |
send_message(message_id, chat_id, "π’ Sorry, I couldn't generate the image after several tries.") | |
# βββββ Startup βββββ | |
def send_startup_message(): | |
send_message_to_chat(BOT_GROUP_CHAT, | |
"π Hi! I'm Eve, your friendly AI assistant. I'm now live and ready to help with images, voice replies, and more!" | |
) | |
# βββββ Help Text βββββ | |
help_text = ( | |
"π€ *Hi there, I'm Eve!* Here are the commands you can use in this group:\n\n" | |
"β’ */help* β Show this help message.\n" | |
"β’ */summarize <text>* β Get a quick summary.\n" | |
"β’ */translate <lang>|<text>* β Translate text.\n" | |
"β’ */joke* β A random joke.\n" | |
"β’ */weather <location>* β Current weather.\n" | |
"β’ */inspire* β Inspirational quote.\n" | |
"β’ */trivia* β Start trivia.\n" | |
"β’ */answer* β Answer or reveal trivia.\n" | |
"β’ */meme <text>* β Generate a meme image.\n" | |
"β’ */poll Q|Opt1|Opt2|β¦* β Create a poll.\n" | |
"β’ */results* β Show poll results.\n" | |
"β’ */endpoll* β End the poll.\n" | |
"β’ */gen <prompt>* β Generate an image.\n\n" | |
"Anything else Iβll reply with a voice message. π" | |
) | |
# βββββ Webhook βββββ | |
async def whatsapp_webhook(request: Request): | |
global last_message_time | |
last_message_time = time.time() | |
if request.headers.get("Authorization", "") != f"Bearer {WEBHOOK_AUTH_TOKEN}": | |
raise HTTPException(403, "Unauthorized") | |
data = await request.json() | |
if data.get("typeWebhook") != "incomingMessageReceived": | |
return {"success": True} | |
sd = data["senderData"] | |
chat_id = sd["chatId"] | |
mid = data["idMessage"] | |
# only process messages from the BOT_GROUP_CHAT group | |
if chat_id != BOT_GROUP_CHAT: | |
return {"success": True} | |
md = data.get("messageData", {}) | |
if "quotedMessage" in md: | |
return {"success": True} | |
# extract text | |
if "textMessageData" in md: | |
body = md["textMessageData"].get("textMessage", "").strip() | |
elif "extendedTextMessageData" in md: | |
body = md["extendedTextMessageData"].get("text", "").strip() | |
else: | |
return {"success": True} | |
low = body.lower() | |
# β Commands β | |
if low == "/help": | |
send_message(mid, chat_id, help_text) | |
return {"success": True} | |
if low.startswith("/summarize "): | |
txt = body[len("/summarize "):].strip() | |
send_message(mid, chat_id, generate_llm(f"Summarize:\n\n{txt}")) | |
return {"success": True} | |
if low.startswith("/translate "): | |
part = body[len("/translate "):] | |
if "|" not in part: | |
send_message(mid, chat_id, "Use `/translate <lang>|<text>` please.") | |
else: | |
lang, txt = part.split("|",1) | |
send_message(mid, chat_id, | |
generate_llm(f"Translate into {lang.strip()}:\n\n{txt.strip()}")) | |
return {"success": True} | |
if low == "/joke": | |
try: | |
joke = requests.get("https://official-joke-api.appspot.com/random_joke", timeout=5).json() | |
send_message(mid, chat_id, f"{joke['setup']}\n\n{joke['punchline']}") | |
except: | |
send_message(mid, chat_id, generate_llm("Tell me a short joke.")) | |
return {"success": True} | |
if low.startswith("/weather "): | |
loc = body[len("/weather "):].strip().replace(" ", "+") | |
try: | |
w = requests.get(f"http://sl.wttr.in/{loc}?format=4", timeout=5).text | |
send_message(mid, chat_id, w) | |
except: | |
send_message(mid, chat_id, "Could not fetch weather.") | |
return {"success": True} | |
if low == "/inspire": | |
send_message(mid, chat_id, f"β¨ {generate_llm('Give me an inspirational quote.')}") | |
return {"success": True} | |
# trivia, answer, meme, poll, results, endpoll omitted for brevityβ | |
# just queue tasks as before | |
if low.startswith("/meme "): | |
prompt = body[len("/meme "):].strip() | |
send_message(mid, chat_id, "π¨ Generating your memeβ¦") | |
task_queue.put({"type":"image","message_id":mid,"chat_id":chat_id,"prompt":f"meme template: {prompt}"}) | |
return {"success": True} | |
if low.startswith("/gen "): | |
prompt = body[len("/gen "):].strip() | |
send_message(mid, chat_id, "β¨ Your image is being generated. Please waitβ¦") | |
task_queue.put({"type":"image","message_id":mid,"chat_id":chat_id,"prompt":prompt}) | |
return {"success": True} | |
# fallback to voice | |
task_queue.put({"type":"audio","message_id":mid,"chat_id":chat_id,"prompt":body}) | |
return {"success": True} | |
def index(): | |
return "Eve is running! π" | |
if __name__ == "__main__": | |
# prepare Supabase schema, then start threads and server | |
prepare_tables() | |
threading.Thread(target=inactivity_monitor, daemon=True).start() | |
threading.Thread(target=worker, daemon=True).start() | |
send_startup_message() | |
import uvicorn | |
uvicorn.run(app, host="0.0.0.0", port=7860) |