1import fetch from "npm:node-fetch";
2import process from "node:process";
3import { BskyAgent } from "npm:@atproto/api";
8});
9
10// ======= Fetch HTML with Improved Retry =======
11async function fetchHtmlWithRetry(url, options = {}, retries = 5, delay = 1000) {
12 for (let i = 0; i < retries; i++) {
13 try {
15 const timeoutId = setTimeout(() => controller.abort(), 15000);
16
17 const response = await fetch(url, {
18 ...options,
19 signal: controller.signal,
28
29 if (!response.ok) {
30 throw new Error(`Failed to fetch: ${response.statusText}`);
31 }
32
39 if (isPrematureClose && i < retries - 1) {
40 console.warn(
41 `Fetch attempt ${i + 1} failed during .text() with "Premature close". Retrying immediately...`,
42 );
43 continue;
44 } else if (i < retries - 1) {
45 console.warn(
46 `Fetch attempt ${i + 1} failed during .text(). Retrying in ${delay}ms...`,
47 );
48 await new Promise((resolve) => setTimeout(resolve, delay));
57 if (isPrematureClose && i < retries - 1) {
58 console.warn(
59 `Fetch attempt ${i + 1} failed with "Premature close". Retrying immediately...`,
60 );
61 continue;
62 } else if (i < retries - 1) {
63 console.warn(`Fetch attempt ${i + 1} failed. Retrying in ${delay}ms...`);
64 await new Promise((resolve) => setTimeout(resolve, delay));
65 } else {
70}
71
72// ======= Fetch News =======
73async function fetchNews() {
74 const url = "https://glastonburyfestivals.co.uk/news/";
75 try {
76 const html = await fetchHtmlWithRetry(url);
77 const $ = cheerio.load(html);
78
88 return news;
89 } catch (error) {
90 console.error("Error fetching news:", error);
91 throw error;
92 }
93}
94
95// ======= Fetch Your Own Recent Bluesky Post Titles =======
96async function fetchOwnRecentPostTitles(agent, handle, limit = 100) {
97 const feed = await agent.getAuthorFeed({ actor: handle, limit });
98 return feed.data.feed.map(item => item.post.record.text);
99}
100
101// ======= Fetch Embed Data =======
102async function fetchEmbedData(url) {
103 try {
104 const html = await fetchHtmlWithRetry(url);
105 const $ = cheerio.load(html);
106
116 imageUrl = new URL(imageUrl, url).href;
117 }
118 console.log("Fetching image for URL:", imageUrl);
119
120 // For images, use the original fetchWithRetry (no .text() call)
121 const imgResponse = await fetchHtmlWithRetry(imageUrl);
122 // However, for images, we need the raw bytes, so use fetchWithRetry as before:
123 const imgResponseRaw = await fetch(imageUrl, {
124 headers: {
125 "User-Agent": "Mozilla/5.0 (compatible; MyNewsBot/1.0; +https://yourdomain.example/contact)",
162 };
163 } catch (error) {
164 console.error("Error fetching embed data:", error);
165 return null;
166 }
177
178 const myHandle = process.env.BLUESKY_USERNAME; // e.g. "yourname.bsky.social"
179 const postedTitlesArr = await fetchOwnRecentPostTitles(agent, myHandle, 100);
180 const postedTitles = new Set(postedTitlesArr);
181
182 const newsItems = await fetchNews();
183 console.log(`Fetched ${newsItems.length} news items.`);
184
185 // Find all news items not yet posted, in chronological order (oldest first)
190 console.log(`Attempting to post new news: ${latestNews.title}`);
191
192 const embedData = await fetchEmbedData(latestNews.link);
193 if (!embedData) {
194 console.error("Failed to fetch embed data. Skipping post.");
195 continue;
196 }