diff --git a/skills/plivo-sms/SKILL.md b/skills/plivo-sms/SKILL.md new file mode 100644 index 0000000..e3de4fc --- /dev/null +++ b/skills/plivo-sms/SKILL.md @@ -0,0 +1,50 @@ +--- +name: plivo-sms +description: Send SMS messages via the Plivo Messages API using Auth ID/Auth Token authentication. +--- + +# Plivo SMS Skill + +Send SMS messages via Plivo. + +## Setup + +1. **Create a Plivo account** and open the console at https://cx.plivo.com +2. **Get your credentials**: + - Copy your **Auth ID** and **Auth Token** from the dashboard + - Rent a Plivo phone number (or use an approved sender ID) to send from + +3. **Configure credentials**: + ```bash + export PLIVO_AUTH_ID="your-auth-id" + export PLIVO_AUTH_TOKEN="your-auth-token" + ``` + + Or create a `.env` file in the skill directory: + ``` + PLIVO_AUTH_ID=your-auth-id + PLIVO_AUTH_TOKEN=your-auth-token + ``` + +## Usage + +```bash +python3 scripts/send_sms.py \ + --from "+14150000002" \ + --to "+14150000001" \ + --text "Message body text" +``` + +Send to multiple recipients by joining numbers with `<`: + +```bash +python3 scripts/send_sms.py \ + --from "+14150000002" \ + --to "+14150000001<+14160000003" \ + --text "Message body text" +``` + +## Requirements + +- Python 3.6+ +- No additional packages needed (uses stdlib urllib) diff --git a/skills/plivo-sms/scripts/send_sms.py b/skills/plivo-sms/scripts/send_sms.py new file mode 100644 index 0000000..ee07c04 --- /dev/null +++ b/skills/plivo-sms/scripts/send_sms.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +""" +Send SMS via Plivo Messages API +""" +import argparse +import base64 +import json +import os +import sys +import urllib.error +import urllib.request +from pathlib import Path + +def load_env(): + """Load environment variables from .env file if it exists""" + env_file = Path(__file__).parent.parent / '.env' + if env_file.exists(): + with open(env_file) as f: + for line in f: + line = line.strip() + if not line or line.startswith('#') or '=' not in line: + continue + key, value = line.split('=', 1) + value = value.strip() + if len(value) >= 2 and value[0] == value[-1] and value[0] in ('"', "'"): + value = value[1:-1] + os.environ[key.strip()] = value + +def send_sms(from_number, to, text, auth_id=None, auth_token=None): + """Send SMS via Plivo Messages API""" + + # Get credentials + auth_id = auth_id or os.getenv('PLIVO_AUTH_ID') + auth_token = auth_token or os.getenv('PLIVO_AUTH_TOKEN') + + if not auth_id or not auth_token: + print("ERROR: Plivo credentials not found!", file=sys.stderr) + print("\nPlease set credentials using one of these methods:", file=sys.stderr) + print("\n1. Environment variables:", file=sys.stderr) + print(" export PLIVO_AUTH_ID='your-auth-id'", file=sys.stderr) + print(" export PLIVO_AUTH_TOKEN='your-auth-token'", file=sys.stderr) + print("\n2. Create a .env file in skills/plivo-sms/:", file=sys.stderr) + print(" PLIVO_AUTH_ID=your-auth-id", file=sys.stderr) + print(" PLIVO_AUTH_TOKEN=your-auth-token", file=sys.stderr) + print("\nFind both in the Plivo console at https://cx.plivo.com", file=sys.stderr) + sys.exit(1) + + # Build request + url = f"https://api.plivo.com/v1/Account/{auth_id}/Message/" + payload = json.dumps({"src": from_number, "dst": to, "text": text, "type": "sms"}).encode() + credentials = base64.b64encode(f"{auth_id}:{auth_token}".encode()).decode() + + request = urllib.request.Request(url, data=payload, method='POST') + request.add_header('Authorization', f"Basic {credentials}") + request.add_header('Content-Type', 'application/json') + + # Send SMS + try: + print(f"Sending SMS to {to}...", file=sys.stderr) + with urllib.request.urlopen(request) as response: + body = json.loads(response.read().decode()) + + uuids = body.get('message_uuid', []) + if not uuids: + print(f"ERROR: Plivo accepted the request but returned no message_uuid: {body}", file=sys.stderr) + return False + print(f"✓ SMS queued successfully (message_uuid: {', '.join(uuids)})") + return True + + except urllib.error.HTTPError as e: + error_body = e.read().decode() + print(f"ERROR: Plivo returned HTTP {e.code}", file=sys.stderr) + print(error_body, file=sys.stderr) + return False + except Exception as e: + print(f"ERROR: Failed to send SMS: {e}", file=sys.stderr) + return False + +def main(): + parser = argparse.ArgumentParser(description='Send SMS via Plivo Messages API') + parser.add_argument('--to', required=True, help='Recipient number in E.164 (join multiple with "<")') + parser.add_argument('--from', dest='from_number', required=True, help='Sender number or sender ID') + parser.add_argument('--text', required=True, help='Message body') + parser.add_argument('--auth-id', dest='auth_id', help='Plivo Auth ID (default: PLIVO_AUTH_ID env var)') + parser.add_argument('--auth-token', dest='auth_token', help='Plivo Auth Token (default: PLIVO_AUTH_TOKEN env var)') + + args = parser.parse_args() + + # Load .env file if exists + load_env() + + # Send SMS + success = send_sms( + from_number=args.from_number, + to=args.to, + text=args.text, + auth_id=args.auth_id, + auth_token=args.auth_token + ) + + sys.exit(0 if success else 1) + +if __name__ == '__main__': + main() diff --git a/skills/plivo-voice/SKILL.md b/skills/plivo-voice/SKILL.md new file mode 100644 index 0000000..b0296cf --- /dev/null +++ b/skills/plivo-voice/SKILL.md @@ -0,0 +1,49 @@ +--- +name: plivo-voice +description: Place outbound voice calls via the Plivo Call API using Auth ID/Auth Token authentication. +--- + +# Plivo Voice Skill + +Place outbound voice calls via Plivo. + +## Setup + +1. **Create a Plivo account** and open the console at https://cx.plivo.com +2. **Get your credentials**: + - Copy your **Auth ID** and **Auth Token** from the dashboard + - Rent a Plivo phone number to call from + +3. **Configure credentials**: + ```bash + export PLIVO_AUTH_ID="your-auth-id" + export PLIVO_AUTH_TOKEN="your-auth-token" + ``` + + Or create a `.env` file in the skill directory: + ``` + PLIVO_AUTH_ID=your-auth-id + PLIVO_AUTH_TOKEN=your-auth-token + ``` + +## Usage + +```bash +python3 scripts/make_call.py \ + --from "+14150000002" \ + --to "+14150000001" \ + --answer-url "https://example.com/answer.xml" +``` + +When the call is answered, Plivo fetches the answer URL and runs the Plivo XML it returns. To speak a message, have that URL return XML such as: + +```xml + + Your task has finished. + +``` + +## Requirements + +- Python 3.6+ +- No additional packages needed (uses stdlib urllib) diff --git a/skills/plivo-voice/scripts/make_call.py b/skills/plivo-voice/scripts/make_call.py new file mode 100644 index 0000000..a8373f6 --- /dev/null +++ b/skills/plivo-voice/scripts/make_call.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +""" +Make an outbound voice call via the Plivo Call API +""" +import argparse +import base64 +import json +import os +import sys +import urllib.error +import urllib.request +from pathlib import Path + +def load_env(): + """Load environment variables from .env file if it exists""" + env_file = Path(__file__).parent.parent / '.env' + if env_file.exists(): + with open(env_file) as f: + for line in f: + line = line.strip() + if not line or line.startswith('#') or '=' not in line: + continue + key, value = line.split('=', 1) + value = value.strip() + if len(value) >= 2 and value[0] == value[-1] and value[0] in ('"', "'"): + value = value[1:-1] + os.environ[key.strip()] = value + +def make_call(from_number, to, answer_url, answer_method='GET', auth_id=None, auth_token=None): + """Place an outbound call via the Plivo Call API""" + + # Get credentials + auth_id = auth_id or os.getenv('PLIVO_AUTH_ID') + auth_token = auth_token or os.getenv('PLIVO_AUTH_TOKEN') + + if not auth_id or not auth_token: + print("ERROR: Plivo credentials not found!", file=sys.stderr) + print("\nPlease set credentials using one of these methods:", file=sys.stderr) + print("\n1. Environment variables:", file=sys.stderr) + print(" export PLIVO_AUTH_ID='your-auth-id'", file=sys.stderr) + print(" export PLIVO_AUTH_TOKEN='your-auth-token'", file=sys.stderr) + print("\n2. Create a .env file in skills/plivo-voice/:", file=sys.stderr) + print(" PLIVO_AUTH_ID=your-auth-id", file=sys.stderr) + print(" PLIVO_AUTH_TOKEN=your-auth-token", file=sys.stderr) + print("\nFind both in the Plivo console at https://cx.plivo.com", file=sys.stderr) + sys.exit(1) + + # Build request + url = f"https://api.plivo.com/v1/Account/{auth_id}/Call/" + payload = json.dumps({ + "from": from_number, + "to": to, + "answer_url": answer_url, + "answer_method": answer_method, + }).encode() + credentials = base64.b64encode(f"{auth_id}:{auth_token}".encode()).decode() + + request = urllib.request.Request(url, data=payload, method='POST') + request.add_header('Authorization', f"Basic {credentials}") + request.add_header('Content-Type', 'application/json') + + # Place call + try: + print(f"Placing call to {to}...", file=sys.stderr) + with urllib.request.urlopen(request) as response: + body = json.loads(response.read().decode()) + + request_uuid = body.get('request_uuid') + if not request_uuid: + print(f"ERROR: Plivo accepted the request but returned no request_uuid: {body}", file=sys.stderr) + return False + print(f"✓ Call placed successfully (request_uuid: {request_uuid})") + return True + + except urllib.error.HTTPError as e: + error_body = e.read().decode() + print(f"ERROR: Plivo returned HTTP {e.code}", file=sys.stderr) + print(error_body, file=sys.stderr) + return False + except Exception as e: + print(f"ERROR: Failed to place call: {e}", file=sys.stderr) + return False + +def main(): + parser = argparse.ArgumentParser(description='Make an outbound voice call via the Plivo Call API') + parser.add_argument('--to', required=True, help='Recipient number in E.164') + parser.add_argument('--from', dest='from_number', required=True, help='Caller ID (a Plivo number)') + parser.add_argument('--answer-url', dest='answer_url', required=True, + help='URL returning Plivo XML for the call (e.g. a response)') + parser.add_argument('--answer-method', dest='answer_method', default='GET', + help='HTTP method Plivo uses to fetch the answer URL (default: GET)') + parser.add_argument('--auth-id', dest='auth_id', help='Plivo Auth ID (default: PLIVO_AUTH_ID env var)') + parser.add_argument('--auth-token', dest='auth_token', help='Plivo Auth Token (default: PLIVO_AUTH_TOKEN env var)') + + args = parser.parse_args() + + # Load .env file if exists + load_env() + + # Place call + success = make_call( + from_number=args.from_number, + to=args.to, + answer_url=args.answer_url, + answer_method=args.answer_method, + auth_id=args.auth_id, + auth_token=args.auth_token + ) + + sys.exit(0 if success else 1) + +if __name__ == '__main__': + main()