mirror of
https://github.com/iptv-org/iptv
synced 2026-09-10 05:47:39 -04:00
Merge pull request #36266 from iptv-org/patch-2026.04.2
Patch 2026.04.2
This commit is contained in:
@@ -1,28 +0,0 @@
|
|||||||
name: 🔍 Channel search
|
|
||||||
description: Ask for help in finding a link to the channel stream.
|
|
||||||
title: 'Find: '
|
|
||||||
labels: ['channel search']
|
|
||||||
|
|
||||||
body:
|
|
||||||
- type: input
|
|
||||||
id: stream_id
|
|
||||||
attributes:
|
|
||||||
label: Stream ID (required)
|
|
||||||
description: Unique ID of the channel and feed from [iptv-org.github.io](https://iptv-org.github.io/). If you cannot find the channel or feed you are looking for in the list, please let us know via one of the [forms](https://github.com/iptv-org/database/issues/new/choose) before posting your request.
|
|
||||||
placeholder: 'BBCAmerica.us@East'
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: notes
|
|
||||||
attributes:
|
|
||||||
label: Notes
|
|
||||||
description: 'Any additional information that may help find a link to the stream faster?'
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: Contributing Guide
|
|
||||||
description: 'Please read this guide before posting your request'
|
|
||||||
options:
|
|
||||||
- label: I have read [Contributing Guide](https://github.com/iptv-org/iptv/blob/master/CONTRIBUTING.md)
|
|
||||||
required: true
|
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
|
- name: 🔍 Channel search
|
||||||
|
url: https://github.com/iptv-org/iptv/discussions/new?category=channel-search
|
||||||
|
about: Ask the community for help finding a link to the stream of the channel
|
||||||
- name: 💡 Ideas
|
- name: 💡 Ideas
|
||||||
url: https://github.com/orgs/iptv-org/discussions/categories/ideas
|
url: https://github.com/orgs/iptv-org/discussions/categories/ideas
|
||||||
about: Share ideas for new features
|
about: Share ideas for new features
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
name: stale
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * *'
|
|
||||||
permissions:
|
|
||||||
actions: write
|
|
||||||
issues: write
|
|
||||||
jobs:
|
|
||||||
stale:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: tibdex/github-app-token@v1.8.2
|
|
||||||
id: create-app-token
|
|
||||||
with:
|
|
||||||
app_id: ${{ secrets.APP_ID }}
|
|
||||||
private_key: ${{ secrets.APP_PRIVATE_KEY }}
|
|
||||||
- uses: actions/stale@v10
|
|
||||||
with:
|
|
||||||
repo-token: ${{ steps.create-app-token.outputs.token }}
|
|
||||||
days-before-stale: 180
|
|
||||||
days-before-close: 7
|
|
||||||
operations-per-run: 500
|
|
||||||
stale-issue-label: 'stale'
|
|
||||||
any-of-issue-labels: 'channel search'
|
|
||||||
close-issue-message: 'This request has been closed because it has been inactive for more than 180 days.'
|
|
||||||
Generated
+481
-454
File diff suppressed because it is too large
Load Diff
@@ -40,6 +40,7 @@
|
|||||||
"@inquirer/prompts": "^7.8.0",
|
"@inquirer/prompts": "^7.8.0",
|
||||||
"@iptv-org/sdk": "^1.1.4",
|
"@iptv-org/sdk": "^1.1.4",
|
||||||
"@octokit/core": "^7.0.3",
|
"@octokit/core": "^7.0.3",
|
||||||
|
"@octokit/plugin-paginate-graphql": "^6.0.0",
|
||||||
"@octokit/plugin-paginate-rest": "^13.1.1",
|
"@octokit/plugin-paginate-rest": "^13.1.1",
|
||||||
"@octokit/plugin-rest-endpoint-methods": "^16.0.0",
|
"@octokit/plugin-rest-endpoint-methods": "^16.0.0",
|
||||||
"@octokit/types": "^14.1.0",
|
"@octokit/types": "^14.1.0",
|
||||||
@@ -76,5 +77,8 @@
|
|||||||
"normalize-url": "^8.1.0",
|
"normalize-url": "^8.1.0",
|
||||||
"socks-proxy-agent": "^8.0.5",
|
"socks-proxy-agent": "^8.0.5",
|
||||||
"tsx": "^4.20.3"
|
"tsx": "^4.20.3"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"esbuild": "0.23.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ async function main() {
|
|||||||
logger.info('adding the missing quality...')
|
logger.info('adding the missing quality...')
|
||||||
const progressBar = new cliProgress.SingleBar({
|
const progressBar = new cliProgress.SingleBar({
|
||||||
clearOnComplete: true,
|
clearOnComplete: true,
|
||||||
format: `[{bar}] {percentage}% | {value}/{total}`
|
format: '[{bar}] {percentage}% | {value}/{total}'
|
||||||
})
|
})
|
||||||
progressBar.start(streams.count(), 0)
|
progressBar.start(streams.count(), 0)
|
||||||
await eachLimit(streams.all(), options.parallel, async (stream: Stream) => {
|
await eachLimit(streams.all(), options.parallel, async (stream: Stream) => {
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
import { IssueLoader, PlaylistParser } from '../../core'
|
import { isURI, getStreamInfo, loadIssues } from '../../utils'
|
||||||
import { Playlist, Issue, Stream } from '../../models'
|
import { Playlist, Issue, Stream } from '../../models'
|
||||||
import { loadData, data as apiData } from '../../api'
|
import { loadData, data as apiData } from '../../api'
|
||||||
import { Logger, Collection } from '@freearhey/core'
|
import { Logger, Collection } from '@freearhey/core'
|
||||||
import { isURI, getStreamInfo } from '../../utils'
|
|
||||||
import { Storage } from '@freearhey/storage-js'
|
import { Storage } from '@freearhey/storage-js'
|
||||||
import { STREAMS_DIR } from '../../constants'
|
import { STREAMS_DIR } from '../../constants'
|
||||||
|
import { PlaylistParser } from '../../core'
|
||||||
import * as sdk from '@iptv-org/sdk'
|
import * as sdk from '@iptv-org/sdk'
|
||||||
|
|
||||||
const processedIssues = new Collection()
|
const processedIssues = new Collection()
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const logger = new Logger({ level: -999 })
|
const logger = new Logger({ level: -999 })
|
||||||
const issueLoader = new IssueLoader()
|
|
||||||
|
|
||||||
logger.info('loading issues...')
|
logger.info('loading issues...')
|
||||||
const issues = await issueLoader.load()
|
const issues = await loadIssues()
|
||||||
|
|
||||||
logger.info('loading data from api...')
|
logger.info('loading data from api...')
|
||||||
await loadData()
|
await loadData()
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
import { isURI, truncate, loadIssues, loadDiscussions } from '../../utils'
|
||||||
import { Logger, Collection, Dictionary } from '@freearhey/core'
|
import { Logger, Collection, Dictionary } from '@freearhey/core'
|
||||||
import { IssueLoader, PlaylistParser } from '../../core'
|
|
||||||
import { Storage } from '@freearhey/storage-js'
|
import { Storage } from '@freearhey/storage-js'
|
||||||
import { isURI, truncate } from '../../utils'
|
|
||||||
import { STREAMS_DIR } from '../../constants'
|
import { STREAMS_DIR } from '../../constants'
|
||||||
import { Issue, Stream } from '../../models'
|
import { Discussion, Issue, Stream } from '../../models'
|
||||||
|
import { PlaylistParser } from '../../core'
|
||||||
import { data, loadData } from '../../api'
|
import { data, loadData } from '../../api'
|
||||||
|
|
||||||
const status = {
|
const status = {
|
||||||
@@ -22,11 +22,13 @@ const status = {
|
|||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const logger = new Logger()
|
const logger = new Logger()
|
||||||
const issueLoader = new IssueLoader()
|
|
||||||
let report = new Collection()
|
let report = new Collection()
|
||||||
|
|
||||||
logger.info('loading issues...')
|
logger.info('loading issues...')
|
||||||
const issues = await issueLoader.load()
|
const issues = await loadIssues()
|
||||||
|
|
||||||
|
logger.info('loading discussions...')
|
||||||
|
const discussions = await loadDiscussions()
|
||||||
|
|
||||||
logger.info('loading data from api...')
|
logger.info('loading data from api...')
|
||||||
await loadData()
|
await loadData()
|
||||||
@@ -135,16 +137,17 @@ async function main() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
logger.info('checking channel search requests...')
|
logger.info('checking channel search requests...')
|
||||||
const channelSearchRequests = issues.filter(issue =>
|
const channelSearchRequests = discussions.filter(
|
||||||
issue.labels.find((label: string) => label === 'channel search')
|
(discussion: Discussion) => discussion.category === 'Channel Search'
|
||||||
)
|
)
|
||||||
const channelSearchRequestsBuffer = new Dictionary()
|
const channelSearchRequestsBuffer = new Dictionary()
|
||||||
channelSearchRequests.forEach((issue: Issue) => {
|
channelSearchRequests.forEach((discussion: Discussion) => {
|
||||||
const streamId = issue.data.getString('stream_id') || issue.data.getString('channel_id') || ''
|
const streamId =
|
||||||
|
discussion.data.getString('stream_id') || discussion.data.getString('channel_id') || ''
|
||||||
const [channelId, feedId] = streamId.split('@')
|
const [channelId, feedId] = streamId.split('@')
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
issueNumber: issue.number,
|
issueNumber: discussion.number,
|
||||||
type: 'channel search',
|
type: 'channel search',
|
||||||
streamId: streamId || undefined,
|
streamId: streamId || undefined,
|
||||||
streamUrl: undefined,
|
streamUrl: undefined,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Dictionary } from '@freearhey/core'
|
import { Dictionary } from '@freearhey/core'
|
||||||
|
|
||||||
export class IssueData {
|
export class DataSet {
|
||||||
_data: Dictionary<string>
|
_data: Dictionary<string>
|
||||||
constructor(data: Dictionary<string>) {
|
constructor(data: Dictionary<string>) {
|
||||||
this._data = data
|
this._data = data
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
export * from './cliTable'
|
export * from './cliTable'
|
||||||
export * from './htmlTable'
|
export * from './htmlTable'
|
||||||
export * from './issueData'
|
export * from './dataSet'
|
||||||
export * from './issueLoader'
|
|
||||||
export * from './issueParser'
|
|
||||||
export * from './logParser'
|
export * from './logParser'
|
||||||
export * from './markdown'
|
export * from './markdown'
|
||||||
export * from './numberParser'
|
export * from './numberParser'
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
import { restEndpointMethods } from '@octokit/plugin-rest-endpoint-methods'
|
|
||||||
import { paginateRest } from '@octokit/plugin-paginate-rest'
|
|
||||||
import { TESTING, OWNER, REPO } from '../constants'
|
|
||||||
import { Collection } from '@freearhey/core'
|
|
||||||
import { Octokit } from '@octokit/core'
|
|
||||||
import { IssueParser } from './'
|
|
||||||
|
|
||||||
const CustomOctokit = Octokit.plugin(paginateRest, restEndpointMethods)
|
|
||||||
const octokit = new CustomOctokit()
|
|
||||||
|
|
||||||
export class IssueLoader {
|
|
||||||
async load(props?: { labels: string | string[] }) {
|
|
||||||
let labels = ''
|
|
||||||
if (props && props.labels) {
|
|
||||||
labels = Array.isArray(props.labels) ? props.labels.join(',') : props.labels
|
|
||||||
}
|
|
||||||
let issues: object[] = []
|
|
||||||
if (TESTING) {
|
|
||||||
issues = (await import('../../tests/__data__/input/issues.js')).default
|
|
||||||
} else {
|
|
||||||
issues = await octokit.paginate(octokit.rest.issues.listForRepo, {
|
|
||||||
owner: OWNER,
|
|
||||||
repo: REPO,
|
|
||||||
per_page: 100,
|
|
||||||
labels,
|
|
||||||
status: 'open',
|
|
||||||
headers: {
|
|
||||||
'X-GitHub-Api-Version': '2022-11-28'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const parser = new IssueParser()
|
|
||||||
|
|
||||||
return new Collection(issues).map(parser.parse)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import { Dictionary } from '@freearhey/core'
|
|
||||||
import { IssueData } from './issueData'
|
|
||||||
import { Issue } from '../models'
|
|
||||||
|
|
||||||
const FIELDS = new Dictionary({
|
|
||||||
'Stream ID': 'stream_id',
|
|
||||||
'Channel ID': 'channel_id',
|
|
||||||
'Feed ID': 'feed_id',
|
|
||||||
'Stream URL': 'stream_url',
|
|
||||||
Label: 'label',
|
|
||||||
Quality: 'quality',
|
|
||||||
'HTTP User-Agent': 'http_user_agent',
|
|
||||||
'HTTP User Agent': 'http_user_agent',
|
|
||||||
'HTTP Referrer': 'http_referrer',
|
|
||||||
'What happened to the stream?': 'reason',
|
|
||||||
Reason: 'reason',
|
|
||||||
Notes: 'notes'
|
|
||||||
})
|
|
||||||
|
|
||||||
export class IssueParser {
|
|
||||||
parse(issue: { number: number; body: string; labels: { name: string }[] }): Issue {
|
|
||||||
const fields = typeof issue.body === 'string' ? issue.body.split('###') : []
|
|
||||||
|
|
||||||
const data = new Dictionary<string>()
|
|
||||||
fields.forEach((field: string) => {
|
|
||||||
const parsed = typeof field === 'string' ? field.split(/\r?\n/).filter(Boolean) : []
|
|
||||||
let _label = parsed.shift()
|
|
||||||
_label = _label ? _label.replace(/ \(optional\)| \(required\)/, '').trim() : ''
|
|
||||||
let _value = parsed.join('\r\n')
|
|
||||||
_value = _value ? _value.trim() : ''
|
|
||||||
|
|
||||||
if (!_label || !_value) return data
|
|
||||||
|
|
||||||
const id = FIELDS.get(_label)
|
|
||||||
const value: string = _value === '_No response_' || _value === 'None' ? '' : _value
|
|
||||||
|
|
||||||
if (!id) return
|
|
||||||
|
|
||||||
data.set(id, value)
|
|
||||||
})
|
|
||||||
|
|
||||||
const labels = issue.labels.map(label => label.name)
|
|
||||||
|
|
||||||
return new Issue({ number: issue.number, labels, data: new IssueData(data) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { DataSet } from '../core'
|
||||||
|
|
||||||
|
type DiscussionProps = {
|
||||||
|
number: number
|
||||||
|
category: string
|
||||||
|
data: DataSet
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Discussion {
|
||||||
|
number: number
|
||||||
|
category: string
|
||||||
|
data: DataSet
|
||||||
|
|
||||||
|
constructor({ number, category, data }: DiscussionProps) {
|
||||||
|
this.number = number
|
||||||
|
this.category = category
|
||||||
|
this.data = data
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
export * from './issue'
|
export * from './issue'
|
||||||
export * from './playlist'
|
export * from './playlist'
|
||||||
export * from './stream'
|
export * from './stream'
|
||||||
|
export * from './discussion'
|
||||||
|
|||||||
+160
-5
@@ -1,10 +1,16 @@
|
|||||||
|
import { restEndpointMethods } from '@octokit/plugin-rest-endpoint-methods'
|
||||||
import axios, { AxiosProxyConfig, AxiosRequestConfig } from 'axios'
|
import axios, { AxiosProxyConfig, AxiosRequestConfig } from 'axios'
|
||||||
|
import { paginateGraphQL } from '@octokit/plugin-paginate-graphql'
|
||||||
import { parse as parsePlaylist, setOptions } from 'hls-parser'
|
import { parse as parsePlaylist, setOptions } from 'hls-parser'
|
||||||
import { parse as parseManifest } from 'mpd-parser'
|
import { paginateRest } from '@octokit/plugin-paginate-rest'
|
||||||
|
import { Collection, Dictionary } from '@freearhey/core'
|
||||||
import { SocksProxyAgent } from 'socks-proxy-agent'
|
import { SocksProxyAgent } from 'socks-proxy-agent'
|
||||||
import { ProxyParser } from './core/proxyParser.js'
|
import { parse as parseManifest } from 'mpd-parser'
|
||||||
import { TESTING } from './constants.js'
|
import { TESTING, OWNER, REPO } from './constants'
|
||||||
|
import { ProxyParser, DataSet } from './core'
|
||||||
|
import { Discussion, Issue } from './models'
|
||||||
import normalizeUrl from 'normalize-url'
|
import normalizeUrl from 'normalize-url'
|
||||||
|
import { Octokit } from '@octokit/core'
|
||||||
import { orderBy } from 'es-toolkit'
|
import { orderBy } from 'es-toolkit'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import fs from 'node:fs'
|
import fs from 'node:fs'
|
||||||
@@ -90,7 +96,9 @@ export async function getStreamInfo(
|
|||||||
const response = await axios(url, request)
|
const response = await axios(url, request)
|
||||||
|
|
||||||
data = response.data
|
data = response.data
|
||||||
} catch {}
|
} catch {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data) return undefined
|
if (!data) return undefined
|
||||||
@@ -115,7 +123,9 @@ export async function getStreamInfo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
} else if (url.includes('.mpd')) {
|
} else if (url.includes('.mpd')) {
|
||||||
const manifest = parseManifest(data, {
|
const manifest = parseManifest(data, {
|
||||||
manifestUri: url,
|
manifestUri: url,
|
||||||
@@ -138,3 +148,148 @@ export async function getStreamInfo(
|
|||||||
|
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function loadIssues(props?: { labels: string | string[] }) {
|
||||||
|
const CustomOctokit = Octokit.plugin(paginateRest, restEndpointMethods)
|
||||||
|
const octokit = new CustomOctokit()
|
||||||
|
|
||||||
|
let labels = ''
|
||||||
|
if (props && props.labels) {
|
||||||
|
labels = Array.isArray(props.labels) ? props.labels.join(',') : props.labels
|
||||||
|
}
|
||||||
|
let issues: object[] = []
|
||||||
|
if (TESTING) {
|
||||||
|
issues = (await import('../tests/__data__/input/issues.js')).default
|
||||||
|
} else {
|
||||||
|
issues = await octokit.paginate(octokit.rest.issues.listForRepo, {
|
||||||
|
owner: OWNER,
|
||||||
|
repo: REPO,
|
||||||
|
per_page: 100,
|
||||||
|
labels,
|
||||||
|
status: 'open',
|
||||||
|
headers: {
|
||||||
|
'X-GitHub-Api-Version': '2022-11-28'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Collection(issues).map(parseIssue)
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseIssue(issue: { number: number; body: string; labels: { name: string }[] }): Issue {
|
||||||
|
const FIELDS = new Dictionary({
|
||||||
|
'Stream ID': 'stream_id',
|
||||||
|
'Channel ID': 'channel_id',
|
||||||
|
'Feed ID': 'feed_id',
|
||||||
|
'Stream URL': 'stream_url',
|
||||||
|
Label: 'label',
|
||||||
|
Quality: 'quality',
|
||||||
|
'HTTP User-Agent': 'http_user_agent',
|
||||||
|
'HTTP User Agent': 'http_user_agent',
|
||||||
|
'HTTP Referrer': 'http_referrer',
|
||||||
|
'What happened to the stream?': 'reason',
|
||||||
|
Reason: 'reason',
|
||||||
|
Notes: 'notes'
|
||||||
|
})
|
||||||
|
|
||||||
|
const fields = typeof issue.body === 'string' ? issue.body.split('###') : []
|
||||||
|
|
||||||
|
const data = new Dictionary<string>()
|
||||||
|
fields.forEach((field: string) => {
|
||||||
|
const parsed = typeof field === 'string' ? field.split(/\r?\n/).filter(Boolean) : []
|
||||||
|
let _label = parsed.shift()
|
||||||
|
_label = _label ? _label.replace(/ \(optional\)| \(required\)/, '').trim() : ''
|
||||||
|
let _value = parsed.join('\r\n')
|
||||||
|
_value = _value ? _value.trim() : ''
|
||||||
|
|
||||||
|
if (!_label || !_value) return data
|
||||||
|
|
||||||
|
const id = FIELDS.get(_label)
|
||||||
|
const value: string = _value === '_No response_' || _value === 'None' ? '' : _value
|
||||||
|
|
||||||
|
if (!id) return
|
||||||
|
|
||||||
|
data.set(id, value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const labels = issue.labels.map(label => label.name)
|
||||||
|
|
||||||
|
return new Issue({ number: issue.number, labels, data: new DataSet(data) })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadDiscussions() {
|
||||||
|
let discussions: object[] = []
|
||||||
|
if (TESTING) {
|
||||||
|
discussions = (await import('../tests/__data__/input/discussions.js')).default
|
||||||
|
} else {
|
||||||
|
const CustomOctokit = Octokit.plugin(paginateGraphQL)
|
||||||
|
const octokit = new CustomOctokit({
|
||||||
|
auth: process.env.GITHUB_TOKEN
|
||||||
|
})
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
query ($owner: String!, $repo: String!, $cursor: String) {
|
||||||
|
repository(owner: $owner, name: $repo) {
|
||||||
|
discussions(first: 100, after: $cursor, states: OPEN) {
|
||||||
|
nodes {
|
||||||
|
number
|
||||||
|
body
|
||||||
|
category {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pageInfo {
|
||||||
|
endCursor
|
||||||
|
hasNextPage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const result = await octokit.graphql.paginate(query, {
|
||||||
|
owner: 'iptv-org',
|
||||||
|
repo: 'iptv'
|
||||||
|
})
|
||||||
|
|
||||||
|
discussions = result.repository.discussions.nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Collection(discussions).map(parseDiscussion)
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDiscussion(discussion: {
|
||||||
|
number: number
|
||||||
|
category: { name: string }
|
||||||
|
body: string
|
||||||
|
}): Discussion {
|
||||||
|
const FIELDS = new Dictionary({
|
||||||
|
'Stream ID': 'stream_id'
|
||||||
|
})
|
||||||
|
|
||||||
|
const fields = typeof discussion.body === 'string' ? discussion.body.split('###') : []
|
||||||
|
|
||||||
|
const data = new Dictionary<string>()
|
||||||
|
fields.forEach((field: string) => {
|
||||||
|
const parsed = typeof field === 'string' ? field.split(/\r?\n/).filter(Boolean) : []
|
||||||
|
let _label = parsed.shift()
|
||||||
|
_label = _label ? _label.replace(/ \(optional\)| \(required\)/, '').trim() : ''
|
||||||
|
let _value = parsed.join('\r\n')
|
||||||
|
_value = _value ? _value.trim() : ''
|
||||||
|
|
||||||
|
if (!_label || !_value) return data
|
||||||
|
|
||||||
|
const id = FIELDS.get(_label)
|
||||||
|
const value: string = _value === '_No response_' || _value === 'None' ? '' : _value
|
||||||
|
|
||||||
|
if (!id) return
|
||||||
|
|
||||||
|
data.set(id, value)
|
||||||
|
})
|
||||||
|
|
||||||
|
return new Discussion({
|
||||||
|
number: discussion.number,
|
||||||
|
category: discussion.category.name,
|
||||||
|
data: new DataSet(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
number: 19956,
|
||||||
|
body: '### Stream ID (required)\r\n\r\nCNBCe.tr\r\n\r\n### Notes\r\n\r\n_No response_',
|
||||||
|
category: { name: 'Channel Search' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
number: 19957,
|
||||||
|
body: '### Stream ID (required)\r\n\r\n13thStreet.au\r\n\r\n### Notes\r\n\r\nI need Disney Channel East Stream m3u Url please\r\n\r\n### Contributing Guide\r\n\r\n- [x] I have read [Contributing Guide](https://github.com/iptv-org/iptv/blob/master/CONTRIBUTING.md)',
|
||||||
|
category: { name: 'Channel Search' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
number: 20956,
|
||||||
|
body: '### Stream ID (required)\r\n\r\nIONTV.us\r\n\r\n### Notes\r\n\r\nI need it fast please thank you\r\n\r\n### Contributing Guide\r\n\r\n- [x] I have read [Contributing Guide](https://github.com/iptv-org/iptv/blob/master/CONTRIBUTING.md)',
|
||||||
|
category: { name: 'Channel Search' }
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user