diff --git a/plivo/1.0.0/Dockerfile b/plivo/1.0.0/Dockerfile new file mode 100644 index 00000000..9bbc5110 --- /dev/null +++ b/plivo/1.0.0/Dockerfile @@ -0,0 +1,27 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app +RUN apk add curl + +# Install any binary dependencies needed in our final image +# RUN apk --no-cache add --update my_binary_dependency + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/plivo/1.0.0/README.md b/plivo/1.0.0/README.md new file mode 100644 index 00000000..7813d0a2 --- /dev/null +++ b/plivo/1.0.0/README.md @@ -0,0 +1,22 @@ +# Plivo + +An app for sending SMS through the Plivo Messages API. Use it in a workflow to text an on-call responder when a monitor fires, a case is created, or an alert changes state. + +## Requirements +A Plivo account with an Auth ID and Auth Token, and a sender (a Plivo phone number, short code, or where allowed an alphanumeric sender). Find your Auth ID and Auth Token in the Plivo console at https://cx.plivo.com. + +## Authentication +1. url - the Messages API URL for your account, `https://api.plivo.com/v1/Account/YOUR_AUTH_ID/Message/` +2. username - your Plivo Auth ID +3. password - your Plivo Auth Token + +## Actions +1. Send_SMS - sends an SMS through the Plivo Messages API. + - From - the sender ID + - To - one receiver, or a comma separated list of receivers sent as a single request + - body - the message text + +## Notes +- A successful call means the message is queued, not yet delivered. Delivery status arrives on a separate callback URL if you configure one. +- Each call sends and bills a new message, so guard against accidental retries in a loop. +- The action returns a summary. The raw Plivo response, including message_uuid, is under results[0].body. diff --git a/plivo/1.0.0/api.yaml b/plivo/1.0.0/api.yaml new file mode 100644 index 00000000..765b2d3c --- /dev/null +++ b/plivo/1.0.0/api.yaml @@ -0,0 +1,108 @@ +walkoff_version: 1.0.0 +app_version: 1.0.0 +name: plivo +description: Send SMS from Shuffle through Plivo. +tags: + - Communication + - SMS +categories: + - Communication +contact_info: + name: "Sarvesh Patil" + url: https://github.com/sarveshpatil-plivo + email: "sarvesh.patil@plivo.com" +authentication: + required: true + parameters: + - name: url + description: Plivo Messages API URL for your account. + example: "https://api.plivo.com/v1/Account/YOUR_AUTH_ID/Message/" + required: true + schema: + type: string + - name: username + description: Your Plivo Auth ID + multiline: false + required: true + example: "Auth ID" + schema: + type: string + - name: password + description: Your Plivo Auth Token + multiline: false + required: true + example: "*****" + schema: + type: string + - name: headers + description: Headers to use + multiline: true + required: false + example: "Content-Type: application/json" + schema: + type: string +actions: + - name: Send_SMS + description: Sends an SMS through the Plivo Messages API. Each call sends and bills a new message. + parameters: + - name: url + description: Plivo Messages API URL for your account + multiline: false + example: "https://api.plivo.com/v1/Account/YOUR_AUTH_ID/Message/" + required: true + schema: + type: string + - name: headers + description: Headers to use + multiline: true + required: false + example: "Content-Type: application/json" + schema: + type: string + - name: username + description: Your Plivo Auth ID + multiline: false + required: true + example: "Auth ID" + schema: + type: string + - name: password + description: Your Plivo Auth Token + multiline: false + required: true + example: "*****" + schema: + type: string + - name: timeout + description: Add a timeout (in seconds) for the request + multiline: false + required: false + example: "10" + schema: + type: string + - name: body + description: The message to send. + multiline: true + example: "Your monitored service is back up." + required: true + schema: + type: string + - name: From + description: The sender ID, a Plivo phone number, short code, or where allowed an alphanumeric text sender. + multiline: false + example: "+14150000002" + required: true + schema: + type: string + - name: To + description: The receiver phone number, or a comma separated list of numbers, sent as one Plivo request. + multiline: false + example: "+14150000001,+14150000003" + required: true + schema: + type: string + returns: + schema: + type: string + example: '{"success": true, "status": "200", "url": "https://api.plivo.com/v1/Account/YOUR_AUTH_ID/Message/", "results": [{"success": true, "status": 202, "url": "https://api.plivo.com/v1/Account/YOUR_AUTH_ID/Message/", "headers": {}, "body": {"message": "message(s) queued", "message_uuid": ["db3ce55a-7f1d-11e1-8ea7-1231380bc196"], "api_id": "db342550-7f1d-11e1-8ea7-1231380bc196"}, "cookies": {}}]}' +large_image: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABmJLR0QA/wD/AP+gvaeTAAAFLUlEQVR4nO3be4hUVRzA8e+uq2aZEUJGplhhJJRgFGlZgpJWaoUEvahIKKJ/JIiSHhD91T8R9qD3w4L6J3rZy6gMekgmFEW0ZtIDK60kpNXSdXf648yw4+w5Z+/szo47y/cDF3TOOb9779zfPefec2ZBkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJUvO0NXl/64GNwGPAjiHEGQ9sBg6v+bwbOA/4o/z/NuBG4CqgF3gOWDuE/WqIeoESsI9wMWYPMs6kcpzYdnJVvbsi5asGuU8N0RjiF+wTYDn19UZFEqAd2B0p/32I56FBGkf6opWALYS7c0KBWEUS4OhEeQ9hCFGTTSCfAJVtB3APMDkTq+gQ8EOk/MsGnY/qNJFiCVDZ/gWeB06JxCqaAEuAvVVl/wDnNva0VNRY4CvqS4IScAB4GZhbFatoAgDMBG4HbgVOaPxpqR5thLvyPepPhF5gTjlOPQkwkowHTgUWA8uA04GjDukRNdF1hLuxYjbwOKGrL5oEl5XbFk2Aj4FtNduWquNYGynfBnxI+q1kLGEuI9bu6USbxcArQFfkeLuBDcD1QEeifcubAHQCj0TKphAe+nbR+ATYm6gzv1x+XybOfOKWZtrcXVN3OuHiFk3w74FzEvttWVOAN+nrxlcT7qJaEwmzdp00LwFOJLwWxurEkhXgxUT9/8rnWjGPMCNZ71C3D1iZ2HfLmQf8Rv+T/IiDv6xqCxNthiMBIExPx+r8Sf9EPYJ4N14CnqyqdxKDu/iVrQe4NPH9tJxJwEv0jXc30X98bSfMBn5Gc4cAgBWZWBfVHOc1mbqVae0xhLmGwV78yrab9E3SctoJizf313w+HrgW+I5iX8pwJEAHsD1R74Wa4303UW99VZ2VA5zDRuAO4BbgNdJDUAl4glHkSmBG+d+TCQ9MO6nvrlhebt/IBAC4N1Gvi9DtAxxD6MFi9S6oivV55thiC1GLSA8re6v2PypMA9aQPuHc9inhQREanwDTCBNOsbpXlOusSpR30jekTaVvxbN2yy1D35w5nxWZdi0n94Qf23qBdwgPhtUanQAAbyTqvl4u35Qov6EqxqLMcZ2R+V4OI31T3Jlp13KKvOuXCOPiOuDMRJzhSIDU+/1+4Czid/YuDv5RytWZ45pI3teJdg8P0G5I2ocz+CB0AQ8S5uyXA180cd/vAj9HPh9LeBiMzQw+Skiyiq5M/EkD7D81JZyLOWQjJQF2Ep6MpxPG2l8OwTH0AE8lymZGPusmJEC1XzPxl2bKTiOce8z2TLuWkxoCltUZZziGAIBjCV1+kWEq9lDXAfyVqL+9HL/WOMLEWGo/s5LfQgOMlB6g+1AfQNkOwsNgEQ9FPjtAWPSJmUqY6LqEvhnGucAHwIJEm28IcySjRqoHWFJnnOHqAQDOz8SubBsy7Wcw8Apnd4E6JeDi7LfQACOlBxhJ3ge2DlDngUzZT4TVzZwOwqtfzqsU741aRiv0AAC3ZeJvZeAbpw14NhNjoG0zTZoBtAeIe4awvBuzhjAnkFMirAmsLlC31jrChNKeOtu1hNQT8uI64xyZiFPi4Fe2PYk6RX50EVv3/5uBJ3RqnU2xH4V0ApfT5L/Wavafhi0gLKpU6wXeJjwU1eNC+l+M/YQ7qHLXLaT/z8t7gLcIP7zIOY7+ifIjoXsejFmE1905wPGEZeOdwLeE899UPjZJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJo9b/qNt0VLkK4aMAAAAASUVORK5CYII= diff --git a/plivo/1.0.0/requirements.txt b/plivo/1.0.0/requirements.txt new file mode 100644 index 00000000..bd6f2345 --- /dev/null +++ b/plivo/1.0.0/requirements.txt @@ -0,0 +1 @@ +requests==2.32.4 diff --git a/plivo/1.0.0/src/app.py b/plivo/1.0.0/src/app.py new file mode 100644 index 00000000..76dc7707 --- /dev/null +++ b/plivo/1.0.0/src/app.py @@ -0,0 +1,181 @@ +import json +import requests + +from walkoff_app_sdk.app_base import AppBase + +class PLIVO(AppBase): + __version__ = "1.0.0" + app_name = "plivo" + + def __init__(self, redis, logger, console_logger=None): + print("INIT") + """ + Each app should have this __init__ to set up Redis and logging. + :param redis: + :param logger: + :param console_logger: + """ + super().__init__(redis, logger, console_logger) + + def splitheaders(self, headers): + parsed_headers = {} + if headers: + split_headers = headers.split("\n") + self.logger.info(split_headers) + for header in split_headers: + if ": " in header: + splititem = ": " + elif ":" in header: + splititem = ":" + elif "= " in header: + splititem = "= " + elif "=" in header: + splititem = "=" + else: + self.logger.info("Skipping header %s as its invalid" % header) + continue + + splitheader = header.split(splititem) + if len(splitheader) == 2: + parsed_headers[splitheader[0]] = splitheader[1] + else: + self.logger.info("Skipping header %s with split %s cus only one item" % (header, splititem)) + continue + + return parsed_headers + + def fix_url(self, url): + # Random bugs seen by users + if "hhttp" in url: + url = url.replace("hhttp", "http") + + if "http:/" in url and not "http://" in url: + url = url.replace("http:/", "http://", -1) + if "https:/" in url and not "https://" in url: + url = url.replace("https:/", "https://", -1) + if "http:///" in url: + url = url.replace("http:///", "http://", -1) + if "https:///" in url: + url = url.replace("https:///", "https://", -1) + if not "http://" in url and not "http" in url: + url = f"http://{url}" + + return url + + def return_file(self, requestdata): + filedata = { + "filename": "response.txt", + "data": requestdata, + } + fileret = self.set_files([filedata]) + if len(fileret) == 1: + return {"success": True, "file_id": fileret[0]} + + return fileret + + def prepare_response(self, request): + try: + parsedheaders = {} + for key, value in request.headers.items(): + parsedheaders[key] = value + + cookies = {} + if request.cookies: + for key, value in request.cookies.items(): + cookies[key] = value + + + jsondata = request.text + try: + jsondata = json.loads(jsondata) + except (ValueError, TypeError): + pass + + return { + "success": 200 <= int(request.status_code) < 300, + "status": request.status_code, + "url": request.url, + "headers": parsedheaders, + "body": jsondata, + "cookies":cookies, + } + except Exception as e: + print(f"[WARNING] Failed in request: {e}") + return { + "success": False, + "status": "XXX", + "error": request.text + } + + + def summarize_responses(self, one_response, summary): + summary["results"].append(one_response) + + if not one_response["success"]: + summary["success"] = False + + summary["status"] = one_response["status"] + + return summary + + + def Send_SMS(self, url, headers="", username="", password="", body="", From="", To="", timeout=5): + url = self.fix_url(url) + + parsed_headers = self.splitheaders(headers) + parsed_headers["User-Agent"] = "Shuffle Automation" + + auth = None + if (username or password) and "Authorization" not in parsed_headers: + auth = requests.auth.HTTPBasicAuth(username, password) + + if not timeout: + timeout = 5 + else: + try: + timeout = int(timeout) + except (ValueError, TypeError): + timeout = 5 + if timeout <= 0: + timeout = 5 + + summary = { + "success": True, + "status": None, + "url": url, + "results": [] + } + + # Plivo takes every receiver in one request, joined with "<" + dst = "<".join(receiver.strip() for receiver in To.split(",") if receiver.strip()) + if not dst: + empty = {"success": False, "status": "400", "error": "No valid recipient in 'To'"} + return json.dumps(self.summarize_responses(empty, summary)) + + payload = {"src": From, "dst": dst, "text": body, "type": "sms"} + + try: + request = requests.post(url, headers=parsed_headers, auth=auth, json=payload, timeout=timeout) + response = self.prepare_response(request) + except Exception as e: + response = {"success": False, "status": "XXX", "error": str(e)} + summary = self.summarize_responses(response, summary) + + return json.dumps(summary) + + +# Run the actual thing after we've checked params +def run(request): + print("Starting cloud!") + action = request.get_json() + print(action) + print(type(action)) + + if action and "name" in action and "app_name" in action: + PLIVO.run(action) + return f'Attempting to execute function {action["name"]} in app {action["app_name"]}' + else: + return f'Invalid action' + +if __name__ == "__main__": + PLIVO.run()