Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e2e387e
small title change
DeveloperAlly Jan 18, 2026
e4e18b4
clean up duplicate assets
DeveloperAlly Jan 18, 2026
d1af4f0
tweaks on Home Pages, adding content
DeveloperAlly Jan 18, 2026
8234de5
home tab ok to ship to preview
DeveloperAlly Jan 18, 2026
3a4eaec
change n8n fetches to github actions
DeveloperAlly Jan 19, 2026
91c35b4
add github workflows
DeveloperAlly Jan 19, 2026
373b524
update gh secret name
DeveloperAlly Jan 19, 2026
03f1e44
Update forum data - 2026-01-19T03:30:43.015-05:00
DeveloperAlly Jan 19, 2026
8bfc8d2
Update Blog Data 2026-01-19T03:35:53.410-05:00
DeveloperAlly Jan 19, 2026
fc6330a
blog workflow
DeveloperAlly Jan 19, 2026
348991f
add youtube ingest
DeveloperAlly Jan 19, 2026
3bc88ba
remove secret
DeveloperAlly Jan 19, 2026
c4fd763
remove from git
DeveloperAlly Jan 19, 2026
fbbb1c2
Update Livepeer YouTube videos - 2026-01-19T11:18:24.988Z
DeveloperAlly Jan 19, 2026
92ab39f
Update Livepeer YouTube videos - 2026-01-19T11:24:35.317Z
DeveloperAlly Jan 19, 2026
44cb743
Update Livepeer YouTube videos - 2026-01-19T11:40:42.960Z
DeveloperAlly Jan 19, 2026
d4d4087
youtube ingest
DeveloperAlly Jan 20, 2026
3e25953
manual pull
DeveloperAlly Jan 20, 2026
8449d34
finalise trending page
DeveloperAlly Jan 20, 2026
637bc09
remove tests
DeveloperAlly Jan 20, 2026
7674c70
Update forum data - 2026-01-20T14:00:14.116-05:00
DeveloperAlly Jan 20, 2026
b382233
add discord announcements automation
DeveloperAlly Jan 21, 2026
b3d55c0
chore: update Discord announcements from workflow
DeveloperAlly Jan 21, 2026
9f3a5db
commitMessage: `chore: create Discord announcements file from workflo…
DeveloperAlly Jan 21, 2026
276a84c
discord announcements integrated
DeveloperAlly Jan 21, 2026
a8d96fe
tweak
DeveloperAlly Jan 21, 2026
2428594
tweak
DeveloperAlly Jan 21, 2026
5d6044e
tweak
DeveloperAlly Jan 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 0 additions & 16 deletions .ai-audit.sh

This file was deleted.

File renamed without changes.
198 changes: 198 additions & 0 deletions .github/scripts/fetch-forum-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
const https = require("https");
const fs = require("fs");

// Fetch JSON from URL
function fetchJSON(url) {
return new Promise((resolve, reject) => {
https
.get(url, (res) => {
let data = "";
res.on("data", (chunk) => {
data += chunk;
});
res.on("end", () => {
try {
resolve(JSON.parse(data));
} catch (e) {
reject(e);
}
});
})
.on("error", reject);
});
}

// Check if topic is old pinned
function isOldPinned(topic) {
const pinned = topic.pinned === true || topic.pinned_globally === true;
if (!pinned) return false;
const created = new Date(topic.created_at);
const now = new Date();
const ageDays = (now - created) / (1000 * 60 * 60 * 24);
return ageDays > 30;
}

// Clean and format HTML
function cleanAndFormatHTML(html) {
let cleanHTML = html;

// Remove anchor navigation links
cleanHTML = cleanHTML.replace(
/<a[^>]*name="[^"]*"[^>]*class="anchor"[^>]*>.*?<\/a>/g,
""
);

// Clean up headings
cleanHTML = cleanHTML.replace(/<h1[^>]*>(.*?)<\/h1>/g, "<h3>$1</h3>");
cleanHTML = cleanHTML.replace(/<h2[^>]*>(.*?)<\/h2>/g, "<h4>$1</h4>");
cleanHTML = cleanHTML.replace(/<h3[^>]*>(.*?)<\/h3>/g, "<h5>$1</h5>");
cleanHTML = cleanHTML.replace(/<h[4-6][^>]*>(.*?)<\/h[4-6]>/g, "<h6>$1</h6>");

// Clean up images and their references
cleanHTML = cleanHTML.replace(/<a[^>]*class="lightbox"[^>]*>.*?<\/a>/g, "");
cleanHTML = cleanHTML.replace(
/<div[^>]*class="lightbox-wrapper"[^>]*>.*?<\/div>/g,
""
);
cleanHTML = cleanHTML.replace(/<img[^>]*>/g, "");
cleanHTML = cleanHTML.replace(/\[!\[.*?\]\(.*?\)\]\(.*?\)/g, "");
cleanHTML = cleanHTML.replace(/image\d+×\d+\s+[\d.]+\s*[KM]B/gi, "");

// Keep paragraphs, lists, emphasis, code
cleanHTML = cleanHTML.replace(/<p>/g, "<p>");
cleanHTML = cleanHTML.replace(/<\/p>/g, "</p>");
cleanHTML = cleanHTML.replace(/<ul>/g, "<ul>");
cleanHTML = cleanHTML.replace(/<\/ul>/g, "</ul>");
cleanHTML = cleanHTML.replace(/<ol>/g, "<ol>");
cleanHTML = cleanHTML.replace(/<\/ol>/g, "</ol>");
cleanHTML = cleanHTML.replace(/<li>/g, "<li>");
cleanHTML = cleanHTML.replace(/<\/li>/g, "</li>");
cleanHTML = cleanHTML.replace(
/<strong>(.*?)<\/strong>/g,
"<strong>$1</strong>"
);
cleanHTML = cleanHTML.replace(/<em>(.*?)<\/em>/g, "<em>$1</em>");
cleanHTML = cleanHTML.replace(/<code>(.*?)<\/code>/g, "<code>$1</code>");

// Simplify links
cleanHTML = cleanHTML.replace(
/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/g,
'<a href="$1" target="_blank">$2</a>'
);

// Decode HTML entities
cleanHTML = cleanHTML.replace(/&amp;/g, "&");
cleanHTML = cleanHTML.replace(/&lt;/g, "<");
cleanHTML = cleanHTML.replace(/&gt;/g, ">");
cleanHTML = cleanHTML.replace(/&quot;/g, '"');
cleanHTML = cleanHTML.replace(/&#39;/g, "'");
cleanHTML = cleanHTML.replace(/&nbsp;/g, " ");

// Clean up whitespace
cleanHTML = cleanHTML.replace(/\s+/g, " ");
cleanHTML = cleanHTML.replace(/<p>\s*<\/p>/g, "");

return cleanHTML.trim();
}

async function main() {
console.log("Fetching latest topics...");
const latestData = await fetchJSON("https://forum.livepeer.org/latest.json");

const topics = latestData.topic_list?.topics || [];
console.log(`Found ${topics.length} topics`);

// Filter out old pinned topics
const filteredTopics = topics.filter((t) => !isOldPinned(t));
console.log(`After filtering: ${filteredTopics.length} topics`);

// Get top 4
const top4 = filteredTopics.slice(0, 4);
console.log(`Processing top 4 topics...`);

const processedTopics = [];

for (const topic of top4) {
console.log(`Processing topic ${topic.id}: ${topic.title}`);

// Fetch full topic data
const topicData = await fetchJSON(
`https://forum.livepeer.org/t/${topic.id}.json`
);

// Extract first post
const firstPost = topicData.post_stream?.posts?.find(
(p) => p.post_number === 1
);

if (!firstPost) {
console.log(` No first post found, skipping`);
continue;
}

const htmlContent = cleanAndFormatHTML(firstPost.cooked || "");
const datePosted = topic.created_at
? new Date(topic.created_at).toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
})
: "";

processedTopics.push({
title: topic.title,
href: `https://forum.livepeer.org/t/${topic.id}`,
author: `By ${firstPost.name || firstPost.username || "Unknown"} (@${
firstPost.username || "unknown"
})`,
content: htmlContent,
replyCount: (topic.posts_count || 1) - 1,
datePosted: datePosted,
});
}

console.log(`Processed ${processedTopics.length} topics`);

// Generate JavaScript export with exact formatting
let jsExport = "export const forumData = [\n";

processedTopics.forEach((item, index) => {
jsExport += " {\n";
jsExport += ` title: "${item.title
.replace(/\\/g, "\\\\")
.replace(/"/g, '\\"')}",\n`;
jsExport += ` href: "${item.href}",\n`;
jsExport += ` author: "${item.author
.replace(/\\/g, "\\\\")
.replace(/"/g, '\\"')}",\n`;

// Content with proper escaping and indentation
const escapedContent = item.content
.replace(/\\/g, "\\\\")
.replace(/"/g, '\\"')
.replace(/\n/g, " ");

jsExport += ` content:\n "${escapedContent}",\n`;
jsExport += ` replyCount: ${item.replyCount},\n`;
jsExport += ` datePosted: "${item.datePosted}",\n`;
jsExport += " }";

if (index < processedTopics.length - 1) {
jsExport += ",";
}
jsExport += "\n";
});

jsExport += "];\n";

// Write to file
const outputPath = "snippets/automations/forum/forumData.jsx";
fs.mkdirSync("snippets/automations/forum", { recursive: true });
fs.writeFileSync(outputPath, jsExport);
console.log(`Written to ${outputPath}`);
}

main().catch((err) => {
console.error("Error:", err);
process.exit(1);
});
101 changes: 101 additions & 0 deletions .github/scripts/fetch-ghost-blog-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
const https = require("https");
const fs = require("fs");

// Fetch JSON from URL
function fetchJSON(url) {
return new Promise((resolve, reject) => {
https
.get(url, (res) => {
let data = "";
res.on("data", (chunk) => {
data += chunk;
});
res.on("end", () => {
try {
resolve(JSON.parse(data));
} catch (e) {
reject(e);
}
});
})
.on("error", reject);
});
}

// Safe HTML escape - only escape backticks for template literals
function safeHTML(html) {
return (html || "").replace(/`/g, "\\`");
}

// Format date
function formatDate(iso) {
return new Date(iso).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
}

async function main() {
console.log("Fetching Ghost blog posts...");

const apiUrl =
"https://livepeer-studio.ghost.io/ghost/api/content/posts/?key=eaf54ba5c9d4ab35ce268663b0&limit=4&include=tags,authors";

const response = await fetchJSON(apiUrl);

if (!response.posts || response.posts.length === 0) {
console.log("No posts found");
return;
}

console.log(`Found ${response.posts.length} posts`);

// Process posts
const posts = response.posts.map((p) => ({
title: p.title,
href: p.url,
author: p.primary_author?.name
? `By ${p.primary_author.name}`
: "By Livepeer Team",
content: safeHTML(p.html),
datePosted: formatDate(p.published_at),
img: p.feature_image || "",
excerpt: safeHTML(p.excerpt),
readingTime: p.reading_time || 0,
}));

// Generate JavaScript export with template literals
let jsExport = "export const ghostData = [\n";

posts.forEach((post, index) => {
jsExport += "{\n";
jsExport += ` title: \`${post.title}\`,\n`;
jsExport += ` href: \`${post.href}\`,\n`;
jsExport += ` author: \`${post.author}\`,\n`;
jsExport += ` content: \`${post.content}\`,\n`;
jsExport += ` datePosted: \`${post.datePosted}\`,\n`;
jsExport += ` img: \`${post.img}\`,\n`;
jsExport += ` excerpt: \`${post.excerpt}\`,\n`;
jsExport += ` readingTime: ${post.readingTime}\n`;
jsExport += "}";

if (index < posts.length - 1) {
jsExport += ",";
}
jsExport += "\n";
});

jsExport += "];\n";

// Write to file
const outputPath = "snippets/automations/ghost/ghostBlogData.jsx";
fs.mkdirSync("snippets/automations/ghost", { recursive: true });
fs.writeFileSync(outputPath, jsExport);
console.log(`Written to ${outputPath}`);
}

main().catch((err) => {
console.error("Error:", err);
process.exit(1);
});
Loading