|
| 1 | +# Copyright 2023 Google LLC |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +# you may not use this file except in compliance with the License. |
| 5 | +# You may obtain a copy of the License at |
| 6 | +# |
| 7 | +# https://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | + |
| 15 | +import pprint |
| 16 | + |
| 17 | +# [START v2import] |
| 18 | +from firebase_functions import params |
| 19 | +from firebase_functions.alerts import ( |
| 20 | + app_distribution_fn, |
| 21 | + crashlytics_fn, |
| 22 | + performance_fn, |
| 23 | +) |
| 24 | +# [END v2import] |
| 25 | + |
| 26 | +import requests |
| 27 | + |
| 28 | +DISCORD_WEBHOOK_URL = params.SecretParam("DISCORD_WEBHOOK_URL") |
| 29 | + |
| 30 | + |
| 31 | +def post_message_to_discord(bot_name: str, message_body: str, |
| 32 | + webhook_url: str) -> requests.Response: |
| 33 | + """Posts a message to Discord with Discord's Webhook API. |
| 34 | +
|
| 35 | + Params: |
| 36 | + bot_name: The bot username to display |
| 37 | + message_body: The message to post (Discord Markdown) |
| 38 | + """ |
| 39 | + if webhook_url == "": |
| 40 | + raise EnvironmentError( |
| 41 | + "No webhook URL found. Set the Discord Webhook URL before deploying. " |
| 42 | + "Learn more about Discord webhooks here: " |
| 43 | + "https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks" |
| 44 | + ) |
| 45 | + |
| 46 | + return requests.post( |
| 47 | + url=webhook_url, |
| 48 | + json={ |
| 49 | + # Here's what the Discord API supports in the payload: |
| 50 | + # https://discord.com/developers/docs/resources/webhook#execute-webhook-jsonform-params |
| 51 | + "username": bot_name, |
| 52 | + "content": message_body, |
| 53 | + }, |
| 54 | + ) |
| 55 | + |
| 56 | + |
| 57 | +# [START v2Alerts] |
| 58 | +# [START v2CrashlyticsAlertTrigger] |
| 59 | +@crashlytics_fn.on_new_fatal_issue_published(secrets=["DISCORD_WEBHOOK_URL"]) |
| 60 | +def post_fatal_issue_to_discord( |
| 61 | + event: crashlytics_fn.CrashlyticsNewFatalIssueEvent) -> None: |
| 62 | + """Publishes a message to Discord whenever a new Crashlytics fatal issue occurs.""" |
| 63 | +# [END v2CrashlyticsAlertTrigger] |
| 64 | + # [START v2CrashlyticsEventPayload] |
| 65 | + # Construct a helpful message to send to Discord. |
| 66 | + app_id = event.app_id |
| 67 | + issue = event.data.payload.issue |
| 68 | + message = f""" |
| 69 | +🚨 New fatal issue for {app_id} in version {issue.app_version} 🚨 |
| 70 | +
|
| 71 | +# {issue.title} |
| 72 | +
|
| 73 | +{issue.subtitle} |
| 74 | +
|
| 75 | +ID: `{issue.id}` |
| 76 | +""".strip() |
| 77 | + # [END v2CrashlyticsEventPayload] |
| 78 | + |
| 79 | + try: |
| 80 | + # [START v2SendToDiscord] |
| 81 | + response = post_message_to_discord("Crashlytics Bot", message, |
| 82 | + DISCORD_WEBHOOK_URL.value()) |
| 83 | + if response.ok: |
| 84 | + print( |
| 85 | + f"Posted fatal Crashlytics alert {issue.id} for {app_id} to Discord." |
| 86 | + ) |
| 87 | + pprint.pp(event.data.payload) |
| 88 | + else: |
| 89 | + response.raise_for_status() |
| 90 | + # [END v2SendToDiscord] |
| 91 | + except (EnvironmentError, requests.HTTPError) as error: |
| 92 | + print( |
| 93 | + f"Unable to post fatal Crashlytics alert {issue.id} for {app_id} to Discord.", |
| 94 | + error, |
| 95 | + ) |
| 96 | + |
| 97 | + |
| 98 | +# [START v2AppDistributionAlertTrigger] |
| 99 | +@app_distribution_fn.on_new_tester_ios_device_published( |
| 100 | + secrets=["DISCORD_WEBHOOK_URL"]) |
| 101 | +def post_new_udid_to_discord( |
| 102 | + event: app_distribution_fn.NewTesterDeviceEvent) -> None: |
| 103 | + """Publishes a message to Discord whenever someone registers a new iOS test device.""" |
| 104 | +# [END v2AppDistributionAlertTrigger] |
| 105 | + # [START v2AppDistributionEventPayload] |
| 106 | + # Construct a helpful message to send to Discord. |
| 107 | + app_id = event.app_id |
| 108 | + app_dist = event.data.payload |
| 109 | + message = f""" |
| 110 | +📱 New iOS device registered by {app_dist.tester_name} <{app_dist.tester_email}> for {app_id} |
| 111 | +
|
| 112 | +UDID **{app_dist.device_id}** for {app_dist.device_model} |
| 113 | +""".strip() |
| 114 | + # [END v2AppDistributionEventPayload] |
| 115 | + |
| 116 | + try: |
| 117 | + # [START v2SendNewTesterIosDeviceToDiscord] |
| 118 | + response = post_message_to_discord("App Distro Bot", message, |
| 119 | + DISCORD_WEBHOOK_URL.value()) |
| 120 | + if response.ok: |
| 121 | + print( |
| 122 | + f"Posted iOS device registration alert for {app_dist.tester_email} to Discord." |
| 123 | + ) |
| 124 | + pprint.pp(event.data.payload) |
| 125 | + else: |
| 126 | + response.raise_for_status() |
| 127 | + # [END v2SendNewTesterIosDeviceToDiscord] |
| 128 | + except (EnvironmentError, requests.HTTPError) as error: |
| 129 | + print( |
| 130 | + f"Unable to post iOS device registration alert for {app_dist.tester_email} to Discord.", |
| 131 | + error, |
| 132 | + ) |
| 133 | + |
| 134 | + |
| 135 | +# [START v2PerformanceAlertTrigger] |
| 136 | +@performance_fn.on_threshold_alert_published(secrets=["DISCORD_WEBHOOK_URL"]) |
| 137 | +def post_performance_alert_to_discord( |
| 138 | + event: performance_fn.PerformanceThresholdAlertEvent) -> None: |
| 139 | + """Publishes a message to Discord whenever a performance threshold alert is fired.""" |
| 140 | +# [END v2PerformanceAlertTrigger] |
| 141 | + # [START v2PerformanceEventPayload] |
| 142 | + # Construct a helpful message to send to Discord. |
| 143 | + app_id = event.app_id |
| 144 | + perf = event.data.payload |
| 145 | + message = f""" |
| 146 | +⚠️ Performance Alert for {perf.metric_type} of {perf.event_type}: **{perf.event_name}** ⚠️ |
| 147 | +
|
| 148 | +App ID: {app_id} |
| 149 | +Alert condition: {perf.threshold_value} {perf.threshold_unit} |
| 150 | +Percentile (if applicable): {perf.condition_percentile} |
| 151 | +App version (if applicable): {perf.app_version} |
| 152 | +
|
| 153 | +Violation: {perf.violation_value} {perf.violation_unit} |
| 154 | +Number of samples checked: {perf.num_samples} |
| 155 | +
|
| 156 | +**Investigate more:** {perf.investigate_uri} |
| 157 | +""".strip() |
| 158 | + # [END v2PerformanceEventPayload] |
| 159 | + |
| 160 | + try: |
| 161 | + # [START v2SendPerformanceAlertToDiscord] |
| 162 | + response = post_message_to_discord("App Performance Bot", message, |
| 163 | + DISCORD_WEBHOOK_URL.value()) |
| 164 | + if response.ok: |
| 165 | + print( |
| 166 | + f"Posted Firebase Performance alert {perf.event_name} to Discord." |
| 167 | + ) |
| 168 | + pprint.pp(event.data.payload) |
| 169 | + else: |
| 170 | + response.raise_for_status() |
| 171 | + # [END v2SendPerformanceAlertToDiscord] |
| 172 | + except (EnvironmentError, requests.HTTPError) as error: |
| 173 | + print( |
| 174 | + f"Unable to post Firebase Performance alert {perf.event_name} to Discord.", |
| 175 | + error, |
| 176 | + ) |
| 177 | +# [END v2Alerts] |
0 commit comments