mirror of
https://github.com/theariatv/theariatv.github.io.git
synced 2025-09-09 00:46:12 +02:00
Create index.html
Added a website that "should" work
This commit is contained in:
parent
b2305bc5b6
commit
5f941659d2
1 changed files with 283 additions and 0 deletions
283
index.html
Normal file
283
index.html
Normal file
|
@ -0,0 +1,283 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Aria | Curated IPTV Channels</title>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--bg-color: #f8f9fa;
|
||||
--text-color: #212529;
|
||||
--muted-text-color: #6c757d;
|
||||
--border-color: #dee2e6;
|
||||
--accent-color: #0d6efd;
|
||||
--header-bg: #ffffff;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg-color: #121212;
|
||||
--text-color: #e9ecef;
|
||||
--muted-text-color: #adb5bd;
|
||||
--border-color: #343a40;
|
||||
--accent-color: #4dabf7;
|
||||
--header-bg: #1e1e1e;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 3rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.project-info {
|
||||
background-color: var(--header-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.project-info h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.project-info a {
|
||||
color: var(--accent-color);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.project-info a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.category-folder {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1rem;
|
||||
background-color: var(--header-bg);
|
||||
}
|
||||
|
||||
.category-header {
|
||||
padding: 1rem 1.5rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.channel-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.channel-table td {
|
||||
text-align: left;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.channel-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.unstable-icon {
|
||||
color: #ffc107;
|
||||
font-weight: bold;
|
||||
margin-left: 8px;
|
||||
cursor: help;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
||||
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="root"></div>
|
||||
|
||||
<script type="text/babel">
|
||||
const { useState, useEffect } = React;
|
||||
|
||||
// --- Funkce pro parsování .m3u souborů ---
|
||||
const parseM3U = (m3uContent, isStable) => {
|
||||
const lines = m3uContent.split('\n');
|
||||
const channels = {};
|
||||
const regex = /group-title="([^"]+)"/
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('#EXTINF:-1')) {
|
||||
try {
|
||||
const countryMatch = line.match(regex);
|
||||
if (!countryMatch) continue;
|
||||
|
||||
const country = countryMatch[1].trim();
|
||||
const channelName = line.split(',').pop().trim();
|
||||
|
||||
if (!channels[country]) {
|
||||
channels[country] = {};
|
||||
}
|
||||
|
||||
// Přidáme kanál, pouze pokud ještě neexistuje, abychom předešli duplicitám
|
||||
if (!channels[country][channelName]) {
|
||||
channels[country][channelName] = { name: channelName, stable: isStable };
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Chyba při parsování řádku:", line, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return channels;
|
||||
};
|
||||
|
||||
// --- Komponenty ---
|
||||
const Category = ({ country, channels }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
return (
|
||||
<div className="category-folder">
|
||||
<div className="category-header" onClick={() => setIsOpen(!isOpen)}>
|
||||
<span>{country}</span>
|
||||
<span>{isOpen ? '−' : '+'}</span>
|
||||
</div>
|
||||
{isOpen && (
|
||||
<table className="channel-table">
|
||||
<tbody>
|
||||
{channels.map(channel => (
|
||||
<tr key={channel.name}>
|
||||
<td>
|
||||
<div className="channel-name">
|
||||
{channel.name}
|
||||
{!channel.stable &&
|
||||
<span className="unstable-icon" title="Potentially unstable stream from aria+.m3u">!</span>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
const [channelData, setChannelData] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAndParseData = async () => {
|
||||
try {
|
||||
// Načtení obou M3U souborů
|
||||
const [ariaRes, ariaPlusRes] = await Promise.all([
|
||||
fetch('aria.m3u'),
|
||||
fetch('aria+.m3u')
|
||||
]);
|
||||
|
||||
const ariaText = await ariaRes.text();
|
||||
const ariaPlusText = await ariaPlusRes.text();
|
||||
|
||||
// Parsování
|
||||
const stableChannels = parseM3U(ariaText, true);
|
||||
const unstableChannels = parseM3U(ariaPlusText, false);
|
||||
|
||||
// Sloučení dat: nestabilní kanály nepřepíší stabilní, pokud mají stejné jméno
|
||||
const allChannels = { ...stableChannels };
|
||||
for (const country in unstableChannels) {
|
||||
if (!allChannels[country]) {
|
||||
allChannels[country] = {};
|
||||
}
|
||||
for (const channelName in unstableChannels[country]) {
|
||||
if (!allChannels[country][channelName]) {
|
||||
allChannels[country][channelName] = unstableChannels[country][channelName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Převod na pole pro snadné renderování a seřazení
|
||||
const formattedData = Object.keys(allChannels).sort().map(country => ({
|
||||
country,
|
||||
channels: Object.values(allChannels[country]).sort((a, b) => a.name.localeCompare(b.name))
|
||||
}));
|
||||
|
||||
setChannelData(formattedData);
|
||||
} catch (error) {
|
||||
console.error("Nepodařilo se načíst data kanálů:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchAndParseData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<header>
|
||||
<h1>Aria</h1>
|
||||
<p>A curated collection of IPTV channels from around the world.</p>
|
||||
</header>
|
||||
<main>
|
||||
<div className="project-info">
|
||||
<h2>About Aria</h2>
|
||||
<p>
|
||||
Aria provides a curated collection of IPTV channels from around the world. The channels are organized by their countries. Unlike our predecessor Mystique, this git is only using official streams, tvheadends, astra control panel among others.
|
||||
</p>
|
||||
<h3>Want to help us?</h3>
|
||||
<p>
|
||||
You can go to the <a href="https://github.com/theariatv/theariatv.github.io/issues" target="_blank" rel="noopener noreferrer">Issues tab</a> on GitHub to request a channel-specific action (e.g., removing a dead link, adding a new channel).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2>Channel List</h2>
|
||||
{loading ? (
|
||||
<p>Loading channels...</p>
|
||||
) : (
|
||||
channelData.map(category => (
|
||||
<Category
|
||||
key={category.country}
|
||||
country={category.country}
|
||||
channels={category.channels}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(<App />);
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Add a link
Reference in a new issue