314 lines
12 KiB
Python
314 lines
12 KiB
Python
import json
|
||
# from fastapi import FastAPI, Request
|
||
from pydantic import BaseModel
|
||
import requests
|
||
import logging
|
||
# import uvicorn
|
||
import time
|
||
import nahj_engine as nahj_chat
|
||
import data_model as dm
|
||
# ===========================
|
||
# پیکربندی اولیه
|
||
# ===========================
|
||
TOKEN = "602738113:OcVhjcsXqvE6D9FUytdoMZ096DPKYIUwnrk"
|
||
API_URL = f"https://tapi.bale.ai/bot{TOKEN}/"
|
||
|
||
# راهاندازی لاگر
|
||
logging.basicConfig(
|
||
filename="./bale_bot/bot.log",
|
||
level=logging.INFO,
|
||
format="%(asctime)s - %(levelname)s - %(message)s"
|
||
)
|
||
|
||
# ===========================
|
||
# define import model class
|
||
# ===========================
|
||
class Message(BaseModel):
|
||
chat: dict
|
||
text: str | None = None
|
||
|
||
class Update(BaseModel):
|
||
message: Message | None = None
|
||
|
||
# ===========================
|
||
# کلاس اصلی ربات بله
|
||
# ===========================
|
||
class BaleBot:
|
||
def __init__(self, token: str):
|
||
# self.api_url = f"https://api.bale.ai/bot{token}/"
|
||
self.api_url = API_URL
|
||
self.user_states = {}
|
||
|
||
async def get_updates(self, offset = None):
|
||
params = {"timeout": 20}
|
||
if offset:
|
||
params["offset"] = offset
|
||
# print("get_updates function")
|
||
resp = requests.get(f"{self.api_url}getUpdates", params=params)
|
||
return resp.json().get("result", [])
|
||
|
||
async def send_message(self, chat_id: int, text: str, keyboard=None):
|
||
payload = {
|
||
"chat_id": chat_id,
|
||
"text": text,
|
||
"parse_mode": "HTML"
|
||
}
|
||
if keyboard:
|
||
payload["reply_markup"] = keyboard
|
||
try:
|
||
response = requests.post(self.api_url + "sendMessage", json=payload)
|
||
return response.json()
|
||
except Exception as e:
|
||
logging.error(f"Error sending message: {e}")
|
||
return None
|
||
|
||
async def get_latest_req_id(self):
|
||
|
||
latest_request = dm.get_last_request()
|
||
latest_req_id = latest_request['update_id']
|
||
if not latest_req_id:
|
||
latest_req_id = 0
|
||
|
||
return latest_req_id + 1
|
||
|
||
async def save_entery(self, update_item):
|
||
is_active = True
|
||
answer = ''
|
||
message = update_item['message']
|
||
fromm = message['from']
|
||
chat = message['chat']
|
||
username, first_name, last_name = '','',''
|
||
if 'username' in fromm:
|
||
username = fromm['username']
|
||
if 'first_name' in fromm:
|
||
first_name = fromm['first_name']
|
||
if 'last_name' in fromm:
|
||
last_name = fromm['last_name']
|
||
try:
|
||
dm.insert_request(update_item['update_id'],username,message['text'], answer, message['message_id'],fromm['id'],fromm['is_bot'],message['date'],chat['id'],chat['type'],first_name,last_name, is_active)
|
||
except:
|
||
return update_item['update_id']
|
||
|
||
return update_item['update_id']
|
||
|
||
async def update_request(self, update_id, answer):
|
||
dm.update_request(update_id= update_id, answer= answer)
|
||
|
||
|
||
async def split_text_into_chunks(self, text, max_length=4000):
|
||
"""
|
||
تقسیم یک متن به چانکهای حداکثر max_length کاراکتری، بدون خراب کردن معنا با رعایت انتهای جملهها.
|
||
|
||
:param text: متن ورودی
|
||
:param max_length: حداکثر طول هر چانک (پیشفرض 4000)
|
||
:return: لیستی از چانکهای متن
|
||
"""
|
||
chunks = [] # لیستی برای ذخیره چانکها
|
||
start = 0 # شروع متن برای هر چانک
|
||
|
||
while start < len(text):
|
||
# اگر متن باقیمانده کوتاهتر از max_length باشد، کل آن را اضافه کنید
|
||
if len(text) - start <= max_length:
|
||
chunks.append(text[start:])
|
||
break
|
||
|
||
# پیدا کردن نقطه پایانی چانک (حداکثر تا max_length کاراکتر جلو بروید)
|
||
end = start + max_length
|
||
# اگر در وسط یک جمله هستیم، به عقب برگردید تا انتهای جمله پیدا شود
|
||
while end > start and text[end - 1] not in '.!?':
|
||
end -= 1
|
||
|
||
# اگر هیچ انتهای جمله پیدا نشد، متن را تا max_length ببرید
|
||
if end == start:
|
||
end = start + max_length
|
||
|
||
# اضافه کردن چانک به لیست
|
||
chunks.append(text[start:end])
|
||
# شروع چانک بعدی
|
||
start = end
|
||
|
||
return chunks
|
||
|
||
async def save_chat_data(self,query, answer, first_name, username):
|
||
chat_data = f'''username: {username}\nfirstname: {first_name}\nquery: {query}\nanswer:{answer}\n+ + + + + + + + + + + + + + + + + + + + \n+ + + + + + + + + + + + + + + + + + + + \n\n'''
|
||
# # should write in DATABASE
|
||
with open('./bale_bot/chat-data.txt', 'a+', encoding='utf-8') as file:
|
||
file.write(chat_data)
|
||
|
||
async def handle_update(self, update_reqs: dict):
|
||
print(f"handle update ...")
|
||
data = update_reqs
|
||
if "message" not in data:
|
||
return
|
||
message = data["message"]
|
||
chat_id = message["chat"]["id"]
|
||
text = message.get("text", "").strip()
|
||
|
||
fromm = message['from']
|
||
# first_name = fromm['first_name']
|
||
# username = fromm['username']
|
||
|
||
logging.info(f"Received message from {chat_id}: {text}")
|
||
|
||
keyboard = {
|
||
"keyboard": [["جستجو","پرسش","پرسش عمیق"]],# ,"شبکه معنایی"
|
||
"resize_keyboard": True,
|
||
"one_time_keyboard": True
|
||
}
|
||
|
||
if text == "/start":
|
||
reply = "سلام، من دستیار هوشمند نهجالبلاغه هستم. لطفا یکی از گزینههای زیر را انتخاب نمائید ..."
|
||
|
||
await self.send_message(chat_id, reply, keyboard)
|
||
return
|
||
|
||
elif text == "پرسش":
|
||
# حذف نوع درخواست قبلی کاربر
|
||
self.user_states.pop(chat_id, None)
|
||
# ایجاد وضعیت پرسش برای کاربر جاری
|
||
self.user_states[chat_id] = "simple_question"
|
||
reply = "لطفا متن «پرسش» را وارد نمائید ..."
|
||
await self.send_message(chat_id, reply, keyboard)
|
||
return
|
||
|
||
elif text == "جستجو":
|
||
# حذف نوع درخواست قبلی کاربر
|
||
self.user_states.pop(chat_id, None)
|
||
# ایجاد وضعیت جستجو برای کاربر جاری
|
||
self.user_states[chat_id] = "search"
|
||
reply = "لطفا متن موردنظر جهت «جستجو» را وارد نمائید ..."
|
||
await self.send_message(chat_id, reply, keyboard)
|
||
return
|
||
|
||
elif text == "شبکه معنایی":
|
||
# حذف نوع درخواست قبلی کاربر
|
||
self.user_states.pop(chat_id, None)
|
||
# ایجاد وضعیت شبکه معنایی برای کاربر جاری
|
||
self.user_states[chat_id] = "semantic-network"
|
||
reply = "لطفا کلمه موردنظر جهت ترسیم «شبکه معنایی» را وارد نمائید ..."
|
||
await self.send_message(chat_id, reply, keyboard)
|
||
return
|
||
|
||
elif text == "پرسش عمیق":
|
||
# حذف نوع درخواست قبلی کاربر
|
||
self.user_states.pop(chat_id, None)
|
||
# ایجاد وضعیت پرسش برای کاربر جاری
|
||
self.user_states[chat_id] = "deep_question"
|
||
reply = "لطفا متن «پرسش عمیق» را وارد نمائید ..."
|
||
await self.send_message(chat_id, reply, keyboard)
|
||
return
|
||
|
||
# elif text == "/help":
|
||
# reply = (
|
||
# "دستورهای موجود:\n"
|
||
# "/start - شروع ربات\n"
|
||
# "/chat - گفتگو با دستیار هوشمند نهج البلاغه\n"
|
||
# "/status - وضعیت ربات"
|
||
# )
|
||
# self.send_message(chat_id, reply)
|
||
|
||
elif text == "ربات":
|
||
reply = "ربات فعال است ✅"
|
||
await self.send_message(chat_id, reply, keyboard)
|
||
return
|
||
|
||
elif self.user_states.get(chat_id) == "semantic-network":
|
||
await self.send_message(chat_id, f"⏳ در حال ایجاد شبکه معنایی برای کلمه «{text}» ...")
|
||
reply = 'با عرض پوزش؛ این امکان، در حال حاضر در دسترس نیست'
|
||
|
||
elif self.user_states.get(chat_id) == "search":
|
||
await self.send_message(chat_id, f"⏳ در حال جستجو برای «{text}» ...")
|
||
|
||
answer = nahj_chat.bale_search(text)
|
||
|
||
if answer:
|
||
reply = answer
|
||
else:
|
||
reply = 'خطا در تولید پاسخ!'
|
||
|
||
elif self.user_states.get(chat_id) == "simple_question":
|
||
|
||
await self.send_message(chat_id, f"⏳ در حال آمادهسازی پاسخ به «{text}» ...")
|
||
|
||
answer = nahj_chat.bale_chat(text)
|
||
|
||
if answer:
|
||
reply = answer
|
||
else:
|
||
reply = 'خطا در تولید پاسخ!'
|
||
|
||
elif self.user_states.get(chat_id) == "deep_question":
|
||
|
||
await self.send_message(chat_id, f"⏳ در حال آمادهسازی پاسخ به «{text}» ...")
|
||
|
||
# answer = nahj_chat.bale_chat(text)
|
||
final_result = await nahj_chat.bale_complex_chat(text)
|
||
|
||
if final_result:
|
||
|
||
sub_questions = 'سوالات جزئی مرتبط با سوال کاربر:\n'
|
||
for i, q in enumerate(final_result.get('sub_qa'),1):
|
||
sub_questions += f'{i}. {q.get("question")}\n'
|
||
|
||
sub_qa_text = ''
|
||
for i, qa in enumerate(final_result.get('sub_qa'),1):
|
||
sub_qa_text += f'{i}. {qa.get("question")}\n{qa.get("answer")}\n\n'
|
||
# reply_content = f'''سوال اصلی: {text}\n\n{sub_questions}\n\n* * * * *سوالات جزئی:\n{sub_qa_text.strip()}\n\nپاسخ نهائی:\n{final_result.get('final_answer',0)}'''
|
||
reply_content = f'''{final_result.get('final_answer',0)}'''
|
||
reply = reply_content.strip()
|
||
|
||
else:
|
||
reply = 'خطا در تولید پاسخ!'
|
||
|
||
else:
|
||
reply = "لطفا یکی از گزینههای زیر را انتخاب نمائید"
|
||
await self.send_message(chat_id, reply, keyboard)
|
||
return
|
||
|
||
reply_len = len(reply.split())
|
||
print(f"len answer: {reply_len}")
|
||
print(f"ready for next ...")
|
||
print('+'*20)
|
||
print('+'*20)
|
||
reply_chuncs = []
|
||
|
||
reply_chuncs = await self.split_text_into_chunks(reply)
|
||
|
||
for i, paragraph in enumerate(reply_chuncs):
|
||
await self.send_message(chat_id, paragraph, keyboard)
|
||
|
||
# await self.save_chat_data(text, reply, first_name, username)
|
||
return reply
|
||
|
||
|
||
async def main():
|
||
print(f'!!! BALE-NAHJ-BOT IS READY !!!')
|
||
while True:
|
||
last_req_id = await bale_bot.get_latest_req_id()
|
||
# print(f"last req id: {last_req_id}")
|
||
update = await bale_bot.get_updates(last_req_id)
|
||
# print(f"reading bale-bot server. len update: {len(update)}")
|
||
if update:
|
||
print(f'{len(update)} requests recognized!')
|
||
for i, item in enumerate(update, 1):
|
||
print(f'handle input {i}/{len(update)}')
|
||
update_id = await bale_bot.save_entery(item)
|
||
answer = await bale_bot.handle_update(item)
|
||
if answer:
|
||
await bale_bot.update_request(update_id, answer)
|
||
time.sleep(1)
|
||
|
||
# ===========================
|
||
# ساخت اپلیکیشن FastAPI
|
||
# ===========================
|
||
# app = FastAPI()
|
||
bale_bot = BaleBot(TOKEN)
|
||
|
||
# ===========================
|
||
# (local execution)
|
||
# ===========================
|
||
|
||
if __name__ == "__main__":
|
||
import asyncio
|
||
result = asyncio.run(main())
|