Skip to content

Instantly share code, notes, and snippets.

@angelolloqui
Last active September 14, 2023 12:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save angelolloqui/43b9fa007e705d3adef67475b0b4befb to your computer and use it in GitHub Desktop.
Save angelolloqui/43b9fa007e705d3adef67475b0b4befb to your computer and use it in GitHub Desktop.
Github API offers a automated release notes generation. This script parses the output of that call and transforms it to a compatible Slack format
# This script transforms a GitHub release json into a Slack message. It takes the content from the body and applies several transformations to make it look better in Slack.
# Usage:
# python3 format_github_release_to_slack.py [-pre-text <pre-text>] [-channel <channel>]
# -pre-text <pre-text>: Optional - text to be added as a first message. It accepts Slack markdown format.
# -channel <channel>: Optional - name of channel, for example #general
#
# Example using a previous message as input:
# `cat github_release.json | python3 scripts/format_github_release_to_slack.py "New Android release 5.74.0-RC1 available"`
#
# The output is printed in the stdout, so it can be piped to slack message sender like:
# `cat github_release.json | python3 scripts/format_github_release_to_slack.py "New Android release 5.74.0-RC1 available" | curl -L -X POST -H "Content-type: application/json" https://hooks.slack.com/services/... -d @-`
import json
import sys
import json
import re
import argparse
# Read the input JSON from stdin
try:
input_json_str = sys.stdin.read()
original_json = json.loads(input_json_str)
except json.JSONDecodeError as e:
sys.stderr.write(f"Error parsing input JSON: {e}")
sys.exit(1)
# Extract the "body" from the original JSON and split it into chunks escaping the special characters, and limiting to 200 lines
body_chunks = original_json["body"].replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").split("\n")[:200]
blocks = []
empty_block = {
"type": "section",
"text": {
"text": "\n",
"type": "mrkdwn"
}
}
# Create a block in Slack format for each chunk
for chunk in body_chunks:
if not chunk:
blocks.append(empty_block)
if chunk.startswith("###"):
blocks.append(empty_block)
block = {
"type": "section",
"text": {
"text": f"*{chunk.replace('#', '')}*",
"type": "mrkdwn"
}
}
blocks.append(block)
blocks.append({"type": "divider"})
elif chunk.startswith("##") or chunk.startswith("#"):
block = {
"type": "header",
"text": {
"text": f"{chunk.replace('#', '', 2)[:150]}",
"type": "plain_text"
}
}
blocks.append(block)
else:
# Remove commented lines
chunk = re.sub(r"&lt;!--.*?--&gt;", "", chunk, flags=re.DOTALL)
# Extract the pull request number from the URL using regular expressions
match = re.search(r'https://github\.com/[^/]+/[^/]+/pull/(\d+)', chunk)
if match:
pull_request_number = match.group(1)
# Replace the final URL with the desired format
chunk = chunk.replace(match.group(0), f'<{match.group(0)}|#{pull_request_number}>')
# Replace bold (**) with single * for Slack
chunk = re.sub(r'\*\*(.*?)\*\*', r'*\1*', chunk)
# Change the * by the list in slack with -
if chunk.startswith('* '):
chunk = chunk.replace('*', '• ', 1)
if chunk:
block = {
"type": "section",
"text": {
"text": f"{chunk}",
"type": "mrkdwn"
}
}
blocks.append(block)
# Second pass to group sections that are not separated by a divider, header or just an empty section. Split them if more than 4000 characters too
merged_blocks = []
merge_block= None
for block in blocks:
if block.get("type") == "divider" or block.get("type") == "header" or block==empty_block:
if merge_block is not None:
merged_blocks.append(merge_block)
merge_block = None
merged_blocks.append(block)
else:
if merge_block is None:
merge_block = block
else:
new_text = merge_block["text"]["text"] + "\n" + block["text"]["text"]
# Can not merge if more than 3000 characters (slack limit)
if len(new_text) >= 3000:
merged_blocks.append(merge_block)
merge_block = block
else:
merge_block["text"]["text"] = new_text
if merge_block is not None:
merged_blocks.append(merge_block)
# Parse arguments with extra parameter
parser = argparse.ArgumentParser(description="Parse multiple argument parameters")
parser.add_argument("-channel", dest="channel", type=str, help="Optional. Channel value. Example: #general")
parser.add_argument("-pre-text", dest="pre_text", type=str, help="Optional. Text to be added at the top of the message. Accepts markdown")
args = parser.parse_args()
# Append the pre-text as a block if provided
if args.pre_text:
merged_blocks.insert(0, {
"type": "section",
"text": {
"text": f"{args.pre_text}",
"type": "mrkdwn"
}
})
# Trim blocks to a maximum of 50 (slack limit)
merged_blocks = merged_blocks[:50]
# Create the final formatted JSON
formatted_json = {
"text": "",
"blocks": merged_blocks
}
# Append the channel if provided
if args.channel:
formatted_json["channel"] = args.channel
# Convert the formatted JSON dictionary to a JSON-formatted string
formatted_json_str = json.dumps(formatted_json, indent=4)
# Print the formatted JSON to stdout
print(formatted_json_str)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment