python-whatsapp-wrapper
Advanced tools
| from whatsapp.bot import WhatsappBot | ||
| import dataclasses as dc | ||
| import requests | ||
| from typing import Any, Self | ||
| from dataclasses_json import dataclass_json | ||
| @dataclass_json | ||
| @dc.dataclass | ||
| class ProfileCommand: | ||
| command_name: str = dc.field() | ||
| "Command name. Max 32 characters" | ||
| command_description: str = dc.field() | ||
| "Command description. Max 256 characters, emojis are not supported" | ||
| @dataclass_json | ||
| @dc.dataclass | ||
| class ProfileComponents: | ||
| welcome_message: bool = dc.field(default=False) | ||
| "Enable/disable welcome message" | ||
| commands: list[ProfileCommand] = dc.field(default_factory=list) | ||
| "Commands like '/comamnd'. Max 32 commands" | ||
| prompts: list[str] = dc.field(default_factory=list) | ||
| @classmethod | ||
| def get_current_config (cls, bot: WhatsappBot, bot_number_id: str) -> dict[str, Any]: | ||
| headers = { "Authorization": bot.bearer_token } | ||
| # TODO: Fix this bad replace | ||
| endpoint = bot.external_endpoint(bot_number_id, "fields=conversational_automation") | ||
| endpoint = endpoint.replace("/fields", "?fields") | ||
| return requests.get(endpoint, headers=headers).json() | ||
| @classmethod | ||
| def load_profile(cls, bot: WhatsappBot, bot_number_id: str) -> Self: | ||
| print(cls.get_current_config(bot, bot_number_id)) | ||
| return cls.from_dict(cls.get_current_config(bot, bot_number_id)) | ||
| def set_current_config (self, bot: WhatsappBot, bot_number_id: str): | ||
| headers = { "Authorization": bot.bearer_token, "Content-Type": "application/json" } | ||
| endpoint = bot.external_endpoint(bot_number_id, "conversational_automation") | ||
| data = self.to_json() | ||
| return requests.post(endpoint, headers=headers, data=data).json() | ||
| def add_command (self, command_name: str, command_description): | ||
| """ | ||
| Add a command to a given Profile | ||
| :param command_name: Command name that will appear when user tap profile or keyboard | ||
| :param command_description: Command description for help user | ||
| """ | ||
| command_name = command_name.lstrip("/") | ||
| self.commands.append(ProfileCommand(command_name, command_description)) |
+1
-1
| Metadata-Version: 2.1 | ||
| Name: python-whatsapp-wrapper | ||
| Version: 0.0.11 | ||
| Version: 0.0.15 | ||
| Summary: Pure python project for META Whatsapp Business API wrapper. | ||
@@ -5,0 +5,0 @@ Author-email: Sergio Pires <sergiodanpires@gmail.com> |
| Metadata-Version: 2.1 | ||
| Name: python-whatsapp-wrapper | ||
| Version: 0.0.11 | ||
| Version: 0.0.15 | ||
| Summary: Pure python project for META Whatsapp Business API wrapper. | ||
@@ -5,0 +5,0 @@ Author-email: Sergio Pires <sergiodanpires@gmail.com> |
@@ -12,4 +12,5 @@ LICENSE | ||
| whatsapp/bot.py | ||
| whatsapp/config_account.py | ||
| whatsapp/error.py | ||
| whatsapp/messages.py | ||
| whatsapp/utils.py |
@@ -1,1 +0,1 @@ | ||
| __version__ = "0.0.11" | ||
| __version__ = "0.0.15" |
+16
-12
@@ -100,3 +100,3 @@ import argparse | ||
| def external_endpoint (self, bot_number_id: str) -> str: | ||
| def external_endpoint (self, bot_number_id: str, service: str) -> str: | ||
| """ | ||
@@ -108,3 +108,3 @@ Returns the external endpoint. | ||
| """ | ||
| return f"{self.endpoint}/{self.api_version}/{bot_number_id}/messages" | ||
| return f"{self.endpoint}/{self.api_version}/{bot_number_id}/{service}" | ||
@@ -121,3 +121,3 @@ async def send_message (self, message: dict[str, Any], bot_number_id: str): | ||
| response = requests.post( | ||
| self.external_endpoint(bot_number_id), data=payload, headers=headers | ||
| self.external_endpoint(bot_number_id, "messages"), data=payload, headers=headers | ||
| ) | ||
@@ -169,3 +169,3 @@ | ||
| :raises UnknownEvent: When don't reache any trigger | ||
| """ | ||
| """ | ||
| if state not in self._state_handlers: | ||
@@ -186,6 +186,2 @@ self._state_handlers[state] = State() | ||
| # Just compiles the regex pattern | ||
| else: | ||
| handler_trigger = re.compile(handler_trigger) | ||
| elif ( | ||
@@ -208,5 +204,5 @@ isinstance(handler_trigger, tuple) | ||
| :param state: State name | ||
| :param on_invalid_state_func: function to handle state | ||
| :param on_invalid_state_func: function to handle state | ||
| :raises UnknownEvent: If state are not valid | ||
| """ | ||
| """ | ||
| if state not in self._state_handlers: | ||
@@ -225,3 +221,3 @@ raise UnknownEvent(f"{state} is not a valid state") | ||
| :return: String challenge | ||
| """ | ||
| """ | ||
| try: | ||
@@ -303,3 +299,3 @@ mode = request.args["hub.mode"] | ||
| time.sleep(interval) | ||
| incoming_update = self._server_queue.get() | ||
| incoming_update = self.get_update() | ||
@@ -327,2 +323,10 @@ if incoming_update is None: | ||
| def get_update (self) -> Incoming | None: | ||
| """ | ||
| Gets an update from the queue (In-memory, mongodb, redis etc). | ||
| :return: Incoming message | ||
| """ | ||
| return self._server_queue.get() | ||
| async def process_update (self, incoming: Incoming): | ||
@@ -329,0 +333,0 @@ """ |
+69
-18
@@ -9,2 +9,3 @@ import dataclasses as dc | ||
| from dataclasses_json import config, dataclass_json | ||
| import json | ||
@@ -31,3 +32,3 @@ from whatsapp.error import NotImplementedMsgType | ||
| :return: Same object | ||
| """ | ||
| """ | ||
| def _decorator (cls: RECEIVED_MESSAGE_T) -> RECEIVED_MESSAGE_T: | ||
@@ -118,3 +119,3 @@ RECEIVED_MSG_TYPE_TO_OBJECT[msg_type] = cls | ||
| display_phone_number: str = dc.field() | ||
| "Phone number that cusomer will see in chat" | ||
| "Phone number that customer will see in chat" | ||
| phone_number_id: str = dc.field() | ||
@@ -221,7 +222,7 @@ "Phone number id. Need to be used to respond an message " | ||
| "Arbitrary string" | ||
| conversation: Conversation = dc.field(default=None) | ||
| conversation: Conversation | None = dc.field(default=None) | ||
| "Conversation info" | ||
| errors: list[Errors] = dc.field(default_factory=list) | ||
| "List of objects describing errors" | ||
| pricing: Pricing = dc.field(default=None) | ||
| pricing: Pricing | None = dc.field(default=None) | ||
| "Pricing information" | ||
@@ -335,3 +336,3 @@ | ||
| Supoorted audio formats: aac, amr, mp3, mp4 audio, ogg (opus codecs, not audio/ogg) | ||
| Supoorted audio formats: aac, amr, mp3, mp4 audio, ogg (opus codecs, not audio/ogg) | ||
| Max size: 16 MB | ||
@@ -386,3 +387,3 @@ """ | ||
| Max Size: 100 MB | ||
| Max Size: 100 MB | ||
| Supported MIMEs (message custom logo): | ||
@@ -474,5 +475,35 @@ - text/plain | ||
| class ButtonUrlMessage (ReceivedMessage): | ||
| ... | ||
| @dataclass_json | ||
| @dc.dataclass | ||
| class ButtonUrlMessage: | ||
| header: str = dc.field(kw_only=True) | ||
| "Header text" | ||
| body: str = dc.field(kw_only=True) | ||
| "Body Text" | ||
| footer: str = dc.field(kw_only=True) | ||
| "Message footer text" | ||
| button_display_text: str = dc.field(kw_only=True) | ||
| "Button text" | ||
| button_url: str = dc.field(kw_only=True) | ||
| "URL that browser will open when user tap the button" | ||
| def to_send (self, to: str) -> dict[str, str]: | ||
| return { | ||
| **ReceivedMessage.default_body_to_send(to, MessageTypes.INTERACTIVE), | ||
| "interactive": { | ||
| "type": "cta_url", | ||
| "header": { "type": "text", "text": self.header }, | ||
| "body": { "text": self.body }, | ||
| "footer": { "text": self.footer }, | ||
| "action": { | ||
| "name": "cta_url", | ||
| "parameters": { | ||
| "display_text": self.button_display_text, | ||
| "url": self.button_url | ||
| } | ||
| } | ||
| } | ||
| } | ||
| class FlowMessage (ReceivedMessage): | ||
@@ -488,4 +519,4 @@ # TODO Implement FlowMessage when I receive API access | ||
| footer: str = dc.field(kw_only=True) | ||
| button_title: str = dc.field(kw_only=True) | ||
| sections: list[Section] = dc.field(kw_only=True) | ||
@@ -520,4 +551,10 @@ | ||
| def list_reply(self) -> Item: | ||
| return Item.from_dict(self.interactive["list_reply"]) | ||
| item = self.interactive["list_reply"] | ||
| if isinstance(item, str): | ||
| item = item.replace("'", "\"") | ||
| item = json.loads(item) | ||
| return Item.from_dict(item) | ||
| @list_reply.setter | ||
@@ -608,5 +645,19 @@ def list_reply(self, value: Item): | ||
| @dc.dataclass | ||
| class AskForLocationMessage (ReceivedMessage): | ||
| ... | ||
| class AskForLocationMessage: | ||
| """ | ||
| Max body text size: 4096 characters | ||
| """ | ||
| body: str = dc.field(kw_only=True) | ||
| "Text body of location request (Accepts URL link). Max 4096 characters" | ||
| def to_send (self, to: str) -> dict[str, str]: | ||
| return { | ||
| **ReceivedMessage.default_body_to_send(to, MessageTypes.INTERACTIVE), | ||
| "interactive": { | ||
| "type": "location_request_message", | ||
| "body": { "text": self.body }, | ||
| "action": { "name": "send_location" } | ||
| } | ||
| } | ||
| @dataclass_json | ||
@@ -665,5 +716,5 @@ @dc.dataclass | ||
| """ | ||
| Text message. Contains information about sent and received texts. | ||
| Text message. Contains information about sent and received texts. | ||
| Shows a website preview if message has a url (startswith http:// or https://) | ||
| and preview_url is True, the url will be previewed. | ||
| and preview_url is True, the url will be previewed. | ||
| Max text size: 4096 characters | ||
@@ -687,3 +738,3 @@ """ | ||
| :return: Dictionary representing the reply message | ||
| """ | ||
| """ | ||
| output_msg = cls.default_body_to_send(to, MessageTypes.TEXT) | ||
@@ -703,4 +754,4 @@ output_msg["text"] = { "body": message } | ||
| Video message with an optinal caption. | ||
| Supoorted audio formats: 3gp and mp4 | ||
| Supoorted audio formats: 3gp and mp4 | ||
| Max size: 16 MB | ||
@@ -755,3 +806,3 @@ """ | ||
| :return: List of converted messages. | ||
| """ | ||
| """ | ||
| if messages is None: | ||
@@ -758,0 +809,0 @@ return [] |
+22
-1
@@ -9,4 +9,4 @@ import logging | ||
| from whatsapp.error import MissingParameters, UnknownEvent, VerificationFailed | ||
| from whatsapp.messages import Incoming, ReadMessage | ||
| def middleware(f: Callable): | ||
@@ -41,1 +41,22 @@ """ | ||
| return _middleware | ||
| def read_message(f: Callable): | ||
| """ | ||
| Decorator read messages when state are reached | ||
| :param f: State handler function | ||
| :return: Decorated state handler function | ||
| """ | ||
| @wraps(f) | ||
| # TODO: Fix circular import for this type hint | ||
| async def _read_message(bot, incoming: Incoming): | ||
| try: | ||
| await bot.send_message( | ||
| ReadMessage.to_send(incoming.message.id), incoming.metadata.phone_number_id | ||
| ) | ||
| except: | ||
| logging.error(traceback.format_exc()) | ||
| await f(bot, incoming) | ||
| return _read_message |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
58932
8.32%17
6.25%1132
10.87%