136 lines
4.5 KiB
Python
Executable File
136 lines
4.5 KiB
Python
Executable File
#! /usr/bin/env python3
|
|
import argparse
|
|
import sys
|
|
import logging
|
|
import subprocess
|
|
import requests
|
|
|
|
try:
|
|
import github
|
|
except ImportError:
|
|
print("Please install the 'PyGithub' package from pip", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
TRELLO_BOARD_ID = "tw3Cn3L6"
|
|
TRELLO_LIST_ID = "6348a3ce5208f505b61d29bf" # To Do
|
|
TRELLO_TAGS = {
|
|
"waiting_to_merge": "65524315edf2d2edb0cc5d09",
|
|
"draft": "65fdd81c83e5d6e00f1b9721",
|
|
"issue": "64eb5d72fb694cd8f0ba7a8d"
|
|
}
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def main() -> int:
|
|
# Handle program arguments
|
|
ap = argparse.ArgumentParser(
|
|
prog="github-to-trello", description="Syncs GitHub issues to Trello cards"
|
|
)
|
|
|
|
ap.add_argument(
|
|
"-v", "--verbose", help="Enable verbose logging", action="store_true"
|
|
)
|
|
args = ap.parse_args()
|
|
|
|
# Configure logging
|
|
logging.basicConfig(
|
|
level=logging.DEBUG if args.verbose else logging.INFO,
|
|
format="%(levelname)s: %(message)s",
|
|
)
|
|
|
|
# Read Trello API credentials
|
|
trello_api_key = subprocess.check_output(
|
|
"op read -n 'op://ewconfig/cbnd5vv3germmc4korkxx3nsim/api key'", shell=True
|
|
).decode()
|
|
trello_api_token = subprocess.check_output(
|
|
"op read -n 'op://ewconfig/cbnd5vv3germmc4korkxx3nsim/credential'",
|
|
shell=True,
|
|
).decode()
|
|
|
|
# Read GitHub credential
|
|
logger.info("Authenticating with GitHub")
|
|
github_pat = subprocess.check_output(
|
|
"op read -n 'op://ewconfig/obs3gaeg7lcff7v5ewbvgxvwgu/credential'", shell=True
|
|
).decode()
|
|
gh_api = github.Github(auth=github.Auth.Token(github_pat))
|
|
|
|
# Get my user object
|
|
user = gh_api.get_user()
|
|
logger.info(f"Authenticated as {user.login}")
|
|
|
|
# Get all my issues
|
|
issues = list(user.get_issues())
|
|
logger.info(f"Found {len(issues)} issues/prs")
|
|
|
|
# Filter out any issue that is in an archived repo
|
|
issues = [issue for issue in issues if not issue.repository.archived]
|
|
logger.info(f"{len(issues)} of those are actually in active repos")
|
|
|
|
# Fetch a list of all of my Trello cards
|
|
trello_cards = requests.get(
|
|
f"https://api.trello.com/1/boards/{TRELLO_BOARD_ID}/cards",
|
|
params={"key": trello_api_key, "token": trello_api_token},
|
|
).json()
|
|
|
|
for issue in issues:
|
|
repo = issue.repository
|
|
is_pr = issue.pull_request is not None
|
|
author = issue.user.login
|
|
logger.info(
|
|
f"Found {'pr' if is_pr else 'issue'} {repo.full_name}#{issue.number} by {author}"
|
|
)
|
|
|
|
# Check if any trello card already mentions this issue in the description
|
|
for card in trello_cards:
|
|
if issue.html_url in card["desc"]:
|
|
logger.info(f"Found Trello card {card['id']} for this issue")
|
|
break
|
|
else:
|
|
logger.info(f"Creating Trello card for this issue")
|
|
|
|
# Sort out the appropriate labels
|
|
card_labels = []
|
|
if is_pr:
|
|
# If this is a draft PR, add the draft label
|
|
if issue.draft:
|
|
card_labels.append(TRELLO_TAGS["draft"])
|
|
else:
|
|
# Otherwise, add the waiting to merge label
|
|
card_labels.append(TRELLO_TAGS["waiting_to_merge"])
|
|
else:
|
|
# If this is an issue, add the issue label
|
|
card_labels.append(TRELLO_TAGS["issue"])
|
|
|
|
print(card_labels)
|
|
# Create the card
|
|
card = requests.post(
|
|
f"https://api.trello.com/1/cards",
|
|
params={
|
|
"key": trello_api_key,
|
|
"token": trello_api_token,
|
|
"idList": TRELLO_LIST_ID,
|
|
"name": f"[{repo.full_name}] {issue.title}",
|
|
"desc": f"**GitHub Link:** {issue.html_url}\n\n**Author:** [{author}]({issue.user.html_url})\n\n---",
|
|
"idLabels": card_labels
|
|
},
|
|
).json()
|
|
logger.info(f"Created Trello card {card['id']} for this issue")
|
|
|
|
# Attach the issue to the card
|
|
requests.post(
|
|
f"https://api.trello.com/1/cards/{card['id']}/attachments",
|
|
params={
|
|
"key": trello_api_key,
|
|
"token": trello_api_token,
|
|
"url": issue.html_url
|
|
}
|
|
)
|
|
logger.info(f"Attached issue to Trello card {card['id']}")
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|