(20250115) Can accept WhatsApp (Nimbus) integrations now.

This commit is contained in:
2025-01-15 16:29:48 +05:30
parent 77b7ad08dd
commit efd7dda9f0
9 changed files with 571 additions and 27 deletions
+57 -18
View File
@@ -60,7 +60,8 @@ from utils_v2.api.async_quart import (
)
# Data Models:
from models.data.api.chat.auth import ChatAuthRequestHeaders, ChatAuthRequestData
from models.api.chat.auth import ChatAuthRequestHeaders, ChatAuthRequestData
from models.core.auth_token import CoreAuthTokenModel
# Common:
from shared import constants
@@ -198,10 +199,18 @@ async def callback_test(
**kwargs
):
# ┏┓
# ┃┃┏┓┏┓┏┓┏┓┏┓┏┏┓┏┏
# ┣┛┛ ┗ ┣┛┛ ┗┛┗┗ ┛┛
# ┛
"""
Use this when a user wants to register a third-party chat client with your service.
:param inbound_headers: auto-extracted by the decorators.
:param inbound_data: auto-extracted by the decorators.
:param inbound_files: auto-extracted by the decorators.
:param kwargs: Any number of extra inputs supplied by the decorators.
:return: A standard response structure.
"""
# ┏┓ ┓ ┏┓┓ ┓
# ┣┫┓┏╋┣┓ ┃ ┣┓┏┓┏┃┏
# ┛┗┗┻┗┛┗ ┗┛┛┗┗ ┗┛┗
# If the session token is invalid/expired:
if kwargs.get("session_info") is None:
@@ -210,22 +219,52 @@ async def callback_test(
http_code = HttpCodes.UNAUTHORIZED
)
# ┏┓ ┏┳┓ ┓
# ┣ ┏┓┏┓ ┃ ┏┓┃┏┓┏┓┏┓┏┓┏┳┓
# ┻ ┗┛┛ ┻ ┗ ┗┗ ┗┫┛ ┗┻┛┗┗
# Start by assuming failure:
success = False
# ┏┓ ┓ ┏┓ ┏┓ ┳┓• ┓
# ┣ ┏┓┏┓ ┃┃┃┣┓┏┓╋┏┣┫┏┓┏┓━━┃┃┓┏┳┓┣┓┓┏┏
# ┻ ┗┛┛ ┗┻┛┛┗┗┻┗┛┛┗┣┛┣┛ ┛┗┗┛┗┗┗┛┗┻┛
# ┛ ┛
if inbound_data.chatClient == "whatsappNimbus":
success = await current_app.whatsapp_nimbus_controller.set_token_direct(
sql_conn = current_app.sql_writer,
mongo_data_conn = current_app.data_mongo,
auth_token = CoreAuthTokenModel(
serviceType = "chat",
client = inbound_data.chatClient,
authType = "auth",
auth = inbound_data.auth.model_dump(),
user = kwargs.get("session_info"),
clientUserId = {
"senderId": inbound_data.auth.senderId
},
status = "active",
syncFreq = 60
),
token_notes = {
"apiKey": inbound_data.auth.apiKey,
"senderId": inbound_data.auth.senderId
},
display_name = inbound_data.auth.senderId,
session_token = inbound_headers["X-Session-Token"]
)
# ┳┓
# ┣┫┏┓┏┏┓┏┓┏┓┏┏┓
# ┛┗┗ ┛┣┛┗┛┛┗┛┗
# ┛
if inbound_data.chatClient == "telegram":
# client_response = await get_telegram_bot_info(bot_token = inbound_data.auth.botToken)
# print("TG BOT:", json.to_string(client_response))
client_response = await set_telegram_webhook(
bot_token = inbound_data.auth.botToken,
webhook_url = f"https://api.thecaoffice.com/converse/chat/webhook/{inbound_data.auth.botToken}"
# Done here:
return ResponseModel(
status_code = StatusCodes.OK if success else StatusCodes.FAILED,
http_code = HttpCodes.SUCCESS if success else HttpCodes.INTERNAL_SERVER_ERROR,
data = {
"client": inbound_data.chatClient,
"authorized": success
}
)
print("WEBHOOK JSON:", json.to_string(client_response))
return "ok"
# *****************************************************************************************************************
+19 -2
View File
@@ -80,6 +80,9 @@ from controllers_v2.message.sms.all_sms import AllSMSController
from controllers_v2.message.sms.nimbus_sms_india import NimbusSMSIndiaController
from controllers_v2.message.sms.savvy_bulk_sms_kenya import SavvyBulkSMSKenyaController
# ---
from controllers_v2.message.chat.all_chat import AllChatController
from controllers_v2.message.chat.whatsapp_nimbus import WhatsAppNimbusController
# ---
from controllers_v2.finstitutions.trading.all_trading import AllTradingController
from controllers_v2.finstitutions.trading.zerodha_kite import ZerodhaKiteTradingController
from controllers_v2.finstitutions.trading.icici_breeze import ICICIBreezeTradingController
@@ -110,7 +113,7 @@ from api.blueprints.sms.list import sms_list_bp
from api.blueprints.sms.tags import sms_update_tags_bp
# Chat Blueprints:
# from api.blueprints.chat.auth import chat_auth_bp
from api.blueprints.chat.auth import chat_auth_bp
# from api.blueprints.chat.webhook import chat_webhook_bp
# Software Blueprints:
@@ -179,7 +182,7 @@ app.register_blueprint(sms_list_bp, url_prefix = f"/{MODULE_BASE}/sms")
app.register_blueprint(sms_update_tags_bp, url_prefix = f"/{MODULE_BASE}/sms")
# Chat Blueprints:
# app.register_blueprint(chat_auth_bp, url_prefix = f"/{MODULE_BASE}/chat")
app.register_blueprint(chat_auth_bp, url_prefix = f"/{MODULE_BASE}/chat")
# app.register_blueprint(chat_webhook_bp, url_prefix = f"/{MODULE_BASE}/chat")
# Software Blueprints:
@@ -455,6 +458,20 @@ async def app_startup(**kwargs):
debug = enable_debugging
)
# Messages / Chat Controllers:
current_app.chat_controller = AllChatController(
cache = current_app.module_cache,
http_client = current_app.http_client,
alert_url = current_app.script_data["alerts"]["url"],
debug = enable_debugging
)
current_app.whatsapp_nimbus_controller = WhatsAppNimbusController(
cache = current_app.module_cache,
http_client = current_app.http_client,
alert_url = current_app.script_data["alerts"]["url"],
debug = enable_debugging
)
# Finstitutions / Trading Controllers:
current_app.trading_controller = AllTradingController(
cache = current_app.module_cache,
+147
View File
@@ -0,0 +1,147 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Wednesday, 15th Jan., 2025.
OBJECTIVE:
To handle all chat-related behaviour from one place.
REFERENCES:
N/A
DOWNLOADS:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")
# My async utils:
from utils_v2.date_time import date_time
from utils_v2.database.async_mongo_v2 import AsyncMongo
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache
# Controllers:
from controllers_v2.message.chat.base import ChatController
# Models:
from models.core.auth_token import CoreAuthTokenModel
from models.core.message import CoreMessageModel
# SMS Clients:
from utils_v2.sms.india.nimbus.controllers.async_nimbus import AsyncNimbusSMS
# To work with datatypes:
from typing import List, Any
# To make HTTP requests:
import httpx
# For asynchronous activities:
import asyncio
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** CLASSES ***
# ***** ****
# *****************************************************************************************************************
class AllChatController(ChatController):
# ┏┓
# ┃ ┏┓┏┓┏╋┏┓┓┏┏╋┏┓┏┓
# ┗┛┗┛┛┗┛┗┛ ┗┻┗┗┗┛┛
def __init__(
self,
cache: AsyncRedisCache = None,
http_client: httpx.AsyncClient = None,
alert_url: str = None,
debug: bool = True,
debug_prefix: str = "All Chat (C) | ",
debug_only_errors: bool = True
):
"""
This is a common controller for chat-related activities when the third-party client is not known.
:param cache: The object to use for caching results from database calls.
:param http_client: The HTTP client
:param debug: Whether, or not, you would like to print debugging messages:
:param debug_prefix: The prefix to print with the debugging messages.
:param debug_only_errors: Whether you would like to print only error messages or all messages.
:return: None.
"""
# Invoke the parent's constructor:
super().__init__(
cache = cache,
alert_url = alert_url,
http_client = http_client,
base_filter = {"client": "whatsappNimbus"},
debug = debug,
debug_prefix = debug_prefix,
debug_only_errors = debug_only_errors
)
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
+162
View File
@@ -0,0 +1,162 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Wednesday, 15th Jan., 2025.
OBJECTIVE:
To handle all chat-related behaviour from one place. The initially known clients are WhatsApp and Telegram.
REFERENCES:
N/A
DOWNLOADS:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")
# My async utils:
from utils_v2.database.async_mongo_v2 import AsyncMongo
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache
# Controllers:
from controllers_v2.core.message import CoreMessageController
# Models:
from models.core.auth_token import CoreAuthTokenModel
from models.api.sms.send import (
NimbusSMSIndiaMessage,
SavvyBulkSMSKenyaMessage,
SMSSendOneResult,
SMSSendManyResults
)
# SMS clients:
from utils_v2.sms.india.nimbus.controllers.async_nimbus import AsyncNimbusSMS
from utils_v2.sms.kenya.savvy_bulk_sms.controllers.async_savvy_bulk_sms import AsyncSavvyBulkSMS
# To work with datatypes:
from typing import List, Any
# To make HTTP requests:
import httpx
# To make abstract classes:
from abc import ABC, abstractmethod
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** CLASSES ***
# ***** ****
# *****************************************************************************************************************
class ChatController(CoreMessageController, ABC):
# ┏┓
# ┃ ┏┓┏┓┏╋┏┓┓┏┏╋┏┓┏┓
# ┗┛┗┛┛┗┛┗┛ ┗┻┗┗┗┛┛
def __init__(
self,
cache: AsyncRedisCache = None,
http_client: httpx.AsyncClient = None,
alert_url: str = None,
base_filter: dict = None,
debug: bool = True,
debug_prefix: str = "Chat (C) | ",
debug_only_errors: bool = True
):
"""
This is the foundational controller for all chat services. This is built on top of the core message controller,
and, in turn, all individual chat client controllers must be built on top of this.
:param cache: The object to use for caching results from database calls.
:param http_client: The HTTP client
:param base_filter: The basic filter that will be applied to all fetching/updating queries. WARNING: THE BASE
FILTER WILL ALWAYS BE APPLIED AUTOMATICALLY. SET THIS UP WISELY.
:param debug: Whether, or not, you would like to print debugging messages:
:param debug_prefix: The prefix to print with the debugging messages.
:param debug_only_errors: Whether you would like to print only error messages or all messages.
:return: None.
"""
# Prepare the combined base filter:
sms_filter = {}
for k, v in (base_filter or {}).items(): sms_filter[k] = v
sms_filter["serviceType"] = "sms"
# Invoke the parent's constructor:
CoreMessageController.__init__(
self,
cache = cache,
alert_url = alert_url,
http_client = http_client,
base_filter = sms_filter,
debug = debug,
debug_prefix = debug_prefix,
debug_only_errors = debug_only_errors
)
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
@@ -0,0 +1,147 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Wednesday, 15th Jan., 2025.
OBJECTIVE:
To handle all WhatsApp-related behaviour for Nimbus IT's service from one place.
REFERENCES:
N/A
DOWNLOADS:
N/A
"""
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
# ***** ****
# *****************************************************************************************************************
# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")
# My async utils:
from utils_v2.date_time import date_time
from utils_v2.database.async_mongo_v2 import AsyncMongo
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache
# Controllers:
from controllers_v2.message.chat.base import ChatController
# Models:
from models.core.auth_token import CoreAuthTokenModel
from models.core.message import CoreMessageModel
# SMS Clients:
from utils_v2.sms.india.nimbus.controllers.async_nimbus import AsyncNimbusSMS
# To work with datatypes:
from typing import List, Any
# To make HTTP requests:
import httpx
# For asynchronous activities:
import asyncio
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** CLASSES ***
# ***** ****
# *****************************************************************************************************************
class WhatsAppNimbusController(ChatController):
# ┏┓
# ┃ ┏┓┏┓┏╋┏┓┓┏┏╋┏┓┏┓
# ┗┛┗┛┛┗┛┗┛ ┗┻┗┗┗┛┛
def __init__(
self,
cache: AsyncRedisCache = None,
http_client: httpx.AsyncClient = None,
alert_url: str = None,
debug: bool = True,
debug_prefix: str = "WhatsApp Nimbus (C) | ",
debug_only_errors: bool = True
):
"""
This is the controller for Nimbus IT's WhatsApp service.
:param cache: The object to use for caching results from database calls.
:param http_client: The HTTP client
:param debug: Whether, or not, you would like to print debugging messages:
:param debug_prefix: The prefix to print with the debugging messages.
:param debug_only_errors: Whether you would like to print only error messages or all messages.
:return: None.
"""
# Invoke the parent's constructor:
super().__init__(
cache = cache,
alert_url = alert_url,
http_client = http_client,
base_filter = {"client": "whatsappNimbus"},
debug = debug,
debug_prefix = debug_prefix,
debug_only_errors = debug_only_errors
)
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass
+36 -4
View File
@@ -95,6 +95,32 @@ class TelegramAuth(BaseModel):
# ---------------------------------------------------------------------------------------------------------------------
class WhatsAppNimbusAuth(BaseModel):
apiKey: str = Field(
description = "???",
min_length = 1,
frozen = True
)
senderId: str = Field(
description = "???",
min_length = 1,
frozen = True
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# ---------------------------------------------------------------------------------------------------------------------
class ChatAuthRequestHeaders(BaseModel):
sessionToken: str = Field(
@@ -121,8 +147,12 @@ class ChatAuthRequestHeaders(BaseModel):
class ChatAuthRequestData(BaseModel):
chatClient: Literal["telegram", "whatsapp"] = Field(alias = "client")
auth: Union[TelegramAuth]
chatClient: Literal[
"telegram", # ........ Official Telegram API.
"whatsapp", # ........ Official WhatsApp API.
"whatsappNimbus" # ... Nimbus IT's unofficial WhatsApp API.
] = Field(alias = "client")
auth: Union[TelegramAuth, WhatsAppNimbusAuth]
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
@@ -138,10 +168,12 @@ class ChatAuthRequestData(BaseModel):
@model_validator(mode = "after")
def ensure_harmony(cls, values):
client = values.client
client = values.chatClient
auth = values.auth
harmony_map = {
"telegram": TelegramAuth
"telegram": TelegramAuth,
# "whatsapp": None,
"whatsappNimbus": WhatsAppNimbusAuth
}
if not isinstance(auth, harmony_map[client]):
raise ValueError(f"incorrect 'auth' for selected client '{client}'")
+1 -1
View File
@@ -106,7 +106,7 @@ class CoreAuthTokenModel(BaseModel):
client: Literal[
"gmail", "outlook", # ............................. Mail Clients
"telegram", "whatsapp", # ......................... Chat Clients
"telegram", "whatsapp", "whatsappNimbus", # ....... Chat Clients
"nimbusSmsIndia", "savvyBulkSmsKenya", # .......... SMS Clients
"razorpay", "safaricomMPesaExpress", # ............ Payment Gateways
"zerodhaKite", "iciciBreeze", "paperTrading", # ... Stock Brokers
+3 -3
View File
@@ -112,9 +112,9 @@ class CoreMessageModel(BaseModel):
)
client: Literal[
"gmail", "outlook", # ...................... Mail Clients
"telegram", "whatsapp", # .................. Chat Clients
"nimbusSmsIndia", "savvyBulkSmsKenya", # ... SMS Clients
"gmail", "outlook", # ........................................ Mail Clients
"telegram", "whatsapp", "whatsappNimbus", # .................. Chat Clients
"nimbusSmsIndia", "savvyBulkSmsKenya", # ..................... SMS Clients
] = Field(
description = "the third-part client that was used",
frozen = True