(20250121) With APIs to send, list, and update tags on WhatsApp messages (through Nimbus).

This commit is contained in:
2025-01-21 15:27:53 +05:30
parent f94d9e1a4b
commit cadbc19f28
13 changed files with 498 additions and 182 deletions
+32 -20
View File
@@ -6,11 +6,11 @@
DATE: DATE:
Thursday, 19th Dec., 2024 Tuesday, 21st Jan., 2025.
OBJECTIVE: OBJECTIVE:
To list SMS messages associated with incoming identifiers. To list chat messages associated with incoming identifiers.
REFERENCES: REFERENCES:
@@ -64,7 +64,7 @@ from shared import constants
# Data Models: # Data Models:
from models.core.user import CoreUserInfoModel from models.core.user import CoreUserInfoModel
from models.api.message.sms.list import SMSListRequestHeaders, SMSListRequestData from models.api.message.chat.list import ChatMessageListRequestHeaders, ChatMessageListRequestData
# Helpers: # Helpers:
from api.helpers.user import token_check from api.helpers.user import token_check
@@ -84,7 +84,7 @@ import asyncio
# Related to Quart: # Related to Quart:
sms_list_bp = Blueprint("sms_list", __name__) chat_list_bp = Blueprint("chat_list", __name__)
# ***************************************************************************************************************** # *****************************************************************************************************************
@@ -104,7 +104,7 @@ sms_list_bp = Blueprint("sms_list", __name__)
# ***************************************************************************************************************** # *****************************************************************************************************************
@sms_list_bp.record_once @chat_list_bp.record_once
def init(blueprint_setup_state): def init(blueprint_setup_state):
# This gets called when the blueprint is registered. # This gets called when the blueprint is registered.
@@ -115,7 +115,7 @@ def init(blueprint_setup_state):
# --------------------------------------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------------------------------
@sms_list_bp.route("/list", methods = ["GET"]) @chat_list_bp.route("/list", methods = ["GET"])
@set_api_version(api_version = "1.0.0") @set_api_version(api_version = "1.0.0")
@read_input(sanitize_headers = False, sanitize_data = False) @read_input(sanitize_headers = False, sanitize_data = False)
@get_session_info(key = "X-Session-Token", session_coro = "get_session") @get_session_info(key = "X-Session-Token", session_coro = "get_session")
@@ -123,7 +123,7 @@ def init(blueprint_setup_state):
attr_name = "logs_mongo", attr_name = "logs_mongo",
project = constants.PROJECT_NAME, project = constants.PROJECT_NAME,
log_type = constants.MODULE_NAME, log_type = constants.MODULE_NAME,
operation = "smsListApi", operation = "chatMsgListApi",
log_input = True, log_input = True,
log_output = True, log_output = True,
sensitive_keys = ["sessionToken", "X-Session-Token", "tokenKeys"] sensitive_keys = ["sessionToken", "X-Session-Token", "tokenKeys"]
@@ -131,13 +131,13 @@ def init(blueprint_setup_state):
@log_chain_to_mongo(attr_name = "logs_mongo") @log_chain_to_mongo(attr_name = "logs_mongo")
@should_not_be_under_maintenance(attr_name = "is_under_maintenance") @should_not_be_under_maintenance(attr_name = "is_under_maintenance")
@validate_input( @validate_input(
header_validator = lambda x: SMSListRequestHeaders(**x).model_dump(), header_validator = lambda x: ChatMessageListRequestHeaders(**x).model_dump(),
data_validator = lambda x: SMSListRequestData(**x) data_validator = lambda x: ChatMessageListRequestData(**x)
) )
@handle_cancelled_request() @handle_cancelled_request()
async def list_sms_messages( async def list_chat_messages(
inbound_headers: dict | SMSListRequestHeaders = None, inbound_headers: dict | ChatMessageListRequestHeaders = None,
inbound_data: dict | SMSListRequestData = None, inbound_data: dict | ChatMessageListRequestData = None,
inbound_files: dict = None, inbound_files: dict = None,
**kwargs **kwargs
): ):
@@ -168,7 +168,7 @@ async def list_sms_messages(
# ┛ # ┛
# Get the tokens from the database: # Get the tokens from the database:
auth_tokens = await current_app.sms_controller.get_tokens_from_keys( auth_tokens = await current_app.chat_controller.get_tokens_from_keys(
mongo_data_conn = current_app.data_mongo, mongo_data_conn = current_app.data_mongo,
token_keys = inbound_data.tokenKeys, token_keys = inbound_data.tokenKeys,
limit = len(inbound_data.tokenKeys) limit = len(inbound_data.tokenKeys)
@@ -177,13 +177,13 @@ async def list_sms_messages(
# Check if these tokens belong to the user claiming ownership: # Check if these tokens belong to the user claiming ownership:
if not await token_check.is_authorized( if not await token_check.is_authorized(
mongo_conn = current_app.data_mongo, mongo_data_conn = current_app.data_mongo,
user_info = CoreUserInfoModel(**kwargs["session_info"]), user_info = CoreUserInfoModel(**kwargs["session_info"]),
token_ids = token_ids token_ids = token_ids
): return ResponseModel( ): return ResponseModel(
status_code = StatusCodes.FAILED, status_code = StatusCodes.FAILED,
http_code = HttpCodes.UNAUTHORIZED, http_code = HttpCodes.UNAUTHORIZED,
message = "User doesn't have rights over one or more SMS accounts." message = "User doesn't have rights over one or more chat accounts."
) )
# ┳┓ ┓ • • # ┳┓ ┓ • •
@@ -191,15 +191,27 @@ async def list_sms_messages(
# ┻┛┗┻┗┗┻ ┗┛┗┛┗┗┛┗┗┫ # ┻┛┗┻┗┗┻ ┗┛┗┛┗┗┛┗┗┫
# ┛ # ┛
messages = await current_app.sms_controller.get_messages( # Construct any additional filter:
additional_filter = {}
if inbound_data.tags: additional_filter["tags"] = {"$in": inbound_data.tags}
# Fetch the messages:
messages = await current_app.chat_controller.get_messages(
mongo_data_conn = current_app.data_mongo, mongo_data_conn = current_app.data_mongo,
token_ids = token_ids, token_ids = token_ids,
limit = inbound_data.count, limit = inbound_data.count,
skip = inbound_data.fromCount, skip = inbound_data.fromCount,
projection = { projection = {
"message.metadata": False, "message.httpCode": False,
"message.rawResponse": False "message.status": False,
} "message.statusCode": False,
"message.apiMessage": False,
"message.requestId": False,
"message.messageCount": False,
"message.messageCost": False,
"message.balance": False,
},
additional_filter = additional_filter
) )
# ┳┓ # ┳┓
@@ -214,7 +226,7 @@ async def list_sms_messages(
status_code = StatusCodes.OK if success else StatusCodes.FAILED, status_code = StatusCodes.OK if success else StatusCodes.FAILED,
http_code = HttpCodes.SUCCESS if success else HttpCodes.NOT_FOUND, http_code = HttpCodes.SUCCESS if success else HttpCodes.NOT_FOUND,
data = [m.full for m in messages] if success else None, data = [m.full for m in messages] if success else None,
message = f"{message_count} SMS message(s) found." message = f"{message_count} message(s) found."
) )
+44 -49
View File
@@ -6,11 +6,11 @@
DATE: DATE:
Thursday, 19th Dec., 2024 Tuesday, 21st Jan., 2025.
OBJECTIVE: OBJECTIVE:
To send SMS messages through various third-party clients. To send chat-app messages through various third-party clients.
REFERENCES: REFERENCES:
@@ -62,12 +62,8 @@ from utils_v2.api.async_quart import (
# Models: # Models:
from models.core.auth_token import CoreAuthTokenModel from models.core.auth_token import CoreAuthTokenModel
from models.api.message.sms.send import SMSSendRequestHeaders, SMSSendRequestData from models.api.message.chat.send import ChatSendRequestHeaders, ChatSendRequestData
from models.message.sms.send import ( from models.message.chat.send import ChatSendOneResult, ChatSendManyResults, NimbusWhatsAppMessage
NimbusSMSIndiaMessage,
SavvyBulkSMSKenyaMessage,
SMSSendManyResults
)
# Common: # Common:
from shared import constants from shared import constants
@@ -89,7 +85,7 @@ import asyncio
# Related to Quart: # Related to Quart:
sms_send_bp = Blueprint("sms_send", __name__) chat_send_bp = Blueprint("chat_send", __name__)
# ***************************************************************************************************************** # *****************************************************************************************************************
@@ -109,7 +105,7 @@ sms_send_bp = Blueprint("sms_send", __name__)
# ***************************************************************************************************************** # *****************************************************************************************************************
@sms_send_bp.record_once @chat_send_bp.record_once
def init(blueprint_setup_state): def init(blueprint_setup_state):
# This gets called when the blueprint is registered. # This gets called when the blueprint is registered.
@@ -120,44 +116,43 @@ def init(blueprint_setup_state):
# --------------------------------------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------------------------------
async def send_sms_messages( async def send_chat_messages(
mongo_data_conn: AsyncMongo,
auth_token: CoreAuthTokenModel, auth_token: CoreAuthTokenModel,
messages: List[NimbusSMSIndiaMessage | SavvyBulkSMSKenyaMessage], messages: List[NimbusWhatsAppMessage],
tags: List[Any] tags: List[Any]
) -> SMSSendManyResults: ) -> ChatSendManyResults:
""" """
This function purely tackles message sending. It is not concerned with authorization and security checks. Please This function purely tackles message sending. It is not concerned with authorization and security checks. Please
ensure that you perform those checks before coming here. ensure that you perform those checks before coming here.
:param mongo_data_conn: The database connection to use to perform this task.
:param auth_token: The auth token that will be used to send this message. :param auth_token: The auth token that will be used to send this message.
:param messages: The list of messages to send out. :param messages: The list of messages to send out.
:param tags: Any tags to attach with these SMS for filtering when querying in the listing service. :param tags: Any tags to attach with these messages for filtering when querying in the listing service.
:return: The structured result of sending many SMS messages. :return: The structured result of sending many chat messages.
""" """
# Start by assuming failure: # Start by assuming failure:
results = SMSSendManyResults() client_controller = None
results = ChatSendManyResults()
# Select the right client: # Select the right client:
match auth_token.client: match auth_token.client:
case "nimbusSmsIndia": case "whatsappNimbus": client_controller = current_app.whatsapp_nimbus_controller
results = await current_app.nimbus_sms_india_controller.send_many_sms(
mongo_data_conn = mongo_data_conn,
auth_token = auth_token,
messages = messages,
tags = tags
)
case "savvyBulkSmsKenya":
results = await current_app.savvy_bulk_sms_kenya_controller.send_many_sms(
mongo_data_conn = mongo_data_conn,
auth_token = auth_token,
messages = messages,
tags = tags
)
case _: case _:
results.message = "Invalid/unimplemented SMS client." client_controller = None
results.message = f"Invalid/unimplemented client '{auth_token.client}'."
# If we have a match:
if client_controller is not None:
results = await client_controller.send_many_messages(
sql_conn = current_app.sql_writer,
mongo_data_conn = current_app.data_mongo,
http_client = current_app.http_client,
auth_token = auth_token,
client = None,
messages = messages,
tags = tags
)
# Done here: # Done here:
return results return results
@@ -166,8 +161,8 @@ async def send_sms_messages(
# --------------------------------------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------------------------------
@sms_send_bp.route("", methods = ["POST"]) @chat_send_bp.route("", methods = ["POST"])
@sms_send_bp.route("/send", methods = ["POST"]) @chat_send_bp.route("/send", methods = ["POST"])
@set_api_version(api_version = "1.0.0") @set_api_version(api_version = "1.0.0")
@read_input(sanitize_headers = False, sanitize_data = False) @read_input(sanitize_headers = False, sanitize_data = False)
@get_session_info(key = "X-Session-Token", session_coro = "get_session") @get_session_info(key = "X-Session-Token", session_coro = "get_session")
@@ -175,27 +170,27 @@ async def send_sms_messages(
attr_name = "logs_mongo", attr_name = "logs_mongo",
project = constants.PROJECT_NAME, project = constants.PROJECT_NAME,
log_type = constants.MODULE_NAME, log_type = constants.MODULE_NAME,
operation = "smsSendApi", operation = "chatSendApi",
log_input = True, log_input = 1,
log_output = True, log_output = True,
sensitive_keys = ["sessionToken", "X-Session-Token", "tokenKey"] sensitive_keys = ["sessionToken", "X-Session-Token", "tokenKey"]
) )
@log_chain_to_mongo(attr_name = "logs_mongo") @log_chain_to_mongo(attr_name = "logs_mongo")
@should_not_be_under_maintenance(attr_name = "is_under_maintenance") @should_not_be_under_maintenance(attr_name = "is_under_maintenance")
@validate_input( @validate_input(
header_validator = lambda x: SMSSendRequestHeaders(**x).model_dump(), header_validator = lambda x: ChatSendRequestHeaders(**x).model_dump(),
data_validator = lambda x: SMSSendRequestData(**x) data_validator = lambda x: ChatSendRequestData(**x)
) )
@handle_cancelled_request() @handle_cancelled_request()
async def send_sms_messages_api( async def send_chat_messages_api(
inbound_headers: dict | SMSSendRequestHeaders = None, inbound_headers: dict | ChatSendRequestHeaders = None,
inbound_data: dict | SMSSendRequestData = None, inbound_data: dict | ChatSendRequestData = None,
inbound_files: dict = None, inbound_files: dict = None,
**kwargs **kwargs
): ):
""" """
Use this API when someone wants to send one or more SMS messages. Use this API when someone wants to send one or more chat messages.
:param inbound_headers: auto-extracted by the decorators. :param inbound_headers: auto-extracted by the decorators.
:param inbound_data: auto-extracted by the decorators. :param inbound_data: auto-extracted by the decorators.
:param inbound_files: auto-extracted by the decorators. :param inbound_files: auto-extracted by the decorators.
@@ -220,7 +215,7 @@ async def send_sms_messages_api(
) )
# Get the token from the token key: # Get the token from the token key:
auth_token = await current_app.sms_controller.get_token_from_key( auth_token = await current_app.chat_controller.get_token_from_key(
mongo_data_conn = current_app.data_mongo, mongo_data_conn = current_app.data_mongo,
token_key = inbound_data.tokenKey token_key = inbound_data.tokenKey
) )
@@ -230,12 +225,12 @@ async def send_sms_messages_api(
message = f"No such token key." message = f"No such token key."
) )
# ┏┓ ┓ ┏┳┓┏┓┳┳┓┏ # ┏┓ ┓ ┳┳
# ┗┓┏┓┏┓┏┫ ┣┓┏┓ ┗┓┃┃┃ # ┗┓┏┓┏┓┏┫ ┣┓┏┓ ┃┃┃┏┓┏┏┏┓┏┓┏
# ┗┛┗ ┛┗┗┻ ┛┗┗ ┗┛┛ ┗┗┛ # ┗┛┗ ┛┗┗┻ ┛┗┗ ┛ ┗┗ ┛┛┗┻┗┫┗
# ┛
sending_results = await send_sms_messages( sending_results = await send_chat_messages(
mongo_data_conn = current_app.data_mongo,
auth_token = auth_token, auth_token = auth_token,
messages = inbound_data.message, messages = inbound_data.message,
tags = inbound_data.tags tags = inbound_data.tags
+14 -14
View File
@@ -6,11 +6,11 @@
DATE: DATE:
Thursday, 19th Dec., 2024 Tuesday, 21st Jan., 2025.
OBJECTIVE: OBJECTIVE:
To update tags on SMS messages. To update tags on chat messages.
REFERENCES: REFERENCES:
@@ -64,7 +64,7 @@ from shared import constants
# Data Models: # Data Models:
from models.core.user import CoreUserInfoModel from models.core.user import CoreUserInfoModel
from models.api.message.sms.tags import SMSUpdateTagsRequestHeaders, SMSUpdateTagsRequestData from models.api.message.chat.tags import ChatMessageUpdateTagsRequestHeaders, ChatMessageUpdateTagsRequestData
# Helpers: # Helpers:
from api.helpers.user import token_check from api.helpers.user import token_check
@@ -84,7 +84,7 @@ import asyncio
# Related to Quart: # Related to Quart:
sms_update_tags_bp = Blueprint("sms_upd_tags", __name__) chat_update_tags_bp = Blueprint("chat_upd_tags", __name__)
# ***************************************************************************************************************** # *****************************************************************************************************************
@@ -104,7 +104,7 @@ sms_update_tags_bp = Blueprint("sms_upd_tags", __name__)
# ***************************************************************************************************************** # *****************************************************************************************************************
@sms_update_tags_bp.record_once @chat_update_tags_bp.record_once
def init(blueprint_setup_state): def init(blueprint_setup_state):
# This gets called when the blueprint is registered. # This gets called when the blueprint is registered.
@@ -115,7 +115,7 @@ def init(blueprint_setup_state):
# --------------------------------------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------------------------------
@sms_update_tags_bp.route("/tags", methods = ["PATCH"]) @chat_update_tags_bp.route("/tags", methods = ["PATCH"])
@set_api_version(api_version = "1.0.0") @set_api_version(api_version = "1.0.0")
@read_input(sanitize_headers = False, sanitize_data = False) @read_input(sanitize_headers = False, sanitize_data = False)
@get_session_info(key = "X-Session-Token", session_coro = "get_session") @get_session_info(key = "X-Session-Token", session_coro = "get_session")
@@ -123,7 +123,7 @@ def init(blueprint_setup_state):
attr_name = "logs_mongo", attr_name = "logs_mongo",
project = constants.PROJECT_NAME, project = constants.PROJECT_NAME,
log_type = constants.MODULE_NAME, log_type = constants.MODULE_NAME,
operation = "smsUpdTagsApi", operation = "chatUpdTagsApi",
log_input = True, log_input = True,
log_output = True, log_output = True,
sensitive_keys = ["sessionToken", "X-Session-Token", "tokenKey"] sensitive_keys = ["sessionToken", "X-Session-Token", "tokenKey"]
@@ -131,13 +131,13 @@ def init(blueprint_setup_state):
@log_chain_to_mongo(attr_name = "logs_mongo") @log_chain_to_mongo(attr_name = "logs_mongo")
@should_not_be_under_maintenance(attr_name = "is_under_maintenance") @should_not_be_under_maintenance(attr_name = "is_under_maintenance")
@validate_input( @validate_input(
header_validator = lambda x: SMSUpdateTagsRequestHeaders(**x).model_dump(), header_validator = lambda x: ChatMessageUpdateTagsRequestHeaders(**x).model_dump(),
data_validator = lambda x: SMSUpdateTagsRequestData(**x) data_validator = lambda x: ChatMessageUpdateTagsRequestData(**x)
) )
@handle_cancelled_request() @handle_cancelled_request()
async def update_sms_tags( async def update_chat_message_tags(
inbound_headers: dict | SMSUpdateTagsRequestHeaders = None, inbound_headers: dict | ChatMessageUpdateTagsRequestHeaders = None,
inbound_data: dict | SMSUpdateTagsRequestData = None, inbound_data: dict | ChatMessageUpdateTagsRequestData = None,
inbound_files: dict = None, inbound_files: dict = None,
**kwargs **kwargs
): ):
@@ -168,7 +168,7 @@ async def update_sms_tags(
# ┛ # ┛
# Get the message: # Get the message:
message = await current_app.sms_controller.get_message( message = await current_app.chat_controller.get_message(
mongo_data_conn = current_app.data_mongo, mongo_data_conn = current_app.data_mongo,
message_id = inbound_data.messageId message_id = inbound_data.messageId
) )
@@ -195,7 +195,7 @@ async def update_sms_tags(
# ┛ ┛ # ┛ ┛
# Update the message: # Update the message:
success = await current_app.sms_controller.update_sms_tags( success = await current_app.chat_controller.update_chat_message_tags(
mongo_data_conn = current_app.data_mongo, mongo_data_conn = current_app.data_mongo,
message_id = inbound_data.messageId, message_id = inbound_data.messageId,
unset_tags = inbound_data.unsetTags, unset_tags = inbound_data.unsetTags,
+11 -2
View File
@@ -62,14 +62,17 @@ from utils_v2.api.async_quart import (
# GMail-related utils: # GMail-related utils:
from utils_v2.goog.controllers.gmail.gmail_client import AsyncGmailClient from utils_v2.goog.controllers.gmail.gmail_client import AsyncGmailClient
# Chat clients:
from utils_v2.whatsapp.nimbus.controllers.async_nimbus_whatsapp import AsyncNimbusWhatsapp
# Core Controller Models: # Core Controller Models:
from controllers.core.message import CoreMessageController from controllers.core.message import CoreMessageController
# from controllers.core.auth_token import CoreAuthTokenController # from controllers.core.auth_token import CoreAuthTokenController
from controllers.core.ai.llm import CoreLLMController from controllers.core.ai.llm import CoreLLMController
from controllers.core.payment import CorePaymentController from controllers.core.payment import CorePaymentController
# API Controller Models: # # API Controller Models:
from controllers.api.mail import MailController # from controllers.api.mail import MailController
# from controllers.api.sms import SMSController # from controllers.api.sms import SMSController
# from controllers.api.payment import PaymentController # from controllers.api.payment import PaymentController
@@ -118,6 +121,9 @@ from api.blueprints.message.sms.tags import sms_update_tags_bp
# Chat Blueprints: # Chat Blueprints:
from api.blueprints.message.chat.auth import chat_auth_bp from api.blueprints.message.chat.auth import chat_auth_bp
# from api.blueprints.message.chat.webhook import chat_webhook_bp # from api.blueprints.message.chat.webhook import chat_webhook_bp
from api.blueprints.message.chat.send import chat_send_bp
from api.blueprints.message.chat.list import chat_list_bp
from api.blueprints.message.chat.tags import chat_update_tags_bp
# Software Blueprints: # Software Blueprints:
from api.blueprints.software.auth import sw_auth_bp from api.blueprints.software.auth import sw_auth_bp
@@ -187,6 +193,9 @@ app.register_blueprint(sms_update_tags_bp, url_prefix = f"/{MODULE_BASE}/sms")
# Chat Blueprints: # 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") # app.register_blueprint(chat_webhook_bp, url_prefix = f"/{MODULE_BASE}/chat")
app.register_blueprint(chat_send_bp, url_prefix = f"/{MODULE_BASE}/chat")
app.register_blueprint(chat_list_bp, url_prefix = f"/{MODULE_BASE}/chat")
app.register_blueprint(chat_update_tags_bp, url_prefix = f"/{MODULE_BASE}/chat")
# Software Blueprints: # Software Blueprints:
app.register_blueprint(sw_auth_bp, url_prefix = f"/{MODULE_BASE}/software") app.register_blueprint(sw_auth_bp, url_prefix = f"/{MODULE_BASE}/software")
+68 -7
View File
@@ -36,19 +36,24 @@ sys.path.append(".")
sys.path.append("..") sys.path.append("..")
# My async utils: # My async utils:
from utils_v2.date_time import date_time from utils_v2.database.async_mysql_v2 import AsyncMySQL
from utils_v2.database.async_mongo_v2 import AsyncMongo from utils_v2.database.async_mongo_v2 import AsyncMongo
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache
# Controllers: # Controllers:
from controllers_v2.core.message import CoreMessageController
from controllers_v2.message.chat.base import ChatController from controllers_v2.message.chat.base import ChatController
# Models: # Models:
from models.core.auth_token import CoreAuthTokenModel from models.core.auth_token import CoreAuthTokenModel
from models.core.message import CoreMessageModel from models.message.chat.send import (
NimbusWhatsAppMessage,
ChatSendOneResult,
ChatSendManyResults
)
# SMS Clients: # Chat clients:
from utils_v2.sms.india.nimbus.controllers.async_nimbus import AsyncNimbusSMS from utils_v2.whatsapp.nimbus.controllers.async_nimbus_whatsapp import AsyncNimbusWhatsapp
# To work with datatypes: # To work with datatypes:
from typing import List, Any from typing import List, Any
@@ -56,8 +61,11 @@ from typing import List, Any
# To make HTTP requests: # To make HTTP requests:
import httpx import httpx
# For asynchronous activities: # to work with MongoDB:
import asyncio from bson.objectid import ObjectId
# To make abstract classes:
from abc import ABC, abstractmethod
# ***************************************************************************************************************** # *****************************************************************************************************************
@@ -128,12 +136,65 @@ class AllChatController(ChatController):
cache = cache, cache = cache,
alert_url = alert_url, alert_url = alert_url,
http_client = http_client, http_client = http_client,
base_filter = {"client": "whatsappNimbus"}, base_filter = {},
debug = debug, debug = debug,
debug_prefix = debug_prefix, debug_prefix = debug_prefix,
debug_only_errors = debug_only_errors debug_only_errors = debug_only_errors
) )
# ┏┓ ┓ ┳┳┓
# ┗┓┏┓┏┓┏┫ ┃┃┃┏┓┏┏┏┓┏┓┏┓┏
# ┗┛┗ ┛┗┗┻ ┛ ┗┗ ┛┛┗┻┗┫┗ ┛
# ┛
async def send_one_message(
self,
sql_conn: AsyncMySQL,
mongo_data_conn: AsyncMongo,
auth_token: CoreAuthTokenModel,
client: AsyncNimbusWhatsapp,
message: NimbusWhatsAppMessage,
tags: List[Any]
) -> ChatSendOneResult:
"""
To send one message from the third-party client.
:param sql_conn: The connection to the database to use for this operation.
:param mongo_data_conn: The connection to the database to use for this operation.
:param auth_token: The auth-token model for the account from which the message has to be sent.
:param client: The connection/instance of the third-party client to use to perform this operation.
:param message: The message that you want to send to the recipient.
:param tags: Any tags that you would like to attach to the message. To be used later for internal filtering.
:return: The structured response model to describe the operation.
"""
raise NotImplementedError
async def send_many_messages(
self,
sql_conn: AsyncMySQL,
mongo_data_conn: AsyncMongo,
http_client: httpx.AsyncClient,
auth_token: CoreAuthTokenModel,
client: AsyncNimbusWhatsapp | None,
messages: List[NimbusWhatsAppMessage],
tags: List[Any]
) -> ChatSendManyResults:
"""
To send many chat messages in one go.
:param sql_conn: The connection to the database to use for this operation.
:param mongo_data_conn: The connection to the database to use for this operation.
:param http_client: An HTTP client to use to make API calls through the third-party client's class.
:param auth_token: The auth-token model for the account from which the message has to be sent.
:param client: The connection/instance of the third-party client to use to perform this operation.
:param messages: The messages that you want to send to the recipients.
:param tags: Any tags that you would like to attach to the message. To be used later for internal filtering.
:return: The structured response model to describe the operation.
"""
raise NotImplementedError
# ***************************************************************************************************************** # *****************************************************************************************************************
# ***** **** # ***** ****
+99 -9
View File
@@ -36,6 +36,7 @@ sys.path.append(".")
sys.path.append("..") sys.path.append("..")
# My async utils: # My async utils:
from utils_v2.database.async_mysql_v2 import AsyncMySQL
from utils_v2.database.async_mongo_v2 import AsyncMongo from utils_v2.database.async_mongo_v2 import AsyncMongo
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache
@@ -44,16 +45,14 @@ from controllers_v2.core.message import CoreMessageController
# Models: # Models:
from models.core.auth_token import CoreAuthTokenModel from models.core.auth_token import CoreAuthTokenModel
from models.api.message.sms.send import ( from models.message.chat.send import (
NimbusSMSIndiaMessage, NimbusWhatsAppMessage,
SavvyBulkSMSKenyaMessage, ChatSendOneResult,
SMSSendOneResult, ChatSendManyResults
SMSSendManyResults
) )
# SMS clients: # Chat clients:
from utils_v2.sms.india.nimbus.controllers.async_nimbus import AsyncNimbusSMS from utils_v2.whatsapp.nimbus.controllers.async_nimbus_whatsapp import AsyncNimbusWhatsapp
from utils_v2.sms.kenya.savvy_bulk_sms.controllers.async_savvy_bulk_sms import AsyncSavvyBulkSMS
# To work with datatypes: # To work with datatypes:
from typing import List, Any from typing import List, Any
@@ -61,6 +60,9 @@ from typing import List, Any
# To make HTTP requests: # To make HTTP requests:
import httpx import httpx
# to work with MongoDB:
from bson.objectid import ObjectId
# To make abstract classes: # To make abstract classes:
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
@@ -135,7 +137,7 @@ class ChatController(CoreMessageController, ABC):
# Prepare the combined base filter: # Prepare the combined base filter:
sms_filter = {} sms_filter = {}
for k, v in (base_filter or {}).items(): sms_filter[k] = v for k, v in (base_filter or {}).items(): sms_filter[k] = v
sms_filter["serviceType"] = "sms" sms_filter["serviceType"] = "chat"
# Invoke the parent's constructor: # Invoke the parent's constructor:
CoreMessageController.__init__( CoreMessageController.__init__(
@@ -149,6 +151,94 @@ class ChatController(CoreMessageController, ABC):
debug_only_errors = debug_only_errors debug_only_errors = debug_only_errors
) )
# ┏┓ ┓ ┳┳┓
# ┗┓┏┓┏┓┏┫ ┃┃┃┏┓┏┏┏┓┏┓┏┓┏
# ┗┛┗ ┛┗┗┻ ┛ ┗┗ ┛┛┗┻┗┫┗ ┛
# ┛
@abstractmethod
async def send_one_message(
self,
sql_conn: AsyncMySQL,
mongo_data_conn: AsyncMongo,
auth_token: CoreAuthTokenModel,
client: AsyncNimbusWhatsapp,
message: NimbusWhatsAppMessage,
tags: List[Any]
) -> ChatSendOneResult:
"""
To send one message from the third-party client.
:param sql_conn: The connection to the database to use for this operation.
:param mongo_data_conn: The connection to the database to use for this operation.
:param auth_token: The auth-token model for the account from which the message has to be sent.
:param client: The connection/instance of the third-party client to use to perform this operation.
:param message: The message that you want to send to the recipient.
:param tags: Any tags that you would like to attach to the message. To be used later for internal filtering.
:return: The structured response model to describe the operation.
"""
pass
@abstractmethod
async def send_many_messages(
self,
sql_conn: AsyncMySQL,
mongo_data_conn: AsyncMongo,
http_client: httpx.AsyncClient,
auth_token: CoreAuthTokenModel,
client: AsyncNimbusWhatsapp | None,
messages: List[NimbusWhatsAppMessage],
tags: List[Any]
) -> ChatSendManyResults:
"""
To send many chat messages in one go.
:param sql_conn: The connection to the database to use for this operation.
:param mongo_data_conn: The connection to the database to use for this operation.
:param http_client: An HTTP client to use to make API calls through the third-party client's class.
:param auth_token: The auth-token model for the account from which the message has to be sent.
:param client: The connection/instance of the third-party client to use to perform this operation.
:param messages: The messages that you want to send to the recipients.
:param tags: Any tags that you would like to attach to the message. To be used later for internal filtering.
:return: The structured response model to describe the operation.
"""
pass
# ┳┳ ┓ ┏┳┓
# ┃┃┏┓┏┫┏┓╋┏┓ ┃ ┏┓┏┓┏
# ┗┛┣┛┗┻┗┻┗┗ ┻ ┗┻┗┫┛
# ┛ ┛
# We cannot modify the SMS messages themselves, but we can set/unset tags on them for internal referencing and
# filtering. This will help the users organize their inboxes well.
async def update_chat_message_tags(
self,
mongo_data_conn: AsyncMongo,
message_id: ObjectId | str,
unset_tags: List[str] = None,
set_tags: List[str] = None
) -> bool:
"""
To set and unset tags on one chat message.
:param mongo_data_conn: The database connection to use to perform this action.
:param message_id: The ObjectId of the document in MongoDb that holds the message.
:param unset_tags: The list of tags to unset (done before setting new tags).
:param set_tags: The list of tags to set (done after unsetting old tags).
:return: True if successful, else False.
"""
# Simply call the core model:
return await self.update_message_tags(
mongo_data_conn = mongo_data_conn,
message_id = message_id,
unset_tags = unset_tags,
set_tags = set_tags
)
# ***************************************************************************************************************** # *****************************************************************************************************************
# ***** **** # ***** ****
+138 -2
View File
@@ -37,6 +37,7 @@ sys.path.append("..")
# My async utils: # My async utils:
from utils_v2.date_time import date_time from utils_v2.date_time import date_time
from utils_v2.database.async_mysql_v2 import AsyncMySQL
from utils_v2.database.async_mongo_v2 import AsyncMongo from utils_v2.database.async_mongo_v2 import AsyncMongo
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache
@@ -46,9 +47,14 @@ from controllers_v2.message.chat.base import ChatController
# Models: # Models:
from models.core.auth_token import CoreAuthTokenModel from models.core.auth_token import CoreAuthTokenModel
from models.core.message import CoreMessageModel from models.core.message import CoreMessageModel
from models.message.chat.send import (
NimbusWhatsAppMessage,
ChatSendOneResult,
ChatSendManyResults
)
# SMS Clients: # Chat clients:
from utils_v2.sms.india.nimbus.controllers.async_nimbus import AsyncNimbusSMS from utils_v2.whatsapp.nimbus.controllers.async_nimbus_whatsapp import AsyncNimbusWhatsapp
# To work with datatypes: # To work with datatypes:
from typing import List, Any from typing import List, Any
@@ -134,6 +140,136 @@ class WhatsAppNimbusController(ChatController):
debug_only_errors = debug_only_errors debug_only_errors = debug_only_errors
) )
# ┏┓ ┓ ┳┳┓
# ┗┓┏┓┏┓┏┫ ┃┃┃┏┓┏┏┏┓┏┓┏┓┏
# ┗┛┗ ┛┗┗┻ ┛ ┗┗ ┛┛┗┻┗┫┗ ┛
# ┛
async def send_one_message(
self,
sql_conn: AsyncMySQL,
mongo_data_conn: AsyncMongo,
auth_token: CoreAuthTokenModel,
client: AsyncNimbusWhatsapp,
message: NimbusWhatsAppMessage,
tags: List[Any]
) -> ChatSendOneResult:
"""
To send one message from the third-party client.
:param sql_conn: The connection to the database to use for this operation.
:param mongo_data_conn: The connection to the database to use for this operation.
:param auth_token: The auth-token model for the account from which the message has to be sent.
:param client: The connection/instance of the third-party client to use to perform this operation.
:param message: The message that you want to send to the recipient.
:param tags: Any tags that you would like to attach to the message. To be used later for internal filtering.
:return: The structured response model to describe the operation.
"""
# Send the SMS:
client_response = await client.send_whatsapp(
recipient_number = message.recipientNo,
message = message.message,
pdf_url = message.pdfUrl,
image_0_url = message.image0Url,
image_1_url = message.image1Url,
schedule_on = message.scheduleTs
)
# Convert the format of the SMS client's response to the core message model.
sent_message_model = CoreMessageModel(
ts = client_response.ts,
syncTs = date_time.get_current_utc_date_time(as_string = False),
tokenId = auth_token.authTokenId,
serviceType = auth_token.serviceType,
client = auth_token.client,
clientMessageId = client_response.requestId,
clientThreadId = message.recipientNo,
isSent = True,
isBroadcast = False,
sentSuccessfully = client_response.success,
sender = auth_token.clientUserId["senderId"],
recipient = message.recipientNo,
chat = message.recipientNo,
message = client_response.model_dump(),
snippet = message.message,
aiSnippet = None,
tags = list(set(tags + ["Chat", "Nimbus", "WhatsApp"]))
)
# Save the result to the database:
message_id = await self.save_one_message(
mongo_data_conn = mongo_data_conn,
message = sent_message_model
)
self._printer(message_id, client_response.success)
# Done here:
success = True if client_response.success and message_id else False
return ChatSendOneResult(
success = success,
message = "Message sent successfully." if client_response.success else "Message sending failed.",
chatMessage = sent_message_model
)
async def send_many_messages(
self,
sql_conn: AsyncMySQL,
mongo_data_conn: AsyncMongo,
http_client: httpx.AsyncClient,
auth_token: CoreAuthTokenModel,
client: AsyncNimbusWhatsapp | None,
messages: List[NimbusWhatsAppMessage],
tags: List[Any]
) -> ChatSendManyResults:
"""
To send many chat messages in one go.
:param sql_conn: The connection to the database to use for this operation.
:param mongo_data_conn: The connection to the database to use for this operation.
:param http_client: An HTTP client to use to make API calls through the third-party client's class.
:param auth_token: The auth-token model for the account from which the message has to be sent.
:param client: The connection/instance of the third-party client to use to perform this operation.
:param messages: The messages that you want to send to the recipients.
:param tags: Any tags that you would like to attach to the message. To be used later for internal filtering.
:return: The structured response model to describe the operation.
"""
# Start with a blank variable:
cumulative_results = ChatSendManyResults()
# Make the client from the auth-token:
client = client or AsyncNimbusWhatsapp(
api_key = auth_token.auth["apiKey"],
http_client = self._http_client,
debug = False
)
# Create and fire all the message-sending tasks:
tasks = [
self.send_one_message(
sql_conn = sql_conn,
mongo_data_conn = mongo_data_conn,
auth_token = auth_token,
client = client,
message = message,
tags = tags
)
for message in messages
]
individual_results = await asyncio.gather(*tasks)
# Prepare the final result:
for result in individual_results:
if result.success: cumulative_results.successCount += 1
else: cumulative_results.failureCount += 1
cumulative_results.totalCount += 1
cumulative_results.chatMessages.append(result.chatMessage)
cumulative_results.message = f"{cumulative_results.successCount}/{cumulative_results.totalCount} messgae(s) sent."
# Done here:
return cumulative_results
# ***************************************************************************************************************** # *****************************************************************************************************************
# ***** **** # ***** ****
+3 -2
View File
@@ -44,7 +44,7 @@ from controllers_v2.core.message import CoreMessageController
# Models: # Models:
from models.core.auth_token import CoreAuthTokenModel from models.core.auth_token import CoreAuthTokenModel
from models.api.message.sms.send import ( from models.message.sms.send import (
NimbusSMSIndiaMessage, NimbusSMSIndiaMessage,
SavvyBulkSMSKenyaMessage, SavvyBulkSMSKenyaMessage,
SMSSendOneResult, SMSSendOneResult,
@@ -157,12 +157,13 @@ class SMSController(CoreMessageController, ABC):
# ┗┛┛ ┗┗┛ ┗┛┗ ┛┗┗┻┗┛┗┗┫ # ┗┛┛ ┗┗┛ ┗┛┗ ┛┗┗┻┗┛┗┗┫
# ┛ # ┛
@abstractmethod
async def send_one_sms( async def send_one_sms(
self, self,
mongo_data_conn: AsyncMongo, mongo_data_conn: AsyncMongo,
auth_token: CoreAuthTokenModel, auth_token: CoreAuthTokenModel,
client: AsyncNimbusSMS | AsyncSavvyBulkSMS, client: AsyncNimbusSMS | AsyncSavvyBulkSMS,
message: NimbusSMSIndiaMessage, message: NimbusSMSIndiaMessage | SavvyBulkSMSKenyaMessage,
tags: List[Any] tags: List[Any]
) -> SMSSendOneResult: ) -> SMSSendOneResult:
@@ -184,7 +184,7 @@ class NimbusSMSIndiaController(SMSController):
isSent = True, isSent = True,
isBroadcast = False, isBroadcast = False,
sentSuccessfully = client_response.success, sentSuccessfully = client_response.success,
sender = auth_token.auth["senderId"], sender = auth_token.clientUserId["senderId"],
recipient = message.recipientNo, recipient = message.recipientNo,
chat = message.recipientNo, chat = message.recipientNo,
message = client_response.model_dump(), message = client_response.model_dump(),
+8 -8
View File
@@ -6,11 +6,11 @@
DATE: DATE:
Tuesday, 3rd Dec., 2024. Tuesday, 21st Jan., 2025.
OBJECTIVE: OBJECTIVE:
To provide a structure to query the full payload of an email. To provide a structure to query the full payload of chat message(s).
REFERENCES: REFERENCES:
@@ -75,10 +75,10 @@ REGEX_SESSION_TOKEN = r"^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]
# ***************************************************************************************************************** # *****************************************************************************************************************
class SMSListRequestHeaders(BaseModel): class ChatMessageListRequestHeaders(BaseModel):
sessionToken: str = Field( sessionToken: str = Field(
description = "the session token of the user who is requesting the service", description = "The session token of the user who is requesting the service.",
pattern = REGEX_SESSION_TOKEN, pattern = REGEX_SESSION_TOKEN,
frozen = True, frozen = True,
alias = "X-Session-Token" alias = "X-Session-Token"
@@ -99,10 +99,10 @@ class SMSListRequestHeaders(BaseModel):
# --------------------------------------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------------------------------
class SMSListRequestData(BaseModel): class ChatMessageListRequestData(BaseModel):
tokenKeys: str | List[str] = Field( tokenKeys: str | List[str] = Field(
description = "the token identifier(s) that tell you which auth-tokens were used for fetching those messages", description = "The token identifier(s) that tell you which auth-tokens were used for fetching those messages.",
frozen = True, frozen = True,
) )
@@ -115,14 +115,14 @@ class SMSListRequestData(BaseModel):
) )
fromCount: int = Field( fromCount: int = Field(
description = "the no. of mails to skip before picking mails to list; useful for pagination", description = "The no. of mails to skip before picking mails to list; useful for pagination.",
ge = 0, ge = 0,
default = 0, default = 0,
frozen = True frozen = True
) )
tags: List[Any] | None = Field( tags: List[Any] | None = Field(
description = "any no. of tags that you want to filter by", description = "Any no. of tags that you want to filter by.",
default = None default = None
) )
+9 -10
View File
@@ -6,11 +6,11 @@
DATE: DATE:
Monday, 9th Dec., 2024. Tuesday, 21st Jan., 2025.
OBJECTIVE: OBJECTIVE:
To provide a structure to receive API calls to send SMS messages from various third-party clients. To provide a structure to receive API calls to send chat messages from various third-party clients.
REFERENCES: REFERENCES:
@@ -46,11 +46,10 @@ from utils_v2.date_time import date_time
# Models: # Models:
from models.core.message import CoreMessageModel from models.core.message import CoreMessageModel
from utils_v2.sms.models.sms_message import SentSMSMessageModel from utils_v2.sms.models.sms_message import SentSMSMessageModel
from models.message.sms.send import ( from models.message.chat.send import (
NimbusSMSIndiaMessage, NimbusWhatsAppMessage,
SavvyBulkSMSKenyaMessage, ChatSendOneResult,
SMSSendOneResult, ChatSendManyResults
SMSSendManyResults
) )
# To work with date and time: # To work with date and time:
@@ -88,7 +87,7 @@ REGEX_SESSION_TOKEN = r"^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]
# ***************************************************************************************************************** # *****************************************************************************************************************
class SMSSendRequestHeaders(BaseModel): class ChatSendRequestHeaders(BaseModel):
sessionToken: str | None = Field( sessionToken: str | None = Field(
description = "the session token of the user who is requesting the service", description = "the session token of the user who is requesting the service",
@@ -113,10 +112,10 @@ class SMSSendRequestHeaders(BaseModel):
# --------------------------------------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------------------------------
class SMSSendRequestData(BaseModel): class ChatSendRequestData(BaseModel):
tokenKey: ObjectId tokenKey: ObjectId
message: List[NimbusSMSIndiaMessage] | List[SavvyBulkSMSKenyaMessage] message: List[NimbusWhatsAppMessage]
tags: List[Any] | None = Field(default = None, validate_default = True) tags: List[Any] | None = Field(default = None, validate_default = True)
# ┏┓ ┏• # ┏┓ ┏•
+8 -8
View File
@@ -6,11 +6,11 @@
DATE: DATE:
Thursday, 19th Dec., 2024. Tuesday, 21st Jan., 2025.
OBJECTIVE: OBJECTIVE:
To provide a structure to work with the tags on SMS messages. To provide a structure to work with the tags on chat messages.
REFERENCES: REFERENCES:
@@ -75,10 +75,10 @@ REGEX_SESSION_TOKEN = r"^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]
# ***************************************************************************************************************** # *****************************************************************************************************************
class SMSUpdateTagsRequestHeaders(BaseModel): class ChatMessageUpdateTagsRequestHeaders(BaseModel):
sessionToken: str = Field( sessionToken: str = Field(
description = "the session token of the user who is requesting the service", description = "The session token of the user who is requesting the service.",
pattern = REGEX_SESSION_TOKEN, pattern = REGEX_SESSION_TOKEN,
frozen = True, frozen = True,
alias = "X-Session-Token" alias = "X-Session-Token"
@@ -99,21 +99,21 @@ class SMSUpdateTagsRequestHeaders(BaseModel):
# --------------------------------------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------------------------------
class SMSUpdateTagsRequestData(BaseModel): class ChatMessageUpdateTagsRequestData(BaseModel):
messageId: str = Field( messageId: str = Field(
description = "the mail identifier (Mongo ObjectId) of the document that holds the mail", description = "The message identifier (Mongo ObjectId) of the document that holds the chat message.",
frozen = True frozen = True
) )
unsetTags: List[Any] | None = Field( unsetTags: List[Any] | None = Field(
description = "the list of tags to remove from the mail", description = "The list of tags to remove from the message.",
frozen = True, frozen = True,
default = None default = None
) )
setTags: List[Any] | None = Field( setTags: List[Any] | None = Field(
description = "the list of tags to add to the mail", description = "The list of tags to add to the message.",
frozen = True, frozen = True,
default = None default = None
) )
+63 -50
View File
@@ -6,11 +6,13 @@
DATE: DATE:
Monday, 9th Dec., 2024. Tuesday, 21st Jan., 2025.
OBJECTIVE: OBJECTIVE:
To provide a structure to receive API calls to send SMS messages from various third-party clients. To provide a structure to receive API calls to send chat messages from various third-party clients. At the time
of creating this file we are starting with Nimbus IT's unofficial WhatsApp services. We intend to add Telegram's
official APIs soon after.
REFERENCES: REFERENCES:
@@ -36,7 +38,7 @@ sys.path.append(".")
sys.path.append("..") sys.path.append("..")
# For making data behaviour_models: # For making data behaviour_models:
from pydantic import BaseModel, Field, field_validator, PastDatetime from pydantic import BaseModel, Field, field_validator, AwareDatetime, PastDatetime
from typing import Optional, Literal, Union, List, Any from typing import Optional, Literal, Union, List, Any
# My utils: # My utils:
@@ -82,24 +84,42 @@ REGEX_SESSION_TOKEN = r"^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]
# ***************************************************************************************************************** # *****************************************************************************************************************
class NimbusSMSIndiaMessage(BaseModel): class NimbusWhatsAppMessage(BaseModel):
recipientNo: str = Field( recipientNo: str = Field(
description = "the phone no. of the target recipient", description = "The phone no. of the target recipient(s)",
pattern = r"\+?\d{0,3}\s*\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}", # pattern = r"\+?\d{0,3}\s*\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}",
frozen = True frozen = True
) )
text: str = Field( message: str = Field(
description = "the actual text that you want to send", description = "The actual text that you want to send.",
min_length = 1, min_length = 1,
frozen = True frozen = True
) )
templateId: str = Field( pdfUrl: str | None = Field(
description = "the id of the template that you are trying to use to send the message", description = "A PDF file to send with your message.",
min_length = 1, frozen = True,
frozen = True default = None
)
image0Url: str | None = Field(
description = "An image file to send with your message.",
frozen = True,
default = None
)
image1Url: str | None = Field(
description = "An image file to send with your message.",
frozen = True,
default = None
)
scheduleTs: AwareDatetime | None = Field(
description = "The time (UTC) at which the message needs to be sent. Null for immediate delivery.",
frozen = True,
default = None
) )
# ┏┓ ┏• # ┏┓ ┏•
@@ -114,6 +134,25 @@ class NimbusSMSIndiaMessage(BaseModel):
# ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓ # ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓
# ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗ # ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗
@field_validator("scheduleTs", mode = "before")
def to_datetime(cls, value):
if not isinstance(value, datetime.datetime):
value = date_time.parse_date_time(
input_value = value,
date_formats = [
"%Y%m%d",
"%Y-%m-%d",
"%Y-%m-%d %H:%M:%S",
"%Y-%m-%d %H:%M:%S%z",
"%Y-%m-%dT%H:%M:%S",
"%Y-%m-%dT%H:%M:%S%z"
]
)
if value:
value = date_time.as_if_timezone(value, date_time.TIMEZONE_UTC)
value = date_time.to_timezone(value, date_time.TIMEZONE_IST)
return value
@field_validator("recipientNo", mode = "before") @field_validator("recipientNo", mode = "before")
def validate_contact_nos(cls, value): def validate_contact_nos(cls, value):
value = regex.replace(text = str(value), pattern = r"[^\d]", substitute_text = "") value = regex.replace(text = str(value), pattern = r"[^\d]", substitute_text = "")
@@ -125,46 +164,20 @@ class NimbusSMSIndiaMessage(BaseModel):
# --------------------------------------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------------------------------
class SavvyBulkSMSKenyaMessage(BaseModel): class ChatSendOneResult(BaseModel):
recipientNo: str = Field(
description = "the phone no. of the target recipient",
min_length = 1,
frozen = True
)
text: str = Field(
description = "the actual text that you want to send",
min_length = 1,
frozen = True
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# ---------------------------------------------------------------------------------------------------------------------
class SMSSendOneResult(BaseModel):
success: bool = Field( success: bool = Field(
description = "whether, or not, the sms was successfully sent", description = "Whether, or not, the chat message was successfully sent.",
default = False default = False
) )
message: str | None = Field( message: str | None = Field(
description = "a brief message to summarize the result of the process", description = "A brief message to summarize the result of the process.",
default = None default = None
) )
smsMessage: NimbusSMSIndiaMessage | SavvyBulkSMSKenyaMessage = Field( chatMessage: CoreMessageModel = Field(
description = "the actual data of the sms", description = "The actual data of the chat message.",
default = None default = None
) )
@@ -180,30 +193,30 @@ class SMSSendOneResult(BaseModel):
# --------------------------------------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------------------------------
class SMSSendManyResults(BaseModel): class ChatSendManyResults(BaseModel):
totalCount: int = Field( totalCount: int = Field(
description = "the total no. of mails that were to be sync'd", description = "The total no. of messages that were attempted.",
default = 0 default = 0
) )
successCount: int = Field( successCount: int = Field(
description = "the no. of mails that were successfully sync'd", description = "The no. of chat messages that were successfully sent.",
default = 0 default = 0
) )
failureCount: int = Field( failureCount: int = Field(
description = "the no. of mails that were successfully sync'd", description = "The no. of chat messages that were NOT sent.",
default = 0 default = 0
) )
message: str = Field( message: str = Field(
description = "a brief message to summarize the results of the process", description = "A brief message to summarize the results of the process.",
default = None default = None
) )
smsMessages: List[CoreMessageModel] = Field( chatMessages: List[CoreMessageModel] = Field(
description = "the actual data of the sms", description = "The actual data of the chat messages.",
default = [] default = []
) )