Merge pull request #71 from iptv-org/update-codes-script

Update codes script
This commit is contained in:
Shadix A 2021-09-05 11:27:10 +02:00 committed by GitHub
commit 1e13bc479b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 1420 additions and 7852 deletions

1
.gh-pages/_items.json Normal file

File diff suppressed because one or more lines are too long

20
.gh-pages/app.js Normal file
View file

@ -0,0 +1,20 @@
document.addEventListener('alpine:init', () => {
Alpine.data('list', () => ({
isLoading: true,
query: '',
_query: '',
items: [],
search() {
this._query = this.query.toLowerCase()
},
async init() {
this.items = await fetch('_items.json')
.then(response => response.json())
.catch(console.log)
this.isLoading = false
}
}))
})

1
.gh-pages/codes.json Normal file

File diff suppressed because one or more lines are too long

154
.gh-pages/index.html Normal file
View file

@ -0,0 +1,154 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>iptv-org/epg</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css" />
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
<body style="background-color: #f6f8fa; min-height: 100vh">
<div class="navbar" style="background-color: transparent">
<div class="navbar-end">
<div class="navbar-item">
<a href="https://github.com/iptv-org/epg">
<span class="icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path
d="M256 32C132.3 32 32 134.9 32 261.7c0 101.5 64.2 187.5 153.2 217.9a17.56 17.56 0 003.8.4c8.3 0 11.5-6.1 11.5-11.4 0-5.5-.2-19.9-.3-39.1a102.4 102.4 0 01-22.6 2.7c-43.1 0-52.9-33.5-52.9-33.5-10.2-26.5-24.9-33.6-24.9-33.6-19.5-13.7-.1-14.1 1.4-14.1h.1c22.5 2 34.3 23.8 34.3 23.8 11.2 19.6 26.2 25.1 39.6 25.1a63 63 0 0025.6-6c2-14.8 7.8-24.9 14.2-30.7-49.7-5.8-102-25.5-102-113.5 0-25.1 8.7-45.6 23-61.6-2.3-5.8-10-29.2 2.2-60.8a18.64 18.64 0 015-.5c8.1 0 26.4 3.1 56.6 24.1a208.21 208.21 0 01112.2 0c30.2-21 48.5-24.1 56.6-24.1a18.64 18.64 0 015 .5c12.2 31.6 4.5 55 2.2 60.8 14.3 16.1 23 36.6 23 61.6 0 88.2-52.4 107.6-102.3 113.3 8 7.1 15.2 21.1 15.2 42.5 0 30.7-.3 55.5-.3 63 0 5.4 3.1 11.5 11.4 11.5a19.35 19.35 0 004-.4C415.9 449.2 480 363.1 480 261.7 480 134.9 379.7 32 256 32z"
/>
</svg>
</span>
</a>
</div>
</div>
</div>
<div class="section">
<div class="container" x-data="list">
<div class="columns is-centered">
<div class="column is-9">
<form class="mb-5" @submit.prevent="search()">
<div class="field has-addons">
<div class="control is-expanded">
<input
class="input"
type="search"
x-model="query"
placeholder="Search by channel name..."
/>
</div>
<div class="control">
<button class="button is-info" type="submit">
<span class="icon is-small is-right">
<svg
xmlns="http://www.w3.org/2000/svg"
style="width: 1.25rem; height: 1.25rem"
viewBox="0 0 512 512"
>
<path
fill="#ffffff"
d="M456.69 421.39L362.6 327.3a173.81 173.81 0 0034.84-104.58C397.44 126.38 319.06 48 222.72 48S48 126.38 48 222.72s78.38 174.72 174.72 174.72A173.81 173.81 0 00327.3 362.6l94.09 94.09a25 25 0 0035.3-35.3zM97.92 222.72a124.8 124.8 0 11124.8 124.8 124.95 124.95 0 01-124.8-124.8z"
/>
</svg>
</span>
</button>
</div>
</div>
</form>
<div x-show="isLoading" class="level">
<div class="level-item">Loading...</div>
</div>
<template x-for="country in items">
<div
class="card mb-3 is-shadowless"
style="border: 1px solid #dbdbdb"
x-data="{
count: 0,
get countryChannels() {
if (!_query) return country.channels
return country.channels.filter(c => {
return c.hash.includes(_query)
})
}
}"
x-show="countryChannels.length > 0"
x-init="$watch('countryChannels', value => {
count = value.length
})"
>
<div
class="card-header is-shadowless is-clickable"
@click="country.expanded = !country.expanded"
>
<span class="card-header-title" x-text="`${country.flag} ${country.name}`"></span>
<button class="card-header-icon" aria-label="more options">
<span class="icon">
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512">
<path
x-show="!country.expanded"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="48"
d="M112 184l144 144 144-144"
/>
<path
x-show="country.expanded"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="48"
d="M112 328l144-144 144 144"
/>
</svg>
</span>
</button>
</div>
<div class="card-content" x-show="country.expanded || (count > 0 && _query.length)">
<div class="table-container">
<table class="table is-fullwidth">
<thead>
<tr>
<th></th>
<th>Name</th>
<th>TVG-ID</th>
</tr>
</thead>
<tbody>
<template x-for="channel in countryChannels">
<tr>
<td style="width: 150px; text-align: center">
<img
loading="lazy"
x-show="channel.logo"
:src="channel.logo"
style="max-width: 100px; max-height: 50px; vertical-align: middle"
/>
</td>
<td class="is-vcentered">
<div x-text="channel.display_name"></div>
</td>
<td class="is-vcentered">
<code x-text="channel.tvg_id"></code>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
</template>
</div>
</div>
</div>
</div>
<script src="app.js"></script>
</body>
</html>

View file

@ -2,7 +2,7 @@ name: update-codes
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
- cron: '0 6 * * *'
jobs:
create-branch:
runs-on: ubuntu-latest
@ -29,23 +29,6 @@ jobs:
run: npm install
- name: Update Codes
run: npm run update-codes
- name: Upload Artifact
uses: actions/upload-artifact@v2
with:
name: codes.csv
path: codes.csv
commit-changes:
runs-on: ubuntu-latest
needs: update-codes
steps:
- name: Checkout
uses: actions/checkout@v2
with:
ref: bot/update-codes
- name: Download Artifacts
uses: actions/download-artifact@v2
with:
name: codes.csv
- name: Commit Changes
uses: stefanzweifel/git-auto-commit-action@v4
with:
@ -54,10 +37,10 @@ jobs:
commit_user_email: 84861620+iptv-bot[bot]@users.noreply.github.com
commit_author: 'iptv-bot[bot] <84861620+iptv-bot[bot]@users.noreply.github.com>'
branch: bot/update-codes
file_pattern: codes.csv
file_pattern: .gh-pages/_items.json .gh-pages/codes.json
pull-request:
if: ${{ github.ref == 'refs/heads/master' }}
needs: commit-changes
needs: update-codes
runs-on: ubuntu-latest
steps:
- name: Checkout
@ -93,3 +76,25 @@ jobs:
with:
github-token: ${{ secrets.PAT }}
number: ${{ steps.pr.outputs.pr_number }}
deploy:
needs: pull-request
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Generate Token
uses: tibdex/github-app-token@v1
id: generate-token
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@4.1.1
with:
branch: gh-pages
folder: .gh-pages
clean: false
token: ${{ steps.generate-token.outputs.token }}
git-config-name: iptv-bot
git-config-email: 84861620+iptv-bot[bot]@users.noreply.github.com
commit-message: '[Bot] Deploy to GitHub Pages'

View file

@ -9,6 +9,10 @@ EPG (Electronic Program Guide) for thousands of TV channels collected from diffe
To load a program guide, all you need to do is copy the link to one of the guides from the list below and paste it into your favorite player.
<details>
<summary>Expand</summary>
<br>
<table>
<thead>
<tr><th align="left">Country</th><th align="left">EPG</th></tr>
@ -77,7 +81,40 @@ To load a program guide, all you need to do is copy the link to one of the guide
</tbody>
</table>
After that, the program should appear. If this does not happen, make sure the channels in the playlist have the same IDs (tvg-id) as in the guide. A list of all currently supported codes can be found [here](codes.csv).
</details>
## List of supported channels
https://iptv-org.github.io/epg
## For Developers
You can also get a list of all available channels and their codes in JSON format by sending a GET request to:
```
https://iptv-org.github.io/epg/codes.json
```
If successful, you should get the following response:
<details>
<summary>Expand</summary>
<br>
```
[
...
{
"tvg_id": "CNNUSA.us",
"display_name": "CNN USA",
"logo": "https://cdn.tvpassport.com/image/station/100x100/cnn.png",
"country": "us"
},
...
]
```
</details>
## Contribution

7788
codes.csv

File diff suppressed because it is too large Load diff

3
package-lock.json generated
View file

@ -4,14 +4,15 @@
"requires": true,
"packages": {
"": {
"name": "epg",
"license": "MIT",
"dependencies": {
"axios": "^0.21.1",
"cheerio": "^1.0.0-rc.10",
"dayjs": "^1.10.4",
"epg-grabber": "^0.6.6",
"epg-parser": "^0.1.3",
"form-data": "^4.0.0",
"glob": "^7.1.6",
"html-to-text": "^7.0.0",
"iconv-lite": "^0.4.24",
"jsdom": "^16.5.0",

View file

@ -7,12 +7,12 @@
"author": "Arhey",
"license": "MIT",
"dependencies": {
"axios": "^0.21.1",
"cheerio": "^1.0.0-rc.10",
"dayjs": "^1.10.4",
"epg-grabber": "^0.6.6",
"epg-parser": "^0.1.3",
"form-data": "^4.0.0",
"glob": "^7.1.6",
"html-to-text": "^7.0.0",
"iconv-lite": "^0.4.24",
"jsdom": "^16.5.0",

1087
scripts/countries.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,32 +1,54 @@
const fs = require('fs')
const glob = require('glob')
const path = require('path')
const convert = require('xml-js')
const axios = require('axios')
const countries = require('./countries.json')
function main() {
let codes = {}
async function main() {
console.log('Starting...')
glob('sites/**/*.xml', null, function (er, files) {
files.forEach(filename => {
const channels = parseChannels(filename)
let codes = {}
for (const filename of files) {
const url = `https://iptv-org.github.io/epg/guides/${filename}.guide.xml`
console.log(`Loading '${url}'...`)
const file = await axios
.get(url)
.then(r => r.data)
.catch(console.log)
const channels = parseChannels(file)
channels.forEach(channel => {
if (!codes[channel.xmltv_id + channel.name]) {
codes[channel.xmltv_id + channel.name] = {
name: channel.name,
code: channel.xmltv_id
}
if (!codes[channel.tvg_id + channel.display_name]) {
codes[channel.tvg_id + channel.display_name] = channel
}
})
})
}
const sorted = Object.values(codes).sort((a, b) => {
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1
if (a.display_name.toLowerCase() < b.display_name.toLowerCase()) return -1
if (a.display_name.toLowerCase() > b.display_name.toLowerCase()) return 1
return 0
})
writeToFile('codes.csv', convertToCSV(sorted))
console.log('Done')
writeToFile('.gh-pages/codes.json', convertToJSON(sorted))
const _items = {}
countries.forEach(country => {
_items[country.code] = {
...country,
expanded: false,
channels: []
}
})
sorted.forEach(channel => {
const item = _items[channel.country]
if (item) {
channel.hash = `${channel.display_name}:${channel.tvg_id}`.toLowerCase()
item.channels.push(channel)
}
})
writeToFile('.gh-pages/_items.json', convertToJSON(_items))
console.log('Done')
}
function writeToFile(filename, data) {
@ -39,32 +61,60 @@ function writeToFile(filename, data) {
fs.writeFileSync(path.resolve(filename), data)
}
function convertToCSV(arr) {
let string = 'Channel Name,EPG Code (tvg-id)\n'
for (const item of arr) {
string += `${item.name},${item.code}\n`
}
return string
function convertToJSON(arr) {
return JSON.stringify(arr)
}
function parseChannels(filename) {
if (!filename) throw new Error('Path to [site].channels.xml is missing')
console.log(`Loading '${filename}'...`)
function parseChannels(file) {
const result = convert.xml2js(file)
const tv = result.elements.find(el => el.name === 'tv')
const xml = fs.readFileSync(path.resolve(filename), { encoding: 'utf-8' })
const result = convert.xml2js(xml)
const site = result.elements.find(el => el.name === 'site')
const channels = site.elements.find(el => el.name === 'channels')
return channels.elements
return tv.elements
.filter(el => el.name === 'channel')
.map(el => {
const channel = el.attributes
channel.name = el.elements.find(el => el.type === 'text').text
.map(channel => {
const tvg_id = (channel.attributes || { id: '' }).id
const logo = (channel.elements.find(el => el.name === 'icon') || { attributes: { src: '' } })
.attributes.src
const displayName = channel.elements.find(el => el.name === 'display-name')
const display_name = displayName
? displayName.elements.find(el => el.type === 'text').text
: null
const country = tvg_id.split('.')[1]
return channel
return { tvg_id, display_name, logo, country }
})
}
const files = [
'andorradifusio.ad',
'astro.com.my',
'comteco.com.bo',
'cosmote.gr',
'digiturk.com.tr',
'elcinema.com',
'guidatv.sky.it',
'hd-plus.de',
'm.tv.sms.cz',
'maxtv.hrvatskitelekom.hr',
'mediaset.it',
'meo.pt',
'mi.tv',
'mncvision.id',
'ontvtonight.com',
'programacion-tv.elpais.com',
'programetv.ro',
'programme-tv.net',
'programtv.onet.pl',
'telkussa.fi',
'tv.lv',
'tv.yandex.ru',
'tvgid.ua',
'tvguide.com',
'tvprofil.com',
'tvtv.ca',
'tvtv.us',
'vidio.com',
'znbc.co.zm'
]
main()