import os, requests, time, base64
DOMAIN, EMAIL = os.environ["JIRA_DOMAIN"], os.environ["JIRA_EMAIL"]
TOKEN, MV = os.environ["JIRA_API_TOKEN"], os.environ["MAVERA_API_KEY"]
JB, MB = f"https://{DOMAIN}.atlassian.net/rest/api/3", "https://app.mavera.io/api/v1"
cred = base64.b64encode(f"{EMAIL}:{TOKEN}".encode()).decode()
JH = {"Authorization": f"Basic {cred}", "Content-Type": "application/json"}
MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
r = requests.post(f"{JB}/search", headers=JH, json={
"jql": 'project = PROJ AND labels = "customer-reported" AND comment IS NOT EMPTY ORDER BY updated DESC',
"startAt": 0, "maxResults": 30, "fields": ["summary"],
})
if r.status_code == 429:
time.sleep(int(r.headers.get("Retry-After", 30)))
r.raise_for_status()
issues = r.json().get("issues", [])
print(f"Found {len(issues)} issues with comments")
all_comments = []
for iss in issues:
time.sleep(0.5)
cr = requests.get(f"{JB}/issue/{iss['key']}/comment", headers=JH,
params={"maxResults": 20, "orderBy": "-created"})
if cr.status_code == 429:
time.sleep(int(cr.headers.get("Retry-After", 30)))
continue
for c in cr.json().get("comments", []):
text = ""
if c.get("body") and c["body"].get("content"):
for block in c["body"]["content"]:
for item in block.get("content", []):
if item.get("text"):
text += item["text"] + " "
if len(text) > 20:
all_comments.append(f"[{iss['key']}: {iss['fields']['summary']}]\n"
f"By {c.get('author',{}).get('displayName','Unknown')}:\n{text.strip()[:500]}")
print(f"Extracted {len(all_comments)} comments")
bv = requests.post(f"{MB}/brand-voices", headers=MH, json={
"name": "Voice of the Customer — Jira Comments",
"extracted_content": "\n\n---\n\n".join(all_comments[:80])[:50000],
}).json()
print(f"Brand Voice: {bv['id']}")
time.sleep(0.3)
messaging = requests.post(f"{MB}/generations", headers=MH, json={
"brand_voice_id": bv["id"],
"prompt": "Using the customer voice from Jira comments, generate:\n"
"1. Homepage hero — headline + subheadline in customer language\n"
"2. 3 feature descriptions written how customers describe value\n"
"3. 3 testimonial-style quotes echoing actual language patterns\n"
"4. 5 email subject lines using phrases customers actually use\n"
"5. 4 pain-to-solution pairs — customer quote → product answer",
}).json()
print(messaging.get("output", messaging.get("content", ""))[:3000])
body.content[].content[].text. Inline code, mentions, and media nodes need type-specific handling.