Update CONTRIBUTING.md

This commit is contained in:
freearhey
2025-10-22 02:29:27 +03:00
parent bc5677b4bb
commit 38e579bb6f

View File

@@ -1,437 +1,437 @@
# Contributing Guide # Contributing Guide
- [How to?](#how-to) - [How to?](#how-to)
- [Project Structure](#project-structure) - [Project Structure](#project-structure)
- [Scripts](#scripts) - [Scripts](#scripts)
## How to? ## How to?
### How to add a channel to the guide? ### How to add a channel to the guide?
To ask for help with adding a channel simply fill out this [form](https://github.com/iptv-org/epg/issues/new?assignees=&labels=channel+request&projects=&template=2_channel-request.yml). To ask for help with adding a channel simply fill out this [form](https://github.com/iptv-org/epg/issues/new?assignees=&labels=channel+request&projects=&template=2_channel-request.yml).
If you want to add a channel to the list yourself, here are the instructions on how to do it. If you want to add a channel to the list yourself, here are the instructions on how to do it.
First select the site from [SITES.md](SITES.md) that you know has a guide for the channel you need. Then go to the folder with its config and open the file `*.channels.xml`. First select the site from [SITES.md](SITES.md) that you know has a guide for the channel you need. Then go to the folder with its config and open the file `*.channels.xml`.
Make sure that the desired channel is not already in the list. If it is not, simply add its description to the end of the list as shown here: Make sure that the desired channel is not already in the list. If it is not, simply add its description to the end of the list as shown here:
```xml ```xml
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<channels> <channels>
... ...
<channel site="SITE" lang="LANGUAGE_CODE" xmltv_id="CHANNEL_ID" site_id="SITE_ID">CHANNEL_NAME</channel> <channel site="SITE" site_id="SITE_ID" lang="LANGUAGE_CODE" xmltv_id="STREAM_ID">CHANNEL_NAME</channel>
</channels> </channels>
``` ```
| Attribute | Description | Example | | Attribute | Description | Example |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| SITE | Site domain name. | `example.com` | | SITE | Site domain name. | `example.com` |
| LANGUAGE_CODE | Language of the guide ([ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) code). | `en` | | SITE_ID | Unique ID of the channel used in the source. | `hbo` |
| CHANNEL_ID | ID of the channel. Full list of supported channels with corresponding ID could be found on [iptv-org.github.io](https://iptv-org.github.io/). | `HBO.us@East` | | LANGUAGE_CODE | Language of the guide ([ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) code). | `en` |
| SITE_ID | Unique ID of the channel used in the source. | `hbo` | | STREAM_ID | ID of the stream (`<CHANNEL_ID>@<FEED_ID>`) for which the guide is intended. Full list of supported channels with corresponding ID could be found on [iptv-org.github.io](https://iptv-org.github.io/). | `HBO.us@East` |
| CHANNEL_NAME | Name of the channel used in the source. | `HBO East` | | CHANNEL_NAME | Name of the channel used in the source. | `HBO East` |
After that just [commit](https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/about-commits) all changes and send a [pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests). After that just [commit](https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/about-commits) all changes and send a [pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests).
### How to report broken guide? ### How to report broken guide?
If you start to get errors when downloading the guide or if nothing loads at all, please let us know via this [form](https://github.com/iptv-org/epg/issues/new?assignees=&labels=broken+guide&projects=&template=3_broken-guide.yml). If you start to get errors when downloading the guide or if nothing loads at all, please let us know via this [form](https://github.com/iptv-org/epg/issues/new?assignees=&labels=broken+guide&projects=&template=3_broken-guide.yml).
### How to add a new source to the repository? ### How to add a new source to the repository?
If you are not familiar with javascript programming, you can ask for help from other community members through this [form](https://github.com/iptv-org/epg/issues/new?assignees=&labels=source+request&projects=&template=1_source-request.yml). Otherwise, below are the instructions for you. If you are not familiar with javascript programming, you can ask for help from other community members through this [form](https://github.com/iptv-org/epg/issues/new?assignees=&labels=source+request&projects=&template=1_source-request.yml). Otherwise, below are the instructions for you.
To start with, you need to create a new folder in the [/sites](/sites) folder and put at least 4 files in it: To start with, you need to create a new folder in the [/sites](/sites) folder and put at least 4 files in it:
<details> <details>
<summary>example.com.config.js</summary> <summary>example.com.config.js</summary>
<br> <br>
This file describes what kind of request we need to send to get the guide for a particular channel on a certain date and how to parse the response. This file describes what kind of request we need to send to get the guide for a particular channel on a certain date and how to parse the response.
```js ```js
module.exports = { module.exports = {
site: 'example.com', site: 'example.com',
url({ channel, date }) { url({ channel, date }) {
return `https://example.com/api/${channel.site_id}/${date.format('YYYY-MM-DD')}` return `https://example.com/api/${channel.site_id}/${date.format('YYYY-MM-DD')}`
}, },
parser(context) { parser(context) {
try { try {
return JSON.parse(context.content) return JSON.parse(context.content)
} catch { } catch {
return [] return []
} }
} }
} }
``` ```
### Context Object ### Context Object
From each function in `config.js` you can access a `context` object containing the following data: From each function in `config.js` you can access a `context` object containing the following data:
- `channel`: The object describing the current channel (xmltv_id, site_id, name, lang) - `channel`: The object describing the current channel (xmltv_id, site_id, name, lang)
- `date`: The 'dayjs' instance with the requested date - `date`: The 'dayjs' instance with the requested date
- `content`: The response data as a String - `content`: The response data as a String
- `buffer`: The response data as an ArrayBuffer - `buffer`: The response data as an ArrayBuffer
- `headers`: The response headers - `headers`: The response headers
- `request`: The request config - `request`: The request config
- `cached`: A boolean to check whether this request was cached or not - `cached`: A boolean to check whether this request was cached or not
### Program Properties ### Program Properties
List of properties that can be assigned to each program during parsing. List of properties that can be assigned to each program during parsing.
| Property | Aliases | Type | Required | | Property | Aliases | Type | Required |
| --------------- | -------------------------------- | ------------------------------------------ | -------- | | --------------- | -------------------------------- | ------------------------------------------ | -------- |
| start | | `String \| Number \| Date()` | true | | start | | `String \| Number \| Date()` | true |
| stop | | `String \| Number \| Date()` | true | | stop | | `String \| Number \| Date()` | true |
| title | titles | `String \| Object \| String[] \| Object[]` | true | | title | titles | `String \| Object \| String[] \| Object[]` | true |
| subTitle | subTitles, sub_title, sub_titles | `String \| Object \| String[] \| Object[]` | false | | subTitle | subTitles, sub_title, sub_titles | `String \| Object \| String[] \| Object[]` | false |
| description | desc, descriptions | `String \| Object \| String[] \| Object[]` | false | | description | desc, descriptions | `String \| Object \| String[] \| Object[]` | false |
| date | | `String \| Number \| Date()` | false | | date | | `String \| Number \| Date()` | false |
| category | categories | `String \| Object \| String[] \| Object[]` | false | | category | categories | `String \| Object \| String[] \| Object[]` | false |
| keyword | keywords | `String \| Object \| String[] \| Object[]` | false | | keyword | keywords | `String \| Object \| String[] \| Object[]` | false |
| language | languages | `String \| Object \| String[] \| Object[]` | false | | language | languages | `String \| Object \| String[] \| Object[]` | false |
| origLanguage | origLanguages | `String \| Object \| String[] \| Object[]` | false | | origLanguage | origLanguages | `String \| Object \| String[] \| Object[]` | false |
| length | | `String \| Object \| String[] \| Object[]` | false | | length | | `String \| Object \| String[] \| Object[]` | false |
| url | urls | `String \| Object \| String[] \| Object[]` | false | | url | urls | `String \| Object \| String[] \| Object[]` | false |
| country | countries | `String \| Object \| String[] \| Object[]` | false | | country | countries | `String \| Object \| String[] \| Object[]` | false |
| video | | `Object` | false | | video | | `Object` | false |
| audio | | `Object` | false | | audio | | `Object` | false |
| season | | `String \| Number` | false | | season | | `String \| Number` | false |
| episode | | `String \| Number` | false | | episode | | `String \| Number` | false |
| episodeNumber | episodeNum, episodeNumbers | `Object` | false | | episodeNumber | episodeNum, episodeNumbers | `Object` | false |
| previouslyShown | | `String \| Object \| String[] \| Object[]` | false | | previouslyShown | | `String \| Object \| String[] \| Object[]` | false |
| premiere | | `String \| Object \| String[] \| Object[]` | false | | premiere | | `String \| Object \| String[] \| Object[]` | false |
| lastChance | | `String \| Object \| String[] \| Object[]` | false | | lastChance | | `String \| Object \| String[] \| Object[]` | false |
| new | | `Boolean` | false | | new | | `Boolean` | false |
| subtitles | | `Object \| Object[]` | false | | subtitles | | `Object \| Object[]` | false |
| rating | ratings | `String \| Object \| String[] \| Object[]` | false | | rating | ratings | `String \| Object \| String[] \| Object[]` | false |
| starRating | starRatings | `String \| Object \| String[] \| Object[]` | false | | starRating | starRatings | `String \| Object \| String[] \| Object[]` | false |
| review | reviews | `String \| Object \| String[] \| Object[]` | false | | review | reviews | `String \| Object \| String[] \| Object[]` | false |
| director | directors | `String \| Object \| String[] \| Object[]` | false | | director | directors | `String \| Object \| String[] \| Object[]` | false |
| actor | actors | `String \| Object \| String[] \| Object[]` | false | | actor | actors | `String \| Object \| String[] \| Object[]` | false |
| writer | writers | `String \| Object \| String[] \| Object[]` | false | | writer | writers | `String \| Object \| String[] \| Object[]` | false |
| adapter | adapters | `String \| Object \| String[] \| Object[]` | false | | adapter | adapters | `String \| Object \| String[] \| Object[]` | false |
| producer | producers | `String \| Object \| String[] \| Object[]` | false | | producer | producers | `String \| Object \| String[] \| Object[]` | false |
| presenter | presenters | `String \| Object \| String[] \| Object[]` | false | | presenter | presenters | `String \| Object \| String[] \| Object[]` | false |
| composer | composers | `String \| Object \| String[] \| Object[]` | false | | composer | composers | `String \| Object \| String[] \| Object[]` | false |
| editor | editors | `String \| Object \| String[] \| Object[]` | false | | editor | editors | `String \| Object \| String[] \| Object[]` | false |
| commentator | commentators | `String \| Object \| String[] \| Object[]` | false | | commentator | commentators | `String \| Object \| String[] \| Object[]` | false |
| guest | guests | `String \| Object \| String[] \| Object[]` | false | | guest | guests | `String \| Object \| String[] \| Object[]` | false |
| image | images | `String \| Object \| String[] \| Object[]` | false | | image | images | `String \| Object \| String[] \| Object[]` | false |
| icon | icons | `String \| Object \| String[] \| Object[]` | false | | icon | icons | `String \| Object \| String[] \| Object[]` | false |
Example: Example:
```js ```js
{ {
start: '2021-03-19T06:00:00.000Z', start: '2021-03-19T06:00:00.000Z',
stop: '2021-03-19T06:30:00.000Z', stop: '2021-03-19T06:30:00.000Z',
title: 'Program 1', title: 'Program 1',
subTitle: 'Sub-title & 1', subTitle: 'Sub-title & 1',
description: 'Description for Program 1', description: 'Description for Program 1',
date: '2022-05-06', date: '2022-05-06',
categories: ['Comedy', 'Drama'], categories: ['Comedy', 'Drama'],
keywords: [ keywords: [
{ lang: 'en', value: 'physical-comedy' }, { lang: 'en', value: 'physical-comedy' },
{ lang: 'en', value: 'romantic' } { lang: 'en', value: 'romantic' }
], ],
language: 'English', language: 'English',
origLanguage: { lang: 'en', value: 'French' }, origLanguage: { lang: 'en', value: 'French' },
length: { units: 'minutes', value: '60' }, length: { units: 'minutes', value: '60' },
url: 'http://example.com/title.html', url: 'http://example.com/title.html',
country: 'US', country: 'US',
video: { video: {
present: 'yes', present: 'yes',
colour: 'no', colour: 'no',
aspect: '16:9', aspect: '16:9',
quality: 'HDTV' quality: 'HDTV'
}, },
audio: { audio: {
present: 'yes', present: 'yes',
stereo: 'Dolby Digital' stereo: 'Dolby Digital'
}, },
season: 9, season: 9,
episode: 239, episode: 239,
previouslyShown: [{ start: '20080711000000', channel: 'channel-two.tv' }], previouslyShown: [{ start: '20080711000000', channel: 'channel-two.tv' }],
premiere: 'First time on British TV', premiere: 'First time on British TV',
lastChance: [{ lang: 'en', value: 'Last time on this channel' }], lastChance: [{ lang: 'en', value: 'Last time on this channel' }],
new: true, new: true,
subtitles: [ subtitles: [
{ type: 'teletext', language: 'English' }, { type: 'teletext', language: 'English' },
{ type: 'onscreen', language: [{ lang: 'en', value: 'Spanish' }] } { type: 'onscreen', language: [{ lang: 'en', value: 'Spanish' }] }
], ],
rating: { rating: {
system: 'MPAA', system: 'MPAA',
value: 'P&G', value: 'P&G',
icon: 'http://example.com/pg_symbol.png' icon: 'http://example.com/pg_symbol.png'
}, },
starRatings: [ starRatings: [
{ {
system: 'TV Guide', system: 'TV Guide',
value: '4/5', value: '4/5',
icon: [{ src: 'stars.png', width: 100, height: 100 }] icon: [{ src: 'stars.png', width: 100, height: 100 }]
}, },
{ {
system: 'IMDB', system: 'IMDB',
value: '8/10' value: '8/10'
} }
], ],
reviews: [ reviews: [
{ {
type: 'text', type: 'text',
source: 'Rotten Tomatoes', source: 'Rotten Tomatoes',
reviewer: 'Joe Bloggs', reviewer: 'Joe Bloggs',
lang: 'en', lang: 'en',
value: 'This is a fantastic show!' value: 'This is a fantastic show!'
}, },
{ {
type: 'text', type: 'text',
source: 'IDMB', source: 'IDMB',
reviewer: 'Jane Doe', reviewer: 'Jane Doe',
lang: 'en', lang: 'en',
value: 'I love this show!' value: 'I love this show!'
}, },
{ {
type: 'url', type: 'url',
source: 'Rotten Tomatoes', source: 'Rotten Tomatoes',
reviewer: 'Joe Bloggs', reviewer: 'Joe Bloggs',
lang: 'en', lang: 'en',
value: 'https://example.com/programme_one_review' value: 'https://example.com/programme_one_review'
} }
], ],
directors: [ directors: [
{ {
value: 'Director 1', value: 'Director 1',
url: { value: 'http://example.com/director1.html', system: 'TestSystem' }, url: { value: 'http://example.com/director1.html', system: 'TestSystem' },
image: [ image: [
'https://example.com/image1.jpg', 'https://example.com/image1.jpg',
{ {
value: 'https://example.com/image2.jpg', value: 'https://example.com/image2.jpg',
type: 'person', type: 'person',
size: '2', size: '2',
system: 'TestSystem', system: 'TestSystem',
orient: 'P' orient: 'P'
} }
] ]
}, },
'Director 2' 'Director 2'
], ],
actors: ['Actor 1', 'Actor 2'], actors: ['Actor 1', 'Actor 2'],
writer: 'Writer 1', writer: 'Writer 1',
producers: 'Roger Dobkowitz', producers: 'Roger Dobkowitz',
presenters: 'Drew Carey', presenters: 'Drew Carey',
images: [ images: [
{ {
type: 'poster', type: 'poster',
size: '1', size: '1',
orient: 'P', orient: 'P',
system: 'tvdb', system: 'tvdb',
value: 'https://tvdb.com/programme_one_poster_1.jpg' value: 'https://tvdb.com/programme_one_poster_1.jpg'
}, },
{ {
type: 'poster', type: 'poster',
size: '2', size: '2',
orient: 'P', orient: 'P',
system: 'tmdb', system: 'tmdb',
value: 'https://tmdb.com/programme_one_poster_2.jpg' value: 'https://tmdb.com/programme_one_poster_2.jpg'
}, },
{ {
type: 'backdrop', type: 'backdrop',
size: '3', size: '3',
orient: 'L', orient: 'L',
system: 'tvdb', system: 'tvdb',
value: 'https://tvdb.com/programme_one_backdrop_3.jpg' value: 'https://tvdb.com/programme_one_backdrop_3.jpg'
} }
], ],
icon: 'https://example.com/images/Program1.png?x=шеллы&sid=777' icon: 'https://example.com/images/Program1.png?x=шеллы&sid=777'
} }
``` ```
</details> </details>
<details> <details>
<summary>example.com.test.js</summary> <summary>example.com.test.js</summary>
<br> <br>
With this file we can test the previously created config and make sure it works as you expect. With this file we can test the previously created config and make sure it works as you expect.
```js ```js
const { parser, url } = require('./example.com.config.js') const { parser, url } = require('./example.com.config.js')
const dayjs = require('dayjs') const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc') const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat') const customParseFormat = require('dayjs/plugin/customParseFormat')
dayjs.extend(customParseFormat) dayjs.extend(customParseFormat)
dayjs.extend(utc) dayjs.extend(utc)
const date = dayjs.utc('2025-01-12', 'YYYY-MM-DD').startOf('d') const date = dayjs.utc('2025-01-12', 'YYYY-MM-DD').startOf('d')
const channel = { site_id: 'bbc1', xmltv_id: 'BBCOne.uk' } const channel = { site_id: 'bbc1', xmltv_id: 'BBCOne.uk' }
it('can generate valid url', () => { it('can generate valid url', () => {
expect(url({ channel, date })).toBe('https://example.com/api/bbc1/2025-01-12') expect(url({ channel, date })).toBe('https://example.com/api/bbc1/2025-01-12')
}) })
it('can parse response', () => { it('can parse response', () => {
const content = const content =
'[{"title":"Program 1","start":"2025-01-12T00:00:00.000Z","stop":"2025-01-12T00:30:00.000Z"},{"title":"Program 2","start":"2025-01-12T00:30:00.000Z","stop":"2025-01-12T01:00:00.000Z"}]' '[{"title":"Program 1","start":"2025-01-12T00:00:00.000Z","stop":"2025-01-12T00:30:00.000Z"},{"title":"Program 2","start":"2025-01-12T00:30:00.000Z","stop":"2025-01-12T01:00:00.000Z"}]'
const results = parser({ content }) const results = parser({ content })
expect(results.length).toBe(2) expect(results.length).toBe(2)
expect(results[0]).toMatchObject({ expect(results[0]).toMatchObject({
title: 'Program 1', title: 'Program 1',
start: '2025-01-12T00:00:00.000Z', start: '2025-01-12T00:00:00.000Z',
stop: '2025-01-12T00:30:00.000Z' stop: '2025-01-12T00:30:00.000Z'
}) })
expect(results[1]).toMatchObject({ expect(results[1]).toMatchObject({
title: 'Program 2', title: 'Program 2',
start: '2025-01-12T00:30:00.000Z', start: '2025-01-12T00:30:00.000Z',
stop: '2025-01-12T01:00:00.000Z' stop: '2025-01-12T01:00:00.000Z'
}) })
}) })
it('can handle empty guide', () => { it('can handle empty guide', () => {
const result = parser({ const result = parser({
date, date,
channel, channel,
content: '' content: ''
}) })
expect(result).toMatchObject([]) expect(result).toMatchObject([])
}) })
``` ```
Detailed documentation for the tests can be found here: https://jestjs.io/docs/using-matchers Detailed documentation for the tests can be found here: https://jestjs.io/docs/using-matchers
</details> </details>
<details> <details>
<summary>example.com.channels.xml</summary> <summary>example.com.channels.xml</summary>
<br> <br>
This file contains a list of channels available at the source. This file contains a list of channels available at the source.
```xml ```xml
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<channels> <channels>
<channel site="example.com" lang="en" xmltv_id="BBCOne.uk" site_id="bbc1">BBC One</channel> <channel site="example.com" lang="en" xmltv_id="BBCOne.uk" site_id="bbc1">BBC One</channel>
</channels> </channels>
``` ```
</details> </details>
<details> <details>
<summary>readme.md</summary> <summary>readme.md</summary>
<br> <br>
This file contains instructions on how to use this config. This file contains instructions on how to use this config.
```` ````
# example.com # example.com
https://example.com https://example.com
### Download the guide ### Download the guide
```sh ```sh
npm run grab --- --site=example.com npm run grab --- --site=example.com
``` ```
### Test ### Test
```sh ```sh
npm test --- example.com npm test --- example.com
``` ```
```` ````
</details> </details>
The fastest way to create all these files is to use the command: The fastest way to create all these files is to use the command:
```sh ```sh
npm run sites:init --- example.com npm run sites:init --- example.com
``` ```
After you finish working on the files you can make sure that everything works by running the config test: After you finish working on the files you can make sure that everything works by running the config test:
``` ```
npm test --- example.com npm test --- example.com
``` ```
Then check that all channels have the correct `xmltv-id`: Then check that all channels have the correct `xmltv-id`:
``` ```
npm run channels:validate sites/example.com/example.com.channels.xml npm run channels:validate sites/example.com/example.com.channels.xml
``` ```
And then try downloading the guide itself: And then try downloading the guide itself:
``` ```
npm run grab --- example.com npm run grab --- example.com
``` ```
If everything goes well just [commit](https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/about-commits) all changes and send us a [pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests). If everything goes well just [commit](https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/about-commits) all changes and send us a [pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests).
### How to map the channels? ### How to map the channels?
In order for the guides to be linked with playlists from [iptv-org/iptv](https://github.com/iptv-org/iptv) and also with our other projects, each channel must have the same ID in the description as in our [iptv-org/database](https://github.com/iptv-org/database). In order for the guides to be linked with playlists from [iptv-org/iptv](https://github.com/iptv-org/iptv) and also with our other projects, each channel must have the same ID in the description as in our [iptv-org/database](https://github.com/iptv-org/database).
To check this, select one of the sites in the [SITES.md](SITES.md), open its `*.channels.xml` file and check that all channels have a valid `xmltv_id`. A list of all channels with corresponding IDs can be found at [iptv-org.github.io](https://iptv-org.github.io/). To check this, select one of the sites in the [SITES.md](SITES.md), open its `*.channels.xml` file and check that all channels have a valid `xmltv_id`. A list of all channels with corresponding IDs can be found at [iptv-org.github.io](https://iptv-org.github.io/).
If the channel is not in our database yet, you can add it to it through this [form](https://github.com/iptv-org/database/issues/new?assignees=&labels=channels%3Aadd&projects=&template=1_channels_add.yml&title=Add%3A+). If the channel is not in our database yet, you can add it to it through this [form](https://github.com/iptv-org/database/issues/new?assignees=&labels=channels%3Aadd&projects=&template=1_channels_add.yml&title=Add%3A+).
If the `*.channels.xml` file contains many channels without `xmltv_id`, you can speed up the process by running the command in the [Console](https://en.wikipedia.org/wiki/Windows_Console) (or [Terminal](<https://en.wikipedia.org/wiki/Terminal_(macOS)>) if you have macOS): If the `*.channels.xml` file contains many channels without `xmltv_id`, you can speed up the process by running the command in the [Console](https://en.wikipedia.org/wiki/Windows_Console) (or [Terminal](<https://en.wikipedia.org/wiki/Terminal_(macOS)>) if you have macOS):
```sh ```sh
npm run channels:edit path/to/channels.xml npm run channels:edit path/to/channels.xml
``` ```
This way, you can map channels by simply selecting the proper ID from the list: This way, you can map channels by simply selecting the proper ID from the list:
```sh ```sh
? Select channel ID for "BBC One" (bbc1): (Use arrow keys) ? Select channel ID for "BBC One" (bbc1): (Use arrow keys)
BBCOne.uk (BBC One, BBC1, BBC Television, BBC Television Service) BBCOne.uk (BBC One, BBC1, BBC Television, BBC Television Service)
BBCOneHD.uk (BBC One HD) BBCOneHD.uk (BBC One HD)
Type... Type...
Skip Skip
``` ```
Once complete, [commit](https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/about-commits) all changes and send a [pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests). Once complete, [commit](https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/about-commits) all changes and send a [pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests).
## Project Structure ## Project Structure
- `.github/` - `.github/`
- `ISSUE_TEMPLATE/`: issue templates for the repository. - `ISSUE_TEMPLATE/`: issue templates for the repository.
- `workflows`: contains [GitHub actions](https://docs.github.com/en/actions/quickstart) workflows. - `workflows`: contains [GitHub actions](https://docs.github.com/en/actions/quickstart) workflows.
- `CODE_OF_CONDUCT.md`: rules you shouldn't break if you don't want to get banned. - `CODE_OF_CONDUCT.md`: rules you shouldn't break if you don't want to get banned.
- `scripts/`: contains all scripts used in the repository. - `scripts/`: contains all scripts used in the repository.
- `sites/`: contains configurations, channel lists and tests for all sites. - `sites/`: contains configurations, channel lists and tests for all sites.
- `tests/`: contains tests to check the scripts. - `tests/`: contains tests to check the scripts.
- `CONTRIBUTING.md`: file you are currently reading. - `CONTRIBUTING.md`: file you are currently reading.
- `README.md`: project description displayed on the home page. - `README.md`: project description displayed on the home page.
- `SITES.md`: list of all supported sites and their current status. - `SITES.md`: list of all supported sites and their current status.
## Scripts ## Scripts
These scripts are created to automate routine processes in the repository and make it a bit easier to maintain. These scripts are created to automate routine processes in the repository and make it a bit easier to maintain.
For scripts to work, you must have [Node.js](https://nodejs.org/en) installed on your computer. For scripts to work, you must have [Node.js](https://nodejs.org/en) installed on your computer.
To run scripts use the `npm run <script-name>` command. To run scripts use the `npm run <script-name>` command.
- `act:check`: allows to test the [check](https://github.com/iptv-org/iptv/blob/master/.github/workflows/check.yml) workflow locally. Depends on [nektos/act](https://github.com/nektos/act). - `act:check`: allows to test the [check](https://github.com/iptv-org/iptv/blob/master/.github/workflows/check.yml) workflow locally. Depends on [nektos/act](https://github.com/nektos/act).
- `act:update`: allows to test the [update](https://github.com/iptv-org/iptv/blob/master/.github/workflows/update.yml) workflow locally. Depends on [nektos/act](https://github.com/nektos/act). - `act:update`: allows to test the [update](https://github.com/iptv-org/iptv/blob/master/.github/workflows/update.yml) workflow locally. Depends on [nektos/act](https://github.com/nektos/act).
- `api:load`: downloads the latest channels data from the [iptv-org/api](https://github.com/iptv-org/api). - `api:load`: downloads the latest channels data from the [iptv-org/api](https://github.com/iptv-org/api).
- `api:generate`: generates a JSON file with all channels for the [iptv-org/api](https://github.com/iptv-org/api) repository. - `api:generate`: generates a JSON file with all channels for the [iptv-org/api](https://github.com/iptv-org/api) repository.
- `channels:lint`: сhecks the channel lists for syntax errors. - `channels:lint`: сhecks the channel lists for syntax errors.
- `channels:parse`: generates a list of channels based on the site configuration. - `channels:parse`: generates a list of channels based on the site configuration.
- `channels:edit`: utility for quick channels mapping. - `channels:edit`: utility for quick channels mapping.
- `channels:validate`: checks the description of channels for errors. - `channels:validate`: checks the description of channels for errors.
- `sites:init`: creates a new site config from the template. - `sites:init`: creates a new site config from the template.
- `sites:update`: updates the list of sites and their status in [SITES.md](SITES.md). - `sites:update`: updates the list of sites and their status in [SITES.md](SITES.md).
- `grab`: downloads a program from a specified source. - `grab`: downloads a program from a specified source.
- `serve`: starts the [web server](https://github.com/vercel/serve). - `serve`: starts the [web server](https://github.com/vercel/serve).
- `lint`: сhecks the scripts for syntax errors. - `lint`: сhecks the scripts for syntax errors.
- `test`: runs a test of all the scripts described above. - `test`: runs a test of all the scripts described above.