diff options
| author | Fuwn <[email protected]> | 2024-07-26 10:10:25 +0000 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2024-07-26 10:15:02 +0000 |
| commit | 2199fe9a0c318db29a61ddcd50289d9b0036c7f7 (patch) | |
| tree | 54025c5b14c4dbd6dff676e4675a6c468bd549b1 /src | |
| download | iptv-jp-browser-2199fe9a0c318db29a61ddcd50289d9b0036c7f7.tar.xz iptv-jp-browser-2199fe9a0c318db29a61ddcd50289d9b0036c7f7.zip | |
feat: initial release
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.d.ts | 13 | ||||
| -rw-r--r-- | src/app.html | 17 | ||||
| -rw-r--r-- | src/lib/m3u.ts | 36 | ||||
| -rw-r--r-- | src/routes/+page.svelte | 116 |
4 files changed, 182 insertions, 0 deletions
diff --git a/src/app.d.ts b/src/app.d.ts new file mode 100644 index 0000000..743f07b --- /dev/null +++ b/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://kit.svelte.dev/docs/types#app +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/src/app.html b/src/app.html new file mode 100644 index 0000000..478a89a --- /dev/null +++ b/src/app.html @@ -0,0 +1,17 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + + <link rel="icon" href="%sveltekit.assets%/favicon.gif" /> + + <title>Japanese IPTV</title> + + %sveltekit.head% + </head> + + <body data-sveltekit-preload-data="hover"> + <div style="display: contents">%sveltekit.body%</div> + </body> +</html> diff --git a/src/lib/m3u.ts b/src/lib/m3u.ts new file mode 100644 index 0000000..e5138e0 --- /dev/null +++ b/src/lib/m3u.ts @@ -0,0 +1,36 @@ +export interface Channel { + groupTitle: string; + tvgId: string; + tvgLogo: string; + name: string; + url: string; +} + +export const parseM3U = (m3uContent: string): Channel[] => { + const lines = m3uContent.split('\n'); + const channels: Channel[] = []; + let currentChannel: Partial<Channel> = {}; + + for (const line of lines) + if (line.startsWith('#EXTINF')) { + const groupTitleMatch = line.match(/group-title="([^"]*)"/); + const tvgIdMatch = line.match(/tvg-id="([^"]*)"/); + const tvgLogoMatch = line.match(/tvg-logo="([^"]*)"/); + const nameMatch = line.match(/,([^,]*)$/); + + currentChannel = { + groupTitle: groupTitleMatch ? groupTitleMatch[1].trim() : '', + tvgId: tvgIdMatch ? tvgIdMatch[1].trim() : '', + tvgLogo: tvgLogoMatch ? tvgLogoMatch[1].trim() : '', + name: nameMatch ? nameMatch[1].trim() : '' + }; + } else if (line && !line.startsWith('#')) { + currentChannel.url = line.trim(); + + channels.push(currentChannel as Channel); + + currentChannel = {}; + } + + return channels; +}; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte new file mode 100644 index 0000000..5aeed62 --- /dev/null +++ b/src/routes/+page.svelte @@ -0,0 +1,116 @@ +<script lang="ts"> + import { browser } from '$app/environment'; + import { parseM3U, type Channel } from '$lib/m3u'; + import { onMount } from 'svelte'; + import Masonry from 'svelte-bricks'; + + let channels: Channel[] = []; + let filter = ''; + + onMount( + async () => + (channels = parseM3U( + await (await fetch('https://raw.githubusercontent.com/luongz/iptv-jp/main/jp.m3u')).text() + ) + .filter((channel) => channel.tvgLogo) + .map((channel, index) => ({ ...channel, id: index }))) + ); +</script> + +<input type="text" placeholder="Search" bind:value={filter} class="filter" /> + +<div class="channels"> + <Masonry + items={channels.filter((channel) => channel.tvgId.toLowerCase().includes(filter.toLowerCase()))} + minColWidth={(browser ? window.innerWidth < 768 : false) ? 150 : 200} + gap={15} + let:item + > + <div class="channel-container"> + <a href={`https://hlsjs.video-dev.org/demo/?src=${item.url}`} target="_blank"> + <img src={item.tvgLogo} alt="Channel icon" class="channel-icon" /> + </a> + + <span class="channel-title"> + <a + href={`https://hlsjs.video-dev.org/demo/?src=${item.url}`} + target="_blank" + class="channel-title-primary" + > + {item.tvgId} ({item.groupTitle}) + </a> + + <br /> + + <a + href={'#'} + on:click={() => { + navigator.clipboard.writeText(item.url); + }} + > + Copy M3U8 URL + </a> + </span> + </div> + </Masonry> +</div> + +<style> + a { + text-decoration: none; + color: #c6c6c6; + transition: color 0.1s ease-in-out; + } + + a:hover { + color: #2a6496; + } + + .channels { + padding: 0.5rem; + } + + .channel-container { + background: #1d1d1d; + padding: 1rem; + border-radius: 2.5%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; + position: relative; + } + + .channel-icon { + border-radius: 2.5%; + max-width: 100%; + max-height: 75%; + padding-bottom: 1rem; + } + + .filter { + width: calc(100% - 3rem); + padding: 1rem; + border-radius: 7px; + border: none; + margin: 0.5rem; + background: #242424; + border: none; + color: #fff; + font-size: 1rem; + } + + .filter:focus { + outline: none; + } + + .channel-title-primary { + color: #fff; + } + + :global(body) { + background-color: #121212; + color: #fff; + } +</style> |