|
| 1 | +import logging |
| 2 | +from typing import List |
| 3 | + |
| 4 | +from pydantic import Field |
| 5 | +import requests |
| 6 | +from llmstack.apps.models import App |
| 7 | +from llmstack.apps.types.app_type_interface import AppTypeInterface, BaseSchema |
| 8 | + |
| 9 | +logger = logging.getLogger(__name__) |
| 10 | + |
| 11 | + |
| 12 | +class TwilioSmsAppConfigSchema(BaseSchema): |
| 13 | + account_sid: str = Field( |
| 14 | + title='Account SID', description="Account SID of the Twilio account. Your account's SID can be found in the console.", required=True, |
| 15 | + ) |
| 16 | + auth_token: str = Field( |
| 17 | + title='Auth Token', widget='password', |
| 18 | + description="Auth token of the Twilio account. Your account's auth token can be found in the console.", required=True, |
| 19 | + ) |
| 20 | + phone_numbers: List[str] = Field( |
| 21 | + title='Phone Numbers', description='Phone numbers to send SMS messages from.', required=True, |
| 22 | + ) |
| 23 | + auto_create_sms_webhook: bool = Field(default=False, title='Auto Create SMS Webhook', |
| 24 | + description='Automatically create an SMS webhook for the phone numbers.', required=False, |
| 25 | + ) |
| 26 | + |
| 27 | + |
| 28 | +class TwilioSmsApp(AppTypeInterface[TwilioSmsAppConfigSchema]): |
| 29 | + @staticmethod |
| 30 | + def slug() -> str: |
| 31 | + return 'twilio_sms' |
| 32 | + |
| 33 | + @staticmethod |
| 34 | + def name() -> str: |
| 35 | + return 'Twilio SMS App' |
| 36 | + |
| 37 | + @staticmethod |
| 38 | + def description() -> str: |
| 39 | + return 'Send SMS messages from Twilio.' |
| 40 | + |
| 41 | + @classmethod |
| 42 | + def pre_save(self, app: App): |
| 43 | + if app.is_published and app.twilio_config: |
| 44 | + config = app.twilio_config |
| 45 | + phone_numbers = config.get('phone_numbers', []) |
| 46 | + |
| 47 | + account_sid = config.get('account_sid', None) |
| 48 | + auth_token = config.get('auth_token', None) |
| 49 | + auto_create_sms_webhook = config.get( |
| 50 | + 'auto_create_sms_webhook', False) |
| 51 | + |
| 52 | + if not phone_numbers: |
| 53 | + raise Exception( |
| 54 | + 'You must provide at least one phone number to send SMS messages from.') |
| 55 | + if not account_sid or not auth_token: |
| 56 | + raise Exception( |
| 57 | + 'You must provide an account SID and auth token to send SMS messages from.') |
| 58 | + |
| 59 | + headers = { |
| 60 | + 'Content-Type': 'application/x-www-form-urlencoded', |
| 61 | + 'Accept': 'application/json' |
| 62 | + } |
| 63 | + auth = (account_sid, auth_token) |
| 64 | + for phone_number in phone_numbers: |
| 65 | + ph_no = phone_number.strip().replace('+', '').replace('-', '') |
| 66 | + response = requests.get( |
| 67 | + f'https://api.twilio.com/2010-04-01/Accounts/{account_sid}/IncomingPhoneNumbers.json?PhoneNumber=%2B{ph_no}', |
| 68 | + headers=headers, auth=auth) |
| 69 | + if response.status_code != 200: |
| 70 | + raise Exception( |
| 71 | + f'Invalid phone number {phone_number}. Please provide a valid phone number that you own.') |
| 72 | + twilio_phone_number_resource = response.json()[ |
| 73 | + 'incoming_phone_numbers'][0] |
| 74 | + sms_url = twilio_phone_number_resource['sms_url'] |
| 75 | + # Create SMS webhook if it doesn't exist |
| 76 | + if auto_create_sms_webhook and (not sms_url or sms_url != f'https://trypromptly.com/api/apps/{app.uuid}/twiliosms/run'): |
| 77 | + # Update twilio phone number resource with voice webhook |
| 78 | + response = requests.post( |
| 79 | + f'https://api.twilio.com/2010-04-01/Accounts/{account_sid}/IncomingPhoneNumbers/{twilio_phone_number_resource["sid"]}.json', |
| 80 | + headers=headers, auth=auth, data={ |
| 81 | + 'SmsUrl': f'https://trypromptly.com/api/apps/{app.uuid}/twiliosms/run', |
| 82 | + }) |
| 83 | + if response.status_code != 200: |
| 84 | + raise Exception( |
| 85 | + f'Failed to update SMS webhook for phone number {phone_number}. Error: {response.text}') |
| 86 | + |
| 87 | + return app |
0 commit comments