Skip to content

Commit 86bb911

Browse files
authored
fix: image rendering after Medium CDN breaking changes (#206)
* fix: image rendering after Medium CDN breaking changes * chore: remove console log * chore: fix image size
1 parent dcdc2a1 commit 86bb911

File tree

2 files changed

+142
-393
lines changed

2 files changed

+142
-393
lines changed

app/util/medium.ts

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ import { feedToJSON } from './feedtojson';
44
import { getRandomUserAgent } from './useragent';
55
import { parse } from 'node-html-parser';
66

7+
// Convert cdn-images-1.medium.com URL to miro.medium.com format
8+
// Example: https://cdn-images-1.medium.com/max/1024/0*abc.png -> https://miro.medium.com/v2/resize:fit:150/0*abc.png
9+
function convertToMiroUrl(cdnUrl: string): string {
10+
const match = cdnUrl.match(/cdn-images-\d\.medium\.com\/max\/\d+\/(.+)$/);
11+
if (match) {
12+
// Use smaller size (150px) for thumbnail to reduce payload
13+
return `https://miro.medium.com/v2/resize:fit:800/${match[1]}`;
14+
}
15+
return cdnUrl;
16+
}
17+
718
export const getArticle = async (index: string, username: string) => {
819
const rssUrl = `https://medium.com/feed/${username}`
920
const res = await feedToJSON(rssUrl);
@@ -24,19 +35,30 @@ export const getArticle = async (index: string, username: string) => {
2435
];
2536

2637
const description = content || desc;
27-
const responseThumbnail = await axios(thumbnail.src, {
28-
responseType: 'arraybuffer',
29-
headers: {
30-
'User-Agent': getRandomUserAgent(),
31-
}
32-
});
33-
const base64Img = Buffer.from(responseThumbnail.data, 'binary').toString('base64');
3438

35-
const imgTypeArr = thumbnail.src.split('.');
36-
const imgType = imgTypeArr[imgTypeArr.length - 1];
39+
// Medium placeholder SVG as fallback
40+
const placeholderSvg = `data:image/svg+xml;base64,${Buffer.from(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150 150"><rect fill="#12100E" width="150" height="150"/><path fill="#fff" d="M40 45h10l22 35 22-35h10v60h-10V65L75 95h-2L55 65v40H40V45z"/></svg>`).toString('base64')}`;
3741

38-
const convertedThumbnail = `data:image/${imgType};base64,${base64Img}`;
42+
let convertedThumbnail = placeholderSvg;
3943

44+
try {
45+
// Use miro.medium.com which doesn't have Cloudflare bot protection
46+
const miroUrl = convertToMiroUrl(thumbnail.src);
47+
48+
const responseThumbnail = await axios(miroUrl, {
49+
responseType: 'arraybuffer',
50+
timeout: 10000,
51+
headers: {
52+
'User-Agent': getRandomUserAgent(),
53+
}
54+
});
55+
const base64Img = Buffer.from(responseThumbnail.data, 'binary').toString('base64');
56+
57+
// miro.medium.com returns JPEG images
58+
convertedThumbnail = `data:image/jpeg;base64,${base64Img}`;
59+
} catch (error) {
60+
console.log('Failed to fetch thumbnail, using placeholder:', error instanceof Error ? error.message : 'Unknown error');
61+
}
4062

4163
const cleanedDescription = stripHTML(description);
4264
return {

0 commit comments

Comments
 (0)