blob: 2a794c564477f1730879651485b10e1104a660c6 [file] [log] [blame]
Philip Zeyliger9e3c8672025-05-27 17:09:14 +00001#!/usr/bin/env python3
2import json
3import os
4import subprocess
5import sys
6import urllib.request
7from datetime import datetime, timezone
8
9def validate_environment():
10 """Validate required environment variables."""
Philip Zeyliger9e3c8672025-05-27 17:09:14 +000011 if not os.environ.get('GITHUB_SHA'):
12 print("Error: GITHUB_SHA environment variable is required")
13 sys.exit(1)
Josh Bleecher Snyderfa306772025-05-28 09:37:04 -070014
Philip Zeyliger9e3c8672025-05-27 17:09:14 +000015 if not os.environ.get('GITHUB_REPOSITORY'):
16 print("Error: GITHUB_REPOSITORY environment variable is required")
17 sys.exit(1)
Josh Bleecher Snyderfa306772025-05-28 09:37:04 -070018
Philip Zeyliger9e3c8672025-05-27 17:09:14 +000019 if not os.environ.get('DISCORD_WEBHOOK_FOR_COMMITS'):
20 print("Error: DISCORD_WEBHOOK_FOR_COMMITS environment variable is required")
21 sys.exit(1)
22
Philip Zeyligercbf8c322025-07-16 15:25:39 -070023def get_commit_info():
Philip Zeyliger9e3c8672025-05-27 17:09:14 +000024 """Extract commit information using git commands."""
25 try:
26 # Get commit message (subject line)
27 commit_message = subprocess.check_output(
Philip Zeyligercbf8c322025-07-16 15:25:39 -070028 ['git', 'log', '-1', '--pretty=format:%s'],
Philip Zeyliger9e3c8672025-05-27 17:09:14 +000029 text=True, stderr=subprocess.DEVNULL
30 ).strip()
Josh Bleecher Snyderfa306772025-05-28 09:37:04 -070031
32 # Get commit body (description)
33 commit_body = subprocess.check_output(
Philip Zeyligercbf8c322025-07-16 15:25:39 -070034 ['git', 'log', '-1', '--pretty=format:%b'],
Josh Bleecher Snyderfa306772025-05-28 09:37:04 -070035 text=True, stderr=subprocess.DEVNULL
36 ).strip()
37
Philip Zeyliger9e3c8672025-05-27 17:09:14 +000038 # Get commit author
39 commit_author = subprocess.check_output(
Philip Zeyligercbf8c322025-07-16 15:25:39 -070040 ['git', 'log', '-1', '--pretty=format:%an'],
Philip Zeyliger9e3c8672025-05-27 17:09:14 +000041 text=True, stderr=subprocess.DEVNULL
42 ).strip()
Josh Bleecher Snyderfa306772025-05-28 09:37:04 -070043
44 return commit_message, commit_body, commit_author
Philip Zeyliger9e3c8672025-05-27 17:09:14 +000045 except subprocess.CalledProcessError as e:
46 print(f"Failed to get commit information: {e}")
47 sys.exit(1)
48
Philip Zeyligerc3ecabb2025-06-06 22:29:03 +000049def truncate_text(text, max_length):
50 """Truncate text to fit within Discord's limits."""
51 if len(text) <= max_length:
52 return text
53 # Find a good place to cut off, preferably at a sentence or paragraph boundary
54 truncated = text[:max_length - 3] # Leave room for "..."
55
56 # Try to cut at paragraph boundary
57 last_double_newline = truncated.rfind('\n\n')
58 if last_double_newline > max_length // 2: # Only if we're not cutting too much
59 return truncated[:last_double_newline] + "\n\n..."
60
61 # Try to cut at sentence boundary
62 last_period = truncated.rfind('. ')
63 if last_period > max_length // 2: # Only if we're not cutting too much
64 return truncated[:last_period + 1] + " ..."
65
66 # Otherwise just truncate with ellipsis
67 return truncated + "..."
68
Philip Zeyliger9e3c8672025-05-27 17:09:14 +000069def main():
70 # Validate we're running in the correct environment
71 validate_environment()
Josh Bleecher Snyderfa306772025-05-28 09:37:04 -070072
Philip Zeyliger9e3c8672025-05-27 17:09:14 +000073 # Check for test mode
74 if os.environ.get('DISCORD_TEST_MODE') == '1':
75 print("Running in test mode - will not send actual webhook")
Josh Bleecher Snyderfa306772025-05-28 09:37:04 -070076
Philip Zeyligercbf8c322025-07-16 15:25:39 -070077 # Get commit information from git
78 commit_message, commit_body, commit_author = get_commit_info()
79
80 # Get remaining info from environment
81 github_sha = os.environ.get('GITHUB_SHA')
82 commit_sha = github_sha[:8]
83 commit_url = f"https://github.com/{os.environ.get('GITHUB_REPOSITORY')}/commit/{github_sha}"
84
85 # Create timestamp
86 timestamp = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.%fZ')[:-3] + 'Z'
87
88 # Truncate fields to fit Discord's limits
89 # Discord embed limits: title (256), description (4096), field value (1024)
90 title = truncate_text(commit_message, 256)
91 description = truncate_text(commit_body, 2000) # Use 2000 to be safe
Philip Zeyligerc3ecabb2025-06-06 22:29:03 +000092
Philip Zeyliger9e3c8672025-05-27 17:09:14 +000093 # Create Discord webhook payload
Philip Zeyligercbf8c322025-07-16 15:25:39 -070094 payload = {
95 "embeds": [
96 {
97 "title": title,
98 "description": description,
99 "color": 5814783,
100 "fields": [
101 {
102 "name": "Author",
103 "value": commit_author,
104 "inline": True
105 },
106 {
107 "name": "Commit",
108 "value": f"[{commit_sha}]({commit_url})",
109 "inline": True
110 },
111 ],
112 "timestamp": timestamp
113 }
114 ]
115 }
Josh Bleecher Snyderfa306772025-05-28 09:37:04 -0700116
Philip Zeyliger9e3c8672025-05-27 17:09:14 +0000117 # Convert to JSON
118 json_payload = json.dumps(payload)
Philip Zeyligerc3ecabb2025-06-06 22:29:03 +0000119
120 # Debug: print payload size info
121 if os.environ.get('DISCORD_TEST_MODE') == '1':
122 print(f"Payload size: {len(json_payload)} bytes")
123 print(f"Title length: {len(payload['embeds'][0]['title'])} chars")
124 print(f"Description length: {len(payload['embeds'][0]['description'])} chars")
Josh Bleecher Snyderfa306772025-05-28 09:37:04 -0700125
Philip Zeyliger9e3c8672025-05-27 17:09:14 +0000126 # Test mode - just print the payload
127 if os.environ.get('DISCORD_TEST_MODE') == '1':
128 print("Generated Discord payload:")
129 print(json.dumps(payload, indent=2))
130 print("✓ Test mode: payload generated successfully")
131 return
Josh Bleecher Snyderfa306772025-05-28 09:37:04 -0700132
Philip Zeyliger9e3c8672025-05-27 17:09:14 +0000133 # Send to Discord webhook
134 webhook_url = os.environ.get('DISCORD_WEBHOOK_FOR_COMMITS')
Josh Bleecher Snyderfa306772025-05-28 09:37:04 -0700135
Philip Zeyliger9e3c8672025-05-27 17:09:14 +0000136 req = urllib.request.Request(
137 webhook_url,
138 data=json_payload.encode('utf-8'),
Philip Zeyligerf1787552025-05-28 02:44:39 +0000139 headers={
140 'Content-Type': 'application/json',
141 'User-Agent': 'sketch.dev developers'
142 }
Philip Zeyliger9e3c8672025-05-27 17:09:14 +0000143 )
Josh Bleecher Snyderfa306772025-05-28 09:37:04 -0700144
Philip Zeyliger9e3c8672025-05-27 17:09:14 +0000145 try:
146 with urllib.request.urlopen(req) as response:
147 if response.status == 204:
Philip Zeyligercbf8c322025-07-16 15:25:39 -0700148 print("Discord notification sent successfully")
Philip Zeyliger9e3c8672025-05-27 17:09:14 +0000149 else:
150 print(f"Discord webhook returned status: {response.status}")
Philip Zeyligerf1787552025-05-28 02:44:39 +0000151 response_body = response.read().decode('utf-8')
152 print(f"Response body: {response_body}")
Philip Zeyliger9e3c8672025-05-27 17:09:14 +0000153 sys.exit(1)
154 except urllib.error.HTTPError as e:
155 print(f"Discord webhook HTTP error: {e.code} - {e.reason}")
156 try:
157 error_body = e.read().decode('utf-8')
158 print(f"Error details: {error_body}")
159 if e.code == 403 and 'error code: 1010' in error_body:
160 print("Error 1010: Webhook not found - the Discord webhook URL may be invalid or expired")
161 except:
162 pass
163 sys.exit(1)
164 except Exception as e:
165 print(f"Failed to send Discord notification: {e}")
166 sys.exit(1)
167
168if __name__ == "__main__":
169 main()