Skip to content

Commit aea7c27

Browse files
committed
Switch from RSS to GraphQL API for fetching discussions
RSS feed returns HTTP 406 from client-side fetch. Use public GraphQL API instead which doesn't require authentication for public repos.
1 parent 8d48277 commit aea7c27

File tree

1 file changed

+49
-61
lines changed

1 file changed

+49
-61
lines changed

public/js/discussion-feed.js

Lines changed: 49 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,66 @@
11
/**
2-
* Fetch and display GitHub Discussions RSS feed for Weekly Updates
2+
* Fetch and display GitHub Discussions for Weekly Updates using GraphQL API
33
*/
44
(function() {
55
'use strict';
66

7-
const RSS_FEED_URL = 'https://github.com/fvutils/fvutils.github.io/discussions.rss';
8-
const CATEGORY_SLUG = 'weekly-updates';
7+
const GRAPHQL_API = 'https://api.github.com/graphql';
8+
const OWNER = 'fvutils';
9+
const REPO = 'fvutils.github.io';
10+
const CATEGORY_ID = 'DIC_kwDOHMTiU84C2BrA';
911
const MAX_ITEMS = 5;
1012

1113
/**
12-
* Fetch and parse the RSS feed
14+
* Fetch discussions using GitHub GraphQL API
1315
*/
1416
async function fetchDiscussions() {
17+
const query = `
18+
query {
19+
repository(owner: "${OWNER}", name: "${REPO}") {
20+
discussions(first: ${MAX_ITEMS}, categoryId: "${CATEGORY_ID}", orderBy: {field: CREATED_AT, direction: DESC}) {
21+
nodes {
22+
number
23+
title
24+
url
25+
createdAt
26+
bodyText
27+
}
28+
}
29+
}
30+
}
31+
`;
32+
1533
try {
16-
const response = await fetch(RSS_FEED_URL);
34+
const response = await fetch(GRAPHQL_API, {
35+
method: 'POST',
36+
headers: {
37+
'Content-Type': 'application/json',
38+
},
39+
body: JSON.stringify({ query })
40+
});
41+
1742
if (!response.ok) {
1843
throw new Error(`HTTP error! status: ${response.status}`);
1944
}
45+
46+
const data = await response.json();
2047

21-
const text = await response.text();
22-
const parser = new DOMParser();
23-
const xmlDoc = parser.parseFromString(text, 'text/xml');
24-
25-
// Check for parsing errors
26-
const parserError = xmlDoc.querySelector('parsererror');
27-
if (parserError) {
28-
throw new Error('XML parsing error');
48+
if (data.errors) {
49+
console.error('GraphQL errors:', data.errors);
50+
return null;
2951
}
30-
31-
return parseRSSItems(xmlDoc);
52+
53+
return data.data.repository.discussions.nodes;
3254
} catch (error) {
3355
console.error('Error fetching discussions:', error);
3456
return null;
3557
}
3658
}
3759

3860
/**
39-
* Parse RSS items and filter for Weekly Updates category
40-
*/
41-
function parseRSSItems(xmlDoc) {
42-
const items = xmlDoc.querySelectorAll('item');
43-
const discussions = [];
44-
45-
items.forEach(item => {
46-
const link = item.querySelector('link')?.textContent || '';
47-
const title = item.querySelector('title')?.textContent || '';
48-
const pubDate = item.querySelector('pubDate')?.textContent || '';
49-
const description = item.querySelector('description')?.textContent || '';
50-
51-
// Filter by category slug in the link
52-
// Discussion links look like: https://github.com/fvutils/fvutils.github.io/discussions/categories/weekly-updates/...
53-
if (link.includes(`/categories/${CATEGORY_SLUG}`)) {
54-
discussions.push({
55-
title: title,
56-
link: link,
57-
date: new Date(pubDate),
58-
description: description
59-
});
60-
}
61-
});
62-
63-
// Sort by date (newest first) and limit to MAX_ITEMS
64-
discussions.sort((a, b) => b.date - a.date);
65-
return discussions.slice(0, MAX_ITEMS);
66-
}
67-
68-
/**
69-
* Format date as readable string
61+
* Get excerpt from body text
7062
*/
71-
function formatDate(date) {
72-
const options = { year: 'numeric', month: 'long', day: 'numeric' };
73-
return date.toLocaleDateString('en-US', options);
74-
}
75-
76-
/**
77-
* Extract plain text excerpt from HTML description
78-
*/
79-
function getExcerpt(html, maxWords = 30) {
80-
const tempDiv = document.createElement('div');
81-
tempDiv.innerHTML = html;
82-
const text = tempDiv.textContent || tempDiv.innerText || '';
63+
function getExcerpt(text, maxWords = 30) {
8364
const words = text.trim().split(/\s+/);
8465

8566
if (words.length > maxWords) {
@@ -115,11 +96,18 @@
11596
`;
11697

11798
discussions.forEach(discussion => {
99+
const date = new Date(discussion.createdAt);
100+
const formattedDate = date.toLocaleDateString('en-US', {
101+
year: 'numeric',
102+
month: 'long',
103+
day: 'numeric'
104+
});
105+
118106
html += `
119107
<li class="news-item">
120-
<div class="news-date">${formatDate(discussion.date)}</div>
121-
<h3 class="news-title"><a href="${discussion.link}">${discussion.title}</a></h3>
122-
<p class="news-excerpt">${getExcerpt(discussion.description)}</p>
108+
<div class="news-date">${formattedDate}</div>
109+
<h3 class="news-title"><a href="${discussion.url}">${discussion.title}</a></h3>
110+
<p class="news-excerpt">${getExcerpt(discussion.bodyText)}</p>
123111
</li>
124112
`;
125113
});

0 commit comments

Comments
 (0)