From 3d33cb8282b2c413b57656e58093f8c321efac0b Mon Sep 17 00:00:00 2001 From: Mahmoud Osama Date: Sat, 3 May 2025 20:17:20 +0300 Subject: [PATCH] Add Slack notifcation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✨ Support Slack incoming webhook notifications with good looking block message design --- ntfy.py | 18 +++++-- send_slack.py | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 send_slack.py diff --git a/ntfy.py b/ntfy.py index 3c2bdce..420049c 100644 --- a/ntfy.py +++ b/ntfy.py @@ -20,6 +20,11 @@ from send_discord import ( docker_send_to_discord, ) +from send_slack import ( + github_send_to_slack, + docker_send_to_slack, +) + # Configuring the logger logging.basicConfig( level=logging.INFO, @@ -39,7 +44,7 @@ discord_webhook_url = os.environ.get("DISCORD_WEBHOOK_URL") def create_dockerhub_token(username, password): - url = "https://hub.docker.com//v2/users/login" + url = "https://hub.docker.com/v2/users/login" headers = {"Content-Type": "application/json"} data = json.dumps({"username": username, "password": password}) @@ -177,7 +182,7 @@ def get_changelog(repo): return latest_release_list["body"] return "Changelog not available" -def notify_all_services(github_latest_release, docker_latest_release, auth, ntfy_url, gotify_url, gotify_token, discord_webhook_url): +def notify_all_services(github_latest_release, docker_latest_release, auth, ntfy_url, gotify_url, gotify_token, discord_webhook_url, slack_webhook_url): threads = [] if ntfy_url: @@ -198,6 +203,12 @@ def notify_all_services(github_latest_release, docker_latest_release, auth, ntfy if docker_latest_release: threads.append(threading.Thread(target=docker_send_to_discord, args=(docker_latest_release, discord_webhook_url))) + if slack_webhook_url: + if github_latest_release: + threads.append(threading.Thread(target=github_send_to_slack, args=(github_latest_release, slack_webhook_url))) + if docker_latest_release: + threads.append(threading.Thread(target=docker_send_to_slack, args=(docker_latest_release, slack_webhook_url))) + for thread in threads: thread.start() @@ -215,6 +226,7 @@ if __name__ == "__main__": gotify_token = os.environ.get("GOTIFY_TOKEN") discord_webhook_url = os.environ.get("DISCORD_WEBHOOK_URL") timeout = float(os.environ.get("GHNTFY_TIMEOUT")) + slack_webhook_url = os.environ.get("SLACK_WEBHOOK_URL") if auth and (ntfy_url or gotify_url or discord_webhook_url): while True: @@ -223,7 +235,7 @@ if __name__ == "__main__": docker_watched_repos_list = get_docker_watched_repos() docker_latest_release = get_latest_docker_releases(docker_watched_repos_list) - notify_all_services(github_latest_release, docker_latest_release, auth, ntfy_url, gotify_url, gotify_token, discord_webhook_url) + notify_all_services(github_latest_release, docker_latest_release, auth, ntfy_url, gotify_url, gotify_token, discord_webhook_url, slack_webhook_url) time.sleep(timeout) else: diff --git a/send_slack.py b/send_slack.py new file mode 100644 index 0000000..3a7a7f5 --- /dev/null +++ b/send_slack.py @@ -0,0 +1,131 @@ +import requests +import sqlite3 +import logging + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", +) +logger = logging.getLogger(__name__) + +def get_db_connection(): + return sqlite3.connect("/github-ntfy/ghntfy_versions.db", check_same_thread=False) + +def github_send_to_slack(releases, webhook_url): + conn = get_db_connection() + cursor = conn.cursor() + for release in releases: + app_name = release["repo"].split("/")[-1] + version_number = release["tag_name"] + app_url = release["html_url"] + changelog = release["changelog"] + release_date = release["published_at"].replace("T", " ").replace("Z", "") + + cursor.execute("SELECT version FROM versions WHERE repo=?", (app_name,)) + previous_version = cursor.fetchone() + if previous_version and previous_version[0] == version_number: + logger.info(f"The version of {app_name} has not changed. No notification sent.") + continue + + message = f"šŸ“Œ *New version*: {version_number}\n\nšŸ“¦*For*: {app_name}\n\nšŸ“… *Published on*: {release_date}\n\nšŸ“ *Changelog*:\n\n```{changelog}```" + if len(message) > 2000: + message = f"šŸ“Œ *New version*: {version_number}\n\nšŸ“¦*For*: {app_name}\n\nšŸ“… *Published on*: {release_date}\n\nšŸ“ *Changelog*:\n\n `check url since the changelog is huge`" + + cursor.execute( + "INSERT OR REPLACE INTO versions (repo, version, changelog) VALUES (?, ?, ?)", + (app_name, version_number, changelog), + ) + conn.commit() + + + message = { + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": f"{message}" + }, + "accessory": { + "type": "button", + "text": { + "type": "plain_text", + "text": "šŸ”— Changelog url" + }, + "url": f"{app_url}", + "action_id": "button-action" + } + }, + { + "type": "divider" + } + ] + } + headers = { + "Content-Type": "application/json" + } + response = requests.post(webhook_url, json=message, headers=headers) + if response.status_code == 200: + logger.info(f"Message sent to Slack for {app_name}") + else: + logger.error(f"Failed to send message to Slack. Status code: {response.status_code}") + logger.error(f"Response: {response.text}") + conn.close() + +def docker_send_to_slack(releases, webhook_url): + conn = get_db_connection() + cursor = conn.cursor() + for release in releases: + app_name = release["repo"].split("/")[-1] + digest_number = release["digest"] + app_url = release["html_url"] + release_date = release["published_at"].replace("T", " ").replace("Z", "") + + cursor.execute("SELECT digest FROM docker_versions WHERE repo=?", (app_name,)) + previous_digest = cursor.fetchone() + if previous_digest and previous_digest[0] == digest_number: + logger.info(f"The digest of {app_name} has not changed. No notification sent.") + continue + + message = f"🐳 *Docker Image Updated!*\n\nšŸ” *New Digest*: `{digest_number}`\n\nšŸ“¦ *App*: {app_name}\n\n*Published*: {release_date}" + + cursor.execute( + "INSERT OR REPLACE INTO docker_versions (repo, digest) VALUES (?, ?)", + (app_name, digest_number), + ) + conn.commit() + + message = { + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": f"{message}" + }, + "accessory": { + "type": "button", + "text": { + "type": "plain_text", + "text": "šŸ”— Changelog url" + }, + "url": f"{app_url}", + "action_id": "button-action" + } + }, + { + "type": "divider" + } + ] + } + headers = { + "Content-Type": "application/json" + } + response = requests.post(webhook_url, json=message, headers=headers) + if 200 <= response.status_code < 300: + logger.info(f"Message sent to Slack for {app_name}") + else: + logger.error(f"Failed to send message to Slack. Status code: {response.status_code}") + logger.error(f"Response: {response.text}") + conn.close() +