之前玩静态博客用过hexo-circle-of-friends
来获取友情链接中的好友文章.
如今这个项目我部署的过程中总是出现各种意外,不想继续折腾了,于是想起在大佬处看到 用FreshRSS 实现友圈rss订阅
于是开始今日的折腾
Docker部署Freshrss
使用docker run 命令
docker run -d --restart unless-stopped --log-opt max-size=10m \ -p 8282:80 \ -e TZ=Asia/Shanghai \ -e 'CRON_MIN=1,31' \ -v /root/rss/data:/var/www/FreshRSS/data \ -v /root/rss/extensions:/var/www/FreshRSS/extensions \ --name freshrss \ freshrss/freshrss
获取API密码
- 在
认证
中打开允许 API 访问 (用于手机应用)
选项 - 在
账户
-API管理
中输入API密码
CF Workers
这时候就要使用CF大法了.
addEventListener('fetch', event => { event.respondWith(handleRequest(event.request));});
const siteurl = 'Freshssr地址';
async function handleRequest(request) { // 直接使用固定的用户名与密码 const user = '用户名'; const password = 'API密码'; try { const authToken = await login(user, password); if (!authToken) { return new Response('Login failed.', { status: 403 }); }
const articles = await fetchArticles(authToken); const subscriptions = await fetchSubscriptions(authToken); const formattedArticles = formatArticles(articles, subscriptions);
return new Response(JSON.stringify(formattedArticles, null, 2), { headers: { 'Content-Type': 'application/json' } }); } catch (error) { return new Response('Error: ' + error.message, { status: 500 }); }}
async function login(user, password) { const loginUrl = `${siteurl}/api/greader.php/accounts/ClientLogin?Email=${encodeURIComponent(user)}&Passwd=${encodeURIComponent(password)}`; const response = await fetch(loginUrl); const text = await response.text(); const match = text.match(/Auth=(.*)/); return match ? match[1] : null;}
async function fetchArticles(authToken) { const articlesUrl = `${siteurl}/api/greader.php/reader/api/0/stream/contents/reading-list?&n=1000`; const response = await fetch(articlesUrl, { headers: { 'Authorization': `GoogleLogin auth=${authToken}` } }); const data = await response.json(); return data.items || [];}
async function fetchSubscriptions(authToken) { const subscriptionsUrl = `${siteurl}/api/greader.php/reader/api/0/subscription/list?output=json`; const response = await fetch(subscriptionsUrl, { headers: { 'Authorization': `GoogleLogin auth=${authToken}` } }); const data = await response.json(); return data.subscriptions || [];}
function formatArticles(articles, subscriptions) { const subscriptionMap = new Map(); subscriptions.forEach(subscription => { subscriptionMap.set(subscription.id, subscription); });
return articles.map(article => { const strippedContent = stripHtml(article.summary.content); const desc_length = strippedContent.length; const short_desc = desc_length > 20 ? strippedContent.substring(0, 99) + '...' : strippedContent;
const subscriptionId = article.origin.streamId; const subscription = subscriptionMap.get(subscriptionId);
return { site_name: article.origin.title, title: article.title, link: article.alternate[0].href, time: new Date(article.published * 1000).toISOString(), // Convert timestamp to ISO string description: short_desc, icon: subscription ? `${siteurl}/${subscription.iconUrl.split('/').pop()}` : '' }; });}
function stripHtml(html) { return html.replace(/<[^>]*>?/gm, ''); // 使用正则表达式去除HTML标签}
访问看看,输出 https://rss.imsun.workers.dev/ 由于cf的加载速度不是很理想,当然也可以使用php或者bash脚本生成json文件,这样可以加速页面加载的速度,大佬已经有代码贴出来了我就不复制了. 如果是使用静态博客的话 则可以选择使用github工作流 生成静态文件并通过vercel加速访问
调用
我把朋友圈文章加在了友情链接页面
使用php
<?php// 获取JSON数据$jsonData = file_get_contents('https://rss.imsun.workers.dev/');
// 检查是否成功获取数据if ($jsonData === false) { die('Error fetching JSON data.');}
// 将JSON数据解析为PHP数组$articles = json_decode($jsonData, true);
// 检查是否成功解析JSONif ($articles === null) { die('Error decoding JSON data.');}
// 对文章按时间排序(最新的排在前面)usort($articles, function ($a, $b) { return strtotime($b['time']) - strtotime($a['time']);});
// 设置每页显示的文章数量$itemsPerPage = 30;
// 生成文章列表echo '<header class="archive--header"><h1 class="post--single__title">朋友文章</h1><h2 class="post--single__subtitle">通过Freshrss获取 </h2></header><div class="articleList">';foreach (array_slice($articles, 0, $itemsPerPage) as $article) { // 格式化发布时间 $date = new DateTime($article['time']); $date->setTimezone(new DateTimeZone('Asia/Shanghai')); // 设置为中国上海时区 $formattedDate = $date->format('Y年m月d日 H:i:s');
echo '<article class="post--item"><div class="content">';
echo '<a target=_blank href="' . htmlspecialchars($article['link']) . '"><h2 class="post--title">' . htmlspecialchars($article['title']) . '</h2></a>'; echo ' <div class="description">' . htmlspecialchars($article['description']) . '</div>'; echo '<div class="meta"><img src="' . htmlspecialchars($article['icon']) . '" class="icon" width="16" height="16" alt="Icon" />'. htmlspecialchars($article['site_name']) .''; echo '<svg class="icon" viewBox="0 0 1024 1024" width="16" height="16"> <path d="M512 97.52381c228.912762 0 414.47619 185.563429 414.47619 414.47619s-185.563429 414.47619-414.47619 414.47619S97.52381 740.912762 97.52381 512 283.087238 97.52381 512 97.52381z m0 73.142857C323.486476 170.666667 170.666667 323.486476 170.666667 512s152.81981 341.333333 341.333333 341.333333 341.333333-152.81981 341.333333-341.333333S700.513524 170.666667 512 170.666667z m36.571429 89.697523v229.86362h160.865523v73.142857H512a36.571429 36.571429 0 0 1-36.571429-36.571429V260.388571h73.142858z"> </path></svg><time> ' . htmlspecialchars($formattedDate) . '</time></div> </div> ';
echo '</article>';}echo '</div>';?>
大功告成
使用JS
<div class="articleList" id="articleList"></div><button id="load-more">加载更多</button>
<script>document.addEventListener('DOMContentLoaded', async () => { const jsonData = await fetchJsonData('https://cdn.jkjoy.cn/rss/rss.json'); if (!jsonData) { console.error('Failed to fetch JSON data.'); return; } const articles = jsonData;
// 对文章按时间排序(最新的排在前面) articles.sort((a, b) => new Date(b.time) - new Date(a.time));
// 初始化分页变量 let currentPage = 1; const itemsPerPage = 30;
// 加载第一页文章 loadArticles(currentPage, itemsPerPage, articles);
// 设置“加载更多”按钮点击事件 const loadMoreButton = document.getElementById('load-more'); loadMoreButton.addEventListener('click', () => { currentPage++; loadArticles(currentPage, itemsPerPage, articles); });});
async function fetchJsonData(url) { try { const response = await fetch(url); if (response.ok) { return await response.json(); } else { console.error('HTTP error:', response.status, response.statusText); } } catch (error) { console.error('Fetch error:', error); } return null;}
function loadArticles(page, perPage, articles) { const startIndex = (page - 1) * perPage; const endIndex = page * perPage; const articleListElem = document.getElementById('articleList');
articles.slice(startIndex, endIndex).forEach(article => { const articleElem = createArticleElement(article); articleListElem.appendChild(articleElem); });}
function createArticleElement(article) { // 格式化发布时间 const date = new Date(article.time); const formattedDate = new Intl.DateTimeFormat('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }).format(date);
const div = document.createElement('div'); div.className = 'post--item';
const contentDiv = document.createElement('div'); contentDiv.className = 'content';
const link = document.createElement('a'); link.href = article.link; link.target = '_blank'; const title = document.createElement('h2'); title.className = 'post--title'; title.textContent = article.title; link.appendChild(title);
const descriptionDiv = document.createElement('div'); descriptionDiv.className = 'description'; descriptionDiv.textContent = article.description;
const metaDiv = document.createElement('div'); metaDiv.className = 'meta';
const iconImg = document.createElement('img'); iconImg.src = article.icon; iconImg.width = 16; iconImg.height = 16; iconImg.alt = 'Icon'; iconImg.className = 'icon';
const siteName = document.createElement('span'); siteName.textContent = article.site_name;
const timeIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); timeIcon.className = 'icon'; timeIcon.setAttribute('viewBox', '0 0 1024 1024'); timeIcon.setAttribute('width', '16'); timeIcon.setAttribute('height', '16'); timeIcon.innerHTML = `<path d="M512 97.52381c228.912762 0 414.47619 185.563429 414.47619 414.47619s-185.563429 414.47619-414.47619 414.47619S97.52381 740.912762 97.52381 512 283.087238 97.52381 512 97.52381z m0 73.142857C323.486476 170.666667 170.666667 323.486476 170.666667 512s152.81981 341.333333 341.333333 341.333333 341.333333-152.81981 341.333333-341.333333S700.513524 170.666667 512 170.666667z m36.571429 89.697523v229.86362h160.865523v73.142857H512a36.571429 36.571429 0 0 1-36.571429-36.571429V260.388571h73.142858z"></path>`;
const timeText = document.createElement('time'); timeText.textContent = formattedDate;
metaDiv.appendChild(iconImg); metaDiv.appendChild(siteName); metaDiv.appendChild(timeIcon); metaDiv.appendChild(timeText);
contentDiv.appendChild(link); contentDiv.appendChild(descriptionDiv); contentDiv.appendChild(metaDiv);
div.appendChild(contentDiv);
return div;}</script>