Skip to content

Commit f5ed8cb

Browse files
committed
first commit
0 parents  commit f5ed8cb

File tree

3 files changed

+275
-0
lines changed

3 files changed

+275
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/downloaded_images
2+
/issues_with_comments_and_images.json

fetch_issues.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import os
2+
import json
3+
import subprocess
4+
import requests
5+
import re
6+
import time # ✅ Import time for delays
7+
from urllib.parse import urlparse
8+
9+
# Prompt for user input
10+
OWNER = input("Enter GitHub repository owner: ")
11+
REPO = input("Enter GitHub repository name: ")
12+
TOKEN = input("Enter your GitHub token: ")
13+
LIMIT = input("Enter the number of issues to fetch (default 10): ") or "10"
14+
STATE = input("Enter issue state (open, closed, all - default all): ") or "all"
15+
16+
HEADERS = {"Authorization": f"token {TOKEN}", "Accept": "application/vnd.github.v3+json"}
17+
18+
# Ensure the image download directory exists
19+
IMAGE_DIR = "downloaded_images"
20+
os.makedirs(IMAGE_DIR, exist_ok=True)
21+
22+
23+
# Function to download an image and return the local path
24+
def download_image(image_url):
25+
try:
26+
print(f"Attempting to download image from URL: {image_url}")
27+
28+
image_name = os.path.basename(urlparse(image_url).path)
29+
local_image_path = os.path.join(IMAGE_DIR, image_name)
30+
31+
if os.path.exists(local_image_path):
32+
print(f"Image already downloaded: {image_name}")
33+
return local_image_path
34+
35+
headers = {"Authorization": f"token {TOKEN}"}
36+
img_data = requests.get(image_url, headers=headers)
37+
38+
if img_data.status_code == 200:
39+
with open(local_image_path, 'wb') as f:
40+
f.write(img_data.content)
41+
print(f"Downloaded image: {image_name}")
42+
return local_image_path
43+
else:
44+
print(f"Failed to download image. HTTP Status: {img_data.status_code} for URL: {image_url}")
45+
return None
46+
47+
except Exception as e:
48+
print(f"Failed to download image from {image_url}: {e}")
49+
return None
50+
51+
52+
# Step 1: Get all issues (open and closed) using GitHub CLI
53+
print("\nFetching issues...")
54+
issues_json = subprocess.run(
55+
["gh", "issue", "list", "--repo", f"{OWNER}/{REPO}", "--limit", LIMIT, "--state", STATE, "--json", "number,title,body,state"],
56+
capture_output=True,
57+
text=True
58+
).stdout
59+
60+
issues = json.loads(issues_json)
61+
62+
# ✅ Add sleep to prevent hitting CLI request limits
63+
time.sleep(2)
64+
65+
# Step 2: Fetch comments for each issue using GitHub API
66+
for issue in issues:
67+
issue_number = issue["number"]
68+
print(f"Fetching comments for issue #{issue_number}...")
69+
70+
comments_url = f"https://api.github.com/repos/{OWNER}/{REPO}/issues/{issue_number}/comments"
71+
response = requests.get(comments_url, headers=HEADERS)
72+
73+
if response.status_code == 200:
74+
comments = response.json()
75+
issue["comments"] = [{"author": c["user"]["login"], "body": c["body"], "date": c["created_at"]} for c in comments]
76+
else:
77+
print(f"Failed to fetch comments for issue #{issue_number} (HTTP {response.status_code})")
78+
issue["comments"] = []
79+
80+
# ✅ Sleep after each comment fetch (prevents API rate limits)
81+
time.sleep(1.5)
82+
83+
# Step 3: Download images from the issue body and comments (if any)
84+
body = issue.get("body", "")
85+
for comment in issue["comments"]:
86+
body += comment["body"]
87+
88+
# Regex to find image URLs in Markdown and HTML formats
89+
img_urls = re.findall(r'!\[.*?\]\((https?://[^\)]+)\)', body)
90+
img_urls += re.findall(r'<img[^>]+src="(https?://[^\"]+)"', body)
91+
92+
for img_url in img_urls:
93+
local_image_path = download_image(img_url)
94+
95+
# ✅ Sleep after each image download (avoids too many requests)
96+
time.sleep(1)
97+
98+
issue["body"] = body
99+
100+
# ✅ Final sleep before saving (ensures all requests complete safely)
101+
time.sleep(2)
102+
103+
# Save the issues with comments and updated image paths to a JSON file
104+
with open("issues_with_comments_and_images.json", "w", encoding="utf-8") as f:
105+
json.dump(issues, f, indent=4)
106+
107+
print("✅ Export completed! Data saved in issues_with_comments_and_images.json")

issue.html

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>GitHub Issue List</title>
7+
<style>
8+
body {
9+
font-family: Arial, sans-serif;
10+
background-color: #f9f9f9;
11+
color: #333;
12+
padding: 20px;
13+
}
14+
h1 {
15+
text-align: center;
16+
color: #333;
17+
}
18+
.issue {
19+
background-color: #fff;
20+
border-radius: 8px;
21+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
22+
padding: 20px;
23+
margin-bottom: 20px;
24+
border-left: 5px solid #4CAF50;
25+
}
26+
.issue h2 {
27+
color: #333;
28+
margin-top: 0;
29+
}
30+
.issue p {
31+
line-height: 1.6;
32+
}
33+
.issue .comments {
34+
margin-top: 15px;
35+
padding-left: 20px;
36+
border-left: 3px solid #ddd;
37+
}
38+
.comment {
39+
background-color: #f9f9f9;
40+
margin-bottom: 10px;
41+
padding: 10px;
42+
border-radius: 5px;
43+
}
44+
.comment p {
45+
margin: 5px 0;
46+
}
47+
.comment .author {
48+
font-weight: bold;
49+
color: #4CAF50;
50+
}
51+
.comment .date {
52+
font-size: 0.9em;
53+
color: #888;
54+
}
55+
.comment .body {
56+
margin-top: 5px;
57+
}
58+
img {
59+
max-width: 100%;
60+
height: auto;
61+
border-radius: 8px;
62+
}
63+
</style>
64+
</head>
65+
<body>
66+
67+
<h1>GitHub Issue List</h1>
68+
<div id="issues-container"></div>
69+
70+
<script>
71+
// Function to render issues dynamically
72+
async function fetchIssues() {
73+
try {
74+
const response = await fetch('issues_with_comments_and_images.json'); // Fetch the JSON file
75+
const issuesData = await response.json(); // Parse the JSON data
76+
77+
// Now render the issues
78+
renderIssues(issuesData);
79+
} catch (error) {
80+
console.error("Error fetching the issues data:", error);
81+
alert("There was an error loading the issues.");
82+
}
83+
}
84+
85+
// Function to render issues dynamically
86+
function renderIssues(issuesData) {
87+
const container = document.getElementById('issues-container');
88+
issuesData.forEach(issue => {
89+
// Create the issue container
90+
const issueDiv = document.createElement('div');
91+
issueDiv.classList.add('issue');
92+
93+
const numberAndState = document.createElement('h3');
94+
numberAndState.innerHTML = `<strong>Issue #${issue.number}</strong> - State: ${issue.state==="OPEN" ? `<span style="color: green;">Open</span>` : `<span style="color: red;">Closed</span>`}`;
95+
issueDiv.appendChild(numberAndState);
96+
97+
// Title and number
98+
const title = document.createElement('h2');
99+
title.textContent = issue.title;
100+
issueDiv.appendChild(title);
101+
102+
103+
104+
// Body - converting Markdown images into HTML <img> tags
105+
const body = document.createElement('p');
106+
body.innerHTML = processBodyText(issue.body);
107+
issueDiv.appendChild(body);
108+
109+
// Comments Section
110+
if (issue.comments.length > 0) {
111+
const commentsDiv = document.createElement('div');
112+
commentsDiv.classList.add('comments');
113+
114+
issue.comments.forEach(comment => {
115+
const commentDiv = document.createElement('div');
116+
commentDiv.classList.add('comment');
117+
118+
const author = document.createElement('p');
119+
author.classList.add('author');
120+
author.innerHTML = `${comment.author} <span class="date">(${comment.date})</span>`;
121+
commentDiv.appendChild(author);
122+
123+
const body = document.createElement('p');
124+
body.classList.add('body');
125+
body.textContent = comment.body;
126+
commentDiv.appendChild(body);
127+
128+
commentsDiv.appendChild(commentDiv);
129+
});
130+
131+
issueDiv.appendChild(commentsDiv);
132+
}
133+
134+
container.appendChild(issueDiv);
135+
});
136+
}
137+
138+
// Function to process body text and convert Markdown-style images to <img> tags
139+
function processBodyText(text) {
140+
// Regex to match Markdown image syntax (e.g. ![Image](url))
141+
const regex = /!\[([^\]]+)\]\((https?:\/\/[^\)]+)\)/g;
142+
143+
// Replace Markdown images with <img> tags pointing to the local 'downloaded_images' folder
144+
return text.replace(regex, (match, altText, url) => {
145+
// Extract image name from the URL
146+
const imageName = url.split('/').pop(); // Get the last part of the URL as the image file name
147+
148+
// Check if the image has an extension and handle accordingly
149+
const imageExtension = imageName.split('.').pop();
150+
const imagePath = `./downloaded_images/${imageName}`;
151+
152+
// Debugging: log the URL and local path to verify
153+
console.log("Original URL:", url); // Check the original URL
154+
console.log("Local Image Path:", imagePath); // Check the local image path
155+
156+
// Return HTML <img> tag
157+
return `<img src="${imagePath}" alt="${altText}" style="max-width: 100%; height: auto;">`;
158+
}).replace(/\n/g, '<br>'); // Replace line breaks with <br> tags
159+
}
160+
161+
// Call the fetchIssues function when the page loads
162+
fetchIssues();
163+
</script>
164+
165+
</body>
166+
</html>

0 commit comments

Comments
 (0)