From f9b828020a1822f02bf145b98650d0a9f47d7da4 Mon Sep 17 00:00:00 2001 From: StrangeDrVN Date: Sun, 22 Feb 2026 16:45:29 +0530 Subject: [PATCH 01/10] add whaletvplus --- .../__data__/content.json | 817 ++++++++++++++++++ sites/watch.whaletvplus.com/readme.md | 21 + .../watch.whaletvplus.com.channels.xml | 348 ++++++++ .../watch.whaletvplus.com.config.js | 165 ++++ .../watch.whaletvplus.com.test.js | 102 +++ 5 files changed, 1453 insertions(+) create mode 100644 sites/watch.whaletvplus.com/__data__/content.json create mode 100644 sites/watch.whaletvplus.com/readme.md create mode 100644 sites/watch.whaletvplus.com/watch.whaletvplus.com.channels.xml create mode 100644 sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js create mode 100644 sites/watch.whaletvplus.com/watch.whaletvplus.com.test.js diff --git a/sites/watch.whaletvplus.com/__data__/content.json b/sites/watch.whaletvplus.com/__data__/content.json new file mode 100644 index 00000000..475b0475 --- /dev/null +++ b/sites/watch.whaletvplus.com/__data__/content.json @@ -0,0 +1,817 @@ +{ + "data": [ + { + "chlId": "972712064533996134", + "chlNm": "123GO!", + "ptList": [ + { + "prgEtm": "1767680730000", + "prgStm": "1767679200000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024814" + }, + { + "prgEtm": "1767682123000", + "prgStm": "1767680730000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024815" + }, + { + "prgEtm": "1767683888000", + "prgStm": "1767682123000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024816" + }, + { + "prgEtm": "1767685585000", + "prgStm": "1767683888000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024817" + }, + { + "prgEtm": "1767687192000", + "prgStm": "1767685585000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024818" + }, + { + "prgEtm": "1767688664000", + "prgStm": "1767687192000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024819" + }, + { + "prgEtm": "1767690132000", + "prgStm": "1767688664000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024820" + }, + { + "prgEtm": "1767691727000", + "prgStm": "1767690132000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024821" + }, + { + "prgEtm": "1767693179000", + "prgStm": "1767691727000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024822" + }, + { + "prgEtm": "1767694741000", + "prgStm": "1767693179000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024823" + }, + { + "prgEtm": "1767696168000", + "prgStm": "1767694741000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024824" + }, + { + "prgEtm": "1767697685000", + "prgStm": "1767696168000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024825" + }, + { + "prgEtm": "1767699105000", + "prgStm": "1767697685000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024826" + }, + { + "prgEtm": "1767700639000", + "prgStm": "1767699105000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024827" + }, + { + "prgEtm": "1767702080000", + "prgStm": "1767700639000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024828" + }, + { + "prgEtm": "1767703536000", + "prgStm": "1767702080000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024829" + }, + { + "prgEtm": "1767704915000", + "prgStm": "1767703536000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024830" + }, + { + "prgEtm": "1767706392000", + "prgStm": "1767704915000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024831" + }, + { + "prgEtm": "1767707772000", + "prgStm": "1767706392000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024832" + }, + { + "prgEtm": "1767709462000", + "prgStm": "1767707772000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024833" + }, + { + "prgEtm": "1767711426000", + "prgStm": "1767709462000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024834" + }, + { + "prgEtm": "1767712932000", + "prgStm": "1767711426000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024835" + }, + { + "prgEtm": "1767714330000", + "prgStm": "1767712932000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024836" + }, + { + "prgEtm": "1767715777000", + "prgStm": "1767714330000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024837" + }, + { + "prgEtm": "1767717501000", + "prgStm": "1767715777000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024838" + }, + { + "prgEtm": "1767719439000", + "prgStm": "1767717501000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024839" + }, + { + "prgEtm": "1767720912000", + "prgStm": "1767719439000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024840" + }, + { + "prgEtm": "1767722448000", + "prgStm": "1767720912000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024841" + }, + { + "prgEtm": "1767723930000", + "prgStm": "1767722448000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024842" + }, + { + "prgEtm": "1767725520000", + "prgStm": "1767723930000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024843" + }, + { + "prgEtm": "1767727050000", + "prgStm": "1767725520000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024844" + }, + { + "prgEtm": "1767728443000", + "prgStm": "1767727050000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024845" + }, + { + "prgEtm": "1767730208000", + "prgStm": "1767728443000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024846" + }, + { + "prgEtm": "1767731905000", + "prgStm": "1767730208000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024847" + }, + { + "prgEtm": "1767733512000", + "prgStm": "1767731905000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024848" + }, + { + "prgEtm": "1767734984000", + "prgStm": "1767733512000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024849" + }, + { + "prgEtm": "1767736452000", + "prgStm": "1767734984000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024850" + }, + { + "prgEtm": "1767738047000", + "prgStm": "1767736452000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024851" + }, + { + "prgEtm": "1767739499000", + "prgStm": "1767738047000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024852" + }, + { + "prgEtm": "1767741061000", + "prgStm": "1767739499000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024853" + }, + { + "prgEtm": "1767742488000", + "prgStm": "1767741061000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024854" + }, + { + "prgEtm": "1767744005000", + "prgStm": "1767742488000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024855" + }, + { + "prgEtm": "1767745425000", + "prgStm": "1767744005000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024856" + }, + { + "prgEtm": "1767746959000", + "prgStm": "1767745425000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024857" + }, + { + "prgEtm": "1767748400000", + "prgStm": "1767746959000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024858" + }, + { + "prgEtm": "1767749856000", + "prgStm": "1767748400000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024859" + }, + { + "prgEtm": "1767751235000", + "prgStm": "1767749856000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024860" + }, + { + "prgEtm": "1767752712000", + "prgStm": "1767751235000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024861" + }, + { + "prgEtm": "1767754092000", + "prgStm": "1767752712000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024862" + }, + { + "prgEtm": "1767755782000", + "prgStm": "1767754092000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024863" + }, + { + "prgEtm": "1767757746000", + "prgStm": "1767755782000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024864" + }, + { + "prgEtm": "1767759132000", + "prgStm": "1767757746000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024865" + }, + { + "prgEtm": "1767760530000", + "prgStm": "1767759132000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024866" + }, + { + "prgEtm": "1767761977000", + "prgStm": "1767760530000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024867" + }, + { + "prgEtm": "1767763701000", + "prgStm": "1767761977000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024868" + }, + { + "prgEtm": "1767765600000", + "prgStm": "1767763701000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024869" + }, + { + "prgEtm": "1767767107000", + "prgStm": "1767765600000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024870" + }, + { + "prgEtm": "1767768860000", + "prgStm": "1767767107000", + "prgTitle": "123GO!", + "prgchId": "1011321495895024871" + } + ] + }, + { + "chlId": "972720861289775954", + "chlNm": "TidPix", + "ptList": [ + { + "prgEtm": "1767675698000", + "prgStm": "1767667211000", + "prgTitle": "Kankan", + "prgchId": "1011997771970447801" + }, + { + "prgEtm": "1767682089000", + "prgStm": "1767675698000", + "prgTitle": "Afrokons", + "prgchId": "1011997771970447802" + }, + { + "prgEtm": "1767689866000", + "prgStm": "1767682089000", + "prgTitle": "Dear Bayo", + "prgchId": "1011997771970447803" + }, + { + "prgEtm": "1767693550000", + "prgStm": "1767689866000", + "prgTitle": "Kukuri", + "prgchId": "1011997771970447804" + }, + { + "prgEtm": "1767697200000", + "prgStm": "1767693550000", + "prgTitle": "Fixed", + "prgchId": "1011997771970447805" + }, + { + "prgEtm": "1767699144000", + "prgStm": "1767697200000", + "prgTitle": "Channel 77", + "prgchId": "1011997771970447806" + }, + { + "prgEtm": "1767701044000", + "prgStm": "1767699144000", + "prgTitle": "Channel 77", + "prgchId": "1011997771970447807" + }, + { + "prgEtm": "1767703052000", + "prgStm": "1767701044000", + "prgTitle": "Channel 77", + "prgchId": "1011997771970447808" + }, + { + "prgEtm": "1767705130000", + "prgStm": "1767703052000", + "prgTitle": "Squatterz", + "prgchId": "1011997771970447809" + }, + { + "prgEtm": "1767707321000", + "prgStm": "1767705130000", + "prgTitle": "Squatterz", + "prgchId": "1011997771970447810" + }, + { + "prgEtm": "1767709669000", + "prgStm": "1767707321000", + "prgTitle": "Squatterz", + "prgchId": "1011997771970447811" + }, + { + "prgEtm": "1767711964000", + "prgStm": "1767709669000", + "prgTitle": "Squatterz", + "prgchId": "1011997771970447812" + }, + { + "prgEtm": "1767719613000", + "prgStm": "1767711964000", + "prgTitle": "Nairobi Half Life", + "prgchId": "1011997771970447813" + }, + { + "prgEtm": "1767728618000", + "prgStm": "1767719613000", + "prgTitle": "Taliya", + "prgchId": "1011997771970447814" + }, + { + "prgEtm": "1767737075000", + "prgStm": "1767728618000", + "prgTitle": "Once Upon a Family", + "prgchId": "1011997771970447815" + }, + { + "prgEtm": "1767740400000", + "prgStm": "1767737075000", + "prgTitle": "Coerced Revenge", + "prgchId": "1011997771970447816" + }, + { + "prgEtm": "1767745888000", + "prgStm": "1767740400000", + "prgTitle": "The Coffin Salesman", + "prgchId": "1011997771970447817" + }, + { + "prgEtm": "1767753665000", + "prgStm": "1767745888000", + "prgTitle": "Dear Bayo", + "prgchId": "1012360208393444142" + }, + { + "prgEtm": "1767760945000", + "prgStm": "1767753665000", + "prgTitle": "Hidden Dreams", + "prgchId": "1012360208393444143" + }, + { + "prgEtm": "1767764600000", + "prgStm": "1767760945000", + "prgTitle": "Poker ", + "prgchId": "1012360208393444144" + }, + { + "prgEtm": "1767773660000", + "prgStm": "1767764600000", + "prgTitle": "The River Goddess (Asuo Subi)", + "prgchId": "1012360208393444145" + } + ] + }, + { + "chlId": "979187723418476852", + "chlNm": "VladTV", + "ptList": [ + { + "prgEtm": "1767677882000", + "prgStm": "1767670807000", + "prgTitle": "Go Yayo - April 2025", + "prgchId": "1011997716962088477" + }, + { + "prgEtm": "1767684540000", + "prgStm": "1767677882000", + "prgTitle": "Skipp Townsend - May 2025", + "prgchId": "1011997716962088478" + }, + { + "prgEtm": "1767686400000", + "prgStm": "1767684540000", + "prgTitle": "Hector Bravo - June 2025", + "prgchId": "1011997716962088479" + }, + { + "prgEtm": "1767698863000", + "prgStm": "1767686400000", + "prgTitle": "Lord Jamar - June 2025", + "prgchId": "1011997716962088480" + }, + { + "prgEtm": "1767708252000", + "prgStm": "1767698863000", + "prgTitle": "Derrick Grace II - April 2025", + "prgchId": "1011997716962088481" + }, + { + "prgEtm": "1767717426000", + "prgStm": "1767708252000", + "prgTitle": "Wack100 - May 2025", + "prgchId": "1011997716962088482" + }, + { + "prgEtm": "1767729194000", + "prgStm": "1767717426000", + "prgTitle": "DJ Akademiks - May 2025", + "prgchId": "1011997716962088483" + }, + { + "prgEtm": "1767740543000", + "prgStm": "1767729194000", + "prgTitle": "Trap Lore Ross - May 2025", + "prgchId": "1011997716962088484" + }, + { + "prgEtm": "1767745895000", + "prgStm": "1767740543000", + "prgTitle": "Freeway Rick - June 2017", + "prgchId": "1011997716962088485" + }, + { + "prgEtm": "1767754872000", + "prgStm": "1767745895000", + "prgTitle": "Wack100 - June 2025", + "prgchId": "1012360104773297334" + }, + { + "prgEtm": "1767761941000", + "prgStm": "1767754872000", + "prgTitle": "Sharay \"Punisher\" Hayes - June 2025", + "prgchId": "1012360104773297335" + }, + { + "prgEtm": "1767771527000", + "prgStm": "1767761941000", + "prgTitle": "Roger Bonds - June 2025", + "prgchId": "1012360104773297336" + } + ] + }, + { + "chlId": "979187723418476854", + "chlNm": "Moonball Sports TV", + "ptList": [ + { + "prgEtm": "1767681000000", + "prgStm": "1767675600000", + "prgTitle": "Ahman Green’s Gamers Lounge", + "prgchId": "1010958633632668647" + }, + { + "prgEtm": "1767686400000", + "prgStm": "1767681000000", + "prgTitle": "Ahman Green’s Gamers Lounge", + "prgchId": "1010958633632668648" + }, + { + "prgEtm": "1767690000000", + "prgStm": "1767686400000", + "prgTitle": "Nicky Breaks 90", + "prgchId": "1010958633632668649" + }, + { + "prgEtm": "1767693600000", + "prgStm": "1767690000000", + "prgTitle": "Diggin' Deep", + "prgchId": "1010958633632668650" + }, + { + "prgEtm": "1767698100000", + "prgStm": "1767693600000", + "prgTitle": "Diggin' Deep", + "prgchId": "1010958633632668651" + }, + { + "prgEtm": "1767700800000", + "prgStm": "1767698100000", + "prgTitle": "Diggin' Deep", + "prgchId": "1010958633632668652" + }, + { + "prgEtm": "1767706200000", + "prgStm": "1767700800000", + "prgTitle": "Athletes Unlimited - Lacrosse", + "prgchId": "1010958633632668653" + }, + { + "prgEtm": "1767711600000", + "prgStm": "1767706200000", + "prgTitle": "Athletes Unlimited - Lacrosse", + "prgchId": "1010958633632668654" + }, + { + "prgEtm": "1767715200000", + "prgStm": "1767711600000", + "prgTitle": "World Chase Tag", + "prgchId": "1010958633632668655" + }, + { + "prgEtm": "1767718800000", + "prgStm": "1767715200000", + "prgTitle": "World Chase Tag", + "prgchId": "1010958633632668656" + }, + { + "prgEtm": "1767722400000", + "prgStm": "1767718800000", + "prgTitle": "World Chase Tag", + "prgchId": "1010958633632668657" + }, + { + "prgEtm": "1767726000000", + "prgStm": "1767722400000", + "prgTitle": "OmegaBall", + "prgchId": "1010958633632668658" + }, + { + "prgEtm": "1767733200000", + "prgStm": "1767726000000", + "prgTitle": "Ultimate Frisbee Association", + "prgchId": "1010958633632668659" + }, + { + "prgEtm": "1767740400000", + "prgStm": "1767733200000", + "prgTitle": "Athletes Unlimited - Women's Basketball", + "prgchId": "1010958633632668660" + }, + { + "prgEtm": "1767744000000", + "prgStm": "1767740400000", + "prgTitle": "Major League Wiffle Ball", + "prgchId": "1010958633632668661" + }, + { + "prgEtm": "1767746700000", + "prgStm": "1767744000000", + "prgTitle": "Major League Wiffle Ball", + "prgchId": "1010958633632668662" + }, + { + "prgEtm": "1767751200000", + "prgStm": "1767746700000", + "prgTitle": "Major League Wiffle Ball", + "prgchId": "1010958633632668663" + }, + { + "prgEtm": "1767755400000", + "prgStm": "1767751200000", + "prgTitle": "Diggin' Deep", + "prgchId": "1010958633632668664" + }, + { + "prgEtm": "1767760200000", + "prgStm": "1767755400000", + "prgTitle": "Diggin' Deep", + "prgchId": "1010958633632668665" + }, + { + "prgEtm": "1767762000000", + "prgStm": "1767760200000", + "prgTitle": "Major League Wiffle Ball", + "prgchId": "1010958633632668666" + }, + { + "prgEtm": "1767767400000", + "prgStm": "1767762000000", + "prgTitle": "Ahman Green’s Gamers Lounge", + "prgchId": "1010958633632668667" + }, + { + "prgEtm": "1767774600000", + "prgStm": "1767767400000", + "prgTitle": "Ahman Green’s Gamers Lounge", + "prgchId": "1010958633632668668" + } + ] + }, + { + "chlId": "979187723418476859", + "chlNm": "a-z Western Grit", + "ptList": [ + { + "prgEtm": "1767677820000", + "prgStm": "1767673500000", + "prgTitle": "Brand Of The Outlaws", + "prgchId": "1011995049988787269" + }, + { + "prgEtm": "1767684480000", + "prgStm": "1767677820000", + "prgTitle": "The Wackiest Wagon Train in the West", + "prgchId": "1011995049988787270" + }, + { + "prgEtm": "1767688980000", + "prgStm": "1767684480000", + "prgTitle": "Loser's End", + "prgchId": "1011995049988787271" + }, + { + "prgEtm": "1767696000000", + "prgStm": "1767688980000", + "prgTitle": "Curse of Demon Mountain", + "prgchId": "1011995049988787272" + }, + { + "prgEtm": "1767700500000", + "prgStm": "1767696000000", + "prgTitle": "Border Phantom", + "prgchId": "1011995049988787273" + }, + { + "prgEtm": "1767705000000", + "prgStm": "1767700500000", + "prgTitle": "The Renegade Ranger", + "prgchId": "1011995049988787274" + }, + { + "prgEtm": "1767711840000", + "prgStm": "1767705000000", + "prgTitle": "This Man Can't Die", + "prgchId": "1011995049988787275" + }, + { + "prgEtm": "1767715740000", + "prgStm": "1767711840000", + "prgTitle": "Texas Terror", + "prgchId": "1011995049988787276" + }, + { + "prgEtm": "1767722820000", + "prgStm": "1767715740000", + "prgTitle": "Pancho Villa", + "prgchId": "1012357431483309932" + }, + { + "prgEtm": "1767730320000", + "prgStm": "1767722820000", + "prgTitle": "War Of The Wildcats", + "prgchId": "1012357431483309933" + }, + { + "prgEtm": "1767735000000", + "prgStm": "1767730320000", + "prgTitle": "Aces And Eights", + "prgchId": "1012357431483309934" + }, + { + "prgEtm": "1767739260000", + "prgStm": "1767735000000", + "prgTitle": "Brothers Of The West", + "prgchId": "1012357431483309935" + }, + { + "prgEtm": "1767743760000", + "prgStm": "1767739260000", + "prgTitle": "Between Fighting Men", + "prgchId": "1012357431483309936" + }, + { + "prgEtm": "1767750660000", + "prgStm": "1767743760000", + "prgTitle": "Savage Guns", + "prgchId": "1012357431483309937" + }, + { + "prgEtm": "1767757500000", + "prgStm": "1767750660000", + "prgTitle": "Quell And Co.", + "prgchId": "1012357431483309938" + }, + { + "prgEtm": "1767763860000", + "prgStm": "1767757500000", + "prgTitle": "Vengeance Valley", + "prgchId": "1012357431483309939" + }, + { + "prgEtm": "1767769800000", + "prgStm": "1767763860000", + "prgTitle": "Dakota 38", + "prgchId": "1012357431483309940" + } + ] + } + ], + "errorCode": "0", + "errorMsg": "ok", + "timestamp": "1767681496847" +} \ No newline at end of file diff --git a/sites/watch.whaletvplus.com/readme.md b/sites/watch.whaletvplus.com/readme.md new file mode 100644 index 00000000..8c23a926 --- /dev/null +++ b/sites/watch.whaletvplus.com/readme.md @@ -0,0 +1,21 @@ +# watch.whaletvplus.com + +https://watch.whaletvplus.com + +### Download the guide + +```sh +npm run grab --- --site=watch.whaletvplus.com +``` + +### Update channel list + +```sh +npm run channels:parse --- --config=./sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js --output=./sites/watch.whaletvplus.com/watch.whaletvplus.com.channels.xml +``` + +### Test + +```sh +npm test --- watch.whaletvplus.com +``` diff --git a/sites/watch.whaletvplus.com/watch.whaletvplus.com.channels.xml b/sites/watch.whaletvplus.com/watch.whaletvplus.com.channels.xml new file mode 100644 index 00000000..3fb2a126 --- /dev/null +++ b/sites/watch.whaletvplus.com/watch.whaletvplus.com.channels.xml @@ -0,0 +1,348 @@ + + + Alaraby News + All Romance + ARTFLIX - Filmklassiker + AUTO BILD TV + AUTO MOTOR UND SPORT + BILD TV + Computer BILD + CrimeStar + Crimify + Curiosity Now + DDR TV-Archiv + Deluxe DeutschPop + Deluxe Lounge HD + euronews deutsch + Fabella + Fantasja + FIFA+ + Focus TV + GoldStar TV + Grjngo - Westernfilme + Heimatkino + Herzfrequenz + Höhenrausch + Just Cooking + just.fishing + Love The Planet + Moconomy - Wirtschaft und Finanzen + Moconomy - Wirtschaft und Finanzen + More Than Sports TV + Motorvision + Motorvision Classic + MovieSphere + Naruto + Netzkino + One Terra + Red Bull TV + Royalworld - Adel & Dynastien + SPIEGEL TV + Spiegel TV Action+Crime + Spiegel TV Konflikte + SPORT BILD TV + Sportdigital Free + Tempora + Terra Mater Wild + Tierwelt Live + TOP Filme + TOP SciFi + TOP Serien + Trailers + Travelxp + Utopja + wedotv Movies + wedotv Sports + wedotv True Stories + Wicked Tuna + Xplore Free + Yu-Gi-Oh! + 5-Minute Crafts + 123GO! + a-z B-Flix + a-z Best Classic TV + a-z Classic Flix + a-z Western Grit + ACL Cornhole TV + Adventure Earth + africanews english + Afroland African + Afroland Comedy + Afroland Crime + Afroland Docus + Afroland Faith + Afroland Family + Afroland Nollywood + Afroland Romance + Afroland Thriller + AfroLandTV + AllHipHop + Are We There Yet? + Autentic History + Autentic Travel + AWE Plus + Bark TV + BEONDTV + Beyond The Score + Bigtime - Free Movies + Billiards TV + Bloomberg Originals + Bloomberg TV+ + Brat TV + China Travel + Confess by Nosey + Cooking Panda + Craftsy TV + crema.tv + Daystar + DeFiance News + DFB Play TV + Discovering China + Drone TV + Drool + Earth Day 365 + euronews english + FailArmy + FIFA+ + Filmzie + France 24 + FUEL TV + FUEL TV + FUEL TV + FUEL TV + FUEL TV + FUEL TV + FUEL TV + FUEL TV + FUEL TV Europe + FUEL TV US/MX + Ghost Hunters + Global Biz + GoUSA TV + Gusto TV + In Touch + + INFAST + INTER 24/7 + INTRAVEL + INTROUBLE + INWILD + INWONDER + Judge Nosey + Just For Laughs Gags + Just For Laughs TV + LatiNation + LatinoCircuit TV + Loop Trivia + Loupe Art + Love The Planet + Love Wine + Moonball Sports TV + MotoRRacing + Motorvision + Motorvision + Movie Mania + Movie Mania + Movie Music + Movie Music + Movieitaly Channel + MovieSphere + NEW KFOOD + Newsmax + NHRA TV + Nolly Africa HD + NOMADLife.tv + Nosey + NTD + OAN Plus + Party Tyme Karaoke + People are Awesome + Pet Collective + + PLL Network + PowerSports World + Qwest TV + RACER International + Red Bull TV Australia + Red Bull TV Europe + Red Bull TV UK + Revry Global + Revry Her + Revry News + Royalworld - Nobility & Dynasties + SHIFT + SKWAD + Smart Healthy Green Living + SportOutdoor.TV + Stingray Cityscapes + Stingray Cozy Café + Stingray Easy Listening + Stingray Naturescape + Stingray Naturescape + Stingray Remember the 80s + Stingray Smooth Jazz + Stingray SPA + Stingray Stargaze + Tasty + Terra Mater Wild + The Boat Show + The Red Green Channel + TidPix + Timber Kings + time2Rlx + Trace Sport Stars + Trace UK + Trace Urban + Trailers + Travel & Food TV + Travelxp + Travelxp + TRT World + Unleashed by DOGTV + VladTV + WatchMojo + WaterBear + Weather Spy + wedotv Big Stories + wedotv Big Stories + wedotv Big Stories + wedotv Big Stories + wedotv Big Stories + wedotv Big Stories + wedotv Movies + wedotv Movies + wedotv Movies + wedotv Movies + wedotv Movies + wedotv Movies Europe + Whoa! That was Wild! + Wicked Tuna + World Billiards TV + World Poker Tour + Yu-Gi-Oh! + ZenLife + ZenLife + 123GO! + Actualidad 360 + America Television + AYM Sports + Azteca Internacional + Azteca Uno + Backstage En Español + Box Cinema + Box Gamers + Box Playlist + Cine en Español + Cine Friki + Cine Friki Latino + Cine Western + Clic + Comercio TV + Crimen & Historia + Daystar Español + Delito + Docs & Historias + euronews español + FIFA+ + FIFA+ + Film&Co + France 24 + FreeTV Acción + FreeTV Clásico + FreeTV Drama + FreeTV Estelar + FreeTV Sureño + GoUSA TV + Historia Y Vida + Ideas En 5 Minutos + Love The Planet + Love Wine + Motorvision + Naturaleza Salvaje + NEGOCIOS TV + Nesting TV + Pelimex + Play Ibiza + Red Bull TV + Revry LatinX + Royalworld - Nobleza y Dinastias + Somos Novelas + Tastemade en Español + Tastemade Hogar + Tastemade Viajes + Todo Novelas + Todo Pasíon + Trace Latina + Trailers + Viajar TV + Viajes & Sabores + wedotv Amor + wedotv Amor: Piel Salvaje + World Poker Tour + Yu-Gi-Oh! + Cap Terre + Ciné Nanar + Ciné Western + Drive TV + Emotion'L + Enquêtes De Choc + euronews français + FIFA+ + France 24 + Fréquence Novelas + Grjngo - Films de Western + Homicide + IntoCrime + Love Crime & History + MotoRRacing + Motorvision + Motus + Trailers + Travelxp + Voyages & Saveurs + Y'a que la vérité qui compte + Yu-Gi-Oh! + Adrenaline Movies + Alberto Sordi & Co + Alta Tensione + Bizzarro Movies + Cinema Italiano + CineWestern + euronews italiano + FIFA+ + Grandi Nomi + Hip Hop TV + Inazuma Eleven Collection + INTER 24/7 + InuYasha + Love The Planet + Per Caso TV + Risate Cult ’80 + Rock TV + Smile + Soap Latino + Soap Turco + SportOutdoor.TV + Storie Criminali + The Boat Show + Trailers + Velvet + Viaggi & Sapori + wedotv Movies + Yu-Gi-Oh! + Food ON + NEW KPOP + Travel ON + AUTO MOTOR OG SPORT + euronews português + Feliz7Play + FIFA+ + FreeTV Crime + FreeTV Drama + Revry Brazil + Sessão Trash + Tastemade Brasil + Tastemade Casa + Tastemade Viagem + Trace Brasil + Ubisoft TV Brasil + World Poker Tour + diff --git a/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js b/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js new file mode 100644 index 00000000..80d4ce8c --- /dev/null +++ b/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js @@ -0,0 +1,165 @@ +const axios = require('axios') +const dayjs = require('dayjs') + +// --- CONFIGURATION --- +const HEADERS = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:147.0) Gecko/20100101 Firefox/147.0', + 'Referer': 'https://watch.whaletvplus.com/', + 'Origin': 'https://watch.whaletvplus.com' +} +const apiToken = '4ef13b5f3d2744e3b0a569feb8dde298' +const LOGO_BASE = 'https://d3b6luslimvglo.cloudfront.net/images/79/rlaxximages/channels-rescaled/icon-white/' + +let authTokenPromise = null + +module.exports = { + site: 'watch.whaletvplus.com', + days: 2, + + request: { + cache: { + ttl: 60 * 60 * 1000 // Cache 1 hour + }, + headers: async function() { + const token = await getAuthToken() + return { + ...HEADERS, + 'token': token + } + } + }, + + url: function ({ channel, date }) { + const start = date.valueOf() + const end = date.add(1, 'day').valueOf() + + return `https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/epg?channelIds=${channel.site_id}&startTime=${start}&endTime=${end}` + }, + + parser: async function ({ content }) { + let json + try { + json = JSON.parse(content) + } catch (e) { + console.error('Error parsing JSON:', e) + return [] + } + + if (!json.data || !Array.isArray(json.data) || !json.data[0] || !Array.isArray(json.data[0].ptList)) { + return [] + } + + const programs = json.data[0].ptList + const detailsCache = {} + + return await limit(programs, async (p) => { + const program = { + title: p.prgTitle, + start: dayjs(Number(p.prgStm)), + stop: dayjs(Number(p.prgEtm)) + } + + if (p.prgchId) { + if (!detailsCache[p.prgchId]) { + detailsCache[p.prgchId] = fetchProgramDetail(p.prgchId) + } + const detail = await detailsCache[p.prgchId] + if (detail) { + program.description = detail.prgDesc || null + program.season = detail.seasonNumber || null + program.episode = detail.episodeNumber || null + program.sub_title = detail.prgTitle || detail.seriesTitle || null + + if (detail.images && Array.isArray(detail.images)) { + const bestImg = detail.images.find((i) => i.pimgWidth === '1920') || detail.images[0] + if (bestImg) program.image = bestImg.pimgUrl + } + } + } + return program + }) + }, + + async channels() { + const token = await getAuthToken() + + const countries = [ + 'IN', 'AU', 'NZ', 'ZA', 'US', 'BR', 'MX', 'AR', 'CO', 'CL', 'CA', + 'GB', 'DE', 'FR', 'IT', 'ES', 'PL', 'TR', 'AT', 'CH', 'NL', 'PT', + 'BE', 'SE', 'NO', 'DK', 'FI' + ] + + const requests = countries.map(country => + axios.get('https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/category/channels', { + params: { countryCode: country, langCode: 'en' }, + headers: { ...HEADERS, token } + }).then(r => r.data?.data || []).catch(() => []) + ) + + const results = await Promise.all(requests) + const allChannels = results.flat().flatMap(group => group.channels || []) + + const uniqueChannels = new Map() + for (const ch of allChannels) { + if (!uniqueChannels.has(ch.chlId)) { + uniqueChannels.set(ch.chlId, { + lang: (ch.chlLangCode ? ch.chlLangCode.split('-')[0] : 'en'), + site_id: ch.chlId, + name: ch.chlName, + logo: ch.imageIdentifier ? `${LOGO_BASE}${ch.imageIdentifier}_white.png` : null + }) + } + } + + return Array.from(uniqueChannels.values()) + } +} + +async function limit(items, fn, concurrency = 20) { + const results = [] + for (let i = 0; i < items.length; i += concurrency) { + const batch = items.slice(i, i + concurrency) + results.push(...(await Promise.all(batch.map(fn)))) + } + return results +} + +async function getAuthToken() { + if (authTokenPromise) return authTokenPromise + + authTokenPromise = (async () => { + try { + const response = await axios.get('https://rlaxx.zeasn.tv/livetv/api/v1/auth/access', { + params: { uuid: '1', apiToken, langCode: 'en' }, + headers: HEADERS + }) + + if (response.data && response.data.data && response.data.data.token) { + return response.data.data.token + } + + throw new Error('apiToken invalid or expired. Please update config.') + } catch (error) { + authTokenPromise = null + throw new Error(error.message) + } + })() + + return authTokenPromise +} + +async function fetchProgramDetail(programId) { + const token = await getAuthToken() + try { + const response = await axios.get(`https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/epg/detail/${programId}`, { + headers: { + ...HEADERS, + 'token': token + }, + timeout: 5000 + }) + return response.data && response.data.data ? response.data.data : null + } catch { + return null + } +} \ No newline at end of file diff --git a/sites/watch.whaletvplus.com/watch.whaletvplus.com.test.js b/sites/watch.whaletvplus.com/watch.whaletvplus.com.test.js new file mode 100644 index 00000000..869300e2 --- /dev/null +++ b/sites/watch.whaletvplus.com/watch.whaletvplus.com.test.js @@ -0,0 +1,102 @@ +const { parser, url, channels } = require('./watch.whaletvplus.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const axios = require('axios') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2026-01-08', 'YYYY-MM-DD').startOf('d') + +const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') + +it('can generate valid url', () => { + const channel = { site_id: '878765717599035555' } + const start = date.valueOf() + const end = date.add(1, 'day').valueOf() + + const generatedUrl = url({ channel, date }) + + expect(generatedUrl).toBe( + `https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/epg?channelIds=878765717599035555&startTime=${start}&endTime=${end}` + ) +}) + +it('can parse response', async () => { + const json = JSON.parse(content) + const firstChannel = json.data && json.data.length > 0 ? json.data[0] : null + const validSiteId = firstChannel ? firstChannel.chlId : '878765717599035555' + + const channel = { + site_id: validSiteId, + xmltv_id: 'Test.Channel' + } + + const result = await parser({ content, channel }) + + expect(result).toBeInstanceOf(Array) + expect(result.length).toBeGreaterThan(0) + + expect(result[0]).toMatchObject({ + title: expect.any(String), + start: expect.any(Object), + stop: expect.any(Object) + }) +}) + +it('can handle empty guide', async () => { + const result = await parser({ + content: '{"data":[]}', + channel: { site_id: '123' } + }) + expect(result).toMatchObject([]) +}) + +it('can parse channel list', async () => { + axios.get.mockImplementation((reqUrl) => { + if (reqUrl.includes('auth/access')) { + return Promise.resolve({ + data: { data: { token: 'mock_token_123' } } + }) + } + + if (reqUrl.includes('category/channels')) { + return Promise.resolve({ + data: { + data: [ + { + channels: [ + { + chlId: '878765717599035555', + chlName: 'Wedo Movies', + chlLangCode: 'en' + }, + { + chlId: '999420644834214633', + chlName: 'FIFA+', + chlLangCode: 'es' + } + ] + } + ] + } + }) + } + return Promise.resolve({ data: {} }) + }) + + const result = await channels() + + expect(result).toBeInstanceOf(Array) + expect(result.length).toBeGreaterThan(0) + expect(result[0]).toMatchObject({ + name: expect.any(String), + site_id: expect.any(String), + lang: expect.any(String) + }) +}) \ No newline at end of file From a4559ea4dd597c668542badf6338de3ff5b0e173 Mon Sep 17 00:00:00 2001 From: StrangeDrVN Date: Sun, 22 Feb 2026 17:33:11 +0530 Subject: [PATCH 02/10] channels validate fix test fix --- .../watch.whaletvplus.com.channels.xml | 84 +++++++++++-------- .../watch.whaletvplus.com.config.js | 7 +- .../watch.whaletvplus.com.test.js | 32 +++++++ 3 files changed, 86 insertions(+), 37 deletions(-) diff --git a/sites/watch.whaletvplus.com/watch.whaletvplus.com.channels.xml b/sites/watch.whaletvplus.com/watch.whaletvplus.com.channels.xml index 3fb2a126..708463c7 100644 --- a/sites/watch.whaletvplus.com/watch.whaletvplus.com.channels.xml +++ b/sites/watch.whaletvplus.com/watch.whaletvplus.com.channels.xml @@ -13,7 +13,7 @@ DDR TV-Archiv Deluxe DeutschPop Deluxe Lounge HD - euronews deutsch + euronews deutsch Fabella Fantasja FIFA+ @@ -31,8 +31,8 @@ More Than Sports TV Motorvision Motorvision Classic - MovieSphere - Naruto + Nachrichten 360 + Naruto Netzkino One Terra Red Bull TV @@ -54,7 +54,6 @@ wedotv Movies wedotv Sports wedotv True Stories - Wicked Tuna Xplore Free Yu-Gi-Oh! 5-Minute Crafts @@ -77,7 +76,7 @@ Afroland Thriller AfroLandTV AllHipHop - Are We There Yet? + Arab Heritage TV Autentic History Autentic Travel AWE Plus @@ -88,8 +87,11 @@ Billiards TV Bloomberg Originals Bloomberg TV+ + Bollywood 4U Brat TV - China Travel + China Travel + Chrono TV + CNA Originals Confess by Nosey Cooking Panda Craftsy TV @@ -97,29 +99,38 @@ Daystar DeFiance News DFB Play TV - Discovering China - Drone TV + Discovering China + DIY Art + Docu Vision + Drive+Speed + Drone TV Drool Earth Day 365 + Encore+ + Escape TV euronews english FailArmy FIFA+ Filmzie France 24 + FUEL TV + FUEL TV FUEL TV FUEL TV FUEL TV + FUEL TV FUEL TV FUEL TV FUEL TV FUEL TV FUEL TV - FUEL TV Europe - FUEL TV US/MX - Ghost Hunters Global Biz + Goal TV GoUSA TV Gusto TV + Homerun TV + Hoop TV + Hunt Fish TV In Touch + INFAST INTER 24/7 @@ -129,13 +140,12 @@ INWONDER Judge Nosey Just For Laughs Gags - Just For Laughs TV + Just For Laughs TV LatiNation - LatinoCircuit TV - Loop Trivia Loupe Art Love The Planet Love Wine + Mercury+ Moonball Sports TV MotoRRacing Motorvision @@ -145,8 +155,7 @@ Movie Music Movie Music Movieitaly Channel - MovieSphere - NEW KFOOD + NEW KFOOD Newsmax NHRA TV Nolly Africa HD @@ -154,23 +163,25 @@ Nosey NTD OAN Plus - Party Tyme Karaoke + Peekflick People are Awesome Pet Collective + PLL Network PowerSports World Qwest TV RACER International - Red Bull TV Australia - Red Bull TV Europe - Red Bull TV UK + Red Bull TV + Red Bull TV + Red Bull TV Revry Global Revry Her - Revry News Royalworld - Nobility & Dynasties + Scifi World SHIFT SKWAD Smart Healthy Green Living + Space Series + Sport Fishing TV SportOutdoor.TV Stingray Cityscapes Stingray Cozy Café @@ -186,19 +197,19 @@ The Boat Show The Red Green Channel TidPix - Timber Kings time2Rlx Trace Sport Stars Trace UK Trace Urban Trailers - Travel & Food TV + Travel & Food TV Travelxp Travelxp TRT World + UnchainedTV Unleashed by DOGTV VladTV - WatchMojo + WatchMojo WaterBear Weather Spy wedotv Big Stories @@ -207,14 +218,14 @@ wedotv Big Stories wedotv Big Stories wedotv Big Stories + wedotv Movies wedotv Movies wedotv Movies wedotv Movies wedotv Movies wedotv Movies - wedotv Movies Europe + WeShort Whoa! That was Wild! - Wicked Tuna World Billiards TV World Poker Tour Yu-Gi-Oh! @@ -230,6 +241,7 @@ Box Cinema Box Gamers Box Playlist + CINDIE Cine en Español Cine Friki Cine Friki Latino @@ -240,7 +252,7 @@ Daystar Español Delito Docs & Historias - euronews español + euronews español FIFA+ FIFA+ Film&Co @@ -284,7 +296,7 @@ Drive TV Emotion'L Enquêtes De Choc - euronews français + euronews français FIFA+ France 24 Fréquence Novelas @@ -292,6 +304,7 @@ Homicide IntoCrime Love Crime & History + Mayday: Catastrophe Aérienne MotoRRacing Motorvision Motus @@ -306,7 +319,7 @@ Bizzarro Movies Cinema Italiano CineWestern - euronews italiano + euronews italiano FIFA+ Grandi Nomi Hip Hop TV @@ -314,8 +327,8 @@ INTER 24/7 InuYasha Love The Planet + Mayday: Disastro Aereo Per Caso TV - Risate Cult ’80 Rock TV Smile Soap Latino @@ -323,21 +336,22 @@ SportOutdoor.TV Storie Criminali The Boat Show + Totò & Co Trailers Velvet Viaggi & Sapori wedotv Movies Yu-Gi-Oh! - Food ON + Food ON NEW KPOP - Travel ON + Travel ON AUTO MOTOR OG SPORT - euronews português + euronews português Feliz7Play FIFA+ FreeTV Crime FreeTV Drama - Revry Brazil + Red Bull TV Sessão Trash Tastemade Brasil Tastemade Casa @@ -345,4 +359,4 @@ Trace Brasil Ubisoft TV Brasil World Poker Tour - + \ No newline at end of file diff --git a/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js b/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js index 80d4ce8c..c03efaa3 100644 --- a/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js +++ b/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js @@ -8,7 +8,6 @@ const HEADERS = { 'Origin': 'https://watch.whaletvplus.com' } const apiToken = '4ef13b5f3d2744e3b0a569feb8dde298' -const LOGO_BASE = 'https://d3b6luslimvglo.cloudfront.net/images/79/rlaxximages/channels-rescaled/icon-white/' let authTokenPromise = null @@ -70,6 +69,10 @@ module.exports = { program.episode = detail.episodeNumber || null program.sub_title = detail.prgTitle || detail.seriesTitle || null + if (program.title === program.sub_title) { + program.sub_title = null + } + if (detail.images && Array.isArray(detail.images)) { const bestImg = detail.images.find((i) => i.pimgWidth === '1920') || detail.images[0] if (bestImg) program.image = bestImg.pimgUrl @@ -106,7 +109,7 @@ module.exports = { lang: (ch.chlLangCode ? ch.chlLangCode.split('-')[0] : 'en'), site_id: ch.chlId, name: ch.chlName, - logo: ch.imageIdentifier ? `${LOGO_BASE}${ch.imageIdentifier}_white.png` : null + // logo: ch.imageIdentifier ? `https://d3b6luslimvglo.cloudfront.net/images/79/rlaxximages/channels-rescaled/icon-white/${ch.imageIdentifier}_white.png` : null }) } } diff --git a/sites/watch.whaletvplus.com/watch.whaletvplus.com.test.js b/sites/watch.whaletvplus.com/watch.whaletvplus.com.test.js index 869300e2..2aa5dce1 100644 --- a/sites/watch.whaletvplus.com/watch.whaletvplus.com.test.js +++ b/sites/watch.whaletvplus.com/watch.whaletvplus.com.test.js @@ -28,6 +28,20 @@ it('can generate valid url', () => { }) it('can parse response', async () => { + axios.get.mockImplementation((url) => { + if (url.includes('auth/access')) { + return Promise.resolve({ + data: { data: { token: 'mock_token' } } + }) + } + if (url.includes('epg/detail')) { + return Promise.resolve({ + data: { data: { prgDesc: 'Test Description' } } + }) + } + return Promise.resolve({ data: {} }) + }) + const json = JSON.parse(content) const firstChannel = json.data && json.data.length > 0 ? json.data[0] : null const validSiteId = firstChannel ? firstChannel.chlId : '878765717599035555' @@ -99,4 +113,22 @@ it('can parse channel list', async () => { site_id: expect.any(String), lang: expect.any(String) }) +}) + +it('can parse token', async () => { + jest.resetModules() + const { request } = require('./watch.whaletvplus.com.config.js') + const axios = require('axios') + + axios.get.mockImplementation((url) => { + if (url.includes('auth/access')) { + return Promise.resolve({ + data: { data: { token: 'test_token' } } + }) + } + return Promise.resolve({ data: {} }) + }) + + const headers = await request.headers() + expect(headers.token).toBe('test_token') }) \ No newline at end of file From bc42d24f8a02cd75ed61a8c39f6fad6d93ce44a3 Mon Sep 17 00:00:00 2001 From: StrangeDrVN Date: Sun, 22 Feb 2026 17:45:42 +0530 Subject: [PATCH 03/10] update readme --- sites/watch.whaletvplus.com/readme.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sites/watch.whaletvplus.com/readme.md b/sites/watch.whaletvplus.com/readme.md index 8c23a926..bc084e5e 100644 --- a/sites/watch.whaletvplus.com/readme.md +++ b/sites/watch.whaletvplus.com/readme.md @@ -19,3 +19,13 @@ npm run channels:parse --- --config=./sites/watch.whaletvplus.com/watch.whaletvp ```sh npm test --- watch.whaletvplus.com ``` + +### Fix `apiToken invalid or expired. Please update config.` + +The `apiToken` rarely changes, but if it does: +1. Go to https://watch.whaletvplus.com +2. Open Developer Tools (press `F12` or right-click and select **Inspect**). +3. Select the **Network** tab. +4. Refresh the page. +5. In the "Filter" box, type `apiToken`. +6. Click on any request found and copy the `apiToken` value from the request URL. \ No newline at end of file From 91dbaf6e5d5b7fa04f8f3bc72969bfe6f1bab0ce Mon Sep 17 00:00:00 2001 From: StrangeDrVN Date: Sun, 22 Feb 2026 17:47:27 +0530 Subject: [PATCH 04/10] formatting --- sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js b/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js index c03efaa3..7e493335 100644 --- a/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js +++ b/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js @@ -1,7 +1,6 @@ const axios = require('axios') const dayjs = require('dayjs') -// --- CONFIGURATION --- const HEADERS = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:147.0) Gecko/20100101 Firefox/147.0', 'Referer': 'https://watch.whaletvplus.com/', From a425f60c0576882c2858902fcb7da28691499ad7 Mon Sep 17 00:00:00 2001 From: StrangeDrVN Date: Sun, 22 Feb 2026 17:55:24 +0530 Subject: [PATCH 05/10] test url parsing --- sites/watch.whaletvplus.com/watch.whaletvplus.com.test.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sites/watch.whaletvplus.com/watch.whaletvplus.com.test.js b/sites/watch.whaletvplus.com/watch.whaletvplus.com.test.js index 2aa5dce1..0896d05a 100644 --- a/sites/watch.whaletvplus.com/watch.whaletvplus.com.test.js +++ b/sites/watch.whaletvplus.com/watch.whaletvplus.com.test.js @@ -17,13 +17,11 @@ const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json') it('can generate valid url', () => { const channel = { site_id: '878765717599035555' } - const start = date.valueOf() - const end = date.add(1, 'day').valueOf() const generatedUrl = url({ channel, date }) expect(generatedUrl).toBe( - `https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/epg?channelIds=878765717599035555&startTime=${start}&endTime=${end}` + 'https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/epg?channelIds=878765717599035555&startTime=1767830400000&endTime=1767916800000' ) }) From ccf40773a25a8ea28258dc86cacf920b9dc70759 Mon Sep 17 00:00:00 2001 From: StrangeDrVN Date: Sun, 22 Feb 2026 18:24:23 +0530 Subject: [PATCH 06/10] crlf fix --- .../watch.whaletvplus.com.config.js | 332 +++++++++--------- .../watch.whaletvplus.com.test.js | 262 +++++++------- 2 files changed, 297 insertions(+), 297 deletions(-) diff --git a/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js b/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js index 7e493335..c6172ddc 100644 --- a/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js +++ b/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js @@ -1,167 +1,167 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -const HEADERS = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:147.0) Gecko/20100101 Firefox/147.0', - 'Referer': 'https://watch.whaletvplus.com/', - 'Origin': 'https://watch.whaletvplus.com' -} -const apiToken = '4ef13b5f3d2744e3b0a569feb8dde298' - -let authTokenPromise = null - -module.exports = { - site: 'watch.whaletvplus.com', - days: 2, - - request: { - cache: { - ttl: 60 * 60 * 1000 // Cache 1 hour - }, - headers: async function() { - const token = await getAuthToken() - return { - ...HEADERS, - 'token': token - } - } - }, - - url: function ({ channel, date }) { - const start = date.valueOf() - const end = date.add(1, 'day').valueOf() - - return `https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/epg?channelIds=${channel.site_id}&startTime=${start}&endTime=${end}` - }, - - parser: async function ({ content }) { - let json - try { - json = JSON.parse(content) - } catch (e) { - console.error('Error parsing JSON:', e) - return [] - } - - if (!json.data || !Array.isArray(json.data) || !json.data[0] || !Array.isArray(json.data[0].ptList)) { - return [] - } - - const programs = json.data[0].ptList - const detailsCache = {} - - return await limit(programs, async (p) => { - const program = { - title: p.prgTitle, - start: dayjs(Number(p.prgStm)), - stop: dayjs(Number(p.prgEtm)) - } - - if (p.prgchId) { - if (!detailsCache[p.prgchId]) { - detailsCache[p.prgchId] = fetchProgramDetail(p.prgchId) - } - const detail = await detailsCache[p.prgchId] - if (detail) { - program.description = detail.prgDesc || null - program.season = detail.seasonNumber || null - program.episode = detail.episodeNumber || null - program.sub_title = detail.prgTitle || detail.seriesTitle || null - - if (program.title === program.sub_title) { - program.sub_title = null - } - - if (detail.images && Array.isArray(detail.images)) { - const bestImg = detail.images.find((i) => i.pimgWidth === '1920') || detail.images[0] - if (bestImg) program.image = bestImg.pimgUrl - } - } - } - return program - }) - }, - - async channels() { - const token = await getAuthToken() - - const countries = [ - 'IN', 'AU', 'NZ', 'ZA', 'US', 'BR', 'MX', 'AR', 'CO', 'CL', 'CA', - 'GB', 'DE', 'FR', 'IT', 'ES', 'PL', 'TR', 'AT', 'CH', 'NL', 'PT', - 'BE', 'SE', 'NO', 'DK', 'FI' - ] - - const requests = countries.map(country => - axios.get('https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/category/channels', { - params: { countryCode: country, langCode: 'en' }, - headers: { ...HEADERS, token } - }).then(r => r.data?.data || []).catch(() => []) - ) - - const results = await Promise.all(requests) - const allChannels = results.flat().flatMap(group => group.channels || []) - - const uniqueChannels = new Map() - for (const ch of allChannels) { - if (!uniqueChannels.has(ch.chlId)) { - uniqueChannels.set(ch.chlId, { - lang: (ch.chlLangCode ? ch.chlLangCode.split('-')[0] : 'en'), - site_id: ch.chlId, - name: ch.chlName, - // logo: ch.imageIdentifier ? `https://d3b6luslimvglo.cloudfront.net/images/79/rlaxximages/channels-rescaled/icon-white/${ch.imageIdentifier}_white.png` : null - }) - } - } - - return Array.from(uniqueChannels.values()) - } -} - -async function limit(items, fn, concurrency = 20) { - const results = [] - for (let i = 0; i < items.length; i += concurrency) { - const batch = items.slice(i, i + concurrency) - results.push(...(await Promise.all(batch.map(fn)))) - } - return results -} - -async function getAuthToken() { - if (authTokenPromise) return authTokenPromise - - authTokenPromise = (async () => { - try { - const response = await axios.get('https://rlaxx.zeasn.tv/livetv/api/v1/auth/access', { - params: { uuid: '1', apiToken, langCode: 'en' }, - headers: HEADERS - }) - - if (response.data && response.data.data && response.data.data.token) { - return response.data.data.token - } - - throw new Error('apiToken invalid or expired. Please update config.') - } catch (error) { - authTokenPromise = null - throw new Error(error.message) - } - })() - - return authTokenPromise -} - -async function fetchProgramDetail(programId) { - const token = await getAuthToken() - try { - const response = await axios.get(`https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/epg/detail/${programId}`, { - headers: { - ...HEADERS, - 'token': token - }, - timeout: 5000 - }) - return response.data && response.data.data ? response.data.data : null - } catch { - return null - } +const axios = require('axios') +const dayjs = require('dayjs') + +const HEADERS = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:147.0) Gecko/20100101 Firefox/147.0', + 'Referer': 'https://watch.whaletvplus.com/', + 'Origin': 'https://watch.whaletvplus.com' +} +const apiToken = '4ef13b5f3d2744e3b0a569feb8dde298' + +let authTokenPromise = null + +module.exports = { + site: 'watch.whaletvplus.com', + days: 2, + + request: { + cache: { + ttl: 60 * 60 * 1000 // Cache 1 hour + }, + headers: async function() { + const token = await getAuthToken() + return { + ...HEADERS, + 'token': token + } + } + }, + + url: function ({ channel, date }) { + const start = date.valueOf() + const end = date.add(1, 'day').valueOf() + + return `https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/epg?channelIds=${channel.site_id}&startTime=${start}&endTime=${end}` + }, + + parser: async function ({ content }) { + let json + try { + json = JSON.parse(content) + } catch (e) { + console.error('Error parsing JSON:', e) + return [] + } + + if (!json.data || !Array.isArray(json.data) || !json.data[0] || !Array.isArray(json.data[0].ptList)) { + return [] + } + + const programs = json.data[0].ptList + const detailsCache = {} + + return await limit(programs, async (p) => { + const program = { + title: p.prgTitle, + start: dayjs(Number(p.prgStm)), + stop: dayjs(Number(p.prgEtm)) + } + + if (p.prgchId) { + if (!detailsCache[p.prgchId]) { + detailsCache[p.prgchId] = fetchProgramDetail(p.prgchId) + } + const detail = await detailsCache[p.prgchId] + if (detail) { + program.description = detail.prgDesc || null + program.season = detail.seasonNumber || null + program.episode = detail.episodeNumber || null + program.sub_title = detail.prgTitle || detail.seriesTitle || null + + if (program.title === program.sub_title) { + program.sub_title = null + } + + if (detail.images && Array.isArray(detail.images)) { + const bestImg = detail.images.find((i) => i.pimgWidth === '1920') || detail.images[0] + if (bestImg) program.image = bestImg.pimgUrl + } + } + } + return program + }) + }, + + async channels() { + const token = await getAuthToken() + + const countries = [ + 'IN', 'AU', 'NZ', 'ZA', 'US', 'BR', 'MX', 'AR', 'CO', 'CL', 'CA', + 'GB', 'DE', 'FR', 'IT', 'ES', 'PL', 'TR', 'AT', 'CH', 'NL', 'PT', + 'BE', 'SE', 'NO', 'DK', 'FI' + ] + + const requests = countries.map(country => + axios.get('https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/category/channels', { + params: { countryCode: country, langCode: 'en' }, + headers: { ...HEADERS, token } + }).then(r => r.data?.data || []).catch(() => []) + ) + + const results = await Promise.all(requests) + const allChannels = results.flat().flatMap(group => group.channels || []) + + const uniqueChannels = new Map() + for (const ch of allChannels) { + if (!uniqueChannels.has(ch.chlId)) { + uniqueChannels.set(ch.chlId, { + lang: (ch.chlLangCode ? ch.chlLangCode.split('-')[0] : 'en'), + site_id: ch.chlId, + name: ch.chlName, + // logo: ch.imageIdentifier ? `https://d3b6luslimvglo.cloudfront.net/images/79/rlaxximages/channels-rescaled/icon-white/${ch.imageIdentifier}_white.png` : null + }) + } + } + + return Array.from(uniqueChannels.values()) + } +} + +async function limit(items, fn, concurrency = 20) { + const results = [] + for (let i = 0; i < items.length; i += concurrency) { + const batch = items.slice(i, i + concurrency) + results.push(...(await Promise.all(batch.map(fn)))) + } + return results +} + +async function getAuthToken() { + if (authTokenPromise) return authTokenPromise + + authTokenPromise = (async () => { + try { + const response = await axios.get('https://rlaxx.zeasn.tv/livetv/api/v1/auth/access', { + params: { uuid: '1', apiToken, langCode: 'en' }, + headers: HEADERS + }) + + if (response.data && response.data.data && response.data.data.token) { + return response.data.data.token + } + + throw new Error('apiToken invalid or expired. Please update config.') + } catch (error) { + authTokenPromise = null + throw new Error(error.message) + } + })() + + return authTokenPromise +} + +async function fetchProgramDetail(programId) { + const token = await getAuthToken() + try { + const response = await axios.get(`https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/epg/detail/${programId}`, { + headers: { + ...HEADERS, + 'token': token + }, + timeout: 5000 + }) + return response.data && response.data.data ? response.data.data : null + } catch { + return null + } } \ No newline at end of file diff --git a/sites/watch.whaletvplus.com/watch.whaletvplus.com.test.js b/sites/watch.whaletvplus.com/watch.whaletvplus.com.test.js index 0896d05a..5b462500 100644 --- a/sites/watch.whaletvplus.com/watch.whaletvplus.com.test.js +++ b/sites/watch.whaletvplus.com/watch.whaletvplus.com.test.js @@ -1,132 +1,132 @@ -const { parser, url, channels } = require('./watch.whaletvplus.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const axios = require('axios') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2026-01-08', 'YYYY-MM-DD').startOf('d') - -const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') - -it('can generate valid url', () => { - const channel = { site_id: '878765717599035555' } - - const generatedUrl = url({ channel, date }) - - expect(generatedUrl).toBe( - 'https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/epg?channelIds=878765717599035555&startTime=1767830400000&endTime=1767916800000' - ) -}) - -it('can parse response', async () => { - axios.get.mockImplementation((url) => { - if (url.includes('auth/access')) { - return Promise.resolve({ - data: { data: { token: 'mock_token' } } - }) - } - if (url.includes('epg/detail')) { - return Promise.resolve({ - data: { data: { prgDesc: 'Test Description' } } - }) - } - return Promise.resolve({ data: {} }) - }) - - const json = JSON.parse(content) - const firstChannel = json.data && json.data.length > 0 ? json.data[0] : null - const validSiteId = firstChannel ? firstChannel.chlId : '878765717599035555' - - const channel = { - site_id: validSiteId, - xmltv_id: 'Test.Channel' - } - - const result = await parser({ content, channel }) - - expect(result).toBeInstanceOf(Array) - expect(result.length).toBeGreaterThan(0) - - expect(result[0]).toMatchObject({ - title: expect.any(String), - start: expect.any(Object), - stop: expect.any(Object) - }) -}) - -it('can handle empty guide', async () => { - const result = await parser({ - content: '{"data":[]}', - channel: { site_id: '123' } - }) - expect(result).toMatchObject([]) -}) - -it('can parse channel list', async () => { - axios.get.mockImplementation((reqUrl) => { - if (reqUrl.includes('auth/access')) { - return Promise.resolve({ - data: { data: { token: 'mock_token_123' } } - }) - } - - if (reqUrl.includes('category/channels')) { - return Promise.resolve({ - data: { - data: [ - { - channels: [ - { - chlId: '878765717599035555', - chlName: 'Wedo Movies', - chlLangCode: 'en' - }, - { - chlId: '999420644834214633', - chlName: 'FIFA+', - chlLangCode: 'es' - } - ] - } - ] - } - }) - } - return Promise.resolve({ data: {} }) - }) - - const result = await channels() - - expect(result).toBeInstanceOf(Array) - expect(result.length).toBeGreaterThan(0) - expect(result[0]).toMatchObject({ - name: expect.any(String), - site_id: expect.any(String), - lang: expect.any(String) - }) -}) - -it('can parse token', async () => { - jest.resetModules() - const { request } = require('./watch.whaletvplus.com.config.js') - const axios = require('axios') - - axios.get.mockImplementation((url) => { - if (url.includes('auth/access')) { - return Promise.resolve({ - data: { data: { token: 'test_token' } } - }) - } - return Promise.resolve({ data: {} }) - }) - - const headers = await request.headers() - expect(headers.token).toBe('test_token') +const { parser, url, channels } = require('./watch.whaletvplus.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const axios = require('axios') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2026-01-08', 'YYYY-MM-DD').startOf('d') + +const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') + +it('can generate valid url', () => { + const channel = { site_id: '878765717599035555' } + + const generatedUrl = url({ channel, date }) + + expect(generatedUrl).toBe( + 'https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/epg?channelIds=878765717599035555&startTime=1767830400000&endTime=1767916800000' + ) +}) + +it('can parse response', async () => { + axios.get.mockImplementation((url) => { + if (url.includes('auth/access')) { + return Promise.resolve({ + data: { data: { token: 'mock_token' } } + }) + } + if (url.includes('epg/detail')) { + return Promise.resolve({ + data: { data: { prgDesc: 'Test Description' } } + }) + } + return Promise.resolve({ data: {} }) + }) + + const json = JSON.parse(content) + const firstChannel = json.data && json.data.length > 0 ? json.data[0] : null + const validSiteId = firstChannel ? firstChannel.chlId : '878765717599035555' + + const channel = { + site_id: validSiteId, + xmltv_id: 'Test.Channel' + } + + const result = await parser({ content, channel }) + + expect(result).toBeInstanceOf(Array) + expect(result.length).toBeGreaterThan(0) + + expect(result[0]).toMatchObject({ + title: expect.any(String), + start: expect.any(Object), + stop: expect.any(Object) + }) +}) + +it('can handle empty guide', async () => { + const result = await parser({ + content: '{"data":[]}', + channel: { site_id: '123' } + }) + expect(result).toMatchObject([]) +}) + +it('can parse channel list', async () => { + axios.get.mockImplementation((reqUrl) => { + if (reqUrl.includes('auth/access')) { + return Promise.resolve({ + data: { data: { token: 'mock_token_123' } } + }) + } + + if (reqUrl.includes('category/channels')) { + return Promise.resolve({ + data: { + data: [ + { + channels: [ + { + chlId: '878765717599035555', + chlName: 'Wedo Movies', + chlLangCode: 'en' + }, + { + chlId: '999420644834214633', + chlName: 'FIFA+', + chlLangCode: 'es' + } + ] + } + ] + } + }) + } + return Promise.resolve({ data: {} }) + }) + + const result = await channels() + + expect(result).toBeInstanceOf(Array) + expect(result.length).toBeGreaterThan(0) + expect(result[0]).toMatchObject({ + name: expect.any(String), + site_id: expect.any(String), + lang: expect.any(String) + }) +}) + +it('can parse token', async () => { + jest.resetModules() + const { request } = require('./watch.whaletvplus.com.config.js') + const axios = require('axios') + + axios.get.mockImplementation((url) => { + if (url.includes('auth/access')) { + return Promise.resolve({ + data: { data: { token: 'test_token' } } + }) + } + return Promise.resolve({ data: {} }) + }) + + const headers = await request.headers() + expect(headers.token).toBe('test_token') }) \ No newline at end of file From e192c8f781dcc67c418a8a7138887a574cca8d48 Mon Sep 17 00:00:00 2001 From: StrangeDrVN Date: Mon, 23 Feb 2026 14:28:15 +0530 Subject: [PATCH 07/10] append region to channels with same name --- .../watch.whaletvplus.com.channels.xml | 199 +++++++++--------- 1 file changed, 101 insertions(+), 98 deletions(-) diff --git a/sites/watch.whaletvplus.com/watch.whaletvplus.com.channels.xml b/sites/watch.whaletvplus.com/watch.whaletvplus.com.channels.xml index 708463c7..3c9c7dd2 100644 --- a/sites/watch.whaletvplus.com/watch.whaletvplus.com.channels.xml +++ b/sites/watch.whaletvplus.com/watch.whaletvplus.com.channels.xml @@ -1,6 +1,8 @@ Alaraby News + France 24 (AR) + TRT Arabi All Romance ARTFLIX - Filmklassiker AUTO BILD TV @@ -16,7 +18,7 @@ euronews deutsch Fabella Fantasja - FIFA+ + FIFA+ (DE) Focus TV GoldStar TV Grjngo - Westernfilme @@ -25,17 +27,17 @@ Höhenrausch Just Cooking just.fishing - Love The Planet - Moconomy - Wirtschaft und Finanzen - Moconomy - Wirtschaft und Finanzen + Love The Planet (DE) + Moconomy - Wirtschaft und Finanzen (CH) + Moconomy - Wirtschaft und Finanzen (DE/AT) More Than Sports TV - Motorvision + Motorvision (DE) Motorvision Classic Nachrichten 360 Naruto Netzkino One Terra - Red Bull TV + Red Bull TV (DE) Royalworld - Adel & Dynastien SPIEGEL TV Spiegel TV Action+Crime @@ -43,21 +45,21 @@ SPORT BILD TV Sportdigital Free Tempora - Terra Mater Wild + Terra Mater Wild (DE) Tierwelt Live TOP Filme TOP SciFi TOP Serien - Trailers - Travelxp + Trailers (DE) + Travelxp (DE) Utopja - wedotv Movies + wedotv Movies (DACH) wedotv Sports wedotv True Stories Xplore Free - Yu-Gi-Oh! + Yu-Gi-Oh! (DE) + 123GO! (EN) 5-Minute Crafts - 123GO! a-z B-Flix a-z Best Classic TV a-z Classic Flix @@ -110,30 +112,30 @@ Escape TV euronews english FailArmy - FIFA+ + FIFA+ (EN) Filmzie - France 24 - FUEL TV - FUEL TV - FUEL TV - FUEL TV - FUEL TV - FUEL TV - FUEL TV - FUEL TV - FUEL TV - FUEL TV - FUEL TV + France 24 (EN) + FUEL TV (AU/NZ) + FUEL TV (BE) + FUEL TV (DACH) + FUEL TV (ES) + FUEL TV (FI) + FUEL TV (FR) + FUEL TV (LATAM) + FUEL TV (NL) + FUEL TV (PT) + FUEL TV (SE) + FUEL TV (UK) Global Biz Goal TV - GoUSA TV + GoUSA TV (EN) Gusto TV Homerun TV Hoop TV Hunt Fish TV In Touch + INFAST - INTER 24/7 + INTER 24/7 (EN) INTRAVEL INTROUBLE INWILD @@ -143,17 +145,17 @@ Just For Laughs TV LatiNation Loupe Art - Love The Planet - Love Wine + Love The Planet (EN) + Love Wine (EN) Mercury+ Moonball Sports TV - MotoRRacing - Motorvision - Motorvision - Movie Mania - Movie Mania - Movie Music - Movie Music + MotoRRacing (EN) + Motorvision (EN) + Motorvision (PT) + Movie Mania (EN) + Movie Mania (US) + Movie Music (CET) + Movie Music (EST) Movieitaly Channel NEW KFOOD Newsmax @@ -170,9 +172,9 @@ PowerSports World Qwest TV RACER International - Red Bull TV - Red Bull TV - Red Bull TV + Red Bull TV (AU) + Red Bull TV (EU/MENA) + Red Bull TV (UK) Revry Global Revry Her Royalworld - Nobility & Dynasties @@ -182,29 +184,29 @@ Smart Healthy Green Living Space Series Sport Fishing TV - SportOutdoor.TV + SportOutdoor.TV (EN) Stingray Cityscapes Stingray Cozy Café Stingray Easy Listening - Stingray Naturescape - Stingray Naturescape + Stingray Naturescape (CET) + Stingray Naturescape (EST) Stingray Remember the 80s Stingray Smooth Jazz Stingray SPA Stingray Stargaze Tasty - Terra Mater Wild - The Boat Show + Terra Mater Wild (EN) + The Boat Show (EN) The Red Green Channel TidPix time2Rlx Trace Sport Stars Trace UK Trace Urban - Trailers + Trailers (EN) Travel & Food TV - Travelxp - Travelxp + Travelxp (NL) + Travelxp (UK/IT) TRT World UnchainedTV Unleashed by DOGTV @@ -212,26 +214,26 @@ WatchMojo WaterBear Weather Spy - wedotv Big Stories - wedotv Big Stories - wedotv Big Stories - wedotv Big Stories - wedotv Big Stories - wedotv Big Stories - wedotv Movies - wedotv Movies - wedotv Movies - wedotv Movies - wedotv Movies - wedotv Movies + wedotv Big Stories (DACH) + wedotv Big Stories (ES) + wedotv Big Stories (FR) + wedotv Big Stories (IT) + wedotv Big Stories (NL) + wedotv Big Stories (SE) + wedotv Movies (BENELUX) + wedotv Movies (DK) + wedotv Movies (FI) + wedotv Movies (NO) + wedotv Movies (SE) + wedotv Movies (UK/IE) WeShort Whoa! That was Wild! World Billiards TV - World Poker Tour - Yu-Gi-Oh! - ZenLife - ZenLife - 123GO! + World Poker Tour (EN) + Yu-Gi-Oh! (EN) + ZenLife (CET) + ZenLife (EST) + 123GO! (ES) Actualidad 360 America Television AYM Sports @@ -253,27 +255,27 @@ Delito Docs & Historias euronews español - FIFA+ - FIFA+ + FIFA+ (ES/LATAM) + FIFA+ (ES/MX) Film&Co - France 24 + France 24 (ES) FreeTV Acción FreeTV Clásico - FreeTV Drama + FreeTV Drama (ES/LATAM) FreeTV Estelar FreeTV Sureño - GoUSA TV + GoUSA TV (ES) Historia Y Vida Ideas En 5 Minutos - Love The Planet - Love Wine - Motorvision + Love The Planet (ES) + Love Wine (ES) + Motorvision (ES) Naturaleza Salvaje NEGOCIOS TV Nesting TV Pelimex Play Ibiza - Red Bull TV + Red Bull TV (ES) Revry LatinX Royalworld - Nobleza y Dinastias Somos Novelas @@ -283,36 +285,36 @@ Todo Novelas Todo Pasíon Trace Latina - Trailers + Trailers (ES) Viajar TV Viajes & Sabores wedotv Amor wedotv Amor: Piel Salvaje - World Poker Tour - Yu-Gi-Oh! + World Poker Tour (ES) + Yu-Gi-Oh! (ES) Cap Terre Ciné Nanar Ciné Western Drive TV - Emotion'L + Emotion'L Enquêtes De Choc euronews français - FIFA+ - France 24 + FIFA+ (FR) + France 24 (FR) Fréquence Novelas Grjngo - Films de Western Homicide IntoCrime Love Crime & History Mayday: Catastrophe Aérienne - MotoRRacing - Motorvision + MotoRRacing (FR) + Motorvision (FR) Motus - Trailers - Travelxp + Trailers (FR) + Travelxp (FR) Voyages & Saveurs - Y'a que la vérité qui compte - Yu-Gi-Oh! + Y'a que la vérité qui compte + Yu-Gi-Oh! (FR) Adrenaline Movies Alberto Sordi & Co Alta Tensione @@ -320,43 +322,44 @@ Cinema Italiano CineWestern euronews italiano - FIFA+ + FIFA+ (IT) Grandi Nomi Hip Hop TV Inazuma Eleven Collection - INTER 24/7 + INTER 24/7 (IT) InuYasha - Love The Planet + Love The Planet (IT) Mayday: Disastro Aereo Per Caso TV Rock TV Smile Soap Latino Soap Turco - SportOutdoor.TV + SportOutdoor.TV (IT) Storie Criminali - The Boat Show + The Boat Show (IT) Totò & Co - Trailers + Trailers (IT) Velvet Viaggi & Sapori - wedotv Movies - Yu-Gi-Oh! + wedotv Movies (IT) + Yu-Gi-Oh! (IT) Food ON NEW KPOP Travel ON AUTO MOTOR OG SPORT euronews português Feliz7Play - FIFA+ + FIFA+ (PT) FreeTV Crime - FreeTV Drama - Red Bull TV + FreeTV Drama (BR) + Red Bull TV (BR) Sessão Trash Tastemade Brasil Tastemade Casa Tastemade Viagem Trace Brasil Ubisoft TV Brasil - World Poker Tour - \ No newline at end of file + World Poker Tour (PT) + 澜湄国际 + From 89cf06fddee5cc06e32b68a03c8a21b6dbdb75f9 Mon Sep 17 00:00:00 2001 From: StrangeDrVN Date: Mon, 23 Feb 2026 14:49:11 +0530 Subject: [PATCH 08/10] adds region to channel names --- .../watch.whaletvplus.com.config.js | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js b/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js index 7e493335..a01c73c8 100644 --- a/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js +++ b/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js @@ -107,13 +107,14 @@ module.exports = { uniqueChannels.set(ch.chlId, { lang: (ch.chlLangCode ? ch.chlLangCode.split('-')[0] : 'en'), site_id: ch.chlId, - name: ch.chlName, + name: ch.chlName.trim(), + short_title: ch.chlShortTitle, // logo: ch.imageIdentifier ? `https://d3b6luslimvglo.cloudfront.net/images/79/rlaxximages/channels-rescaled/icon-white/${ch.imageIdentifier}_white.png` : null }) } } - return Array.from(uniqueChannels.values()) + return handleDuplicateNames(Array.from(uniqueChannels.values())) } } @@ -164,4 +165,24 @@ async function fetchProgramDetail(programId) { } catch { return null } +} + +function handleDuplicateNames(channels) { + const counts = {} + channels.forEach(ch => counts[ch.name] = (counts[ch.name] || 0) + 1) + + channels.forEach(ch => { + if (counts[ch.name] > 1) { + let suffix = ch.short_title && ch.short_title.split('_').slice(1).join('_') + if (suffix) { + if (suffix.startsWith('en-') && suffix.length > 3) suffix = suffix.slice(3) + ch.name += ` (${suffix.replace(/-/g, '/').toUpperCase()})` + } else if (ch.lang) { + ch.name += ` (${ch.lang.toUpperCase()})` + } + } + delete ch.short_title + }) + + return channels } \ No newline at end of file From be9078ec68ada0236a2dfaf2a4c3dc15c2d1e12f Mon Sep 17 00:00:00 2001 From: StrangeDrVN Date: Mon, 23 Feb 2026 14:56:06 +0530 Subject: [PATCH 09/10] crlf fix --- .../watch.whaletvplus.com.config.js | 374 +++++++++--------- 1 file changed, 187 insertions(+), 187 deletions(-) diff --git a/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js b/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js index a01c73c8..d65292e6 100644 --- a/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js +++ b/sites/watch.whaletvplus.com/watch.whaletvplus.com.config.js @@ -1,188 +1,188 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -const HEADERS = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:147.0) Gecko/20100101 Firefox/147.0', - 'Referer': 'https://watch.whaletvplus.com/', - 'Origin': 'https://watch.whaletvplus.com' -} -const apiToken = '4ef13b5f3d2744e3b0a569feb8dde298' - -let authTokenPromise = null - -module.exports = { - site: 'watch.whaletvplus.com', - days: 2, - - request: { - cache: { - ttl: 60 * 60 * 1000 // Cache 1 hour - }, - headers: async function() { - const token = await getAuthToken() - return { - ...HEADERS, - 'token': token - } - } - }, - - url: function ({ channel, date }) { - const start = date.valueOf() - const end = date.add(1, 'day').valueOf() - - return `https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/epg?channelIds=${channel.site_id}&startTime=${start}&endTime=${end}` - }, - - parser: async function ({ content }) { - let json - try { - json = JSON.parse(content) - } catch (e) { - console.error('Error parsing JSON:', e) - return [] - } - - if (!json.data || !Array.isArray(json.data) || !json.data[0] || !Array.isArray(json.data[0].ptList)) { - return [] - } - - const programs = json.data[0].ptList - const detailsCache = {} - - return await limit(programs, async (p) => { - const program = { - title: p.prgTitle, - start: dayjs(Number(p.prgStm)), - stop: dayjs(Number(p.prgEtm)) - } - - if (p.prgchId) { - if (!detailsCache[p.prgchId]) { - detailsCache[p.prgchId] = fetchProgramDetail(p.prgchId) - } - const detail = await detailsCache[p.prgchId] - if (detail) { - program.description = detail.prgDesc || null - program.season = detail.seasonNumber || null - program.episode = detail.episodeNumber || null - program.sub_title = detail.prgTitle || detail.seriesTitle || null - - if (program.title === program.sub_title) { - program.sub_title = null - } - - if (detail.images && Array.isArray(detail.images)) { - const bestImg = detail.images.find((i) => i.pimgWidth === '1920') || detail.images[0] - if (bestImg) program.image = bestImg.pimgUrl - } - } - } - return program - }) - }, - - async channels() { - const token = await getAuthToken() - - const countries = [ - 'IN', 'AU', 'NZ', 'ZA', 'US', 'BR', 'MX', 'AR', 'CO', 'CL', 'CA', - 'GB', 'DE', 'FR', 'IT', 'ES', 'PL', 'TR', 'AT', 'CH', 'NL', 'PT', - 'BE', 'SE', 'NO', 'DK', 'FI' - ] - - const requests = countries.map(country => - axios.get('https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/category/channels', { - params: { countryCode: country, langCode: 'en' }, - headers: { ...HEADERS, token } - }).then(r => r.data?.data || []).catch(() => []) - ) - - const results = await Promise.all(requests) - const allChannels = results.flat().flatMap(group => group.channels || []) - - const uniqueChannels = new Map() - for (const ch of allChannels) { - if (!uniqueChannels.has(ch.chlId)) { - uniqueChannels.set(ch.chlId, { - lang: (ch.chlLangCode ? ch.chlLangCode.split('-')[0] : 'en'), - site_id: ch.chlId, - name: ch.chlName.trim(), - short_title: ch.chlShortTitle, - // logo: ch.imageIdentifier ? `https://d3b6luslimvglo.cloudfront.net/images/79/rlaxximages/channels-rescaled/icon-white/${ch.imageIdentifier}_white.png` : null - }) - } - } - - return handleDuplicateNames(Array.from(uniqueChannels.values())) - } -} - -async function limit(items, fn, concurrency = 20) { - const results = [] - for (let i = 0; i < items.length; i += concurrency) { - const batch = items.slice(i, i + concurrency) - results.push(...(await Promise.all(batch.map(fn)))) - } - return results -} - -async function getAuthToken() { - if (authTokenPromise) return authTokenPromise - - authTokenPromise = (async () => { - try { - const response = await axios.get('https://rlaxx.zeasn.tv/livetv/api/v1/auth/access', { - params: { uuid: '1', apiToken, langCode: 'en' }, - headers: HEADERS - }) - - if (response.data && response.data.data && response.data.data.token) { - return response.data.data.token - } - - throw new Error('apiToken invalid or expired. Please update config.') - } catch (error) { - authTokenPromise = null - throw new Error(error.message) - } - })() - - return authTokenPromise -} - -async function fetchProgramDetail(programId) { - const token = await getAuthToken() - try { - const response = await axios.get(`https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/epg/detail/${programId}`, { - headers: { - ...HEADERS, - 'token': token - }, - timeout: 5000 - }) - return response.data && response.data.data ? response.data.data : null - } catch { - return null - } -} - -function handleDuplicateNames(channels) { - const counts = {} - channels.forEach(ch => counts[ch.name] = (counts[ch.name] || 0) + 1) - - channels.forEach(ch => { - if (counts[ch.name] > 1) { - let suffix = ch.short_title && ch.short_title.split('_').slice(1).join('_') - if (suffix) { - if (suffix.startsWith('en-') && suffix.length > 3) suffix = suffix.slice(3) - ch.name += ` (${suffix.replace(/-/g, '/').toUpperCase()})` - } else if (ch.lang) { - ch.name += ` (${ch.lang.toUpperCase()})` - } - } - delete ch.short_title - }) - - return channels +const axios = require('axios') +const dayjs = require('dayjs') + +const HEADERS = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:147.0) Gecko/20100101 Firefox/147.0', + 'Referer': 'https://watch.whaletvplus.com/', + 'Origin': 'https://watch.whaletvplus.com' +} +const apiToken = '4ef13b5f3d2744e3b0a569feb8dde298' + +let authTokenPromise = null + +module.exports = { + site: 'watch.whaletvplus.com', + days: 2, + + request: { + cache: { + ttl: 60 * 60 * 1000 // Cache 1 hour + }, + headers: async function() { + const token = await getAuthToken() + return { + ...HEADERS, + 'token': token + } + } + }, + + url: function ({ channel, date }) { + const start = date.valueOf() + const end = date.add(1, 'day').valueOf() + + return `https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/epg?channelIds=${channel.site_id}&startTime=${start}&endTime=${end}` + }, + + parser: async function ({ content }) { + let json + try { + json = JSON.parse(content) + } catch (e) { + console.error('Error parsing JSON:', e) + return [] + } + + if (!json.data || !Array.isArray(json.data) || !json.data[0] || !Array.isArray(json.data[0].ptList)) { + return [] + } + + const programs = json.data[0].ptList + const detailsCache = {} + + return await limit(programs, async (p) => { + const program = { + title: p.prgTitle, + start: dayjs(Number(p.prgStm)), + stop: dayjs(Number(p.prgEtm)) + } + + if (p.prgchId) { + if (!detailsCache[p.prgchId]) { + detailsCache[p.prgchId] = fetchProgramDetail(p.prgchId) + } + const detail = await detailsCache[p.prgchId] + if (detail) { + program.description = detail.prgDesc || null + program.season = detail.seasonNumber || null + program.episode = detail.episodeNumber || null + program.sub_title = detail.prgTitle || detail.seriesTitle || null + + if (program.title === program.sub_title) { + program.sub_title = null + } + + if (detail.images && Array.isArray(detail.images)) { + const bestImg = detail.images.find((i) => i.pimgWidth === '1920') || detail.images[0] + if (bestImg) program.image = bestImg.pimgUrl + } + } + } + return program + }) + }, + + async channels() { + const token = await getAuthToken() + + const countries = [ + 'IN', 'AU', 'NZ', 'ZA', 'US', 'BR', 'MX', 'AR', 'CO', 'CL', 'CA', + 'GB', 'DE', 'FR', 'IT', 'ES', 'PL', 'TR', 'AT', 'CH', 'NL', 'PT', + 'BE', 'SE', 'NO', 'DK', 'FI' + ] + + const requests = countries.map(country => + axios.get('https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/category/channels', { + params: { countryCode: country, langCode: 'en' }, + headers: { ...HEADERS, token } + }).then(r => r.data?.data || []).catch(() => []) + ) + + const results = await Promise.all(requests) + const allChannels = results.flat().flatMap(group => group.channels || []) + + const uniqueChannels = new Map() + for (const ch of allChannels) { + if (!uniqueChannels.has(ch.chlId)) { + uniqueChannels.set(ch.chlId, { + lang: (ch.chlLangCode ? ch.chlLangCode.split('-')[0] : 'en'), + site_id: ch.chlId, + name: ch.chlName.trim(), + short_title: ch.chlShortTitle, + // logo: ch.imageIdentifier ? `https://d3b6luslimvglo.cloudfront.net/images/79/rlaxximages/channels-rescaled/icon-white/${ch.imageIdentifier}_white.png` : null + }) + } + } + + return handleDuplicateNames(Array.from(uniqueChannels.values())) + } +} + +async function limit(items, fn, concurrency = 20) { + const results = [] + for (let i = 0; i < items.length; i += concurrency) { + const batch = items.slice(i, i + concurrency) + results.push(...(await Promise.all(batch.map(fn)))) + } + return results +} + +async function getAuthToken() { + if (authTokenPromise) return authTokenPromise + + authTokenPromise = (async () => { + try { + const response = await axios.get('https://rlaxx.zeasn.tv/livetv/api/v1/auth/access', { + params: { uuid: '1', apiToken, langCode: 'en' }, + headers: HEADERS + }) + + if (response.data && response.data.data && response.data.data.token) { + return response.data.data.token + } + + throw new Error('apiToken invalid or expired. Please update config.') + } catch (error) { + authTokenPromise = null + throw new Error(error.message) + } + })() + + return authTokenPromise +} + +async function fetchProgramDetail(programId) { + const token = await getAuthToken() + try { + const response = await axios.get(`https://rlaxx.zeasn.tv/livetv/api/device/browser/v1/epg/detail/${programId}`, { + headers: { + ...HEADERS, + 'token': token + }, + timeout: 5000 + }) + return response.data && response.data.data ? response.data.data : null + } catch { + return null + } +} + +function handleDuplicateNames(channels) { + const counts = {} + channels.forEach(ch => counts[ch.name] = (counts[ch.name] || 0) + 1) + + channels.forEach(ch => { + if (counts[ch.name] > 1) { + let suffix = ch.short_title && ch.short_title.split('_').slice(1).join('_') + if (suffix) { + if (suffix.startsWith('en-') && suffix.length > 3) suffix = suffix.slice(3) + ch.name += ` (${suffix.replace(/-/g, '/').toUpperCase()})` + } else if (ch.lang) { + ch.name += ` (${ch.lang.toUpperCase()})` + } + } + delete ch.short_title + }) + + return channels } \ No newline at end of file From 88dd7bfccb98de7fe2d0e975d08e2af1e4e51459 Mon Sep 17 00:00:00 2001 From: StrangeDrVN Date: Thu, 5 Mar 2026 15:16:41 +0530 Subject: [PATCH 10/10] channels mapping --- .../watch.whaletvplus.com.channels.xml | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/sites/watch.whaletvplus.com/watch.whaletvplus.com.channels.xml b/sites/watch.whaletvplus.com/watch.whaletvplus.com.channels.xml index 3c9c7dd2..d515966c 100644 --- a/sites/watch.whaletvplus.com/watch.whaletvplus.com.channels.xml +++ b/sites/watch.whaletvplus.com/watch.whaletvplus.com.channels.xml @@ -1,8 +1,8 @@ - Alaraby News - France 24 (AR) - TRT Arabi + Alaraby News + France 24 (AR) + TRT Arabi All Romance ARTFLIX - Filmklassiker AUTO BILD TV @@ -11,7 +11,7 @@ Computer BILD CrimeStar Crimify - Curiosity Now + Curiosity Now DDR TV-Archiv Deluxe DeutschPop Deluxe Lounge HD @@ -27,7 +27,7 @@ Höhenrausch Just Cooking just.fishing - Love The Planet (DE) + Love The Planet (DE) Moconomy - Wirtschaft und Finanzen (CH) Moconomy - Wirtschaft und Finanzen (DE/AT) More Than Sports TV @@ -51,7 +51,7 @@ TOP SciFi TOP Serien Trailers (DE) - Travelxp (DE) + Travelxp (DE) Utopja wedotv Movies (DACH) wedotv Sports @@ -88,10 +88,10 @@ Bigtime - Free Movies Billiards TV Bloomberg Originals - Bloomberg TV+ + Bloomberg TV+ Bollywood 4U Brat TV - China Travel + China Travel Chrono TV CNA Originals Confess by Nosey @@ -101,7 +101,7 @@ Daystar DeFiance News DFB Play TV - Discovering China + Discovering China DIY Art Docu Vision Drive+Speed @@ -126,7 +126,7 @@ FUEL TV (PT) FUEL TV (SE) FUEL TV (UK) - Global Biz + Global Biz Goal TV GoUSA TV (EN) Gusto TV @@ -145,7 +145,7 @@ Just For Laughs TV LatiNation Loupe Art - Love The Planet (EN) + Love The Planet (EN) Love Wine (EN) Mercury+ Moonball Sports TV @@ -189,7 +189,7 @@ Stingray Cozy Café Stingray Easy Listening Stingray Naturescape (CET) - Stingray Naturescape (EST) + Stingray Naturescape (EST) Stingray Remember the 80s Stingray Smooth Jazz Stingray SPA @@ -265,9 +265,9 @@ FreeTV Estelar FreeTV Sureño GoUSA TV (ES) - Historia Y Vida + Historia Y Vida Ideas En 5 Minutos - Love The Planet (ES) + Love The Planet (ES) Love Wine (ES) Motorvision (ES) Naturaleza Salvaje @@ -361,5 +361,5 @@ Trace Brasil Ubisoft TV Brasil World Poker Tour (PT) - 澜湄国际 - + 澜湄国际 + \ No newline at end of file