Để đọc full bài viết, bạn có 2 cách:
- Dùng proxy CORS (như https://api.allorigins.win hoặc tự dựng server nhỏ) → an toàn và ổn định hơn.
- Tự host PHP/Python proxy trên server của bạn → đảm bảo không bị chặn và luôn đọc được RSS + full content.
Đây là đoạn code html bạn cần.
HTML:
<!doctype html>
<html lang="vi">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Simple Full RSS Reader</title>
<style>
:root{--accent:#185886}
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,'Helvetica Neue',Arial;margin:0;background:#f7f9fb;color:#111}
header{background:linear-gradient(90deg,var(--accent),#0f5a83);color:#fff;padding:16px}
.wrap{max-width:980px;margin:18px auto;padding:12px}
.controls{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px}
input[type=text]{flex:1;padding:8px;border:1px solid #ddd;border-radius:6px}
button{background:var(--accent);color:#fff;border:none;padding:8px 12px;border-radius:6px;cursor:pointer}
.hint{font-size:13px;color:#555;margin-bottom:8px}
.item{background:#fff;border:1px solid #e6eef6;border-radius:8px;padding:12px;margin-bottom:12px;box-shadow:0 1px 2px rgba(0,0,0,.03)}
.meta{font-size:13px;color:#666;margin-bottom:6px}
.title{font-weight:700;color:#073b57;margin:0 0 6px}
.content{margin-top:8px}
.search{margin-left:auto}
.small{font-size:13px;color:#666}
.footer{font-size:13px;color:#666;margin-top:18px;text-align:center}
.error{color:#a33}
</style>
</head>
<body>
<header>
<div class="wrap">
<h1 style="margin:0;font-size:20px">Simple Full RSS Reader</h1>
<div style="font-size:13px;opacity:.9">Nhập URL RSS/Atom để đọc bài toàn văn. ⚠️ Lưu ý: nhiều trang chặn CORS nên cần proxy hoặc chạy file này trên server.</div>
</div>
</header>
<main class="wrap">
<div class="controls">
<input id="feedUrl" type="text" placeholder="Ví dụ: https://vnexpress.net/rss/tin-moi-nhat.rss" value="">
<select id="proxySelect" title="CORS proxy">
<option value="https://api.allorigins.win/raw?url=">AllOrigins (free)</option>
<option value="https://api.allorigins.cf/raw?url=">AllOrigins CF</option>
<option value="https://cors.bridged.cc/">Bridge</option>
</select>
<button id="loadBtn">Tải</button>
<input id="search" class="search" type="text" placeholder="Tìm trong tiêu đề/nội dung...">
</div>
<div class="hint">Nếu chọn "Không proxy" sẽ lỗi vì trình duyệt chặn CORS. Để đọc full bài, hãy luôn dùng proxy hoặc chạy qua server riêng.</div>
<div id="status" class="small"></div>
<div id="list"></div>
<div class="footer">https://fxvnn.com • Trang này chèn HTML từ feed—cẩn trọng khi đọc nội dung từ nguồn lạ.</div>
</main>
<script>
const $ = id => document.getElementById(id);
const loadBtn = $('loadBtn');
const list = $('list');
const status = $('status');
const search = $('search');
function setStatus(txt, isError){ status.textContent = txt; status.className = isError? 'error small':'small'; }
function getText(node, tagNames){
for(const t of tagNames){
const el = node.getElementsByTagName(t)[0];
if(el && el.textContent.trim()) return el.textContent.trim();
}
return '';
}
function getHtml(node, tagCandidates){
for(const name of tagCandidates){
const byName = node.getElementsByTagName(name);
if(byName && byName.length){
const n = byName[0];
if(n.childNodes.length>0){
return n.textContent || n.innerHTML || '';
}
}
}
return '';
}
function renderItems(items){
list.innerHTML = '';
if(items.length===0){ list.innerHTML = '<div class="small">Không có mục nào.</div>'; return }
items.forEach(it=>{
const div = document.createElement('div'); div.className='item';
const title = document.createElement('div'); title.className='title'; title.innerHTML = it.title || '(No title)';
const meta = document.createElement('div'); meta.className='meta'; meta.innerHTML = (it.pubDate? ('<strong>'+it.pubDate+'</strong> • '):'') + (it.author? ('by '+it.author):'') + (it.link? (' • <a target="_blank" href="'+it.link+'">nguồn</a>'):'');
const content = document.createElement('div'); content.className='content';
content.innerHTML = it.fullContent || it.summary || '';
div.appendChild(title); div.appendChild(meta); div.appendChild(content);
list.appendChild(div);
});
}
function parseFeed(text){
const parser = new DOMParser();
const xml = parser.parseFromString(text, 'application/xml');
const parseError = xml.getElementsByTagName('parsererror')[0];
if(parseError) throw new Error('XML parse error');
const rssItems = Array.from(xml.getElementsByTagName('item'));
const atomItems = Array.from(xml.getElementsByTagName('entry'));
const nodes = rssItems.length? rssItems : atomItems;
const out = nodes.map(node => {
const title = getText(node, ['title']);
let link = '';
if(node.getElementsByTagName('link').length){
const l = node.getElementsByTagName('link')[0];
link = l.getAttribute('href') || l.textContent || '';
}
const pubDate = getText(node, ['pubDate','dc:date','updated','published']);
const author = getText(node, ['author','dc:creator','creator']);
let full = getHtml(node, ['content:encoded','encoded','content','description','summary']);
if(!full){
full = getText(node, ['description','summary']);
}
return {title, link, pubDate, author, fullContent: full, summary: full};
});
return out;
}
async function fetchFeed(rawUrl, proxyPrefix){
try{
setStatus('Đang tải...');
const url = proxyPrefix? (proxyPrefix + encodeURIComponent(rawUrl)) : rawUrl;
const res = await fetch(url);
if(!res.ok) throw new Error('HTTP ' + res.status);
const text = await res.text();
const items = parseFeed(text);
setStatus('Đã tải ' + items.length + ' mục.');
return items;
}catch(err){
setStatus('Lỗi: ' + err.message, true);
throw err;
}
}
async function load(){
const raw = $('feedUrl').value.trim();
if(!raw) return setStatus('Vui lòng nhập URL feed.', true);
const proxy = $('proxySelect').value || '';
try{
const items = await fetchFeed(raw, proxy);
renderItems(items);
}catch(e){}
}
loadBtn.addEventListener('click', e=>{ load(); });
$('feedUrl').addEventListener('keydown', e=>{ if(e.key==='Enter') load(); });
search.addEventListener('input', ()=>{
const q = search.value.trim().toLowerCase();
const nodes = Array.from(list.children);
nodes.forEach(n=>{
const text = (n.innerText||'').toLowerCase();
n.style.display = text.includes(q)? '' : 'none';
});
});
</script>
</body>
</html>
Mọi người có thể chỉnh code html hoặc css theo ý muốn của các bạn
