Add list of guides

This commit is contained in:
freearhey 2023-11-17 07:53:33 +03:00
parent cfe99ee188
commit eae420e080
7 changed files with 172 additions and 1 deletions

View file

@ -1,6 +1,7 @@
<script>
import { getContext } from 'svelte'
import StreamsPopup from './StreamsPopup.svelte'
import GuidesPopup from './GuidesPopup.svelte'
import ChannelPopup from './ChannelPopup.svelte'
import Checkbox from './Checkbox.svelte'
import { downloadMode, selected, query } from '~/store'
@ -8,6 +9,7 @@
export let channel
const guides = channel._guides
const streams = channel._streams
const [name, country] = channel.id.split('.')
@ -25,6 +27,12 @@
const onClose = () => {
window.history.pushState({}, `iptv-org`, prevUrl)
}
const showGuides = () =>
open(
GuidesPopup,
{ guides, title: channel.displayName },
{ transitionBgProps: { duration: 0 }, transitionWindowProps: { duration: 0 } }
)
const showStreams = () =>
open(
StreamsPopup,
@ -136,7 +144,30 @@
</div>
<div class="w-40 px-6 sm:w-[150px]">
<div class="text-right flex justify-end space-x-3 items-center">
{#if streams.length}
{#if guides.length}
<button
on:click={showGuides}
class="text-sm text-gray-500 dark:text-gray-100 inline-flex space-x-1 flex items-center hover:text-blue-500 dark:hover:text-blue-400"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5"
>
<path
d="M5.25 12a.75.75 0 01.75-.75h.01a.75.75 0 01.75.75v.01a.75.75 0 01-.75.75H6a.75.75 0 01-.75-.75V12zM6 13.25a.75.75 0 00-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 00.75-.75V14a.75.75 0 00-.75-.75H6zM7.25 12a.75.75 0 01.75-.75h.01a.75.75 0 01.75.75v.01a.75.75 0 01-.75.75H8a.75.75 0 01-.75-.75V12zM8 13.25a.75.75 0 00-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 00.75-.75V14a.75.75 0 00-.75-.75H8zM9.25 10a.75.75 0 01.75-.75h.01a.75.75 0 01.75.75v.01a.75.75 0 01-.75.75H10a.75.75 0 01-.75-.75V10zM10 11.25a.75.75 0 00-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 00.75-.75V12a.75.75 0 00-.75-.75H10zM9.25 14a.75.75 0 01.75-.75h.01a.75.75 0 01.75.75v.01a.75.75 0 01-.75.75H10a.75.75 0 01-.75-.75V14zM12 9.25a.75.75 0 00-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 00.75-.75V10a.75.75 0 00-.75-.75H12zM11.25 12a.75.75 0 01.75-.75h.01a.75.75 0 01.75.75v.01a.75.75 0 01-.75.75H12a.75.75 0 01-.75-.75V12zM12 13.25a.75.75 0 00-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 00.75-.75V14a.75.75 0 00-.75-.75H12zM13.25 10a.75.75 0 01.75-.75h.01a.75.75 0 01.75.75v.01a.75.75 0 01-.75.75H14a.75.75 0 01-.75-.75V10zM14 11.25a.75.75 0 00-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 00.75-.75V12a.75.75 0 00-.75-.75H14z"
/>
<path
fill-rule="evenodd"
d="M5.75 2a.75.75 0 01.75.75V4h7V2.75a.75.75 0 011.5 0V4h.25A2.75 2.75 0 0118 6.75v8.5A2.75 2.75 0 0115.25 18H4.75A2.75 2.75 0 012 15.25v-8.5A2.75 2.75 0 014.75 4H5V2.75A.75.75 0 015.75 2zm-1 5.5c-.69 0-1.25.56-1.25 1.25v6.5c0 .69.56 1.25 1.25 1.25h10.5c.69 0 1.25-.56 1.25-1.25v-6.5c0-.69-.56-1.25-1.25-1.25H4.75z"
clip-rule="evenodd"
/>
</svg>
<div>{guides.length}</div>
<div>{pluralize(guides.length, 'guide')}</div>
</button>
{/if}{#if streams.length}
<button
on:click={showStreams}
class="text-sm text-gray-500 dark:text-gray-100 inline-flex space-x-1 flex items-center hover:text-blue-500 dark:hover:text-blue-400"

View file

@ -0,0 +1,40 @@
<script>
export let guide
const url = `https://${guide.site}`
</script>
<div
class="w-full inline-flex justify-between px-4 border-b-[1px] dark:border-gray-700 last:border-0"
>
<div class="flex space-x-4 items-center w-full min-h-11 py-3">
<div class="text-gray-400 w-8 text-sm">{guide.lang}</div>
<a
class="whitespace-nowrap text-sm text-gray-600 dark:text-gray-100 hover:text-blue-500 hover:underline inline-flex align-middle max-w-[50%] w-full"
href={url}
title={url}
target="_blank"
rel="noreferrer"
>
<span class="truncate">{guide.site}</span><span
class="inline-flex items-center pl-1 font-semibold text-gray-500 rounded-full"
>
<svg
class="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
></path>
</svg>
</span></a
>
<div class="text-right text-gray-400 text-sm w-full">{guide.site_name}</div>
</div>
</div>

View file

@ -0,0 +1,64 @@
<script>
import GuideItem from '~/components/GuideItem.svelte'
import { getContext } from 'svelte'
const { close } = getContext('simple-modal')
export let title = 'Guides'
export let guides = []
</script>
<div class="relative px-2 py-32 flex justify-center" on:keypress on:click|self={close}>
<div class="relative bg-white rounded-md shadow dark:bg-gray-800 w-full max-w-2xl">
<div
class="flex justify-between items-center py-4 pl-5 pr-4 rounded-t border-b dark:border-gray-700"
>
<h3 class="text-l font-medium text-gray-800 dark:text-white inline-flex items-center">
<span
class="inline-flex items-center pr-2 text-sm font-semibold text-gray-500 dark:text-gray-100 rounded-full"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5"
>
<path
d="M5.25 12a.75.75 0 01.75-.75h.01a.75.75 0 01.75.75v.01a.75.75 0 01-.75.75H6a.75.75 0 01-.75-.75V12zM6 13.25a.75.75 0 00-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 00.75-.75V14a.75.75 0 00-.75-.75H6zM7.25 12a.75.75 0 01.75-.75h.01a.75.75 0 01.75.75v.01a.75.75 0 01-.75.75H8a.75.75 0 01-.75-.75V12zM8 13.25a.75.75 0 00-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 00.75-.75V14a.75.75 0 00-.75-.75H8zM9.25 10a.75.75 0 01.75-.75h.01a.75.75 0 01.75.75v.01a.75.75 0 01-.75.75H10a.75.75 0 01-.75-.75V10zM10 11.25a.75.75 0 00-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 00.75-.75V12a.75.75 0 00-.75-.75H10zM9.25 14a.75.75 0 01.75-.75h.01a.75.75 0 01.75.75v.01a.75.75 0 01-.75.75H10a.75.75 0 01-.75-.75V14zM12 9.25a.75.75 0 00-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 00.75-.75V10a.75.75 0 00-.75-.75H12zM11.25 12a.75.75 0 01.75-.75h.01a.75.75 0 01.75.75v.01a.75.75 0 01-.75.75H12a.75.75 0 01-.75-.75V12zM12 13.25a.75.75 0 00-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 00.75-.75V14a.75.75 0 00-.75-.75H12zM13.25 10a.75.75 0 01.75-.75h.01a.75.75 0 01.75.75v.01a.75.75 0 01-.75.75H14a.75.75 0 01-.75-.75V10zM14 11.25a.75.75 0 00-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 00.75-.75V12a.75.75 0 00-.75-.75H14z"
/>
<path
fill-rule="evenodd"
d="M5.75 2a.75.75 0 01.75.75V4h7V2.75a.75.75 0 011.5 0V4h.25A2.75 2.75 0 0118 6.75v8.5A2.75 2.75 0 0115.25 18H4.75A2.75 2.75 0 012 15.25v-8.5A2.75 2.75 0 014.75 4H5V2.75A.75.75 0 015.75 2zm-1 5.5c-.69 0-1.25.56-1.25 1.25v6.5c0 .69.56 1.25 1.25 1.25h10.5c.69 0 1.25-.56 1.25-1.25v-6.5c0-.69-.56-1.25-1.25-1.25H4.75z"
clip-rule="evenodd"
/>
</svg>
</span>{title}
</h3>
<button
on:click={close}
type="button"
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-full text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"
>
<svg
class="w-5 h-5"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"
></path>
</svg>
</button>
</div>
<div class="overflow-y-auto overflow-x-hidden w-full">
<div class="p-6">
<div class="dark:border-gray-700 rounded-md border border-gray-200">
{#each guides as guide}
<GuideItem {guide} />
{/each}
</div>
</div>
</div>
</div>
</div>

View file

@ -5,6 +5,7 @@ curl -L -o src/data/blocklist.json https://iptv-org.github.io/api/blocklist.json
curl -L -o src/data/categories.json https://iptv-org.github.io/api/categories.json
curl -L -o src/data/channels.json https://iptv-org.github.io/api/channels.json
curl -L -o src/data/streams.json https://iptv-org.github.io/api/streams.json
curl -L -o src/data/guides.json https://iptv-org.github.io/api/guides.json
curl -L -o src/data/countries.json https://iptv-org.github.io/api/countries.json
curl -L -o src/data/languages.json https://iptv-org.github.io/api/languages.json
curl -L -o src/data/regions.json https://iptv-org.github.io/api/regions.json

View file

@ -8,6 +8,7 @@ import categories from '~/data/categories.json'
import blocklist from '~/data/blocklist.json'
import languages from '~/data/languages.json'
import streams from '~/data/streams.json'
import guides from '~/data/guides.json'
import isURL from 'validator/lib/isURL'
let _streams = streams.filter(stream => isURL(stream.url))
@ -19,6 +20,7 @@ data.subdivisions = _.keyBy(subdivisions, 'code')
data.languages = _.keyBy(languages, 'code')
data.categories = _.keyBy(categories, 'id')
data.streams = _.groupBy(_streams, 'channel')
data.guides = _.groupBy(guides, 'channel')
data.blocklist = _.groupBy(blocklist, 'channel')
data.channels = _.keyBy(channels, channel => channel.id.toLowerCase())
data.nameIndex = _.groupBy(channels, channel => channel.name.toLowerCase())

View file

@ -1,4 +1,5 @@
<script>
import GuideItem from '~/components/GuideItem.svelte'
import StreamItem from '~/components/StreamItem.svelte'
import HTMLPreview from '~/components/HTMLPreview.svelte'
import EditButton from '~/components/EditButton.svelte'
@ -9,6 +10,7 @@
let isLoading = false
let channel = data.channel
let streams = channel ? channel._streams : []
let guides = channel ? channel._guides : []
const structuredData = {
'@context': 'https://schema.org/',
@ -101,5 +103,23 @@
</div>
</div>
{/if}
{#if guides.length}
<div class="border rounded-md border-gray-200 dark:border-gray-700 dark:bg-gray-800 bg-white">
<div
class="flex justify-between items-center py-3 pl-5 pr-4 rounded-t border-b dark:border-gray-700"
>
<div class="w-1/3 overflow-hidden">
<h2 class="text-l font-medium text-gray-900 dark:text-white">Guides</h2>
</div>
</div>
<div class="overflow-y-auto overflow-x-hidden w-full p-6">
<div class="dark:border-gray-700 rounded-md border border-gray-200">
{#each guides as guide, index}
<GuideItem {guide} {index} />
{/each}
</div>
</div>
</div>
{/if}
</section>
</main>

View file

@ -54,6 +54,7 @@ export async function fetchChannels() {
'closed',
'replaced_by',
'streams',
'guides',
'is_nsfw',
'is_closed',
'is_blocked'
@ -139,6 +140,16 @@ async function loadAPI() {
return []
})
api.guides = await fetch('https://iptv-org.github.io/api/guides.json')
.then(r => r.json())
.then(data => (data.length ? data : []))
.then(data => _.sortBy(data, 'lang'))
.then(data => _.groupBy(data, 'channel'))
.catch(err => {
console.error(err)
return []
})
api.nameIndex = _.groupBy(api.channels, channel => channel.name.toLowerCase())
return api
@ -146,6 +157,7 @@ async function loadAPI() {
export function transformChannel(channel, data) {
channel._streams = data.streams[channel.id] || []
channel._guides = data.guides[channel.id] || []
channel._country = data.countries[channel.country]
channel._subdivision = data.subdivisions[channel.subdivision]
channel._languages = channel.languages.map(code => data.languages[code]).filter(i => i)
@ -164,6 +176,7 @@ export function transformChannel(channel, data) {
channel.is_closed = !!channel.closed || !!channel.replaced_by
channel.is_blocked = !!data.blocklist[channel.id]
channel.streams = channel._streams.length
channel.guides = channel._guides.length
const isChannelNameRepeated = data.nameIndex[channel.name.toLowerCase()].length > 1
channel.displayName = isChannelNameRepeated