Last active
September 14, 2023 12:22
-
-
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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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("&", "&").replace("<", "<").replace(">", ">").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"<!--.*?-->", "", 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