From 4f3548721405ed0941d145cd0cc5091b81f1efd0 Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Thu, 7 Sep 2023 21:43:20 +0300 Subject: [PATCH 01/12] Delete checker.js --- scripts/core/checker.js | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 scripts/core/checker.js diff --git a/scripts/core/checker.js b/scripts/core/checker.js deleted file mode 100644 index 014a92246..000000000 --- a/scripts/core/checker.js +++ /dev/null @@ -1,19 +0,0 @@ -const IPTVChecker = require('iptv-checker') - -const checker = {} - -checker.check = async function (item, config) { - const ic = new IPTVChecker(config) - const result = await ic.checkStream({ url: item.url, http: item.http }) - - return { - _id: item._id, - url: item.url, - http: item.http, - error: !result.status.ok ? { code: result.status.code, message: result.status.message } : null, - streams: result.status.ok ? result.status.metadata.streams : [], - requests: result.status.ok ? result.status.metadata.requests : [] - } -} - -module.exports = checker From 9c7e222c183e1576bdb8cc56e3480f704d59753b Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Fri, 15 Sep 2023 18:37:31 +0300 Subject: [PATCH 02/12] Update tests data --- tests/__data__/expected/_readme.md | 267 +- .../expected/database/db_create.streams.db | 14 +- tests/__data__/expected/logs/generators.log | 72 + .../expected/logs/generators/categories.log | 29 - .../expected/logs/generators/countries.log | 7 - .../logs/generators/index_category_m3u.log | 1 - .../logs/generators/index_country_m3u.log | 1 - .../logs/generators/index_language_m3u.log | 1 - .../expected/logs/generators/index_m3u.log | 1 - .../logs/generators/index_nsfw_m3u.log | 1 - .../logs/generators/index_region_m3u.log | 1 - .../expected/logs/generators/languages.log | 4 - .../expected/logs/generators/regions.log | 26 - tests/__data__/expected/streams/in.m3u | 3 + tests/__data__/expected/streams/my.m3u | 5 + tests/__data__/expected/streams/nl.m3u | 4 +- tests/__data__/expected/streams/pe.m3u | 3 - tests/__data__/expected/streams/us.m3u | 5 + tests/__data__/input/.readme/config.json | 4 + tests/__data__/input/.readme/template.md | 158 + tests/__data__/input/_readme.json | 4 - tests/__data__/input/data/blocklist.json | 2 +- tests/__data__/input/data/channels.json | 96 + tests/__data__/input/data/streams.json | 1 + .../input/database/api_generate.streams.db | 14 +- .../input/database/playlist_format.streams.db | 20 - .../database/playlist_generate.streams.db | 28 +- .../input/database/playlist_update.streams.db | 21 + tests/__data__/input/issues/streams_add.js | 406 +++ .../input/issues/streams_add_approved.js | 2651 +++++++++++++++++ .../input/issues/streams_edit_approved.js | 81 + .../input/issues/streams_remove_approved.js | 1845 ++++++++++++ tests/__data__/input/logs/generators.log | 73 + .../input/logs/generators/categories.log | 29 - .../input/logs/generators/countries.log | 253 -- .../input/logs/generators/languages.log | 5 - .../input/logs/generators/regions.log | 26 - 37 files changed, 5466 insertions(+), 696 deletions(-) create mode 100644 tests/__data__/expected/logs/generators.log delete mode 100644 tests/__data__/expected/logs/generators/categories.log delete mode 100644 tests/__data__/expected/logs/generators/countries.log delete mode 100644 tests/__data__/expected/logs/generators/index_category_m3u.log delete mode 100644 tests/__data__/expected/logs/generators/index_country_m3u.log delete mode 100644 tests/__data__/expected/logs/generators/index_language_m3u.log delete mode 100644 tests/__data__/expected/logs/generators/index_m3u.log delete mode 100644 tests/__data__/expected/logs/generators/index_nsfw_m3u.log delete mode 100644 tests/__data__/expected/logs/generators/index_region_m3u.log delete mode 100644 tests/__data__/expected/logs/generators/languages.log delete mode 100644 tests/__data__/expected/logs/generators/regions.log create mode 100644 tests/__data__/expected/streams/in.m3u create mode 100644 tests/__data__/expected/streams/my.m3u delete mode 100644 tests/__data__/expected/streams/pe.m3u create mode 100644 tests/__data__/expected/streams/us.m3u create mode 100644 tests/__data__/input/.readme/config.json create mode 100644 tests/__data__/input/.readme/template.md delete mode 100644 tests/__data__/input/_readme.json create mode 100644 tests/__data__/input/data/streams.json delete mode 100644 tests/__data__/input/database/playlist_format.streams.db create mode 100644 tests/__data__/input/database/playlist_update.streams.db create mode 100644 tests/__data__/input/issues/streams_add.js create mode 100644 tests/__data__/input/issues/streams_add_approved.js create mode 100644 tests/__data__/input/issues/streams_edit_approved.js create mode 100644 tests/__data__/input/issues/streams_remove_approved.js create mode 100644 tests/__data__/input/logs/generators.log delete mode 100644 tests/__data__/input/logs/generators/categories.log delete mode 100644 tests/__data__/input/logs/generators/countries.log delete mode 100644 tests/__data__/input/logs/generators/languages.log delete mode 100644 tests/__data__/input/logs/generators/regions.log diff --git a/tests/__data__/expected/_readme.md b/tests/__data__/expected/_readme.md index cd7ec179f..7cd308c41 100644 --- a/tests/__data__/expected/_readme.md +++ b/tests/__data__/expected/_readme.md @@ -1,4 +1,4 @@ -# IPTV +# IPTV [](https://github.com/iptv-org/iptv/actions/workflows/update.yml) Collection of publicly available IPTV (Internet Protocol television) channels from all over the world. @@ -59,8 +59,8 @@ Same thing, but split up into separate files:
https://iptv-org.github.io/iptv/categories/auto.m3u
https://iptv-org.github.io/iptv/categories/animation.m3u
https://iptv-org.github.io/iptv/categories/auto.m3u
https://iptv-org.github.io/iptv/categories/business.m3u
https://iptv-org.github.io/iptv/categories/classic.m3u
https://iptv-org.github.io/iptv/categories/comedy.m3u
https://iptv-org.github.io/iptv/categories/outdoor.m3u
https://iptv-org.github.io/iptv/categories/relax.m3u
https://iptv-org.github.io/iptv/categories/religious.m3u
https://iptv-org.github.io/iptv/categories/series.m3u
https://iptv-org.github.io/iptv/categories/science.m3u
https://iptv-org.github.io/iptv/categories/series.m3u
https://iptv-org.github.io/iptv/categories/shop.m3u
https://iptv-org.github.io/iptv/categories/sports.m3u
https://iptv-org.github.io/iptv/categories/travel.m3u
https://iptv-org.github.io/iptv/countries/af.m3u
https://iptv-org.github.io/iptv/countries/al.m3u
https://iptv-org.github.io/iptv/countries/dz.m3u
https://iptv-org.github.io/iptv/countries/as.m3u
https://iptv-org.github.io/iptv/countries/ad.m3u
https://iptv-org.github.io/iptv/countries/ao.m3u
https://iptv-org.github.io/iptv/countries/ai.m3u
https://iptv-org.github.io/iptv/countries/aq.m3u
https://iptv-org.github.io/iptv/countries/ag.m3u
https://iptv-org.github.io/iptv/countries/ar.m3u
https://iptv-org.github.io/iptv/countries/am.m3u
https://iptv-org.github.io/iptv/countries/aw.m3u
https://iptv-org.github.io/iptv/countries/au.m3u
https://iptv-org.github.io/iptv/countries/at.m3u
https://iptv-org.github.io/iptv/countries/az.m3u
https://iptv-org.github.io/iptv/countries/bs.m3u
https://iptv-org.github.io/iptv/countries/bh.m3u
https://iptv-org.github.io/iptv/countries/bd.m3u
https://iptv-org.github.io/iptv/countries/bb.m3u
https://iptv-org.github.io/iptv/countries/by.m3u
https://iptv-org.github.io/iptv/countries/be.m3u
https://iptv-org.github.io/iptv/countries/bz.m3u
https://iptv-org.github.io/iptv/countries/bj.m3u
https://iptv-org.github.io/iptv/countries/bm.m3u
https://iptv-org.github.io/iptv/countries/bt.m3u
https://iptv-org.github.io/iptv/countries/bo.m3u
https://iptv-org.github.io/iptv/countries/bq.m3u
https://iptv-org.github.io/iptv/countries/ba.m3u
https://iptv-org.github.io/iptv/countries/bw.m3u
https://iptv-org.github.io/iptv/countries/bv.m3u
https://iptv-org.github.io/iptv/countries/br.m3u
https://iptv-org.github.io/iptv/countries/io.m3u
https://iptv-org.github.io/iptv/countries/vg.m3u
https://iptv-org.github.io/iptv/countries/bn.m3u
https://iptv-org.github.io/iptv/countries/bg.m3u
https://iptv-org.github.io/iptv/countries/bf.m3u
https://iptv-org.github.io/iptv/countries/bi.m3u
https://iptv-org.github.io/iptv/countries/kh.m3u
https://iptv-org.github.io/iptv/countries/cm.m3u
https://iptv-org.github.io/iptv/countries/ca.m3u
https://iptv-org.github.io/iptv/subdivisions/ca-on.m3u
https://iptv-org.github.io/iptv/countries/cv.m3u
https://iptv-org.github.io/iptv/countries/ky.m3u
https://iptv-org.github.io/iptv/countries/cf.m3u
https://iptv-org.github.io/iptv/countries/td.m3u
https://iptv-org.github.io/iptv/countries/cl.m3u
https://iptv-org.github.io/iptv/countries/cn.m3u
https://iptv-org.github.io/iptv/countries/cx.m3u
https://iptv-org.github.io/iptv/countries/cc.m3u
https://iptv-org.github.io/iptv/countries/co.m3u
https://iptv-org.github.io/iptv/countries/km.m3u
https://iptv-org.github.io/iptv/countries/ck.m3u
https://iptv-org.github.io/iptv/countries/cr.m3u
https://iptv-org.github.io/iptv/countries/hr.m3u
https://iptv-org.github.io/iptv/countries/cu.m3u
https://iptv-org.github.io/iptv/countries/cw.m3u
https://iptv-org.github.io/iptv/countries/cy.m3u
https://iptv-org.github.io/iptv/countries/cz.m3u
https://iptv-org.github.io/iptv/countries/cd.m3u
https://iptv-org.github.io/iptv/countries/dk.m3u
https://iptv-org.github.io/iptv/countries/dj.m3u
https://iptv-org.github.io/iptv/countries/dm.m3u
https://iptv-org.github.io/iptv/countries/do.m3u
https://iptv-org.github.io/iptv/countries/tl.m3u
https://iptv-org.github.io/iptv/countries/ec.m3u
https://iptv-org.github.io/iptv/countries/eg.m3u
https://iptv-org.github.io/iptv/countries/sv.m3u
https://iptv-org.github.io/iptv/countries/gq.m3u
https://iptv-org.github.io/iptv/countries/er.m3u
https://iptv-org.github.io/iptv/countries/ee.m3u
https://iptv-org.github.io/iptv/countries/et.m3u
https://iptv-org.github.io/iptv/countries/fk.m3u
https://iptv-org.github.io/iptv/countries/fo.m3u
https://iptv-org.github.io/iptv/countries/fj.m3u
https://iptv-org.github.io/iptv/countries/fi.m3u
https://iptv-org.github.io/iptv/countries/fr.m3u
https://iptv-org.github.io/iptv/countries/gf.m3u
https://iptv-org.github.io/iptv/countries/pf.m3u
https://iptv-org.github.io/iptv/countries/tf.m3u
https://iptv-org.github.io/iptv/countries/ga.m3u
https://iptv-org.github.io/iptv/countries/gm.m3u
https://iptv-org.github.io/iptv/countries/ge.m3u
https://iptv-org.github.io/iptv/countries/de.m3u
https://iptv-org.github.io/iptv/countries/gh.m3u
https://iptv-org.github.io/iptv/countries/gi.m3u
https://iptv-org.github.io/iptv/countries/gr.m3u
https://iptv-org.github.io/iptv/countries/gl.m3u
https://iptv-org.github.io/iptv/countries/gd.m3u
https://iptv-org.github.io/iptv/countries/gp.m3u
https://iptv-org.github.io/iptv/countries/gu.m3u
https://iptv-org.github.io/iptv/countries/gt.m3u
https://iptv-org.github.io/iptv/countries/gg.m3u
https://iptv-org.github.io/iptv/countries/gn.m3u
https://iptv-org.github.io/iptv/countries/gw.m3u
https://iptv-org.github.io/iptv/countries/gy.m3u
https://iptv-org.github.io/iptv/countries/ht.m3u
https://iptv-org.github.io/iptv/countries/hm.m3u
https://iptv-org.github.io/iptv/countries/hn.m3u
https://iptv-org.github.io/iptv/countries/hk.m3u
https://iptv-org.github.io/iptv/countries/hu.m3u
https://iptv-org.github.io/iptv/countries/is.m3u
https://iptv-org.github.io/iptv/countries/in.m3u
https://iptv-org.github.io/iptv/countries/id.m3u
https://iptv-org.github.io/iptv/countries/ir.m3u
https://iptv-org.github.io/iptv/countries/iq.m3u
https://iptv-org.github.io/iptv/countries/ie.m3u
https://iptv-org.github.io/iptv/countries/im.m3u
https://iptv-org.github.io/iptv/countries/il.m3u
https://iptv-org.github.io/iptv/countries/it.m3u
https://iptv-org.github.io/iptv/countries/ci.m3u
https://iptv-org.github.io/iptv/countries/jm.m3u
https://iptv-org.github.io/iptv/countries/jp.m3u
https://iptv-org.github.io/iptv/countries/je.m3u
https://iptv-org.github.io/iptv/countries/jo.m3u
https://iptv-org.github.io/iptv/countries/kz.m3u
https://iptv-org.github.io/iptv/countries/ke.m3u
https://iptv-org.github.io/iptv/countries/ki.m3u
https://iptv-org.github.io/iptv/countries/xk.m3u
https://iptv-org.github.io/iptv/countries/kw.m3u
https://iptv-org.github.io/iptv/countries/kg.m3u
https://iptv-org.github.io/iptv/countries/la.m3u
https://iptv-org.github.io/iptv/countries/lv.m3u
https://iptv-org.github.io/iptv/countries/lb.m3u
https://iptv-org.github.io/iptv/countries/ls.m3u
https://iptv-org.github.io/iptv/countries/lr.m3u
https://iptv-org.github.io/iptv/countries/ly.m3u
https://iptv-org.github.io/iptv/countries/li.m3u
https://iptv-org.github.io/iptv/countries/lt.m3u
https://iptv-org.github.io/iptv/countries/lu.m3u
https://iptv-org.github.io/iptv/countries/mo.m3u
https://iptv-org.github.io/iptv/countries/mg.m3u
https://iptv-org.github.io/iptv/countries/mw.m3u
https://iptv-org.github.io/iptv/countries/my.m3u
https://iptv-org.github.io/iptv/countries/mv.m3u
https://iptv-org.github.io/iptv/countries/ml.m3u
https://iptv-org.github.io/iptv/countries/mt.m3u
https://iptv-org.github.io/iptv/countries/mh.m3u
https://iptv-org.github.io/iptv/countries/mq.m3u
https://iptv-org.github.io/iptv/countries/mr.m3u
https://iptv-org.github.io/iptv/countries/mu.m3u
https://iptv-org.github.io/iptv/countries/yt.m3u
https://iptv-org.github.io/iptv/countries/mx.m3u
https://iptv-org.github.io/iptv/countries/fm.m3u
https://iptv-org.github.io/iptv/countries/md.m3u
https://iptv-org.github.io/iptv/countries/mc.m3u
https://iptv-org.github.io/iptv/countries/mn.m3u
https://iptv-org.github.io/iptv/countries/me.m3u
https://iptv-org.github.io/iptv/countries/ms.m3u
https://iptv-org.github.io/iptv/countries/ma.m3u
https://iptv-org.github.io/iptv/countries/mz.m3u
https://iptv-org.github.io/iptv/countries/mm.m3u
https://iptv-org.github.io/iptv/countries/na.m3u
https://iptv-org.github.io/iptv/countries/nr.m3u
https://iptv-org.github.io/iptv/countries/np.m3u
https://iptv-org.github.io/iptv/countries/nl.m3u
https://iptv-org.github.io/iptv/countries/nc.m3u
https://iptv-org.github.io/iptv/countries/nz.m3u
https://iptv-org.github.io/iptv/countries/ni.m3u
https://iptv-org.github.io/iptv/countries/ne.m3u
https://iptv-org.github.io/iptv/countries/ng.m3u
https://iptv-org.github.io/iptv/countries/nu.m3u
https://iptv-org.github.io/iptv/countries/nf.m3u
https://iptv-org.github.io/iptv/countries/kp.m3u
https://iptv-org.github.io/iptv/countries/mk.m3u
https://iptv-org.github.io/iptv/countries/mp.m3u
https://iptv-org.github.io/iptv/countries/no.m3u
https://iptv-org.github.io/iptv/countries/om.m3u
https://iptv-org.github.io/iptv/countries/pk.m3u
https://iptv-org.github.io/iptv/countries/pw.m3u
https://iptv-org.github.io/iptv/countries/ps.m3u
https://iptv-org.github.io/iptv/countries/pa.m3u
https://iptv-org.github.io/iptv/countries/pg.m3u
https://iptv-org.github.io/iptv/countries/py.m3u
https://iptv-org.github.io/iptv/countries/pe.m3u
https://iptv-org.github.io/iptv/countries/ph.m3u
https://iptv-org.github.io/iptv/countries/pn.m3u
https://iptv-org.github.io/iptv/countries/pl.m3u
https://iptv-org.github.io/iptv/countries/pt.m3u
https://iptv-org.github.io/iptv/countries/pr.m3u
https://iptv-org.github.io/iptv/countries/qa.m3u
https://iptv-org.github.io/iptv/countries/cg.m3u
https://iptv-org.github.io/iptv/countries/re.m3u
https://iptv-org.github.io/iptv/countries/ro.m3u
https://iptv-org.github.io/iptv/countries/ru.m3u
https://iptv-org.github.io/iptv/countries/rw.m3u
https://iptv-org.github.io/iptv/countries/re.m3u
https://iptv-org.github.io/iptv/countries/bl.m3u
https://iptv-org.github.io/iptv/countries/sh.m3u
https://iptv-org.github.io/iptv/countries/kn.m3u
https://iptv-org.github.io/iptv/countries/lc.m3u
https://iptv-org.github.io/iptv/countries/mf.m3u
https://iptv-org.github.io/iptv/countries/pm.m3u
https://iptv-org.github.io/iptv/countries/vc.m3u
https://iptv-org.github.io/iptv/countries/ws.m3u
https://iptv-org.github.io/iptv/countries/sm.m3u
https://iptv-org.github.io/iptv/countries/sa.m3u
https://iptv-org.github.io/iptv/countries/sn.m3u
https://iptv-org.github.io/iptv/countries/rs.m3u
https://iptv-org.github.io/iptv/countries/sc.m3u
https://iptv-org.github.io/iptv/countries/sl.m3u
https://iptv-org.github.io/iptv/countries/sg.m3u
https://iptv-org.github.io/iptv/countries/sx.m3u
https://iptv-org.github.io/iptv/countries/sk.m3u
https://iptv-org.github.io/iptv/countries/si.m3u
https://iptv-org.github.io/iptv/countries/sb.m3u
https://iptv-org.github.io/iptv/countries/so.m3u
https://iptv-org.github.io/iptv/countries/za.m3u
https://iptv-org.github.io/iptv/countries/gs.m3u
https://iptv-org.github.io/iptv/countries/kr.m3u
https://iptv-org.github.io/iptv/countries/ss.m3u
https://iptv-org.github.io/iptv/countries/es.m3u
https://iptv-org.github.io/iptv/countries/lk.m3u
https://iptv-org.github.io/iptv/countries/sd.m3u
https://iptv-org.github.io/iptv/countries/sr.m3u
https://iptv-org.github.io/iptv/countries/sj.m3u
https://iptv-org.github.io/iptv/countries/sz.m3u
https://iptv-org.github.io/iptv/countries/se.m3u
https://iptv-org.github.io/iptv/countries/ch.m3u
https://iptv-org.github.io/iptv/countries/sy.m3u
https://iptv-org.github.io/iptv/countries/st.m3u
https://iptv-org.github.io/iptv/countries/tw.m3u
https://iptv-org.github.io/iptv/countries/tj.m3u
https://iptv-org.github.io/iptv/countries/tz.m3u
https://iptv-org.github.io/iptv/countries/th.m3u
https://iptv-org.github.io/iptv/countries/tg.m3u
https://iptv-org.github.io/iptv/countries/tk.m3u
https://iptv-org.github.io/iptv/countries/to.m3u
https://iptv-org.github.io/iptv/countries/tt.m3u
https://iptv-org.github.io/iptv/countries/tn.m3u
https://iptv-org.github.io/iptv/countries/tr.m3u
https://iptv-org.github.io/iptv/countries/tm.m3u
https://iptv-org.github.io/iptv/countries/tc.m3u
https://iptv-org.github.io/iptv/countries/tv.m3u
https://iptv-org.github.io/iptv/countries/um.m3u
https://iptv-org.github.io/iptv/countries/vi.m3u
https://iptv-org.github.io/iptv/countries/ug.m3u
https://iptv-org.github.io/iptv/countries/ua.m3u
https://iptv-org.github.io/iptv/countries/ae.m3u
https://iptv-org.github.io/iptv/countries/uk.m3u
https://iptv-org.github.io/iptv/countries/us.m3u
https://iptv-org.github.io/iptv/countries/uy.m3u
https://iptv-org.github.io/iptv/countries/uz.m3u
https://iptv-org.github.io/iptv/countries/vu.m3u
https://iptv-org.github.io/iptv/countries/va.m3u
https://iptv-org.github.io/iptv/countries/ve.m3u
https://iptv-org.github.io/iptv/countries/vn.m3u
https://iptv-org.github.io/iptv/countries/wf.m3u
https://iptv-org.github.io/iptv/countries/eh.m3u
https://iptv-org.github.io/iptv/countries/ye.m3u
https://iptv-org.github.io/iptv/countries/zm.m3u
https://iptv-org.github.io/iptv/countries/zw.m3u
https://iptv-org.github.io/iptv/countries/ax.m3u
https://iptv-org.github.io/iptv/countries/int.m3u
https://iptv-org.github.io/iptv/regions/afr.m3u
https://iptv-org.github.io/iptv/regions/amer.m3u
https://iptv-org.github.io/iptv/regions/apac.m3u
https://iptv-org.github.io/iptv/regions/arab.m3u
https://iptv-org.github.io/iptv/regions/asean.m3u
https://iptv-org.github.io/iptv/regions/asia.m3u
https://iptv-org.github.io/iptv/regions/apac.m3u
https://iptv-org.github.io/iptv/regions/asean.m3u
https://iptv-org.github.io/iptv/regions/carib.m3u
https://iptv-org.github.io/iptv/regions/cas.m3u
https://iptv-org.github.io/iptv/regions/cenamer.m3u
https://iptv-org.github.io/iptv/regions/cas.m3u
https://iptv-org.github.io/iptv/regions/cis.m3u
https://iptv-org.github.io/iptv/regions/emea.m3u
https://iptv-org.github.io/iptv/regions/eur.m3u
https://iptv-org.github.io/iptv/regions/emea.m3u
https://iptv-org.github.io/iptv/regions/hispam.m3u
https://iptv-org.github.io/iptv/regions/lac.m3u
https://iptv-org.github.io/iptv/regions/latam.m3u
https://iptv-org.github.io/iptv/regions/lac.m3u
https://iptv-org.github.io/iptv/regions/maghreb.m3u
https://iptv-org.github.io/iptv/regions/mena.m3u
https://iptv-org.github.io/iptv/regions/mideast.m3u
https://iptv-org.github.io/iptv/regions/nam.m3u
https://iptv-org.github.io/iptv/regions/noram.m3u
https://iptv-org.github.io/iptv/regions/mena.m3u
https://iptv-org.github.io/iptv/regions/nord.m3u
https://iptv-org.github.io/iptv/regions/noram.m3u
https://iptv-org.github.io/iptv/regions/nam.m3u
https://iptv-org.github.io/iptv/regions/oce.m3u
https://iptv-org.github.io/iptv/regions/sas.m3u
https://iptv-org.github.io/iptv/regions/southam.m3u
https://iptv-org.github.io/iptv/regions/sas.m3u
https://iptv-org.github.io/iptv/regions/ssa.m3u
https://iptv-org.github.io/iptv/regions/wafr.m3u
https://iptv-org.github.io/iptv/${item.filepath}
`
- })
- }
-
- const table = createTable(rows, [
- { name: 'Category' },
- { name: 'Channels', align: 'right' },
- { name: 'Playlist', nowrap: true }
- ])
-
- await file.create('./.readme/_categories.md', table)
-}
-
-async function createCountryTable() {
- logger.info('creating country table...')
- const rows = []
- await api.countries.load()
- await api.subdivisions.load()
- const items = await parser.parseLogs(`${LOGS_DIR}/countries.log`)
- for (const item of items) {
- const code = file.getFilename(item.filepath)
- const country = await api.countries.find({ code: code.toUpperCase() })
- if (country) {
- rows.push({
- name: `${country.flag} ${country.name}`,
- channels: item.count,
- playlist: `https://iptv-org.github.io/iptv/${item.filepath}
`
- })
- } else if (code === 'int') {
- rows.push({
- name: `๐ International`,
- channels: item.count,
- playlist: `https://iptv-org.github.io/iptv/${item.filepath}
`
- })
- } else {
- const subdivision = await api.subdivisions.find({ code: code.toUpperCase() })
- if (subdivision) {
- rows.push({
- name: ` ${subdivision.name}`,
- channels: item.count,
- playlist: `https://iptv-org.github.io/iptv/${item.filepath}
`
- })
- }
- }
- }
-
- const table = createTable(rows, [
- { name: 'Country' },
- { name: 'Channels', align: 'right' },
- { name: 'Playlist', nowrap: true }
- ])
-
- await file.create('./.readme/_countries.md', table)
-}
-
-async function createLanguageTable() {
- logger.info('creating language table...')
- const rows = []
- await api.languages.load()
- const items = await parser.parseLogs(`${LOGS_DIR}/languages.log`)
- for (const item of items) {
- const code = file.getFilename(item.filepath)
- const language = await api.languages.find({ code })
- rows.push({
- name: language ? language.name : 'Undefined',
- channels: item.count,
- playlist: `https://iptv-org.github.io/iptv/${item.filepath}
`
- })
- }
-
- const table = createTable(rows, [
- { name: 'Language', align: 'left' },
- { name: 'Channels', align: 'right' },
- { name: 'Playlist', align: 'left', nowrap: true }
- ])
-
- await file.create('./.readme/_languages.md', table)
-}
-
-async function createRegionTable() {
- logger.info('creating region table...')
- const rows = []
- await api.regions.load()
- const items = await parser.parseLogs(`${LOGS_DIR}/regions.log`)
- for (const item of items) {
- const code = file.getFilename(item.filepath)
- const region = await api.regions.find({ code: code.toUpperCase() })
- if (region) {
- rows.push({
- name: region.name,
- channels: item.count,
- playlist: `https://iptv-org.github.io/iptv/${item.filepath}
`
- })
- }
- }
-
- const table = createTable(rows, [
- { name: 'Region', align: 'left' },
- { name: 'Channels', align: 'right' },
- { name: 'Playlist', align: 'left', nowrap: true }
- ])
-
- await file.create('./.readme/_regions.md', table)
-}
-
-async function updateReadme() {
- logger.info('updating readme.md...')
- const config = require(file.resolve(options.config))
- await file.createDir(file.dirname(config.build))
- await markdown.compile(options.config)
-}
diff --git a/scripts/commands/readme/update.ts b/scripts/commands/readme/update.ts
new file mode 100644
index 000000000..5dc06ae98
--- /dev/null
+++ b/scripts/commands/readme/update.ts
@@ -0,0 +1,24 @@
+import { CategoryTable, CountryTable, LanguageTable, RegionTable } from '../../tables'
+import { Logger, Markdown } from '../../core'
+import { README_DIR } from '../../constants'
+import path from 'path'
+
+async function main() {
+ const logger = new Logger()
+
+ logger.info('creating category table...')
+ await new CategoryTable().make()
+ logger.info('creating country table...')
+ await new CountryTable().make()
+ logger.info('creating language table...')
+ await new LanguageTable().make()
+ logger.info('creating region table...')
+ await new RegionTable().make()
+
+ logger.info('updating readme.md...')
+ const configPath = path.join(README_DIR, 'config.json')
+ const readme = new Markdown(configPath)
+ readme.compile()
+}
+
+main()
diff --git a/scripts/commands/report/create.js b/scripts/commands/report/create.js
deleted file mode 100644
index df29fb360..000000000
--- a/scripts/commands/report/create.js
+++ /dev/null
@@ -1,106 +0,0 @@
-const { api } = require('../../core')
-const { Octokit } = require('@octokit/core')
-const { paginateRest } = require('@octokit/plugin-paginate-rest')
-const CustomOctokit = Octokit.plugin(paginateRest)
-const _ = require('lodash')
-
-const octokit = new CustomOctokit()
-
-const DATA_DIR = process.env.DATA_DIR || './tmp/data'
-const OWNER = 'iptv-org'
-const REPO = 'iptv'
-
-async function main() {
- try {
- await api.channels.load()
- let channels = await api.channels.all()
- channels = _.keyBy(channels, 'id')
-
- await api.blocklist.load()
- let blocklist = await api.blocklist.all()
- blocklist = _.keyBy(blocklist, 'channel')
-
- await api.streams.load()
- let streams = await api.streams.all()
- streams = _.keyBy(streams, 'channel')
-
- const channelRequests = await loadChannelRequests()
- const buffer = {}
- const report = channelRequests.map(r => {
- let result = {
- issueNumber: r.issue.number,
- channelId: r.channel.id || undefined,
- status: undefined
- }
-
- if (!r.channel || !r.channel.id) result.status = 'error'
- else if (blocklist[r.channel.id]) result.status = 'blocked'
- else if (!channels[r.channel.id]) result.status = 'invalid_id'
- else if (streams[r.channel.id]) result.status = 'fullfilled'
- else if (buffer[r.channel.id] && !r.channel.url) result.status = 'duplicate'
- else result.status = 'pending'
-
- buffer[r.channel.id] = true
-
- return result
- })
- console.table(report)
- } catch (err) {
- console.log(err.message)
- }
-}
-
-main()
-
-async function loadChannelRequests() {
- const issues = await fetchIssues('channel request')
-
- return issues.map(parseIssue)
-}
-
-async function fetchIssues(labels) {
- const issues = await octokit.paginate('GET /repos/{owner}/{repo}/issues', {
- owner: OWNER,
- repo: REPO,
- per_page: 100,
- labels,
- direction: 'asc',
- headers: {
- 'X-GitHub-Api-Version': '2022-11-28'
- }
- })
-
- return issues
-}
-
-function parseIssue(issue) {
- const buffer = {}
- const channel = {}
- const fields = {
- 'Channel ID (required)': 'id',
- 'Channel ID': 'id',
- 'Stream URL (optional)': 'url',
- 'Stream URL': 'url',
- 'Notes (optional)': 'notes',
- Notes: 'notes'
- }
-
- const matches = issue.body.match(/### ([^\r\n]+)\s+([^\r\n]+)/g)
-
- if (!matches) return { issue, channel: null }
-
- matches.forEach(item => {
- const [, fieldLabel, value] = item.match(/### ([^\r\n]+)\s+([^\r\n]+)/)
- const field = fields[fieldLabel]
-
- if (!field) return
-
- buffer[field] = value === '_No response_' ? undefined : value.trim()
- })
-
- for (let field in buffer) {
- channel[field] = buffer[field]
- }
-
- return { issue, channel }
-}
diff --git a/scripts/commands/report/create.ts b/scripts/commands/report/create.ts
new file mode 100644
index 000000000..6194449fd
--- /dev/null
+++ b/scripts/commands/report/create.ts
@@ -0,0 +1,53 @@
+import { DATA_DIR } from '../../constants'
+import { Collection, Dictionary, IssueLoader, Storage } from '../../core'
+import { Blocked, Channel, Stream } from '../../models'
+
+async function main() {
+ const loader = new IssueLoader()
+
+ const storage = new Storage(DATA_DIR)
+
+ const channelsContent = await storage.json('channels.json')
+ const groupedChannels = new Collection(channelsContent)
+ .map(data => new Channel(data))
+ .groupBy((channel: Channel) => channel.id)
+
+ const streamsContent = await storage.json('streams.json')
+ const groupedStreams = new Collection(streamsContent)
+ .map(data => new Stream(data))
+ .groupBy((stream: Stream) => stream.url)
+
+ const blocklistContent = await storage.json('blocklist.json')
+ const groupedBlocklist = new Collection(blocklistContent)
+ .map(data => new Blocked(data))
+ .groupBy((blocked: Blocked) => blocked.channel)
+
+ const issues = await loader.load({ labels: ['streams:add'] })
+
+ const buffer = new Dictionary()
+ const report = issues.map(data => {
+ const channelId = data.get('channel_id') || undefined
+ const streamUrl = data.get('stream_url') || undefined
+
+ const result = new Dictionary({
+ issueNumber: data.get('issue_number'),
+ channelId,
+ status: undefined
+ })
+
+ if (!channelId || !streamUrl) result.set('status', 'error')
+ else if (groupedBlocklist.has(channelId)) result.set('status', 'blocked')
+ else if (groupedChannels.missing(channelId)) result.set('status', 'invalid_id')
+ else if (groupedStreams.has(streamUrl)) result.set('status', 'fullfilled')
+ else if (buffer.has(streamUrl)) result.set('status', 'duplicate')
+ else result.set('status', 'pending')
+
+ buffer.set(streamUrl, true)
+
+ return result.data()
+ })
+
+ console.table(report.all())
+}
+
+main()
diff --git a/scripts/constants.ts b/scripts/constants.ts
new file mode 100644
index 000000000..7609d051f
--- /dev/null
+++ b/scripts/constants.ts
@@ -0,0 +1,11 @@
+export const ROOT_DIR = process.env.ROOT_DIR || './'
+export const STREAMS_DIR = process.env.STREAMS_DIR || './streams'
+export const PUBLIC_DIR = process.env.PUBLIC_DIR || './.gh-pages'
+export const README_DIR = process.env.README_DIR || './.readme'
+export const API_DIR = process.env.API_DIR || './.api'
+export const DATA_DIR = process.env.DATA_DIR || './temp/data'
+export const LOGS_DIR = process.env.LOGS_DIR || './temp/logs'
+export const DB_DIR = process.env.DB_DIR || './temp/database'
+export const TESTING = process.env.NODE_ENV === 'test' ? true : false
+export const OWNER = 'iptv-org'
+export const REPO = 'iptv'
diff --git a/scripts/core/api.js b/scripts/core/api.js
deleted file mode 100644
index abf6cf04b..000000000
--- a/scripts/core/api.js
+++ /dev/null
@@ -1,41 +0,0 @@
-const _ = require('lodash')
-const file = require('./file')
-
-const DATA_DIR = process.env.DATA_DIR || './scripts/tmp/data'
-
-class API {
- constructor(filepath) {
- this.filepath = file.resolve(filepath)
- }
-
- async load() {
- const data = await file.read(this.filepath)
- this.collection = JSON.parse(data)
- }
-
- find(query) {
- return _.find(this.collection, query)
- }
-
- filter(query) {
- return _.filter(this.collection, query)
- }
-
- all() {
- return this.collection
- }
-}
-
-const api = {}
-
-api.channels = new API(`${DATA_DIR}/channels.json`)
-api.streams = new API(`${DATA_DIR}/streams.json`)
-api.countries = new API(`${DATA_DIR}/countries.json`)
-api.guides = new API(`${DATA_DIR}/guides.json`)
-api.categories = new API(`${DATA_DIR}/categories.json`)
-api.languages = new API(`${DATA_DIR}/languages.json`)
-api.regions = new API(`${DATA_DIR}/regions.json`)
-api.blocklist = new API(`${DATA_DIR}/blocklist.json`)
-api.subdivisions = new API(`${DATA_DIR}/subdivisions.json`)
-
-module.exports = api
diff --git a/scripts/core/collection.ts b/scripts/core/collection.ts
new file mode 100644
index 000000000..92836dcb5
--- /dev/null
+++ b/scripts/core/collection.ts
@@ -0,0 +1,175 @@
+import _ from 'lodash'
+import { orderBy, Order } from 'natural-orderby'
+import { Dictionary } from './'
+
+type Iteratee = (value: any, value2?: any) => void
+
+export class Collection {
+ _items: any[]
+
+ constructor(items?: any[]) {
+ this._items = Array.isArray(items) ? items : []
+ }
+
+ first(predicate?: Iteratee) {
+ if (predicate) {
+ return this._items.find(predicate)
+ }
+
+ return this._items[0]
+ }
+
+ last(predicate?: Iteratee) {
+ if (predicate) {
+ return _.findLast(this._items, predicate)
+ }
+
+ return this._items[this._items.length - 1]
+ }
+
+ find(iteratee: Iteratee): Collection {
+ const found = this._items.filter(iteratee)
+
+ return new Collection(found)
+ }
+
+ add(data: any) {
+ this._items.push(data)
+
+ return this
+ }
+
+ intersects(collection: Collection): boolean {
+ return _.intersection(this._items, collection.all()).length > 0
+ }
+
+ count() {
+ return this._items.length
+ }
+
+ join(separator: string) {
+ return this._items.join(separator)
+ }
+
+ indexOf(value: string) {
+ return this._items.indexOf(value)
+ }
+
+ push(data: any) {
+ this.add(data)
+ }
+
+ uniq() {
+ const items = _.uniq(this._items)
+
+ return new Collection(items)
+ }
+
+ reduce(iteratee: Iteratee, accumulator: any) {
+ const items = _.reduce(this._items, iteratee, accumulator)
+
+ return new Collection(items)
+ }
+
+ filter(iteratee: Iteratee) {
+ const items = _.filter(this._items, iteratee)
+
+ return new Collection(items)
+ }
+
+ forEach(iteratee: Iteratee) {
+ for (let item of this._items) {
+ iteratee(item)
+ }
+
+ return this
+ }
+
+ remove(iteratee: Iteratee): Collection {
+ const removed = _.remove(this._items, iteratee)
+
+ return new Collection(removed)
+ }
+
+ concat(collection: Collection) {
+ const items = this._items.concat(collection._items)
+
+ return new Collection(items)
+ }
+
+ isEmpty(): boolean {
+ return this._items.length === 0
+ }
+
+ notEmpty(): boolean {
+ return this._items.length > 0
+ }
+
+ sort() {
+ const items = this._items.sort()
+
+ return new Collection(items)
+ }
+
+ orderBy(iteratees: Iteratee | Iteratee[], orders?: Order | Order[]) {
+ const items = orderBy(this._items, iteratees, orders)
+
+ return new Collection(items)
+ }
+
+ keyBy(iteratee: Iteratee) {
+ const items = _.keyBy(this._items, iteratee)
+
+ return new Dictionary(items)
+ }
+
+ empty() {
+ return this._items.length === 0
+ }
+
+ includes(value: any) {
+ if (typeof value === 'function') {
+ const found = this._items.find(value)
+
+ return !!found
+ }
+
+ return this._items.includes(value)
+ }
+
+ missing(value: any) {
+ if (typeof value === 'function') {
+ const found = this._items.find(value)
+
+ return !found
+ }
+
+ return !this._items.includes(value)
+ }
+
+ uniqBy(iteratee: Iteratee) {
+ const items = _.uniqBy(this._items, iteratee)
+
+ return new Collection(items)
+ }
+
+ groupBy(iteratee: Iteratee) {
+ const object = _.groupBy(this._items, iteratee)
+
+ return new Dictionary(object)
+ }
+
+ map(iteratee: Iteratee) {
+ const items = this._items.map(iteratee)
+
+ return new Collection(items)
+ }
+
+ all() {
+ return this._items
+ }
+
+ toJSON() {
+ return JSON.stringify(this._items)
+ }
+}
diff --git a/scripts/core/database.ts b/scripts/core/database.ts
new file mode 100644
index 000000000..c2d231320
--- /dev/null
+++ b/scripts/core/database.ts
@@ -0,0 +1,22 @@
+import Datastore from '@seald-io/nedb'
+import * as path from 'path'
+
+export class Database {
+ rootDir: string
+
+ constructor(rootDir: string) {
+ this.rootDir = rootDir
+ }
+
+ async load(filepath: string) {
+ const absFilepath = path.join(this.rootDir, filepath)
+
+ return new Datastore({
+ filename: path.resolve(absFilepath),
+ autoload: true,
+ onload: (error: Error): any => {
+ if (error) console.error(error.message)
+ }
+ })
+ }
+}
diff --git a/scripts/core/date.js b/scripts/core/date.js
deleted file mode 100644
index 9872b5a41..000000000
--- a/scripts/core/date.js
+++ /dev/null
@@ -1,12 +0,0 @@
-const dayjs = require('dayjs')
-const utc = require('dayjs/plugin/utc')
-
-dayjs.extend(utc)
-
-const date = {}
-
-date.utc = d => {
- return dayjs.utc(d)
-}
-
-module.exports = date
diff --git a/scripts/core/db.js b/scripts/core/db.js
deleted file mode 100644
index 6d016e816..000000000
--- a/scripts/core/db.js
+++ /dev/null
@@ -1,82 +0,0 @@
-const nedb = require('nedb-promises')
-const fs = require('fs-extra')
-const file = require('./file')
-
-const DB_DIR = process.env.DB_DIR || './scripts/tmp/database'
-
-fs.ensureDirSync(DB_DIR)
-
-class Database {
- constructor(filepath) {
- this.filepath = filepath
- }
-
- load() {
- this.db = nedb.create({
- filename: file.resolve(this.filepath),
- autoload: true,
- onload: err => {
- if (err) console.error(err)
- },
- compareStrings: (a, b) => {
- a = a.replace(/\s/g, '_')
- b = b.replace(/\s/g, '_')
-
- return a.localeCompare(b, undefined, {
- sensitivity: 'accent',
- numeric: true
- })
- }
- })
- }
-
- removeIndex(field) {
- return this.db.removeIndex(field)
- }
-
- addIndex(options) {
- return this.db.ensureIndex(options)
- }
-
- compact() {
- return this.db.persistence.compactDatafile()
- }
-
- stopAutocompact() {
- return this.db.persistence.stopAutocompaction()
- }
-
- reset() {
- return file.clear(this.filepath)
- }
-
- count(query) {
- return this.db.count(query)
- }
-
- insert(doc) {
- return this.db.insert(doc)
- }
-
- update(query, update) {
- return this.db.update(query, update)
- }
-
- find(query) {
- return this.db.find(query)
- }
-
- all() {
- return this.find({})
- }
-
- remove(query, options) {
- return this.db.remove(query, options)
- }
-}
-
-const db = {}
-
-db.streams = new Database(`${DB_DIR}/streams.db`)
-
-module.exports = db
diff --git a/scripts/core/dictionary.ts b/scripts/core/dictionary.ts
new file mode 100644
index 000000000..0058f58e4
--- /dev/null
+++ b/scripts/core/dictionary.ts
@@ -0,0 +1,31 @@
+export class Dictionary {
+ dict: any
+
+ constructor(dict?: any) {
+ this.dict = dict || {}
+ }
+
+ set(key: string, value: any) {
+ this.dict[key] = value
+ }
+
+ has(key: string): boolean {
+ return !!this.dict[key]
+ }
+
+ missing(key: string): boolean {
+ return !this.dict[key]
+ }
+
+ get(key: string): any {
+ return this.dict[key] ? this.dict[key] : undefined
+ }
+
+ keys(): string[] {
+ return Object.keys(this.dict)
+ }
+
+ data() {
+ return this.dict
+ }
+}
diff --git a/scripts/core/file.js b/scripts/core/file.js
deleted file mode 100644
index b36db5ee3..000000000
--- a/scripts/core/file.js
+++ /dev/null
@@ -1,70 +0,0 @@
-const { create: createPlaylist } = require('./playlist')
-const store = require('./store')
-const path = require('path')
-const glob = require('glob')
-const fs = require('fs-extra')
-const _ = require('lodash')
-
-const file = {}
-
-file.list = function (pattern) {
- return new Promise(resolve => {
- glob(pattern, function (err, files) {
- resolve(files)
- })
- })
-}
-
-file.getFilename = function (filepath) {
- return path.parse(filepath).name
-}
-
-file.createDir = async function (dir) {
- if (await file.exists(dir)) return
-
- return fs.mkdir(dir, { recursive: true }).catch(console.error)
-}
-
-file.exists = function (filepath) {
- return fs.exists(path.resolve(filepath))
-}
-
-file.read = function (filepath) {
- return fs.readFile(path.resolve(filepath), { encoding: 'utf8' }).catch(console.error)
-}
-
-file.append = function (filepath, data) {
- return fs.appendFile(path.resolve(filepath), data).catch(console.error)
-}
-
-file.create = function (filepath, data = '') {
- filepath = path.resolve(filepath)
- const dir = path.dirname(filepath)
-
- return file
- .createDir(dir)
- .then(() => fs.writeFile(filepath, data, { encoding: 'utf8', flag: 'w' }))
- .catch(console.error)
-}
-
-file.write = function (filepath, data = '') {
- return fs.writeFile(path.resolve(filepath), data).catch(console.error)
-}
-
-file.clear = function (filepath) {
- return file.write(filepath, '')
-}
-
-file.resolve = function (filepath) {
- return path.resolve(filepath)
-}
-
-file.dirname = function (filepath) {
- return path.dirname(filepath)
-}
-
-file.basename = function (filepath) {
- return path.basename(filepath)
-}
-
-module.exports = file
diff --git a/scripts/core/file.ts b/scripts/core/file.ts
new file mode 100644
index 000000000..f12e8ba6d
--- /dev/null
+++ b/scripts/core/file.ts
@@ -0,0 +1,31 @@
+import * as path from 'path'
+
+export class File {
+ filepath: string
+ content: string
+
+ constructor(filepath: string, content?: string) {
+ this.filepath = filepath
+ this.content = content || ''
+ }
+
+ getFilename() {
+ return path.parse(this.filepath).name
+ }
+
+ dirname() {
+ return path.dirname(this.filepath)
+ }
+
+ basename() {
+ return path.basename(this.filepath)
+ }
+
+ append(data: string) {
+ this.content = this.content + data
+ }
+
+ extension() {
+ return this.filepath.split('.').pop()
+ }
+}
diff --git a/scripts/core/generator.js b/scripts/core/generator.js
deleted file mode 100644
index 5f22e901b..000000000
--- a/scripts/core/generator.js
+++ /dev/null
@@ -1,33 +0,0 @@
-const { create: createPlaylist } = require('./playlist')
-const generators = require('../generators')
-const logger = require('./logger')
-const file = require('./file')
-
-const PUBLIC_DIR = process.env.PUBLIC_DIR || '.gh-pages'
-const LOGS_DIR = process.env.LOGS_DIR || 'scripts/tmp/logs/generators'
-
-const generator = {}
-
-generator.generate = async function (name, streams = []) {
- if (typeof generators[name] === 'function') {
- try {
- let output = await generators[name].bind()(streams)
- output = Array.isArray(output) ? output : [output]
- for (const type of output) {
- const playlist = createPlaylist(type.items, { public: true })
- await file.create(`${PUBLIC_DIR}/${type.filepath}`, playlist.toString())
- }
- await file.create(`${LOGS_DIR}/${name}.log`, output.map(toJSON).join('\n'))
- } catch (error) {
- logger.error(`generators/${name}.js: ${error.message}`)
- }
- }
-}
-
-module.exports = generator
-
-function toJSON(type) {
- type.count = type.items.length
- delete type.items
- return JSON.stringify(type)
-}
diff --git a/scripts/core/htmlTable.ts b/scripts/core/htmlTable.ts
new file mode 100644
index 000000000..12c39bbea
--- /dev/null
+++ b/scripts/core/htmlTable.ts
@@ -0,0 +1,46 @@
+type Column = {
+ name: string
+ nowrap?: boolean
+ align?: string
+}
+
+type DataItem = string[]
+
+export class HTMLTable {
+ data: DataItem[]
+ columns: Column[]
+
+ constructor(data: DataItem[], columns: Column[]) {
+ this.data = data
+ this.columns = columns
+ }
+
+ toString() {
+ let output = '${column.name} | ` + } + output += '
---|
${item[prop]} | ` + i++ + } + output += '
${column.name} | ` - } - output += '
---|
${item[prop]} | ` - i++ - } - output += '
https://iptv-org.github.io/iptv/${logItem.filepath}
`
+ ])
+ })
+
+ data = data
+ .orderBy(item => item[0])
+ .map(item => {
+ item.shift()
+ return item
+ })
+
+ const table = new HTMLTable(data.all(), [
+ { name: 'Category' },
+ { name: 'Channels', align: 'right' },
+ { name: 'Playlist', nowrap: true }
+ ])
+
+ const readmeStorage = new Storage(README_DIR)
+ await readmeStorage.save('_categories.md', table.toString())
+ }
+}
diff --git a/scripts/tables/countryTable.ts b/scripts/tables/countryTable.ts
new file mode 100644
index 000000000..c78a23eaa
--- /dev/null
+++ b/scripts/tables/countryTable.ts
@@ -0,0 +1,81 @@
+import { Storage, HTMLTable, Collection, LogParser, LogItem, File } from '../core'
+import { Country, Subdivision } from '../models'
+import { DATA_DIR, LOGS_DIR, README_DIR } from '../constants'
+import { Table } from './table'
+
+export class CountryTable implements Table {
+ constructor() {}
+
+ async make() {
+ const dataStorage = new Storage(DATA_DIR)
+
+ const countriesContent = await dataStorage.json('countries.json')
+ const countries = new Collection(countriesContent).map(data => new Country(data))
+
+ const subdivisionsContent = await dataStorage.json('subdivisions.json')
+ const subdivisions = new Collection(subdivisionsContent).map(data => new Subdivision(data))
+
+ const parser = new LogParser()
+ const logsStorage = new Storage(LOGS_DIR)
+ const generatorsLog = await logsStorage.read('generators.log')
+
+ let data = new Collection()
+ parser
+ .parse(generatorsLog)
+ .filter(
+ (logItem: LogItem) =>
+ logItem.filepath.includes('countries/') || logItem.filepath.includes('subdivisions/')
+ )
+ .forEach((logItem: LogItem) => {
+ const file = new File(logItem.filepath)
+ const code = file.getFilename().toUpperCase()
+ const [countryCode, subdivisionCode] = code.split('-') || ['', '']
+
+ if (subdivisionCode) {
+ const subdivision = subdivisions.first(
+ (subdivision: Subdivision) => subdivision.code === code
+ )
+ const country = countries.first(
+ (country: Country) => country.code === subdivision.country
+ )
+ data.add([
+ `${country.name}/${subdivision.name}`,
+ ` ${subdivision.name}`,
+ logItem.count,
+ `https://iptv-org.github.io/iptv/${logItem.filepath}
`
+ ])
+ } else if (countryCode === 'INT') {
+ data.add([
+ 'ZZ',
+ `๐ International`,
+ logItem.count,
+ `https://iptv-org.github.io/iptv/${logItem.filepath}
`
+ ])
+ } else {
+ const country = countries.first((country: Country) => country.code === countryCode)
+ data.add([
+ country.name,
+ `${country.flag} ${country.name}`,
+ logItem.count,
+ `https://iptv-org.github.io/iptv/${logItem.filepath}
`
+ ])
+ }
+ })
+
+ data = data
+ .orderBy(item => item[0])
+ .map(item => {
+ item.shift()
+ return item
+ })
+
+ const table = new HTMLTable(data.all(), [
+ { name: 'Country' },
+ { name: 'Channels', align: 'right' },
+ { name: 'Playlist', nowrap: true }
+ ])
+
+ const readmeStorage = new Storage(README_DIR)
+ await readmeStorage.save('_countries.md', table.toString())
+ }
+}
diff --git a/scripts/tables/index.ts b/scripts/tables/index.ts
new file mode 100644
index 000000000..f951a21f2
--- /dev/null
+++ b/scripts/tables/index.ts
@@ -0,0 +1,4 @@
+export * from './categoryTable'
+export * from './countryTable'
+export * from './languageTable'
+export * from './regionTable'
diff --git a/scripts/tables/languageTable.ts b/scripts/tables/languageTable.ts
new file mode 100644
index 000000000..2b298b180
--- /dev/null
+++ b/scripts/tables/languageTable.ts
@@ -0,0 +1,53 @@
+import { Storage, HTMLTable, Collection, LogParser, LogItem, File } from '../core'
+import { Language } from '../models'
+import { DATA_DIR, LOGS_DIR, README_DIR } from '../constants'
+import { Table } from './table'
+
+export class LanguageTable implements Table {
+ constructor() {}
+
+ async make() {
+ const dataStorage = new Storage(DATA_DIR)
+ const languagesContent = await dataStorage.json('languages.json')
+ const languages = new Collection(languagesContent).map(data => new Language(data))
+
+ const parser = new LogParser()
+ const logsStorage = new Storage(LOGS_DIR)
+ const generatorsLog = await logsStorage.read('generators.log')
+
+ let data = new Collection()
+ parser
+ .parse(generatorsLog)
+ .filter((logItem: LogItem) => logItem.filepath.includes('languages/'))
+ .forEach((logItem: LogItem) => {
+ const file = new File(logItem.filepath)
+ const languageCode = file.getFilename()
+ const language: Language = languages.first(
+ (language: Language) => language.code === languageCode
+ )
+
+ data.add([
+ language ? language.name : 'ZZ',
+ language ? language.name : 'Undefined',
+ logItem.count,
+ `https://iptv-org.github.io/iptv/${logItem.filepath}
`
+ ])
+ })
+
+ data = data
+ .orderBy(item => item[0])
+ .map(item => {
+ item.shift()
+ return item
+ })
+
+ const table = new HTMLTable(data.all(), [
+ { name: 'Language', align: 'left' },
+ { name: 'Channels', align: 'right' },
+ { name: 'Playlist', align: 'left', nowrap: true }
+ ])
+
+ const readmeStorage = new Storage(README_DIR)
+ await readmeStorage.save('_languages.md', table.toString())
+ }
+}
diff --git a/scripts/tables/regionTable.ts b/scripts/tables/regionTable.ts
new file mode 100644
index 000000000..ee29f4088
--- /dev/null
+++ b/scripts/tables/regionTable.ts
@@ -0,0 +1,47 @@
+import { Storage, HTMLTable, Collection, LogParser, LogItem, File } from '../core'
+import { Region } from '../models'
+import { DATA_DIR, LOGS_DIR, README_DIR } from '../constants'
+import { Table } from './table'
+
+export class RegionTable implements Table {
+ constructor() {}
+
+ async make() {
+ const dataStorage = new Storage(DATA_DIR)
+ const regionsContent = await dataStorage.json('regions.json')
+ const regions = new Collection(regionsContent).map(data => new Region(data))
+
+ const parser = new LogParser()
+ const logsStorage = new Storage(LOGS_DIR)
+ const generatorsLog = await logsStorage.read('generators.log')
+
+ let data = new Collection()
+ parser
+ .parse(generatorsLog)
+ .filter((logItem: LogItem) => logItem.filepath.includes('regions/'))
+ .forEach((logItem: LogItem) => {
+ const file = new File(logItem.filepath)
+ const regionCode = file.getFilename().toUpperCase()
+ const region: Region = regions.first((region: Region) => region.code === regionCode)
+
+ if (region) {
+ data.add([
+ region.name,
+ logItem.count,
+ `https://iptv-org.github.io/iptv/${logItem.filepath}
`
+ ])
+ }
+ })
+
+ data = data.orderBy(item => item[0])
+
+ const table = new HTMLTable(data.all(), [
+ { name: 'Region', align: 'left' },
+ { name: 'Channels', align: 'right' },
+ { name: 'Playlist', align: 'left', nowrap: true }
+ ])
+
+ const readmeStorage = new Storage(README_DIR)
+ await readmeStorage.save('_regions.md', table.toString())
+ }
+}
diff --git a/scripts/tables/table.ts b/scripts/tables/table.ts
new file mode 100644
index 000000000..b8bd21bd7
--- /dev/null
+++ b/scripts/tables/table.ts
@@ -0,0 +1,3 @@
+export interface Table {
+ make(): void
+}
diff --git a/scripts/tmp/.gitignore b/scripts/tmp/.gitignore
deleted file mode 100644
index c96a04f00..000000000
--- a/scripts/tmp/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-*
-!.gitignore
\ No newline at end of file
diff --git a/scripts/types/markdown-include.d.ts b/scripts/types/markdown-include.d.ts
new file mode 100644
index 000000000..d607a01b5
--- /dev/null
+++ b/scripts/types/markdown-include.d.ts
@@ -0,0 +1 @@
+declare module 'markdown-include'
From d12fd1f6ce9637766795372a161da4c84ba849a2 Mon Sep 17 00:00:00 2001
From: freearhey <7253922+freearhey@users.noreply.github.com>
Date: Fri, 15 Sep 2023 18:40:46 +0300
Subject: [PATCH 08/12] Update .gitignore
---
.gitignore | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index f9fc4b735..3fe46ea01 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,5 @@ node_modules
.DS_Store
.gh-pages
.api
-.env
\ No newline at end of file
+.env
+/temp
\ No newline at end of file
From 4a5a4f03c85486a3b1eb2cca044b10ab90b2aafc Mon Sep 17 00:00:00 2001
From: freearhey <7253922+freearhey@users.noreply.github.com>
Date: Fri, 15 Sep 2023 18:40:49 +0300
Subject: [PATCH 09/12] Update update.yml
---
.github/workflows/update.yml | 50 +++++++++++++++++++++---------------
1 file changed, 30 insertions(+), 20 deletions(-)
diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml
index e5b3ee34e..3e13b6b69 100644
--- a/.github/workflows/update.yml
+++ b/.github/workflows/update.yml
@@ -2,7 +2,7 @@ name: update
on:
workflow_dispatch:
schedule:
- - cron: '0 */6 * * *'
+ - cron: '0 0 * * *'
jobs:
main:
runs-on: ubuntu-latest
@@ -10,7 +10,7 @@ jobs:
contents: write
steps:
- uses: actions/checkout@v3
- - uses: tibdex/github-app-token@v1
+ - uses: getsentry/action-github-app-token@v2
if: ${{ !env.ACT }}
id: create-app-token
with:
@@ -20,7 +20,12 @@ jobs:
if: ${{ !env.ACT }}
with:
token: ${{ steps.create-app-token.outputs.token }}
+ - name: setup git
+ run: |
+ git config user.name "iptv-bot[bot]"
+ git config user.email "84861620+iptv-bot[bot]@users.noreply.github.com"
- uses: actions/setup-node@v3
+ if: ${{ !env.ACT }}
with:
node-version: 18
cache: 'npm'
@@ -28,31 +33,36 @@ jobs:
run: npm install
- name: load api data
run: npm run api:load
- - name: validate playlists
+ - name: setup database
+ run: npm run db:create
+ - name: update internal playlists
+ run: npm run playlist:update --silent >> $GITHUB_OUTPUT
+ id: playlist-update
+ - name: check internal playlists
run: |
npm run playlist:lint
npm run playlist:validate
- - name: setup database
- run: npm run db:create
- - name: generate playlists
+ - name: generate public playlists
run: npm run playlist:generate
- - name: generate streams.json
+ - name: generate .api/streams.json
run: npm run api:generate
- name: update readme.md
run: npm run readme:update
- - name: commit changes
- uses: stefanzweifel/git-auto-commit-action@v4
+ - run: git status
+ - name: commit changes to /streams
+ run: |
+ git add streams
+ git status
+ git commit -m "[Bot] Update /streams" -m "Committed by [iptv-bot](https://github.com/apps/iptv-bot) via [update](https://github.com/iptv-org/iptv/actions/runs/${{ github.run_id }}) workflow." -m "${{ steps.playlist-update.outputs.OUTPUT }}" --no-verify
+ - name: commit changes to readme.md
+ run: |
+ git add README.md
+ git status
+ git commit -m "[Bot] Update README.md" -m "Committed by [iptv-bot](https://github.com/apps/iptv-bot) via [update](https://github.com/iptv-org/iptv/actions/runs/${{ github.run_id }}) workflow." --no-verify
+ - name: push all changes to the repository
if: ${{ !env.ACT && github.ref == 'refs/heads/master' }}
- with:
- commit_message: "[Bot] Update README.md"
- branch: master
- commit_options: '--no-verify'
- file_pattern: README.md
- repository: .
- commit_user_name: iptv-bot[bot]
- commit_user_email: 84861620+iptv-bot[bot]@users.noreply.github.com
- commit_author: iptv-bot[bot] <84861620+iptv-bot[bot]@users.noreply.github.com>
- - name: deploy to github pages
+ run: git push
+ - name: deploy public playlists to github pages
uses: JamesIves/github-pages-deploy-action@4.1.1
if: ${{ !env.ACT && github.ref == 'refs/heads/master' }}
with:
@@ -64,7 +74,7 @@ jobs:
git-config-email: 84861620+iptv-bot[bot]@users.noreply.github.com
commit-message: '[Bot] Deploy to GitHub Pages'
clean: true
- - name: deploy to iptv-org/api
+ - name: move .api/streams.json to iptv-org/api
uses: JamesIves/github-pages-deploy-action@4.1.1
if: ${{ !env.ACT && github.ref == 'refs/heads/master' }}
with:
From 543008e7af8e573f70f13958c9c1f84863077e17 Mon Sep 17 00:00:00 2001
From: freearhey <7253922+freearhey@users.noreply.github.com>
Date: Fri, 15 Sep 2023 18:40:53 +0300
Subject: [PATCH 10/12] Update FAQ.md
---
FAQ.md | 28 ----------------------------
1 file changed, 28 deletions(-)
diff --git a/FAQ.md b/FAQ.md
index 95de5104b..a18e991dd 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -6,18 +6,6 @@ Start by asking our community for help via [Discussions](https://github.com/orgs
But keep in mind that not all TV channels are available for viewing online, and in this case there is little we can do about it.
-### How can I add stream to playlists?
-
-You have several options:
-
-1. Create a new [issue](https://github.com/iptv-org/iptv/issues/new?assignees=&labels=streams:add&projects=&template=-----streams_add.yml&title=Add%3A+) with a valid channel ID and a link to the stream. If the request is approved , the link will be added to the playlist in the next update. For or more info, see [Issue Reporting Guidelines](CONTRIBUTING.md#issue-reporting-guidelines).
-
-2. Or you can add the link to the playlist directly via pull request. For more info, see [Pull Request Guidelines](CONTRIBUTING.md#pull-request-guidelines).
-
-### How to report a broken stream?
-
-Fill out this [form](https://github.com/iptv-org/iptv/issues/new?assignees=&labels=broken+stream&projects=&template=---broken-stream.yml&title=Broken%3A+) and as soon as there is a working replacement, we will add it to the playlist or at least remove the non-working one.
-
### Does the playlist have a channel guide?
Yes. See [iptv-org/epg](https://github.com/iptv-org/epg) for more info.
@@ -30,10 +18,6 @@ No.
The site contains a list of all TV channels in the world and only those of them for which we have working stream links are included in the playlists.
-### How can I add a link to YouTube live?
-
-Since not all players allow you to open links to YouTube directly, we also cannot add them to playlists yet. However, some services like [abskmj/youtube-hls-m3u8](https://github.com/abskmj/youtube-hls-m3u8) allow you to get around this limitation by creating permalinks to the feed that can be played as normal. And these are the kind of links you can add to the playlist.
-
### Can I add a radio broadcast?
Yes, if it is a [visual radio](https://en.wikipedia.org/wiki/Visual_radio) in which a video and audio are shown at the same time.
@@ -41,15 +25,3 @@ Yes, if it is a [visual radio](https://en.wikipedia.org/wiki/Visual_radio) in wh
### Why don't you accept links to Xtream Codes server?
Xtream Codes streams tend to be very unstable, and often links to them fail very quickly, so it's easier for us to initially exclude them from the playlist than to search for expired ones every day.
-
-### How to distinguish a link to an Xtream Codes server from a regular one?
-
-Most of them have this form:
-
-`http(s)://{hostname}:{port}/{username}/{password}/{channelID}` (port is often `25461`)
-
-To make sure that the link leads to the Xtream Codes server, copy the `hostname`, `port`, `username` and `password` into the link below and try to open it in a browser:
-
-`http(s)://{hostname}:{port}/panel_api.php?username={username}&password={password}`
-
-If the link answers, you're with an Xtream Codes server.
From 81146a825bac75fa482108eb0833f1b858870954 Mon Sep 17 00:00:00 2001
From: freearhey <7253922+freearhey@users.noreply.github.com>
Date: Fri, 15 Sep 2023 18:40:56 +0300
Subject: [PATCH 11/12] Update CONTRIBUTING.md
---
CONTRIBUTING.md | 142 +++++++++++++++++++++++++-----------------------
1 file changed, 73 insertions(+), 69 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index cd6a30e31..8444f3849 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,89 +1,67 @@
# Contributing Guide
-- [Issue Reporting Guidelines](#issue-reporting-guidelines)
-- [Pull Request Guidelines](#pull-request-guidelines)
+- [How to?](#how-to)
- [Stream Description Scheme](#stream-description-scheme)
- [Project Structure](#project-structure)
+- [Scripts](#scripts)
-## Issue Reporting Guidelines
+## How to?
-### Add stream link
+### How to add a new stream link to a playlists?
-Before posting your request, make sure that:
+You have several options:
-- Channel ID is valid. A complete list of all supported channels and their IDs can be found on [iptv-org.github.io](https://iptv-org.github.io/).
-- The link you want to add works stably. To check this, open it in one of the players (for example, [VLC player](https://www.videolan.org/vlc/index.html)) and watch the broadcast for at least a minute (some test streams are interrupted after 15-30 seconds).
-- The link is not already in the playlist. This can be done by [searching](https://github.com/search?q=repo%3Aiptv-org%2Fiptv+http%3A%2F%2Fexample.com&type=code) the repository.
-- The link does not lead to Xtream Codes server. [Why don't you accept links to Xtream Codes server?](FAQ.md#why-dont-you-accept-links-to-xtream-codes-server)
+1. Create a new [issue](https://github.com/iptv-org/iptv/issues/new?assignees=&labels=streams:add&projects=&template=-----streams_add.yml&title=Add%3A+) and provide all the required information. If the request is approved, the link will be added to the playlist in the next update.
+
+2. Add the link to the playlist directly using a [pull request](https://github.com/iptv-org/iptv/pulls).
+
+Regardless of which option you choose, before posting your request please do the following:
+
+- Make sure the link you want to add works stably. To check this, open it in one of the players (for example, [VLC player](https://www.videolan.org/vlc/index.html)) and watch the broadcast for at least a minute (some test streams are interrupted after 15-30 seconds).
+- Make sure the link is not already in the playlist. This can be done by [searching](https://github.com/search?q=repo%3Aiptv-org%2Fiptv+http%3A%2F%2Fexample.com&type=code) the repository.
+- Find the ID of the channel you want to add in our [database](https://iptv-org.github.io/). If this particular channel is not in the database, then leave a request to add it [here](https://github.com/iptv-org/database/issues/new/choose) and wait until it is approved before continuing.
+- Make sure the channel is not blacklisted. This can be done by checking the [blocklist.csv](https://github.com/iptv-org/database/blob/master/data/blocklist.csv) file.
+- The link does not lead to the Xtream Codes server. [Why don't you accept links to Xtream Codes server?](FAQ.md#why-dont-you-accept-links-to-xtream-codes-server)
- If you know that the broadcast only works in certain countries or it is periodically interrupted, do not forget to indicate this in the request.
-An issue without a valid channel ID or working link to the stream will be closed immediately.
+A requests without a valid channel ID or working link to the stream will be closed immediately.
-### Edit stream description
+Note all links in playlists are sorted automatically by scripts so there is no need to sort them manually. For more info, see [Scripts](#scripts).
-Before posting your request, make sure that:
+### How to add a link to YouTube live?
-- The link is still in our playlists. This can be verified by [searching](https://github.com/search?q=repo%3Aiptv-org%2Fiptv+http%3A%2F%2Fexample.com&type=code) the repository.
+You can use one of the services like [abskmj/youtube-hls-m3u8](https://github.com/abskmj/youtube-hls-m3u8) that allow you to create permanent link to the broadcast that can be opened in most players.
+
+### How to distinguish a link to an Xtream Codes server from a regular one?
+
+Most of them have this form:
+
+`http(s)://{hostname}:{port}/{username}/{password}/{channelID}` (port is often `25461`)
+
+To make sure that the link leads to the Xtream Codes server, copy the `hostname`, `port`, `username` and `password` into the link below and try to open it in a browser:
+
+`http(s)://{hostname}:{port}/panel_api.php?username={username}&password={password}`
+
+If the link answers, you're with an Xtream Codes server.
+
+### How to report a broken stream?
+
+Fill out this [form](https://github.com/iptv-org/iptv/issues/new?assignees=&labels=broken+stream&projects=&template=---broken-stream.yml&title=Broken%3A+) and as soon as a working replacement appears, we will add it to the playlist or at least remove the non-working one.
+
+The only thing before publishing your report is to make sure that:
+
+- The link is still in our playlists. You can verify this by [searching](https://github.com/search?q=repo%3Aiptv-org%2Fiptv+http%3A%2F%2Fexample.com&type=code) the repository.
+- The link really doesn't work and is not just [geo-blocked](https://en.wikipedia.org/wiki/Geo-blocking). To check this, you can either use a [VPN](https://en.wikipedia.org/wiki/Virtual_private_network) or services such as [streamtest.in](https://streamtest.in/).
An issue without a valid link will be closed immediately.
-### Report broken link
+### How do I remove my channel from playlist?
-Before posting your report, make sure that:
+To request removal of a link to a channel from the repository, you need to fill out this [form](https://github.com/iptv-org/iptv/issues/new?assignees=&labels=removal+request&projects=&template=-removal-request.yml&title=Remove%3A+) and wait for the request to be reviewed (this usually takes no more than 1 business day). And if the request is approved, links to the channel will be immediately removed from the repository.
-- The link is still in our playlists. This can be verified by [searching](https://github.com/search?q=repo%3Aiptv-org%2Fiptv+http%3A%2F%2Fexample.com&type=code) the repository.
-- The link is not blocked in your country. To check this, you can use either a [VPN](https://en.wikipedia.org/wiki/Virtual_private_network) or services such as [streamtest.in](https://streamtest.in/).
+The channel will also be added to our [blocklist](https://github.com/iptv-org/database/blob/master/data/blocklist.csv) to avoid its appearance in our playlists in the future.
-An issue should contain a report for only one channel, otherwise it will be closed immediately.
-
-### Bug report
-
-Please use this form only if you have found a bug in one of the scripts or the repository as a whole. To report broken link or an error in the stream description, use one of the methods described above.
-
-### Removal request
-
-To request the removal of a link to a channel from repository, you need to fill out this [form](https://github.com/iptv-org/iptv/issues/new?assignees=&labels=removal+request&projects=&template=-removal-request.yml&title=Remove%3A+) and if your request is approved the link will be removed within 1 business day. The channel will also be added to our [blocklist](https://github.com/iptv-org/database/blob/master/data/blocklist.csv) to avoid its appearance in our playlists in the future.
-
-Please keep in mind that we only accept removal requests from channel copyright holders and their official representatives, any other requests will be closed immediately.
-
-## Pull Request Guidelines
-
-### Add stream link
-
-If you want to add a new stream link to playlists, please do the following:
-
-- Make sure that the link you want to add works stably. To do this, open it in one of the players (for example, [VLC player](https://www.videolan.org/vlc/index.html)) and watch the broadcast for at least a minute (some test streams are interrupted after 15-30 seconds).
-- Make sure the link does not lead to Xtream Codes server. [How to distinguish a link to an Xtream Codes server from a regular one?](FAQ.md#how-to-distinguish-a-link-to-an-xtream-codes-server-from-a-regular-one)
-- Find in our [database](https://iptv-org.github.io/) the ID of the channel you want to add. If this particular channel is not in the database, then first leave a request to add it [here](https://github.com/iptv-org/database/issues/new/choose) and once the request is approved, you can proceed further.
-- Then open the [/streams](/streams) folder and select the file corresponding to the country of this channel (for example, for `TF1.fr` it will be `fr.m3u`) and then insert the description of the stream and a link to it at the very end of the file. For more info, see [Stream Description Scheme](#stream-description-scheme).
-- If you know that the broadcast only works in certain countries, do not forget to add the `[Geo-blocked]` label to the stream description.
-- For broadcasts that may be periodically interrupted, there is the label `[Not 24/7]`.
-- Finally, commit all changes and submit a pull request.
-
-If the request is approved by other community members, then the link will appear in the playlist on the next update.
-
-### Remove broken link
-
-If you find a link in the playlist that does not work, follow the steps below:
-
-- Verify that the link is indeed not working and has not just been [geo-blocked](https://en.wikipedia.org/wiki/Geo-blocking). To do this, you can either use a [VPN](https://en.wikipedia.org/wiki/Virtual_private_network) or services such as [streamtest.in](https://streamtest.in/).
-- If the link works, but only when using a VPN, then tag it with [Geo-blocked]. For more info, see [Stream Description Scheme](#stream-description-scheme)
-- If it turns out that the link works but not 24/7, then add the [Not 24/7] label to it.
-- If the link is still not working, then continue.
-- Use a [search](https://github.com/search?q=repo%3Aiptv-org%2Fiptv+http%3A%2F%2Fexample.com&type=code) to find which file this link is stored in, open it and delete the link along with the description.
-- Commit the changes and make a pull request.
-
-### Update README.md
-
-- Open `.readme/template.md`.
-- Make the necessary changes.
-- Commit all changes and send a pull request.
-
-### Update this Guide
-
-- Open `.github/CONTRIBUTING.md`.
-- Make the necessary changes.
-- Commit all changes and send a pull request.
+Please note that we only accept removal requests from channel owners and their official representatives, all other requests will be closed immediately.
## Stream Description Scheme
@@ -97,7 +75,7 @@ STREAM_URL
| Attribute | Description | Required | Valid values |
| -------------- | ------------------------------------------------------------------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------- |
| `CHANNEL_ID` | Channel ID. | Optional | Full list of supported channels with corresponding ID could be found on [iptv-org.github.io](https://iptv-org.github.io/). |
-| `CHANNEL_NAME` | Full name of the channel. May contain any characters except: `,`, `(`, `)`, `[`, `]`. | Required | - |
+| `CHANNEL_NAME` | Full name of the channel. May contain any characters except: `,`, `[`, `]`. | Required | - |
| `RESOLUTION` | Maximum stream resolution | Optional | `2160p`, `1080p`, `720p`, `480p`, `360p` etc |
| `LABEL` | Specified in cases where the broadcast for some reason may not be available to some users. | Optional | `Geo-blocked` or `Not 24/7` |
| `STREAM_URL` | Stream URL. | Required | - |
@@ -109,7 +87,7 @@ Example:
https://example.com/playlist.m3u8
```
-Also, if necessary, you can specify custom HTTP User-Agent and Referrer via the `#EXTVLCOPT` tag:
+Also, if necessary, you can specify custom [HTTP User-Agent](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent) and [Referrer](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer) via the `#EXTVLCOPT` tag:
```xml
#EXTINF:-1 tvg-id="ExampleTV.us",Example TV
@@ -134,3 +112,29 @@ http://example.com/stream.m3u8
- `tests/`: contains tests to check the scripts.
- `CONTRIBUTING.md`: file you are currently reading.
- `README.md`: project description generated from the contents of the `.readme/` folder.
+
+## Scripts
+
+For scripts to work, you must have [Node.js](https://nodejs.org/en) installed on your computer.
+
+To run scripts use the `npm run