diff --git a/plugins/lineuparr/AU_Foxtel_lineup.json b/plugins/lineuparr/AU_Foxtel_lineup.json index 93f07e7..38320fd 100644 --- a/plugins/lineuparr/AU_Foxtel_lineup.json +++ b/plugins/lineuparr/AU_Foxtel_lineup.json @@ -1,179 +1,179 @@ -{ - "package": "Foxtel Platinum Plus", - "date": "2026-04-10", - "description": "Foxtel Platinum Plus AU lineup with sports, movies, and entertainment add-on packs", - "source": "Foxtel Channel Lineup", - "notes": "Lineup excludes local network affiliates and their digital multi-channels (Seven, Nine, Ten, ABC, SBS). High-definition (HD) swaps are applied where available. Some channels are delivered over the internet or as on-demand services as noted. Regional availability of free-to-air channels may vary by postcode.", - "categories": { - "News": [ - { "name": "Sky News", "number": 103 }, - { "name": "Sky News Weather", "number": 601 }, - { "name": "Sky News Extra", "number": 603 }, - { "name": "Sky News UK", "number": 604 }, - { "name": "Fox News Channel", "number": 608 }, - { "name": "CNN International", "number": 609 }, - { "name": "MS NOW", "number": 612 }, - { "name": "NBC News NOW", "number": 613 }, - { "name": "CNBC", "number": 620 }, - { "name": "Bloomberg Television", "number": 621 }, - { "name": "ABC News", "number": 642 }, - { "name": "SBS WorldWatch", "number": 644 }, - { "name": "Al Jazeera English", "number": 651 }, - { "name": "CGTN", "number": 653 }, - { "name": "NHK World", "number": 656 }, - { "name": "GB News", "number": 658 } - ], - "Sports": [ - { "name": "Fox Sports News", "number": 500 }, - { "name": "Fox Cricket", "number": 501, "addon": "Sports" }, - { "name": "Fox League", "number": 502, "addon": "Sports" }, - { "name": "Fox Sports 503", "number": 503, "addon": "Sports" }, - { "name": "Fox Footy", "number": 504, "addon": "Sports" }, - { "name": "Fox Sports 505", "number": 505, "addon": "Sports" }, - { "name": "Fox Sports 506", "number": 506, "addon": "Sports" }, - { "name": "Fox Sports More+", "number": 507, "addon": "Sports" }, - { "name": "ESPN", "number": 509, "addon": "Sports" }, - { "name": "ESPN2", "number": 510, "addon": "Sports" }, - { "name": "Main Event", "number": 522, "notes": "Pay-per-view" }, - { "name": "Main Event UFC", "number": 523 }, - { "name": "Sky Racing 1", "number": 526 }, - { "name": "Sky Racing 2", "number": 527 }, - { "name": "Sky Thoroughbred Central", "number": 528 }, - { "name": "Racing.com", "number": 529 }, - { "name": "Fox Sports Ultra HD", "number": 592, "addon": "Sports", "notes": "Channels 592-595" } - ], - "Regional Sports": [], - "Movies": [ - { "name": "Movies 4K Ultra HD", "number": 400, "addon": "Movies" }, - { "name": "Movies Premiere", "number": 401, "addon": "Movies" }, - { "name": "Movies Hits", "number": 402, "addon": "Movies" }, - { "name": "Movies Family", "number": 403, "addon": "Movies" }, - { "name": "Movies Action", "number": 405, "addon": "Movies" }, - { "name": "Movies Comedy", "number": 406, "addon": "Movies" }, - { "name": "Movies Romance", "number": 408, "addon": "Movies" }, - { "name": "Movies Drama", "number": 409, "addon": "Movies" }, - { "name": "Movies Greats", "number": 410, "addon": "Movies" }, - { "name": "British Cinema", "number": 413 }, - { "name": "Aussie Classics", "number": 414 }, - { "name": "PULP", "number": 415, "addon": "Movies" }, - { "name": "SBS World Movies", "number": 433 }, - { "name": "More Movies", "number": 424, "addon": "Movies", "notes": "On-demand" } - ], - "Kids": [ - { "name": "DreamWorks", "number": 717 } - ], - "Entertainment": [ - { "name": "Foxtel One", "number": 101 }, - { "name": "BBC UKTV", "number": 105 }, - { "name": "FOX8", "number": 108 }, - { "name": "Showcase", "number": 112 }, - { "name": "Classics", "number": 117 }, - { "name": "British", "number": 118 }, - { "name": "Universal TV", "number": 120 }, - { "name": "Foxtel Pop Up", "number": 128 }, - { "name": "More Drama", "number": 145, "notes": "On-demand" }, - { "name": "More British", "number": 146, "notes": "On-demand" }, - { "name": "More Classics", "number": 147, "notes": "On-demand" }, - { "name": "BoxSets", "number": 150 }, - { "name": "Aurora Community Channel", "number": 173 }, - { "name": "NITV", "number": 172 } - ], - "Reality & Lifestyle": [ - { "name": "Lifestyle", "number": 106 }, - { "name": "Arena", "number": 111 }, - { "name": "Real Life", "number": 121 }, - { "name": "TLC", "number": 123 }, - { "name": "LifeStyle Home", "number": 126 }, - { "name": "More Lifestyle", "number": 127, "notes": "On-demand" }, - { "name": "Fashion TV", "number": 151 } - ], - "Comedy": [ - { "name": "Comedy", "number": 114 }, - { "name": "Seinfeld", "number": 115 }, - { "name": "More Comedy", "number": 144, "notes": "On-demand" } - ], - "Discovery": [ - { "name": "DocPlay Channel", "number": 119 }, - { "name": "Real History", "number": 130 }, - { "name": "Trace Sport Stars", "number": 132 }, - { "name": "Discovery", "number": 133 }, - { "name": "Nature Time", "number": 134 }, - { "name": "HauntTV", "number": 137 }, - { "name": "Love Pets", "number": 138 }, - { "name": "More Docos", "number": 140, "notes": "On-demand" }, - { "name": "Animal Planet", "number": 141 }, - { "name": "Discovery Turbo", "number": 143 }, - { "name": "CGTN Documentary", "number": 654 } - ], - "Crime": [ - { "name": "Crime", "number": 113 }, - { "name": "Real Crime", "number": 135 }, - { "name": "Investigation Discovery", "number": 136 } - ], - "Faith": [ - { "name": "GOOD.", "number": 182 }, - { "name": "Daystar", "number": 185 }, - { "name": "SBN", "number": 186 } - ], - "Music": [ - { "name": "Trending", "number": 801 }, - { "name": "Kids Music", "number": 802 }, - { "name": "Club", "number": 803 }, - { "name": "Retro", "number": 804 }, - { "name": "CMC", "number": 805 }, - { "name": "Max", "number": 806 }, - { "name": "Australian Played", "number": 807 }, - { "name": "Vevo Pop", "number": 810 }, - { "name": "Vevo 2K", "number": 811 }, - { "name": "Vevo '90s", "number": 812 }, - { "name": "Vevo '70s", "number": 813 }, - { "name": "Vevo Retro Rock", "number": 814 }, - { "name": "Vevo Country", "number": 815 }, - { "name": "Good Vibes", "number": 830, "notes": "Audio" }, - { "name": "Triple M 70s", "number": 831, "notes": "Audio" }, - { "name": "Oldskool 80s Hits", "number": 832, "notes": "Audio" }, - { "name": "Triple M 90s", "number": 833, "notes": "Audio" }, - { "name": "Triple M 2000s", "number": 834, "notes": "Audio" }, - { "name": "Almost Acoustic", "number": 835, "notes": "Audio" }, - { "name": "Rock 'n' Road Trip", "number": 836, "notes": "Audio" }, - { "name": "Triple M Classic Rock", "number": 837, "notes": "Audio" }, - { "name": "Indie & Alt", "number": 838, "notes": "Audio" }, - { "name": "60s Hits", "number": 839, "notes": "Audio" }, - { "name": "Blender Beats", "number": 840, "notes": "Audio" }, - { "name": "Triple M 80s", "number": 841, "notes": "Audio" }, - { "name": "Almost Acoustic Pop", "number": 842, "notes": "Audio" }, - { "name": "Easy Hits", "number": 843, "notes": "Audio" }, - { "name": "Deep Calm", "number": 844, "notes": "Audio" }, - { "name": "RnB Chill", "number": 845, "notes": "Audio" }, - { "name": "Chill Pop Hits", "number": 847, "notes": "Audio" }, - { "name": "Oldskool 90s Hits", "number": 848, "notes": "Audio" }, - { "name": "Top 30", "number": 851, "notes": "Audio" }, - { "name": "90s Soft Pop", "number": 852, "notes": "Audio" }, - { "name": "RnB Fridays Radio", "number": 853, "notes": "Audio" }, - { "name": "Dance Hits", "number": 854, "notes": "Audio" }, - { "name": "2010s", "number": 855, "notes": "Audio" }, - { "name": "Triple M Country", "number": 856, "notes": "Audio" }, - { "name": "Country Classics", "number": 857, "notes": "Audio" }, - { "name": "BBC World Service", "number": 858, "notes": "Audio" }, - { "name": "SBS Radio 1", "number": 867, "notes": "Audio" }, - { "name": "SBS Radio 2", "number": 868, "notes": "Audio" }, - { "name": "SBS Chill", "number": 869, "notes": "Audio" }, - { "name": "Easy 97.2", "number": 940, "addon": "Antenna Pacific", "notes": "Audio" }, - { "name": "Rai Radio 1", "number": 943, "addon": "RAI International", "notes": "Audio" } - ], - "Shopping": [ - { "name": "TVSN", "number": 176 } - ], - "Spanish": [ - { "name": "Antenna Pacific", "number": 941, "addon": "Antenna Pacific", "notes": "Greek" }, - { "name": "Rai Italia", "number": 942, "addon": "RAI International", "notes": "Italian" } - ], - "Outdoors": [ - { "name": "Outdoor Channel", "number": 131 } - ], - "Food & Travel": [ - { "name": "LifeStyle Food", "number": 125 }, - { "name": "SBS Food", "number": 171 } - ] - } +{ + "package": "Foxtel Platinum Plus", + "date": "2026-04-10", + "description": "Foxtel Platinum Plus AU lineup with sports, movies, and entertainment add-on packs", + "source": "Foxtel Channel Lineup", + "notes": "Lineup excludes local network affiliates and their digital multi-channels (Seven, Nine, Ten, ABC, SBS). High-definition (HD) swaps are applied where available. Some channels are delivered over the internet or as on-demand services as noted. Regional availability of free-to-air channels may vary by postcode.", + "categories": { + "News": [ + { "name": "Sky News", "number": 103 }, + { "name": "Sky News Weather", "number": 601 }, + { "name": "Sky News Extra", "number": 603 }, + { "name": "Sky News UK", "number": 604 }, + { "name": "Fox News Channel", "number": 608 }, + { "name": "CNN International", "number": 609 }, + { "name": "MS NOW", "number": 612 }, + { "name": "NBC News NOW", "number": 613 }, + { "name": "CNBC", "number": 620 }, + { "name": "Bloomberg Television", "number": 621 }, + { "name": "ABC News", "number": 642 }, + { "name": "SBS WorldWatch", "number": 644 }, + { "name": "Al Jazeera English", "number": 651 }, + { "name": "CGTN", "number": 653 }, + { "name": "NHK World", "number": 656 }, + { "name": "GB News", "number": 658 } + ], + "Sports": [ + { "name": "Fox Sports News", "number": 500 }, + { "name": "Fox Cricket", "number": 501, "addon": "Sports" }, + { "name": "Fox League", "number": 502, "addon": "Sports" }, + { "name": "Fox Sports 503", "number": 503, "addon": "Sports" }, + { "name": "Fox Footy", "number": 504, "addon": "Sports" }, + { "name": "Fox Sports 505", "number": 505, "addon": "Sports" }, + { "name": "Fox Sports 506", "number": 506, "addon": "Sports" }, + { "name": "Fox Sports More+", "number": 507, "addon": "Sports" }, + { "name": "ESPN", "number": 509, "addon": "Sports" }, + { "name": "ESPN2", "number": 510, "addon": "Sports" }, + { "name": "Main Event", "number": 522, "notes": "Pay-per-view" }, + { "name": "Main Event UFC", "number": 523 }, + { "name": "Sky Racing 1", "number": 526 }, + { "name": "Sky Racing 2", "number": 527 }, + { "name": "Sky Thoroughbred Central", "number": 528 }, + { "name": "Racing.com", "number": 529 }, + { "name": "Fox Sports Ultra HD", "number": 592, "addon": "Sports", "notes": "Channels 592-595" } + ], + "Regional Sports": [], + "Movies": [ + { "name": "Movies 4K Ultra HD", "number": 400, "addon": "Movies" }, + { "name": "Movies Premiere", "number": 401, "addon": "Movies" }, + { "name": "Movies Hits", "number": 402, "addon": "Movies" }, + { "name": "Movies Family", "number": 403, "addon": "Movies" }, + { "name": "Movies Action", "number": 405, "addon": "Movies" }, + { "name": "Movies Comedy", "number": 406, "addon": "Movies" }, + { "name": "Movies Romance", "number": 408, "addon": "Movies" }, + { "name": "Movies Drama", "number": 409, "addon": "Movies" }, + { "name": "Movies Greats", "number": 410, "addon": "Movies" }, + { "name": "British Cinema", "number": 413 }, + { "name": "Aussie Classics", "number": 414 }, + { "name": "PULP", "number": 415, "addon": "Movies" }, + { "name": "SBS World Movies", "number": 433 }, + { "name": "More Movies", "number": 424, "addon": "Movies", "notes": "On-demand" } + ], + "Kids": [ + { "name": "DreamWorks", "number": 717 } + ], + "Entertainment": [ + { "name": "Foxtel One", "number": 101 }, + { "name": "BBC UKTV", "number": 105 }, + { "name": "FOX8", "number": 108 }, + { "name": "Showcase", "number": 112 }, + { "name": "Classics", "number": 117 }, + { "name": "British", "number": 118 }, + { "name": "Universal TV", "number": 120 }, + { "name": "Foxtel Pop Up", "number": 128 }, + { "name": "More Drama", "number": 145, "notes": "On-demand" }, + { "name": "More British", "number": 146, "notes": "On-demand" }, + { "name": "More Classics", "number": 147, "notes": "On-demand" }, + { "name": "BoxSets", "number": 150 }, + { "name": "Aurora Community Channel", "number": 173 }, + { "name": "NITV", "number": 172 } + ], + "Reality & Lifestyle": [ + { "name": "Lifestyle", "number": 106 }, + { "name": "Arena", "number": 111 }, + { "name": "Real Life", "number": 121 }, + { "name": "TLC", "number": 123 }, + { "name": "LifeStyle Home", "number": 126 }, + { "name": "More Lifestyle", "number": 127, "notes": "On-demand" }, + { "name": "Fashion TV", "number": 151 } + ], + "Comedy": [ + { "name": "Comedy", "number": 114 }, + { "name": "Seinfeld", "number": 115 }, + { "name": "More Comedy", "number": 144, "notes": "On-demand" } + ], + "Discovery": [ + { "name": "DocPlay Channel", "number": 119 }, + { "name": "Real History", "number": 130 }, + { "name": "Trace Sport Stars", "number": 132 }, + { "name": "Discovery", "number": 133 }, + { "name": "Nature Time", "number": 134 }, + { "name": "HauntTV", "number": 137 }, + { "name": "Love Pets", "number": 138 }, + { "name": "More Docos", "number": 140, "notes": "On-demand" }, + { "name": "Animal Planet", "number": 141 }, + { "name": "Discovery Turbo", "number": 143 }, + { "name": "CGTN Documentary", "number": 654 } + ], + "Crime": [ + { "name": "Crime", "number": 113 }, + { "name": "Real Crime", "number": 135 }, + { "name": "Investigation Discovery", "number": 136 } + ], + "Faith": [ + { "name": "GOOD.", "number": 182 }, + { "name": "Daystar", "number": 185 }, + { "name": "SBN", "number": 186 } + ], + "Music": [ + { "name": "Trending", "number": 801 }, + { "name": "Kids Music", "number": 802 }, + { "name": "Club", "number": 803 }, + { "name": "Retro", "number": 804 }, + { "name": "CMC", "number": 805 }, + { "name": "Max", "number": 806 }, + { "name": "Australian Played", "number": 807 }, + { "name": "Vevo Pop", "number": 810 }, + { "name": "Vevo 2K", "number": 811 }, + { "name": "Vevo '90s", "number": 812 }, + { "name": "Vevo '70s", "number": 813 }, + { "name": "Vevo Retro Rock", "number": 814 }, + { "name": "Vevo Country", "number": 815 }, + { "name": "Good Vibes", "number": 830, "notes": "Audio" }, + { "name": "Triple M 70s", "number": 831, "notes": "Audio" }, + { "name": "Oldskool 80s Hits", "number": 832, "notes": "Audio" }, + { "name": "Triple M 90s", "number": 833, "notes": "Audio" }, + { "name": "Triple M 2000s", "number": 834, "notes": "Audio" }, + { "name": "Almost Acoustic", "number": 835, "notes": "Audio" }, + { "name": "Rock 'n' Road Trip", "number": 836, "notes": "Audio" }, + { "name": "Triple M Classic Rock", "number": 837, "notes": "Audio" }, + { "name": "Indie & Alt", "number": 838, "notes": "Audio" }, + { "name": "60s Hits", "number": 839, "notes": "Audio" }, + { "name": "Blender Beats", "number": 840, "notes": "Audio" }, + { "name": "Triple M 80s", "number": 841, "notes": "Audio" }, + { "name": "Almost Acoustic Pop", "number": 842, "notes": "Audio" }, + { "name": "Easy Hits", "number": 843, "notes": "Audio" }, + { "name": "Deep Calm", "number": 844, "notes": "Audio" }, + { "name": "RnB Chill", "number": 845, "notes": "Audio" }, + { "name": "Chill Pop Hits", "number": 847, "notes": "Audio" }, + { "name": "Oldskool 90s Hits", "number": 848, "notes": "Audio" }, + { "name": "Top 30", "number": 851, "notes": "Audio" }, + { "name": "90s Soft Pop", "number": 852, "notes": "Audio" }, + { "name": "RnB Fridays Radio", "number": 853, "notes": "Audio" }, + { "name": "Dance Hits", "number": 854, "notes": "Audio" }, + { "name": "2010s", "number": 855, "notes": "Audio" }, + { "name": "Triple M Country", "number": 856, "notes": "Audio" }, + { "name": "Country Classics", "number": 857, "notes": "Audio" }, + { "name": "BBC World Service", "number": 858, "notes": "Audio" }, + { "name": "SBS Radio 1", "number": 867, "notes": "Audio" }, + { "name": "SBS Radio 2", "number": 868, "notes": "Audio" }, + { "name": "SBS Chill", "number": 869, "notes": "Audio" }, + { "name": "Easy 97.2", "number": 940, "addon": "Antenna Pacific", "notes": "Audio" }, + { "name": "Rai Radio 1", "number": 943, "addon": "RAI International", "notes": "Audio" } + ], + "Shopping": [ + { "name": "TVSN", "number": 176 } + ], + "Spanish": [ + { "name": "Antenna Pacific", "number": 941, "addon": "Antenna Pacific", "notes": "Greek" }, + { "name": "Rai Italia", "number": 942, "addon": "RAI International", "notes": "Italian" } + ], + "Outdoors": [ + { "name": "Outdoor Channel", "number": 131 } + ], + "Food & Travel": [ + { "name": "LifeStyle Food", "number": 125 }, + { "name": "SBS Food", "number": 171 } + ] + } } \ No newline at end of file diff --git a/plugins/lineuparr/CA_Telus-Optik_lineup.json b/plugins/lineuparr/CA_Telus-Optik_lineup.json index ed60ef8..331844d 100644 --- a/plugins/lineuparr/CA_Telus-Optik_lineup.json +++ b/plugins/lineuparr/CA_Telus-Optik_lineup.json @@ -1,169 +1,169 @@ -{ - "package": "TELUS Optik TV Ultimate", - "date": "2026-04-05", - "description": "TELUS Optik TV Ultimate package for Canadian markets (AB/BC) with premium and multicultural add-ons", - "source": "TELUS Optik TV Channel Guide", - "notes": "Lineup excludes local network affiliates (ABC, CBS, NBC, FOX, PBS, CBC, CTV, City, Global, Omni). Regional availability of some channels (e.g., Sportsnet feeds) varies by province (Alberta vs. British Columbia). Premium movie, sports, and multicultural channels require additional subscriptions.", - "categories": { - "News": [ - { "name": "Al Jazeera English", "number": 825 }, - { "name": "BBC World News", "number": 821 }, - { "name": "BNN Bloomberg", "number": 815 }, - { "name": "CBC News Network", "number": 800 }, - { "name": "CCTV News", "number": 834 }, - { "name": "CNBC", "number": 806 }, - { "name": "CNN", "number": 802 }, - { "name": "CNNI", "number": 827 }, - { "name": "CTV News Channel", "number": 801 }, - { "name": "FOX News", "number": 811 }, - { "name": "HLN", "number": 819 }, - { "name": "MSNBC", "number": 804 }, - { "name": "The Weather Network", "number": 128 } - ], - "Sports": [ - { "name": "beIN SPORTS", "number": 983, "addon": "Premium Sports" }, - { "name": "Fight Network", "number": 945 }, - { "name": "FNTSY Sports Network", "number": 921 }, - { "name": "Golf Channel", "number": 922 }, - { "name": "MLB Extra Innings", "number": "1301-1311", "addon": "Premium Sports" }, - { "name": "NBA League Pass", "number": "1401-1409", "addon": "Premium Sports" }, - { "name": "NBA TV Canada", "number": 937 }, - { "name": "NFL Network", "number": 926 }, - { "name": "NFL RedZone", "number": 927 }, - { "name": "NFL Sunday Ticket", "number": "1501-1516", "addon": "Premium Sports" }, - { "name": "NHL Centre Ice", "number": "1101-1114", "addon": "Premium Sports" }, - { "name": "radX", "number": 931 }, - { "name": "Sportsnet 360", "number": 920 }, - { "name": "Sportsnet ONE", "number": 907 }, - { "name": "Sportsnet World", "number": "1001-1007", "addon": "Premium Sports" }, - { "name": "TSN1", "number": 900 }, - { "name": "TSN2", "number": 901 }, - { "name": "TSN3", "number": 902 }, - { "name": "TSN4", "number": 903 }, - { "name": "TSN5", "number": 904 }, - { "name": "WWE Network", "number": 947 } - ], - "Regional Sports": [ - { "name": "Sportsnet East", "number": 917 }, - { "name": "Sportsnet Flames", "number": 910 }, - { "name": "Sportsnet Oilers", "number": 909 }, - { "name": "Sportsnet Ontario", "number": 916 }, - { "name": "Sportsnet Pacific/West", "number": 915 }, - { "name": "Sportsnet Vancouver Hockey", "number": 909 } - ], - "Movies": [ - { "name": "Action", "number": 449 }, - { "name": "CraveTV On Demand", "number": 411, "addon": "Movies & Series" }, - { "name": "FX", "number": 413 }, - { "name": "FXX", "number": 414 }, - { "name": "HBO Canada", "number": 400, "addon": "Movies & Series" }, - { "name": "Hollywood Suite 70", "number": 426, "addon": "Movies & Series" }, - { "name": "Hollywood Suite 80", "number": 427, "addon": "Movies & Series" }, - { "name": "Hollywood Suite 90", "number": 428, "addon": "Movies & Series" }, - { "name": "Hollywood Suite 00", "number": 429, "addon": "Movies & Series" }, - { "name": "MovieTime", "number": 445 }, - { "name": "Rewind", "number": 457 }, - { "name": "Silver Screen Classics", "number": 460 }, - { "name": "SundanceTV", "number": 453 }, - { "name": "Super Channel 1", "number": 417, "addon": "Movies & Series" }, - { "name": "Super Channel 2", "number": 418, "addon": "Movies & Series" }, - { "name": "Super Channel 3", "number": 419, "addon": "Movies & Series" }, - { "name": "Super Channel 4", "number": 420, "addon": "Movies & Series" }, - { "name": "Crave 1", "number": 406, "addon": "Movies & Series" }, - { "name": "Crave 2", "number": 431, "addon": "Movies & Series" }, - { "name": "Turner Classic Movies", "number": 462 }, - { "name": "W Movies", "number": 455 } - ], - "Kids": [ - { "name": "BabyTV", "number": 639 }, - { "name": "Cartoon Network", "number": 627 }, - { "name": "Disney Channel", "number": 607 }, - { "name": "Disney Junior", "number": 602 }, - { "name": "Disney XD", "number": 616 }, - { "name": "Family Channel", "number": 604 }, - { "name": "Family Jr", "number": 603 }, - { "name": "Nickelodeon", "number": 614 }, - { "name": "Treehouse", "number": 601 }, - { "name": "TumbleBooks TV", "number": 610 }, - { "name": "YTV", "number": 600 } - ], - "Entertainment": [ - { "name": "A&E", "number": 300 }, - { "name": "AMC", "number": 303 }, - { "name": "CTV Drama Channel", "number": 302 }, - { "name": "DejaView", "number": 506 }, - { "name": "E!", "number": 333 }, - { "name": "Game Show Network", "number": 370 }, - { "name": "Lifetime", "number": 386 }, - { "name": "One", "number": 349 }, - { "name": "OUTtv", "number": 347 }, - { "name": "OWN", "number": 341 }, - { "name": "Showcase", "number": 301 }, - { "name": "CTV Sci-Fi Channel", "number": 392 }, - { "name": "Paramount Network", "number": 390 }, - { "name": "W Network", "number": 329 } - ], - "Reality & Lifestyle": [ - { "name": "Cottage Life", "number": 355 }, - { "name": "DIY Network", "number": 311 }, - { "name": "DTOUR", "number": 504 }, - { "name": "HGTV", "number": 306 }, - { "name": "Slice", "number": 331 } - ], - "Comedy": [ - { "name": "Peachtree TV", "number": 511 }, - { "name": "CTV Comedy Channel", "number": 500 } - ], - "Discovery": [ - { "name": "CTV Wild Channel", "number": 721 }, - { "name": "USA Network", "number": 701 }, - { "name": "CTV Nature Channel", "number": 702 }, - { "name": "CTV Speed Channel", "number": 711 }, - { "name": "Documentary", "number": 731 }, - { "name": "H2", "number": 706 }, - { "name": "History", "number": 704 }, - { "name": "Love Nature", "number": 713 }, - { "name": "National Geographic Channel", "number": 700 }, - { "name": "Nat Geo Wild", "number": 723 }, - { "name": "Smithsonian Channel", "number": 715 } - ], - "Crime": [ - { "name": "Crime+ Investigation", "number": 380 }, - { "name": "Oxygen True Crime", "number": 378 } - ], - "Faith": [ - { "name": "Daystar Canada", "number": 872 }, - { "name": "EWTN", "number": 874 }, - { "name": "Hope TV", "number": 875 }, - { "name": "Miracle Channel", "number": 870 }, - { "name": "Salt + Light", "number": 873 }, - { "name": "Vision TV", "number": 871 } - ], - "Music": [ - { "name": "BET", "number": 585 }, - { "name": "CMT", "number": 555 }, - { "name": "HIFI", "number": 551 }, - { "name": "MTV", "number": 561 }, - { "name": "MTV2", "number": 582 }, - { "name": "Much", "number": 557 }, - { "name": "Stingray Music", "number": "7500-7962" } - ], - "Shopping": [ - { "name": "The Shopping Channel", "number": 129 } - ], - "Spanish": [ - { "name": "Telelatino", "number": 2659, "addon": "Multicultural" }, - { "name": "Univision", "number": 2661, "addon": "Multicultural" } - ], - "Outdoors": [ - { "name": "OLN", "number": 357 }, - { "name": "Wild TV", "number": 359 }, - { "name": "World Fishing Network", "number": 361 } - ], - "Food & Travel": [ - { "name": "Food Network", "number": 313 }, - { "name": "CTV Life Channel", "number": 315 }, - { "name": "T+E", "number": 717 } - ] - } +{ + "package": "TELUS Optik TV Ultimate", + "date": "2026-04-05", + "description": "TELUS Optik TV Ultimate package for Canadian markets (AB/BC) with premium and multicultural add-ons", + "source": "TELUS Optik TV Channel Guide", + "notes": "Lineup excludes local network affiliates (ABC, CBS, NBC, FOX, PBS, CBC, CTV, City, Global, Omni). Regional availability of some channels (e.g., Sportsnet feeds) varies by province (Alberta vs. British Columbia). Premium movie, sports, and multicultural channels require additional subscriptions.", + "categories": { + "News": [ + { "name": "Al Jazeera English", "number": 825 }, + { "name": "BBC World News", "number": 821 }, + { "name": "BNN Bloomberg", "number": 815 }, + { "name": "CBC News Network", "number": 800 }, + { "name": "CCTV News", "number": 834 }, + { "name": "CNBC", "number": 806 }, + { "name": "CNN", "number": 802 }, + { "name": "CNNI", "number": 827 }, + { "name": "CTV News Channel", "number": 801 }, + { "name": "FOX News", "number": 811 }, + { "name": "HLN", "number": 819 }, + { "name": "MSNBC", "number": 804 }, + { "name": "The Weather Network", "number": 128 } + ], + "Sports": [ + { "name": "beIN SPORTS", "number": 983, "addon": "Premium Sports" }, + { "name": "Fight Network", "number": 945 }, + { "name": "FNTSY Sports Network", "number": 921 }, + { "name": "Golf Channel", "number": 922 }, + { "name": "MLB Extra Innings", "number": "1301-1311", "addon": "Premium Sports" }, + { "name": "NBA League Pass", "number": "1401-1409", "addon": "Premium Sports" }, + { "name": "NBA TV Canada", "number": 937 }, + { "name": "NFL Network", "number": 926 }, + { "name": "NFL RedZone", "number": 927 }, + { "name": "NFL Sunday Ticket", "number": "1501-1516", "addon": "Premium Sports" }, + { "name": "NHL Centre Ice", "number": "1101-1114", "addon": "Premium Sports" }, + { "name": "radX", "number": 931 }, + { "name": "Sportsnet 360", "number": 920 }, + { "name": "Sportsnet ONE", "number": 907 }, + { "name": "Sportsnet World", "number": "1001-1007", "addon": "Premium Sports" }, + { "name": "TSN1", "number": 900 }, + { "name": "TSN2", "number": 901 }, + { "name": "TSN3", "number": 902 }, + { "name": "TSN4", "number": 903 }, + { "name": "TSN5", "number": 904 }, + { "name": "WWE Network", "number": 947 } + ], + "Regional Sports": [ + { "name": "Sportsnet East", "number": 917 }, + { "name": "Sportsnet Flames", "number": 910 }, + { "name": "Sportsnet Oilers", "number": 909 }, + { "name": "Sportsnet Ontario", "number": 916 }, + { "name": "Sportsnet Pacific/West", "number": 915 }, + { "name": "Sportsnet Vancouver Hockey", "number": 909 } + ], + "Movies": [ + { "name": "Action", "number": 449 }, + { "name": "CraveTV On Demand", "number": 411, "addon": "Movies & Series" }, + { "name": "FX", "number": 413 }, + { "name": "FXX", "number": 414 }, + { "name": "HBO Canada", "number": 400, "addon": "Movies & Series" }, + { "name": "Hollywood Suite 70", "number": 426, "addon": "Movies & Series" }, + { "name": "Hollywood Suite 80", "number": 427, "addon": "Movies & Series" }, + { "name": "Hollywood Suite 90", "number": 428, "addon": "Movies & Series" }, + { "name": "Hollywood Suite 00", "number": 429, "addon": "Movies & Series" }, + { "name": "MovieTime", "number": 445 }, + { "name": "Rewind", "number": 457 }, + { "name": "Silver Screen Classics", "number": 460 }, + { "name": "SundanceTV", "number": 453 }, + { "name": "Super Channel 1", "number": 417, "addon": "Movies & Series" }, + { "name": "Super Channel 2", "number": 418, "addon": "Movies & Series" }, + { "name": "Super Channel 3", "number": 419, "addon": "Movies & Series" }, + { "name": "Super Channel 4", "number": 420, "addon": "Movies & Series" }, + { "name": "Crave 1", "number": 406, "addon": "Movies & Series" }, + { "name": "Crave 2", "number": 431, "addon": "Movies & Series" }, + { "name": "Turner Classic Movies", "number": 462 }, + { "name": "W Movies", "number": 455 } + ], + "Kids": [ + { "name": "BabyTV", "number": 639 }, + { "name": "Cartoon Network", "number": 627 }, + { "name": "Disney Channel", "number": 607 }, + { "name": "Disney Junior", "number": 602 }, + { "name": "Disney XD", "number": 616 }, + { "name": "Family Channel", "number": 604 }, + { "name": "Family Jr", "number": 603 }, + { "name": "Nickelodeon", "number": 614 }, + { "name": "Treehouse", "number": 601 }, + { "name": "TumbleBooks TV", "number": 610 }, + { "name": "YTV", "number": 600 } + ], + "Entertainment": [ + { "name": "A&E", "number": 300 }, + { "name": "AMC", "number": 303 }, + { "name": "CTV Drama Channel", "number": 302 }, + { "name": "DejaView", "number": 506 }, + { "name": "E!", "number": 333 }, + { "name": "Game Show Network", "number": 370 }, + { "name": "Lifetime", "number": 386 }, + { "name": "One", "number": 349 }, + { "name": "OUTtv", "number": 347 }, + { "name": "OWN", "number": 341 }, + { "name": "Showcase", "number": 301 }, + { "name": "CTV Sci-Fi Channel", "number": 392 }, + { "name": "Paramount Network", "number": 390 }, + { "name": "W Network", "number": 329 } + ], + "Reality & Lifestyle": [ + { "name": "Cottage Life", "number": 355 }, + { "name": "DIY Network", "number": 311 }, + { "name": "DTOUR", "number": 504 }, + { "name": "HGTV", "number": 306 }, + { "name": "Slice", "number": 331 } + ], + "Comedy": [ + { "name": "Peachtree TV", "number": 511 }, + { "name": "CTV Comedy Channel", "number": 500 } + ], + "Discovery": [ + { "name": "CTV Wild Channel", "number": 721 }, + { "name": "USA Network", "number": 701 }, + { "name": "CTV Nature Channel", "number": 702 }, + { "name": "CTV Speed Channel", "number": 711 }, + { "name": "Documentary", "number": 731 }, + { "name": "H2", "number": 706 }, + { "name": "History", "number": 704 }, + { "name": "Love Nature", "number": 713 }, + { "name": "National Geographic Channel", "number": 700 }, + { "name": "Nat Geo Wild", "number": 723 }, + { "name": "Smithsonian Channel", "number": 715 } + ], + "Crime": [ + { "name": "Crime+ Investigation", "number": 380 }, + { "name": "Oxygen True Crime", "number": 378 } + ], + "Faith": [ + { "name": "Daystar Canada", "number": 872 }, + { "name": "EWTN", "number": 874 }, + { "name": "Hope TV", "number": 875 }, + { "name": "Miracle Channel", "number": 870 }, + { "name": "Salt + Light", "number": 873 }, + { "name": "Vision TV", "number": 871 } + ], + "Music": [ + { "name": "BET", "number": 585 }, + { "name": "CMT", "number": 555 }, + { "name": "HIFI", "number": 551 }, + { "name": "MTV", "number": 561 }, + { "name": "MTV2", "number": 582 }, + { "name": "Much", "number": 557 }, + { "name": "Stingray Music", "number": "7500-7962" } + ], + "Shopping": [ + { "name": "The Shopping Channel", "number": 129 } + ], + "Spanish": [ + { "name": "Telelatino", "number": 2659, "addon": "Multicultural" }, + { "name": "Univision", "number": 2661, "addon": "Multicultural" } + ], + "Outdoors": [ + { "name": "OLN", "number": 357 }, + { "name": "Wild TV", "number": 359 }, + { "name": "World Fishing Network", "number": 361 } + ], + "Food & Travel": [ + { "name": "Food Network", "number": 313 }, + { "name": "CTV Life Channel", "number": 315 }, + { "name": "T+E", "number": 717 } + ] + } } \ No newline at end of file diff --git a/plugins/lineuparr/NL_ODIDO_lineup.json b/plugins/lineuparr/NL_ODIDO_lineup.json index a0079cc..e5296df 100644 --- a/plugins/lineuparr/NL_ODIDO_lineup.json +++ b/plugins/lineuparr/NL_ODIDO_lineup.json @@ -1,191 +1,191 @@ -{ - "package": "ODIDO Dispatcharr", - "date": "2026-04-05", - "description": "ODIDO Netherlands lineup with regional Dutch and international channels", - "source": "ODIDO Channel Lineup", - "notes": "Channel numbers were not provided in the source data. Local and regional channels have been categorized based on their primary content; most regional Dutch channels are placed under Entertainment as they provide general interest and local programming.", - "categories": { - "News": [ - { "name": "NPO Politiek en Nieuws", "number": 0 }, - { "name": "RTL Z", "number": 0 }, - { "name": "BBC News", "number": 0 }, - { "name": "BBC World News", "number": 0 }, - { "name": "CNN", "number": 0 }, - { "name": "CNN Turk", "number": 0 }, - { "name": "Euronews", "number": 0 }, - { "name": "France24 (English)", "number": 0 }, - { "name": "Al Jazeera (English)", "number": 0 }, - { "name": "Tagesschau24", "number": 0 } - ], - "Sports": [ - { "name": "Viaplay TV", "number": 0 } - ], - "Regional Sports": [], - "Movies": [ - { "name": "Film1 Première", "number": 0 }, - { "name": "Film1 Action", "number": 0 }, - { "name": "Film1 Drama", "number": 0 }, - { "name": "Film1 Family", "number": 0 } - ], - "Kids": [ - { "name": "RTL Telekids", "number": 0 }, - { "name": "BabyTV", "number": 0 }, - { "name": "Cartoon Network", "number": 0 }, - { "name": "Cartoonito", "number": 0 }, - { "name": "CBBC", "number": 0 }, - { "name": "Disney Channel", "number": 0 }, - { "name": "Dreamworks", "number": 0 }, - { "name": "Duck TV", "number": 0 }, - { "name": "Ketnet", "number": 0 }, - { "name": "Kika", "number": 0 }, - { "name": "Nick Jr.", "number": 0 }, - { "name": "Nick Music", "number": 0 }, - { "name": "Nick Toons", "number": 0 }, - { "name": "Nickelodeon", "number": 0 } - ], - "Entertainment": [ - { "name": "NPO 1", "number": 0 }, - { "name": "NPO 2", "number": 0 }, - { "name": "NPO 3", "number": 0 }, - { "name": "NPO 1 Extra", "number": 0 }, - { "name": "NPO 2 Extra", "number": 0 }, - { "name": "RTL 4", "number": 0 }, - { "name": "RTL 5", "number": 0 }, - { "name": "RTL 7", "number": 0 }, - { "name": "RTL 8", "number": 0 }, - { "name": "RTL Television", "number": 0 }, - { "name": "SBS 6", "number": 0 }, - { "name": "NET 5", "number": 0 }, - { "name": "Veronica / Disney Junior", "number": 0 }, - { "name": "ONS", "number": 0 }, - { "name": "OUT TV", "number": 0 }, - { "name": "Paramount Network", "number": 0 }, - { "name": "Pebble TV", "number": 0 }, - { "name": "STAR Channel", "number": 0 }, - { "name": "GINX", "number": 0 }, - { "name": "Shorts TV", "number": 0 }, - { "name": "1Twente", "number": 0 }, - { "name": "A1 Regio TV", "number": 0 }, - { "name": "AT5", "number": 0 }, - { "name": "Bo", "number": 0 }, - { "name": "BredaNu", "number": 0 }, - { "name": "DTV-OSS", "number": 0 }, - { "name": "HAARLEM105-tv", "number": 0 }, - { "name": "INPLUS", "number": 0 }, - { "name": "KempenTV", "number": 0 }, - { "name": "L1 TV", "number": 0 }, - { "name": "Langstraat TV", "number": 0 }, - { "name": "Lelystad TV", "number": 0 }, - { "name": "LINQ TV", "number": 0 }, - { "name": "MeerTelevisie", "number": 0 }, - { "name": "NH", "number": 0 }, - { "name": "Omroep Brabant", "number": 0 }, - { "name": "Omroep Flevoland TV", "number": 0 }, - { "name": "Omrop Fryslan", "number": 0 }, - { "name": "Omroep Meierij TV", "number": 0 }, - { "name": "Omroep Zeeland", "number": 0 }, - { "name": "ONE", "number": 0 }, - { "name": "Oost", "number": 0 }, - { "name": "Open Rotterdam", "number": 0 }, - { "name": "ORTS", "number": 0 }, - { "name": "Regio TV Utrecht", "number": 0 }, - { "name": "RN7", "number": 0 }, - { "name": "RTV Connect", "number": 0 }, - { "name": "RTV Dordrecht", "number": 0 }, - { "name": "RTV Drenthe", "number": 0 }, - { "name": "RTV Elburg", "number": 0 }, - { "name": "RTV Lansingerland Berkel en Rodenrijs", "number": 0 }, - { "name": "RTV Maastricht", "number": 0 }, - { "name": "RTV-7", "number": 0 }, - { "name": "Salto 1 (Amsterdam)", "number": 0 }, - { "name": "SIRIS TV", "number": 0 }, - { "name": "SOL2", "number": 0 }, - { "name": "Studio040 TV", "number": 0 }, - { "name": "TV Gelderland", "number": 0 }, - { "name": "TV Hoeksche Waard", "number": 0 }, - { "name": "TV Noord", "number": 0 }, - { "name": "TV Rijnmond", "number": 0 }, - { "name": "TV West", "number": 0 }, - { "name": "VOS TV", "number": 0 }, - { "name": "WOS TV", "number": 0 }, - { "name": "ZFMInfobox", "number": 0 }, - { "name": "ZO!34", "number": 0 }, - { "name": "BBC NL", "number": 0 }, - { "name": "BBC One", "number": 0 }, - { "name": "BBC Two", "number": 0 }, - { "name": "VRT Canvas", "number": 0 }, - { "name": "TWEE", "number": 0 }, - { "name": "VRT1", "number": 0 }, - { "name": "ARTE", "number": 0 }, - { "name": "Das Erste (ARD)", "number": 0 }, - { "name": "NDR", "number": 0 }, - { "name": "ProSieben", "number": 0 }, - { "name": "Sat.1", "number": 0 }, - { "name": "WDR", "number": 0 }, - { "name": "ZDF", "number": 0 }, - { "name": "3Sat", "number": 0 }, - { "name": "TV5MONDE", "number": 0 }, - { "name": "RAI Uno", "number": 0 }, - { "name": "Mediaset Italia", "number": 0 }, - { "name": "2M Maroc", "number": 0 } - ], - "Reality & Lifestyle": [ - { "name": "RTL Lounge", "number": 0 }, - { "name": "E! Entertainment", "number": 0 }, - { "name": "Fashion TV", "number": 0 }, - { "name": "LUXE.tv", "number": 0 }, - { "name": "HGTV", "number": 0 }, - { "name": "Horse & Country TV", "number": 0 }, - { "name": "MyZen TV", "number": 0 }, - { "name": "TLC", "number": 0 } - ], - "Comedy": [ - { "name": "Comedy Central", "number": 0 } - ], - "Discovery": [ - { "name": "Animal Planet", "number": 0 }, - { "name": "Discovery", "number": 0 }, - { "name": "Discovery Science", "number": 0 }, - { "name": "History Channel", "number": 0 }, - { "name": "Nat Geo Wild", "number": 0 }, - { "name": "National Geographic", "number": 0 }, - { "name": "Museum TV", "number": 0 } - ], - "Crime": [ - { "name": "RTL Crime", "number": 0 }, - { "name": "Crime+Investigation", "number": 0 }, - { "name": "ID", "number": 0 } - ], - "Faith": [ - { "name": "Family 7", "number": 0 } - ], - "Music": [ - { "name": "Mezzo", "number": 0 }, - { "name": "MTV", "number": 0 }, - { "name": "MTV 00s", "number": 0 }, - { "name": "MTV 80s", "number": 0 }, - { "name": "MTV 90s", "number": 0 }, - { "name": "MTV Hits", "number": 0 }, - { "name": "MTV Live", "number": 0 }, - { "name": "SchlagerTV", "number": 0 }, - { "name": "SLAM!TV", "number": 0 }, - { "name": "Stingray Classica", "number": 0 }, - { "name": "Stingray Djazz", "number": 0 }, - { "name": "Stingray I-Concerts", "number": 0 }, - { "name": "Stingray Lite TV", "number": 0 }, - { "name": "Trace Urban", "number": 0 }, - { "name": "TV Oranje", "number": 0 }, - { "name": "TV538", "number": 0 }, - { "name": "XITE", "number": 0 } - ], - "Shopping": [], - "Spanish": [ - { "name": "TVE", "number": 0 }, - { "name": "Antena 3", "number": 0 } - ], - "Outdoors": [], - "Food & Travel": [ - { "name": "24Kitchen", "number": 0 } - ] - } +{ + "package": "ODIDO Dispatcharr", + "date": "2026-04-05", + "description": "ODIDO Netherlands lineup with regional Dutch and international channels", + "source": "ODIDO Channel Lineup", + "notes": "Channel numbers were not provided in the source data. Local and regional channels have been categorized based on their primary content; most regional Dutch channels are placed under Entertainment as they provide general interest and local programming.", + "categories": { + "News": [ + { "name": "NPO Politiek en Nieuws", "number": 0 }, + { "name": "RTL Z", "number": 0 }, + { "name": "BBC News", "number": 0 }, + { "name": "BBC World News", "number": 0 }, + { "name": "CNN", "number": 0 }, + { "name": "CNN Turk", "number": 0 }, + { "name": "Euronews", "number": 0 }, + { "name": "France24 (English)", "number": 0 }, + { "name": "Al Jazeera (English)", "number": 0 }, + { "name": "Tagesschau24", "number": 0 } + ], + "Sports": [ + { "name": "Viaplay TV", "number": 0 } + ], + "Regional Sports": [], + "Movies": [ + { "name": "Film1 Première", "number": 0 }, + { "name": "Film1 Action", "number": 0 }, + { "name": "Film1 Drama", "number": 0 }, + { "name": "Film1 Family", "number": 0 } + ], + "Kids": [ + { "name": "RTL Telekids", "number": 0 }, + { "name": "BabyTV", "number": 0 }, + { "name": "Cartoon Network", "number": 0 }, + { "name": "Cartoonito", "number": 0 }, + { "name": "CBBC", "number": 0 }, + { "name": "Disney Channel", "number": 0 }, + { "name": "Dreamworks", "number": 0 }, + { "name": "Duck TV", "number": 0 }, + { "name": "Ketnet", "number": 0 }, + { "name": "Kika", "number": 0 }, + { "name": "Nick Jr.", "number": 0 }, + { "name": "Nick Music", "number": 0 }, + { "name": "Nick Toons", "number": 0 }, + { "name": "Nickelodeon", "number": 0 } + ], + "Entertainment": [ + { "name": "NPO 1", "number": 0 }, + { "name": "NPO 2", "number": 0 }, + { "name": "NPO 3", "number": 0 }, + { "name": "NPO 1 Extra", "number": 0 }, + { "name": "NPO 2 Extra", "number": 0 }, + { "name": "RTL 4", "number": 0 }, + { "name": "RTL 5", "number": 0 }, + { "name": "RTL 7", "number": 0 }, + { "name": "RTL 8", "number": 0 }, + { "name": "RTL Television", "number": 0 }, + { "name": "SBS 6", "number": 0 }, + { "name": "NET 5", "number": 0 }, + { "name": "Veronica / Disney Junior", "number": 0 }, + { "name": "ONS", "number": 0 }, + { "name": "OUT TV", "number": 0 }, + { "name": "Paramount Network", "number": 0 }, + { "name": "Pebble TV", "number": 0 }, + { "name": "STAR Channel", "number": 0 }, + { "name": "GINX", "number": 0 }, + { "name": "Shorts TV", "number": 0 }, + { "name": "1Twente", "number": 0 }, + { "name": "A1 Regio TV", "number": 0 }, + { "name": "AT5", "number": 0 }, + { "name": "Bo", "number": 0 }, + { "name": "BredaNu", "number": 0 }, + { "name": "DTV-OSS", "number": 0 }, + { "name": "HAARLEM105-tv", "number": 0 }, + { "name": "INPLUS", "number": 0 }, + { "name": "KempenTV", "number": 0 }, + { "name": "L1 TV", "number": 0 }, + { "name": "Langstraat TV", "number": 0 }, + { "name": "Lelystad TV", "number": 0 }, + { "name": "LINQ TV", "number": 0 }, + { "name": "MeerTelevisie", "number": 0 }, + { "name": "NH", "number": 0 }, + { "name": "Omroep Brabant", "number": 0 }, + { "name": "Omroep Flevoland TV", "number": 0 }, + { "name": "Omrop Fryslan", "number": 0 }, + { "name": "Omroep Meierij TV", "number": 0 }, + { "name": "Omroep Zeeland", "number": 0 }, + { "name": "ONE", "number": 0 }, + { "name": "Oost", "number": 0 }, + { "name": "Open Rotterdam", "number": 0 }, + { "name": "ORTS", "number": 0 }, + { "name": "Regio TV Utrecht", "number": 0 }, + { "name": "RN7", "number": 0 }, + { "name": "RTV Connect", "number": 0 }, + { "name": "RTV Dordrecht", "number": 0 }, + { "name": "RTV Drenthe", "number": 0 }, + { "name": "RTV Elburg", "number": 0 }, + { "name": "RTV Lansingerland Berkel en Rodenrijs", "number": 0 }, + { "name": "RTV Maastricht", "number": 0 }, + { "name": "RTV-7", "number": 0 }, + { "name": "Salto 1 (Amsterdam)", "number": 0 }, + { "name": "SIRIS TV", "number": 0 }, + { "name": "SOL2", "number": 0 }, + { "name": "Studio040 TV", "number": 0 }, + { "name": "TV Gelderland", "number": 0 }, + { "name": "TV Hoeksche Waard", "number": 0 }, + { "name": "TV Noord", "number": 0 }, + { "name": "TV Rijnmond", "number": 0 }, + { "name": "TV West", "number": 0 }, + { "name": "VOS TV", "number": 0 }, + { "name": "WOS TV", "number": 0 }, + { "name": "ZFMInfobox", "number": 0 }, + { "name": "ZO!34", "number": 0 }, + { "name": "BBC NL", "number": 0 }, + { "name": "BBC One", "number": 0 }, + { "name": "BBC Two", "number": 0 }, + { "name": "VRT Canvas", "number": 0 }, + { "name": "TWEE", "number": 0 }, + { "name": "VRT1", "number": 0 }, + { "name": "ARTE", "number": 0 }, + { "name": "Das Erste (ARD)", "number": 0 }, + { "name": "NDR", "number": 0 }, + { "name": "ProSieben", "number": 0 }, + { "name": "Sat.1", "number": 0 }, + { "name": "WDR", "number": 0 }, + { "name": "ZDF", "number": 0 }, + { "name": "3Sat", "number": 0 }, + { "name": "TV5MONDE", "number": 0 }, + { "name": "RAI Uno", "number": 0 }, + { "name": "Mediaset Italia", "number": 0 }, + { "name": "2M Maroc", "number": 0 } + ], + "Reality & Lifestyle": [ + { "name": "RTL Lounge", "number": 0 }, + { "name": "E! Entertainment", "number": 0 }, + { "name": "Fashion TV", "number": 0 }, + { "name": "LUXE.tv", "number": 0 }, + { "name": "HGTV", "number": 0 }, + { "name": "Horse & Country TV", "number": 0 }, + { "name": "MyZen TV", "number": 0 }, + { "name": "TLC", "number": 0 } + ], + "Comedy": [ + { "name": "Comedy Central", "number": 0 } + ], + "Discovery": [ + { "name": "Animal Planet", "number": 0 }, + { "name": "Discovery", "number": 0 }, + { "name": "Discovery Science", "number": 0 }, + { "name": "History Channel", "number": 0 }, + { "name": "Nat Geo Wild", "number": 0 }, + { "name": "National Geographic", "number": 0 }, + { "name": "Museum TV", "number": 0 } + ], + "Crime": [ + { "name": "RTL Crime", "number": 0 }, + { "name": "Crime+Investigation", "number": 0 }, + { "name": "ID", "number": 0 } + ], + "Faith": [ + { "name": "Family 7", "number": 0 } + ], + "Music": [ + { "name": "Mezzo", "number": 0 }, + { "name": "MTV", "number": 0 }, + { "name": "MTV 00s", "number": 0 }, + { "name": "MTV 80s", "number": 0 }, + { "name": "MTV 90s", "number": 0 }, + { "name": "MTV Hits", "number": 0 }, + { "name": "MTV Live", "number": 0 }, + { "name": "SchlagerTV", "number": 0 }, + { "name": "SLAM!TV", "number": 0 }, + { "name": "Stingray Classica", "number": 0 }, + { "name": "Stingray Djazz", "number": 0 }, + { "name": "Stingray I-Concerts", "number": 0 }, + { "name": "Stingray Lite TV", "number": 0 }, + { "name": "Trace Urban", "number": 0 }, + { "name": "TV Oranje", "number": 0 }, + { "name": "TV538", "number": 0 }, + { "name": "XITE", "number": 0 } + ], + "Shopping": [], + "Spanish": [ + { "name": "TVE", "number": 0 }, + { "name": "Antena 3", "number": 0 } + ], + "Outdoors": [], + "Food & Travel": [ + { "name": "24Kitchen", "number": 0 } + ] + } } \ No newline at end of file diff --git a/plugins/lineuparr/UK_Freeview_lineup.json b/plugins/lineuparr/UK_Freeview_lineup.json index 9b5884f..920de0c 100644 --- a/plugins/lineuparr/UK_Freeview_lineup.json +++ b/plugins/lineuparr/UK_Freeview_lineup.json @@ -1,667 +1,667 @@ -{ - "package": "Freeview UK", - "date": "2026-05-20", - "description": "Freeview UK channel lineup based with data from freeview.co.uk", - "source": "Freeview", - "notes": "Categories are derived from the Freeview LCN specification.", - "categories": { - "Entertainment": [ - { - "name": "BBC One", - "number": 1 - }, - { - "name": "BBC Two", - "number": 2 - }, - { - "name": "ITV", - "number": 3 - }, - { - "name": "Channel 4", - "number": 4 - }, - { - "name": "5", - "number": 5 - }, - { - "name": "ITV2", - "number": 6 - }, - { - "name": "London TV", - "number": 8 - }, - { - "name": "BBC Four", - "number": 9 - }, - { - "name": "ITV3", - "number": 10 - }, - { - "name": "Sky Mix", - "number": 11 - }, - { - "name": "HGTV", - "number": 12 - }, - { - "name": "E4", - "number": 13 - }, - { - "name": "Film4", - "number": 14 - }, - { - "name": "Channel 4 +1", - "number": 15 - }, - { - "name": "QVC", - "number": 16 - }, - { - "name": "QUEST", - "number": 17 - }, - { - "name": "More4", - "number": 18 - }, - { - "name": "Dave", - "number": 19 - }, - { - "name": "Drama", - "number": 20 - }, - { - "name": "5USA", - "number": 21 - }, - { - "name": "TJC", - "number": 22 - }, - { - "name": "BBC Three", - "number": 23 - }, - { - "name": "W", - "number": 25 - }, - { - "name": "ITV4", - "number": 26 - }, - { - "name": "Yesterday", - "number": 27 - }, - { - "name": "ITVBe", - "number": 28 - }, - { - "name": "ITV2 +1", - "number": 29 - }, - { - "name": "E4 +1", - "number": 30 - }, - { - "name": "E4 Extra", - "number": 31 - }, - { - "name": "5STAR", - "number": 32 - }, - { - "name": "5ACTION", - "number": 33 - }, - { - "name": "GREAT! tv", - "number": 34 - }, - { - "name": "ITV +1", - "number": 35 - }, - { - "name": "Sky Arts", - "number": 36 - }, - { - "name": "QVC Beauty", - "number": 37 - }, - { - "name": "5 +1", - "number": 38 - }, - { - "name": "DMAX", - "number": 39 - }, - { - "name": "Quest Red", - "number": 40 - }, - { - "name": "LEGEND", - "number": 41 - }, - { - "name": "Great! Action", - "number": 42 - }, - { - "name": "Food Network", - "number": 43 - }, - { - "name": "Really", - "number": 44 - }, - { - "name": "Gems TV", - "number": 45 - }, - { - "name": "5SELECT", - "number": 46 - }, - { - "name": "Film4 +1", - "number": 47 - }, - { - "name": "Challenge", - "number": 48 - }, - { - "name": "4seven", - "number": 49 - }, - { - "name": "Great! Mystery", - "number": 50 - }, - { - "name": "Ideal World", - "number": 51 - }, - { - "name": "Great! Christmas", - "number": 52 - }, - { - "name": "That's TV (UK)", - "number": 56 - }, - { - "name": "U&Eden", - "number": 57 - }, - { - "name": "ITV3 +1", - "number": 58 - }, - { - "name": "ITV4 +1", - "number": 59 - }, - { - "name": "Drama +1", - "number": 60 - }, - { - "name": "Great! TV +1", - "number": 61 - }, - { - "name": "Great! Movies", - "number": 62 - }, - { - "name": "Great! Player", - "number": 63 - }, - { - "name": "Blaze", - "number": 64 - }, - { - "name": "That's TV 2", - "number": 65 - }, - { - "name": "TBN UK", - "number": 66 - }, - { - "name": "TRUE CRIME", - "number": 67 - }, - { - "name": "TRUE CRIME XTRA", - "number": 68 - }, - { - "name": "LEGEND XTRA", - "number": 69 - }, - { - "name": "QUEST +1", - "number": 70 - }, - { - "name": "TCC", - "number": 71 - }, - { - "name": "Jewellery Maker", - "number": 72 - }, - { - "name": "HobbyMaker", - "number": 73 - }, - { - "name": "Dave ja vu", - "number": 74 - }, - { - "name": "That's 60s", - "number": 76 - }, - { - "name": "That's 70s", - "number": 78 - }, - { - "name": "Rewind TV", - "number": 81 - }, - { - "name": "TalkingPictures TV", - "number": 82 - }, - { - "name": "Together TV", - "number": 83 - }, - { - "name": "PBS America", - "number": 84 - }, - { - "name": "Shop On TV", - "number": 89 - }, - { - "name": "Together TV+1", - "number": 90 - }, - { - "name": "WildEarth", - "number": 91 - }, - { - "name": "Blaze +1", - "number": 92 - }, - { - "name": "SonLife", - "number": 94 - }, - { - "name": "High Street TV", - "number": 95 - }, - { - "name": "Must Have Ideas", - "number": 96 - }, - { - "name": "Shop Unlimited", - "number": 97 - }, - { - "name": "wedotv Movies UK", - "number": 98 - }, - { - "name": "TLC+1", - "number": 99 - } - ], - "HD": [ - { - "name": "BBC One HD", - "number": 101 - }, - { - "name": "BBC Two HD", - "number": 102 - }, - { - "name": "ITV HD", - "number": 103 - }, - { - "name": "Channel 4 HD", - "number": 104 - }, - { - "name": "5 HD", - "number": 105 - }, - { - "name": "BBC Four HD", - "number": 106 - }, - { - "name": "BBC Three HD", - "number": 107 - } - ], - "Kids": [ - { - "name": "CBBC", - "number": 201 - }, - { - "name": "CBeebies", - "number": 202 - }, - { - "name": "CBBC HD", - "number": 203 - }, - { - "name": "CBeebies HD", - "number": 204 - }, - { - "name": "POP", - "number": 205 - }, - { - "name": "Tiny Pop", - "number": 206 - }, - { - "name": "Pop Up", - "number": 207 - }, - { - "name": "Ketchup TV", - "number": 209 - }, - { - "name": "Ketchup Too", - "number": 210 - }, - { - "name": "Cartoon Classics", - "number": 214 - } - ], - "News": [ - { - "name": "BBC News", - "number": 231 - }, - { - "name": "BBC Parliament", - "number": 232 - }, - { - "name": "Sky News", - "number": 233 - }, - { - "name": "GB News", - "number": 236 - } - ], - "Hybrid Streamed and Text": [ - { - "name": "BBC Red Button", - "number": 250 - }, - { - "name": "Al Jazeera English", - "number": 251 - }, - { - "name": "Al Jazeera Arabic", - "number": 252 - }, - { - "name": "Trailblazer", - "number": 253 - }, - { - "name": "On Demand 365", - "number": 254 - }, - { - "name": "FRANCE 24", - "number": 255 - }, - { - "name": "Odyssey TV", - "number": 256 - }, - { - "name": "EuroNews", - "number": 257 - }, - { - "name": "MBC", - "number": 259 - }, - { - "name": "ASHARQ NEWS", - "number": 260 - }, - { - "name": "AL ARABIYA", - "number": 261 - }, - { - "name": "Sonlife", - "number": 263 - }, - { - "name": "Alaraby Network", - "number": 264 - }, - { - "name": "ROK", - "number": 265 - }, - { - "name": "Revelation TV", - "number": 266 - }, - { - "name": "It Is Written TV", - "number": 268 - }, - { - "name": "New Media TV", - "number": 269 - }, - { - "name": "Together TV IP", - "number": 270 - }, - { - "name": "Channelbox", - "number": 271 - }, - { - "name": "NHK WORLD", - "number": 272 - }, - { - "name": "Newsmax", - "number": 273 - }, - { - "name": "Amazing Facts", - "number": 274 - }, - { - "name": "wedotv Big Stories", - "number": 275 - }, - { - "name": "CNA Originals", - "number": 276 - }, - { - "name": "Nosey", - "number": 278 - }, - { - "name": "Purpose Media", - "number": 279 - }, - { - "name": "Talk", - "number": 280 - }, - { - "name": "Global Arabic +", - "number": 283 - }, - { - "name": "GIGS", - "number": 284 - }, - { - "name": "Nolly Africa", - "number": 287 - }, - { - "name": "OUTflix Proud", - "number": 288 - }, - { - "name": "MBC Group", - "number": 289 - }, - { - "name": "Music & Memories", - "number": 290 - }, - { - "name": "Outdoor Channel", - "number": 291 - } - ], - "Interactive Services": [ - { - "name": "BBC Red Button", - "number": 601 - } - ], - "Radio": [ - { - "name": "BBC Radio 1", - "number": 700 - }, - { - "name": "BBC Radio 1Xtra", - "number": 701 - }, - { - "name": "BBC Radio 2", - "number": 702 - }, - { - "name": "BBC Radio 3", - "number": 703 - }, - { - "name": "BBC Radio 4", - "number": 704 - }, - { - "name": "BBC Radio 5 Live", - "number": 705 - }, - { - "name": "BBC Radio 5 Sports Extra", - "number": 706 - }, - { - "name": "BBC Radio 6 Music", - "number": 707 - }, - { - "name": "BBC Radio 4 Extra", - "number": 708 - }, - { - "name": "BBC Asian Network", - "number": 709 - }, - { - "name": "BBC World Service", - "number": 710 - }, - { - "name": "BBC Local Radio", - "number": 711 - }, - { - "name": "Smooth Radio", - "number": 718 - }, - { - "name": "talkSPORT", - "number": 723 - }, - { - "name": "Capital", - "number": 724 - }, - { - "name": "Premier Radio", - "number": 725 - }, - { - "name": "Heart", - "number": 728 - }, - { - "name": "RNIB Connect", - "number": 730 - }, - { - "name": "Classic FM", - "number": 731 - }, - { - "name": "LBC", - "number": 732 - } - ] - } +{ + "package": "Freeview UK", + "date": "2026-05-20", + "description": "Freeview UK channel lineup based with data from freeview.co.uk", + "source": "Freeview", + "notes": "Categories are derived from the Freeview LCN specification.", + "categories": { + "Entertainment": [ + { + "name": "BBC One", + "number": 1 + }, + { + "name": "BBC Two", + "number": 2 + }, + { + "name": "ITV", + "number": 3 + }, + { + "name": "Channel 4", + "number": 4 + }, + { + "name": "5", + "number": 5 + }, + { + "name": "ITV2", + "number": 6 + }, + { + "name": "London TV", + "number": 8 + }, + { + "name": "BBC Four", + "number": 9 + }, + { + "name": "ITV3", + "number": 10 + }, + { + "name": "Sky Mix", + "number": 11 + }, + { + "name": "HGTV", + "number": 12 + }, + { + "name": "E4", + "number": 13 + }, + { + "name": "Film4", + "number": 14 + }, + { + "name": "Channel 4 +1", + "number": 15 + }, + { + "name": "QVC", + "number": 16 + }, + { + "name": "QUEST", + "number": 17 + }, + { + "name": "More4", + "number": 18 + }, + { + "name": "Dave", + "number": 19 + }, + { + "name": "Drama", + "number": 20 + }, + { + "name": "5USA", + "number": 21 + }, + { + "name": "TJC", + "number": 22 + }, + { + "name": "BBC Three", + "number": 23 + }, + { + "name": "W", + "number": 25 + }, + { + "name": "ITV4", + "number": 26 + }, + { + "name": "Yesterday", + "number": 27 + }, + { + "name": "ITVBe", + "number": 28 + }, + { + "name": "ITV2 +1", + "number": 29 + }, + { + "name": "E4 +1", + "number": 30 + }, + { + "name": "E4 Extra", + "number": 31 + }, + { + "name": "5STAR", + "number": 32 + }, + { + "name": "5ACTION", + "number": 33 + }, + { + "name": "GREAT! tv", + "number": 34 + }, + { + "name": "ITV +1", + "number": 35 + }, + { + "name": "Sky Arts", + "number": 36 + }, + { + "name": "QVC Beauty", + "number": 37 + }, + { + "name": "5 +1", + "number": 38 + }, + { + "name": "DMAX", + "number": 39 + }, + { + "name": "Quest Red", + "number": 40 + }, + { + "name": "LEGEND", + "number": 41 + }, + { + "name": "Great! Action", + "number": 42 + }, + { + "name": "Food Network", + "number": 43 + }, + { + "name": "Really", + "number": 44 + }, + { + "name": "Gems TV", + "number": 45 + }, + { + "name": "5SELECT", + "number": 46 + }, + { + "name": "Film4 +1", + "number": 47 + }, + { + "name": "Challenge", + "number": 48 + }, + { + "name": "4seven", + "number": 49 + }, + { + "name": "Great! Mystery", + "number": 50 + }, + { + "name": "Ideal World", + "number": 51 + }, + { + "name": "Great! Christmas", + "number": 52 + }, + { + "name": "That's TV (UK)", + "number": 56 + }, + { + "name": "U&Eden", + "number": 57 + }, + { + "name": "ITV3 +1", + "number": 58 + }, + { + "name": "ITV4 +1", + "number": 59 + }, + { + "name": "Drama +1", + "number": 60 + }, + { + "name": "Great! TV +1", + "number": 61 + }, + { + "name": "Great! Movies", + "number": 62 + }, + { + "name": "Great! Player", + "number": 63 + }, + { + "name": "Blaze", + "number": 64 + }, + { + "name": "That's TV 2", + "number": 65 + }, + { + "name": "TBN UK", + "number": 66 + }, + { + "name": "TRUE CRIME", + "number": 67 + }, + { + "name": "TRUE CRIME XTRA", + "number": 68 + }, + { + "name": "LEGEND XTRA", + "number": 69 + }, + { + "name": "QUEST +1", + "number": 70 + }, + { + "name": "TCC", + "number": 71 + }, + { + "name": "Jewellery Maker", + "number": 72 + }, + { + "name": "HobbyMaker", + "number": 73 + }, + { + "name": "Dave ja vu", + "number": 74 + }, + { + "name": "That's 60s", + "number": 76 + }, + { + "name": "That's 70s", + "number": 78 + }, + { + "name": "Rewind TV", + "number": 81 + }, + { + "name": "TalkingPictures TV", + "number": 82 + }, + { + "name": "Together TV", + "number": 83 + }, + { + "name": "PBS America", + "number": 84 + }, + { + "name": "Shop On TV", + "number": 89 + }, + { + "name": "Together TV+1", + "number": 90 + }, + { + "name": "WildEarth", + "number": 91 + }, + { + "name": "Blaze +1", + "number": 92 + }, + { + "name": "SonLife", + "number": 94 + }, + { + "name": "High Street TV", + "number": 95 + }, + { + "name": "Must Have Ideas", + "number": 96 + }, + { + "name": "Shop Unlimited", + "number": 97 + }, + { + "name": "wedotv Movies UK", + "number": 98 + }, + { + "name": "TLC+1", + "number": 99 + } + ], + "HD": [ + { + "name": "BBC One HD", + "number": 101 + }, + { + "name": "BBC Two HD", + "number": 102 + }, + { + "name": "ITV HD", + "number": 103 + }, + { + "name": "Channel 4 HD", + "number": 104 + }, + { + "name": "5 HD", + "number": 105 + }, + { + "name": "BBC Four HD", + "number": 106 + }, + { + "name": "BBC Three HD", + "number": 107 + } + ], + "Kids": [ + { + "name": "CBBC", + "number": 201 + }, + { + "name": "CBeebies", + "number": 202 + }, + { + "name": "CBBC HD", + "number": 203 + }, + { + "name": "CBeebies HD", + "number": 204 + }, + { + "name": "POP", + "number": 205 + }, + { + "name": "Tiny Pop", + "number": 206 + }, + { + "name": "Pop Up", + "number": 207 + }, + { + "name": "Ketchup TV", + "number": 209 + }, + { + "name": "Ketchup Too", + "number": 210 + }, + { + "name": "Cartoon Classics", + "number": 214 + } + ], + "News": [ + { + "name": "BBC News", + "number": 231 + }, + { + "name": "BBC Parliament", + "number": 232 + }, + { + "name": "Sky News", + "number": 233 + }, + { + "name": "GB News", + "number": 236 + } + ], + "Hybrid Streamed and Text": [ + { + "name": "BBC Red Button", + "number": 250 + }, + { + "name": "Al Jazeera English", + "number": 251 + }, + { + "name": "Al Jazeera Arabic", + "number": 252 + }, + { + "name": "Trailblazer", + "number": 253 + }, + { + "name": "On Demand 365", + "number": 254 + }, + { + "name": "FRANCE 24", + "number": 255 + }, + { + "name": "Odyssey TV", + "number": 256 + }, + { + "name": "EuroNews", + "number": 257 + }, + { + "name": "MBC", + "number": 259 + }, + { + "name": "ASHARQ NEWS", + "number": 260 + }, + { + "name": "AL ARABIYA", + "number": 261 + }, + { + "name": "Sonlife", + "number": 263 + }, + { + "name": "Alaraby Network", + "number": 264 + }, + { + "name": "ROK", + "number": 265 + }, + { + "name": "Revelation TV", + "number": 266 + }, + { + "name": "It Is Written TV", + "number": 268 + }, + { + "name": "New Media TV", + "number": 269 + }, + { + "name": "Together TV IP", + "number": 270 + }, + { + "name": "Channelbox", + "number": 271 + }, + { + "name": "NHK WORLD", + "number": 272 + }, + { + "name": "Newsmax", + "number": 273 + }, + { + "name": "Amazing Facts", + "number": 274 + }, + { + "name": "wedotv Big Stories", + "number": 275 + }, + { + "name": "CNA Originals", + "number": 276 + }, + { + "name": "Nosey", + "number": 278 + }, + { + "name": "Purpose Media", + "number": 279 + }, + { + "name": "Talk", + "number": 280 + }, + { + "name": "Global Arabic +", + "number": 283 + }, + { + "name": "GIGS", + "number": 284 + }, + { + "name": "Nolly Africa", + "number": 287 + }, + { + "name": "OUTflix Proud", + "number": 288 + }, + { + "name": "MBC Group", + "number": 289 + }, + { + "name": "Music & Memories", + "number": 290 + }, + { + "name": "Outdoor Channel", + "number": 291 + } + ], + "Interactive Services": [ + { + "name": "BBC Red Button", + "number": 601 + } + ], + "Radio": [ + { + "name": "BBC Radio 1", + "number": 700 + }, + { + "name": "BBC Radio 1Xtra", + "number": 701 + }, + { + "name": "BBC Radio 2", + "number": 702 + }, + { + "name": "BBC Radio 3", + "number": 703 + }, + { + "name": "BBC Radio 4", + "number": 704 + }, + { + "name": "BBC Radio 5 Live", + "number": 705 + }, + { + "name": "BBC Radio 5 Sports Extra", + "number": 706 + }, + { + "name": "BBC Radio 6 Music", + "number": 707 + }, + { + "name": "BBC Radio 4 Extra", + "number": 708 + }, + { + "name": "BBC Asian Network", + "number": 709 + }, + { + "name": "BBC World Service", + "number": 710 + }, + { + "name": "BBC Local Radio", + "number": 711 + }, + { + "name": "Smooth Radio", + "number": 718 + }, + { + "name": "talkSPORT", + "number": 723 + }, + { + "name": "Capital", + "number": 724 + }, + { + "name": "Premier Radio", + "number": 725 + }, + { + "name": "Heart", + "number": 728 + }, + { + "name": "RNIB Connect", + "number": 730 + }, + { + "name": "Classic FM", + "number": 731 + }, + { + "name": "LBC", + "number": 732 + } + ] + } } \ No newline at end of file diff --git a/plugins/lineuparr/UK_SkyTV_ENG_full_lineup.json b/plugins/lineuparr/UK_SkyTV_ENG_full_lineup.json index a5a040d..7c7e260 100644 --- a/plugins/lineuparr/UK_SkyTV_ENG_full_lineup.json +++ b/plugins/lineuparr/UK_SkyTV_ENG_full_lineup.json @@ -1,1299 +1,1299 @@ -{ - "package": "Sky TV UK (England) Full Lineup", - "date": "2026-05-04", - "description": "Sky TV UK (England) lineup with timeshift, HD, and regional variations", - "source": "Sky Q", - "notes": "This lineup includes the main channels available on Sky TV in the UK (England), categorized by genre. It also includes timeshift channels (+1) and HD channels where available. Regional variations are included in this full lineup.", - "categories": { - "Entertainment": [ - { - "name": "BBC One HD", - "number": 101 - }, - { - "name": "BBC Two HD", - "number": 102 - }, - { - "name": "ITV1 HD", - "number": 103 - }, - { - "name": "Channel 4 HD", - "number": 104 - }, - { - "name": "Channel 5 HD", - "number": 105 - }, - { - "name": "Sky One HD", - "number": 106 - }, - { - "name": "Sky Witness HD", - "number": 107 - }, - { - "name": "Sky Atlantic HD", - "number": 108 - }, - { - "name": "U&Alibi HD", - "number": 109 - }, - { - "name": "U&Gold HD", - "number": 110 - }, - { - "name": "U&Dave HD", - "number": 111 - }, - { - "name": "Comedy Central HD", - "number": 112 - }, - { - "name": "Sky Comedy HD", - "number": 113 - }, - { - "name": "Sky Documentaries HD", - "number": 114 - }, - { - "name": "BBC Three HD", - "number": 115 - }, - { - "name": "BBC Four HD", - "number": 116 - }, - { - "name": "London TV", - "number": 117 - }, - { - "name": "ITV2 HD", - "number": 118 - }, - { - "name": "ITV3 HD", - "number": 119 - }, - { - "name": "ITV4 HD", - "number": 120 - }, - { - "name": "Sky Crime HD", - "number": 121 - }, - { - "name": "Sky Arts HD", - "number": 122 - }, - { - "name": "Sky History HD", - "number": 123 - }, - { - "name": "Sky Nature HD", - "number": 124 - }, - { - "name": "Discovery Channel HD", - "number": 125 - }, - { - "name": "MTV HD", - "number": 126 - }, - { - "name": "Comedy Central Extra", - "number": 127 - }, - { - "name": "5STAR", - "number": 128 - }, - { - "name": "National Geographic HD", - "number": 129 - }, - { - "name": "Challenge", - "number": 130 - }, - { - "name": "ITV Quiz HD", - "number": 131 - }, - { - "name": "U&W", - "number": 132 - }, - { - "name": "TLC HD", - "number": 133 - }, - { - "name": "S4C HD", - "number": 134 - }, - { - "name": "E4 HD", - "number": 135 - }, - { - "name": "More4 HD", - "number": 136 - }, - { - "name": "4seven", - "number": 137 - }, - { - "name": "E4 Extra", - "number": 138 - }, - { - "name": "Crime+Investigation HD", - "number": 139 - }, - { - "name": "Quest HD", - "number": 140 - }, - { - "name": "5USA", - "number": 141 - }, - { - "name": "Really", - "number": 142 - }, - { - "name": "U&Drama", - "number": 143 - }, - { - "name": "Food Network", - "number": 144 - }, - { - "name": "Sky Sci-Fi HD", - "number": 145 - }, - { - "name": "True Crime", - "number": 146 - }, - { - "name": "True Crime Xtra", - "number": 147 - }, - { - "name": "Legend", - "number": 148 - }, - { - "name": "Quest Red", - "number": 149 - }, - { - "name": "5ACTION", - "number": 150 - }, - { - "name": "Sky Mix HD", - "number": 151 - }, - { - "name": "5SELECT", - "number": 153 - }, - { - "name": "Investigation Discovery", - "number": 154 - }, - { - "name": "U&Yesterday", - "number": 155 - }, - { - "name": "Blaze", - "number": 156 - }, - { - "name": "GREAT! TV", - "number": 157 - }, - { - "name": "Discovery Turbo", - "number": 158 - }, - { - "name": "DMAX", - "number": 159 - }, - { - "name": "Discovery History", - "number": 161 - }, - { - "name": "Animal Planet", - "number": 162 - }, - { - "name": "Sky History 2", - "number": 163 - }, - { - "name": "National Geographic Wild HD", - "number": 165 - }, - { - "name": "U&Eden", - "number": 166 - }, - { - "name": "Discovery Science", - "number": 167 - }, - { - "name": "BBC Alba HD", - "number": 169 - }, - { - "name": "Together TV", - "number": 170 - }, - { - "name": "That's TV", - "number": 171 - }, - { - "name": "PBS America", - "number": 173 - }, - { - "name": "Court TV", - "number": 176 - }, - { - "name": "Travelxp", - "number": 178 - }, - { - "name": "BBC Scotland HD", - "number": 180 - }, - { - "name": "That's TV 2", - "number": 181 - }, - { - "name": "Rewind TV", - "number": 182 - }, - { - "name": "That's 20th Century", - "number": 183 - } - ], - "Entertainment & Documentaries +1": [ - { - "name": "ITV1 London +1", - "number": 203 - }, - { - "name": "Channel 4 +1", - "number": 204 - }, - { - "name": "Channel 5 +1", - "number": 205 - }, - { - "name": "Sky Witness +1", - "number": 207 - }, - { - "name": "Sky Atlantic +1", - "number": 208 - }, - { - "name": "U&Alibi +1", - "number": 209 - }, - { - "name": "U&Gold +1", - "number": 210 - }, - { - "name": "U&DaveJaVu", - "number": 211 - }, - { - "name": "Comedy Central +1", - "number": 212 - }, - { - "name": "ITV2 +1", - "number": 218 - }, - { - "name": "ITV3 +1", - "number": 219 - }, - { - "name": "ITV4 +1", - "number": 220 - }, - { - "name": "Sky Crime +1", - "number": 221 - }, - { - "name": "Discovery Channel +1", - "number": 225 - }, - { - "name": "5STAR +1", - "number": 228 - }, - { - "name": "National Geographic +1", - "number": 229 - }, - { - "name": "U&W +1", - "number": 232 - }, - { - "name": "TLC +1", - "number": 233 - }, - { - "name": "E4 +1", - "number": 235 - }, - { - "name": "More4 +1", - "number": 236 - }, - { - "name": "Crime+Investigation +1", - "number": 239 - }, - { - "name": "Quest +1", - "number": 240 - }, - { - "name": "5USA +1", - "number": 241 - }, - { - "name": "Really +1", - "number": 242 - }, - { - "name": "U&Drama +1", - "number": 243 - }, - { - "name": "Food Network +1", - "number": 244 - }, - { - "name": "True Crime +1", - "number": 246 - }, - { - "name": "Quest Red +1", - "number": 249 - }, - { - "name": "Investigation Discovery +1", - "number": 254 - }, - { - "name": "U&Yesterday +1", - "number": 255 - }, - { - "name": "GREAT! TV +1", - "number": 257 - }, - { - "name": "Discovery Turbo +1", - "number": 258 - }, - { - "name": "DMAX +1", - "number": 259 - }, - { - "name": "Discovery History +1", - "number": 261 - }, - { - "name": "Animal Planet +1", - "number": 262 - }, - { - "name": "U&Eden +1", - "number": 266 - }, - { - "name": "Discovery Science +1", - "number": 267 - } - ], - "Movies": [ - { - "name": "Sky Cinema Premiere HD", - "number": 301 - }, - { - "name": "Sky Cinema Decades HD", - "number": 302 - }, - { - "name": "Sky Cinema Fast HD", - "number": 303 - }, - { - "name": "Sky Cinema Family HD", - "number": 304 - }, - { - "name": "Disney+ Cinema HD", - "number": 305 - }, - { - "name": "Sky Cinema Action HD", - "number": 306 - }, - { - "name": "Sky Cinema Greats HD", - "number": 307 - }, - { - "name": "Sky Cinema Comedy HD", - "number": 308 - }, - { - "name": "Sky Cinema Thriller HD", - "number": 309 - }, - { - "name": "Sky Cinema Drama HD", - "number": 310 - }, - { - "name": "Sky Cinema Sci-Fi & Horror HD", - "number": 311 - }, - { - "name": "Movies24", - "number": 312 - }, - { - "name": "Film4 HD", - "number": 313 - }, - { - "name": "Film4 +1", - "number": 314 - }, - { - "name": "Movies24+", - "number": 315 - }, - { - "name": "Legend Xtra", - "number": 316 - }, - { - "name": "Legend Xtra +1", - "number": 317 - }, - { - "name": "GREAT! Action", - "number": 318 - }, - { - "name": "GREAT! Action +1", - "number": 319 - }, - { - "name": "GREAT! Mystery", - "number": 320 - }, - { - "name": "GREAT! Mystery +1", - "number": 321 - }, - { - "name": "GREAT! Romance", - "number": 322 - }, - { - "name": "GREAT! Romance +1", - "number": 323 - }, - { - "name": "Talking Pictures TV", - "number": 324 - } - ], - "Music": [ - { - "name": "Clubland TV", - "number": 354 - }, - { - "name": "Now 70s", - "number": 355 - }, - { - "name": "Now 80s", - "number": 356 - }, - { - "name": "Now 90s & 00s", - "number": 357 - }, - { - "name": "Now Rock", - "number": 358 - } - ], - "Sports": [ - { - "name": "Sky Sports Main Event UHD", - "number": 401 - }, - { - "name": "Sky Sports Premier League HD", - "number": 402 - }, - { - "name": "Sky Sports Football HD", - "number": 403 - }, - { - "name": "Sky Sports+ HD", - "number": 404 - }, - { - "name": "Sky Sports Cricket HD", - "number": 405 - }, - { - "name": "Sky Sports Golf HD", - "number": 406 - }, - { - "name": "Sky Sports F1 UHD", - "number": 407 - }, - { - "name": "Sky Sports Tennis HD", - "number": 408 - }, - { - "name": "Sky Sports News HD", - "number": 409 - }, - { - "name": "TNT Sports 1 HD", - "number": 410 - }, - { - "name": "TNT Sports 2 HD", - "number": 411 - }, - { - "name": "Sky Sports Action HD", - "number": 412 - }, - { - "name": "TNT Sports 3 HD", - "number": 413 - }, - { - "name": "TNT Sports 4 HD", - "number": 414 - }, - { - "name": "Sky Sports Racing HD", - "number": 415 - }, - { - "name": "Sky Sports Mix HD", - "number": 416 - }, - { - "name": "MUTV HD", - "number": 418 - }, - { - "name": "Premier Sports 1 HD", - "number": 419 - }, - { - "name": "Premier Sports 2 HD", - "number": 420 - }, - { - "name": "LFCTV HD", - "number": 423 - }, - { - "name": "Racing TV HD", - "number": 424 - }, - { - "name": "TNT Sports Box Office HD", - "number": 490 - }, - { - "name": "Sky Sports Box Office HD", - "number": 491 - }, - { - "name": "TNT Sports Ultimate UHD", - "number": 493 - }, - { - "name": "TNT Sports Box Office 2 HD", - "number": 494 - }, - { - "name": "TNT Sports 5 HD", - "number": 3006 - }, - { - "name": "TNT Sports 6", - "number": 3007 - }, - { - "name": "TNT Sports 7", - "number": 3008 - }, - { - "name": "TNT Sports 8", - "number": 3009 - }, - { - "name": "TNT Sports 9", - "number": 3010 - }, - { - "name": "TNT Sports 10", - "number": 3011 - } - ], - "News": [ - { - "name": "Sky News HD", - "number": 501 - }, - { - "name": "Bloomberg HD", - "number": 502 - }, - { - "name": "BBC News HD", - "number": 503 - }, - { - "name": "BBC Parliament HD", - "number": 504 - }, - { - "name": "CNBC HD", - "number": 505 - }, - { - "name": "CNN HD", - "number": 506 - }, - { - "name": "NHK World HD", - "number": 507 - }, - { - "name": "Euronews", - "number": 508 - }, - { - "name": "NDTV World", - "number": 509 - }, - { - "name": "France 24 English HD", - "number": 510 - }, - { - "name": "Al Jazeera HD", - "number": 511 - }, - { - "name": "GB News HD", - "number": 512 - }, - { - "name": "TRT World HD", - "number": 513 - }, - { - "name": "Channels 24", - "number": 515 - }, - { - "name": "Arise News", - "number": 516 - }, - { - "name": "Arirang TV HD", - "number": 518 - }, - { - "name": "TVC News", - "number": 520 - }, - { - "name": "NTD", - "number": 521 - }, - { - "name": "DM News English", - "number": 522 - } - ], - "Faith": [ - { - "name": "God Channel", - "number": 580 - }, - { - "name": "Revelation", - "number": 581 - }, - { - "name": "TBN UK", - "number": 582 - }, - { - "name": "Daystar HD", - "number": 583 - }, - { - "name": "LoveWorld HD", - "number": 585 - }, - { - "name": "EWTN Catholic", - "number": 586 - }, - { - "name": "Faith World TV", - "number": 588 - }, - { - "name": "Faith UK", - "number": 590 - }, - { - "name": "Dunamis TV", - "number": 592 - }, - { - "name": "SonLife Broadcasting Network", - "number": 593 - } - ], - "Kids": [ - { - "name": "Cartoon Network HD", - "number": 601 - }, - { - "name": "Cartoon Network +1", - "number": 602 - }, - { - "name": "Boomerang HD", - "number": 603 - }, - { - "name": "Nickelodeon HD", - "number": 604 - }, - { - "name": "Nicktoons", - "number": 605 - }, - { - "name": "Nick Jr. HD", - "number": 606 - }, - { - "name": "CBBC HD", - "number": 607 - }, - { - "name": "CBeebies HD", - "number": 608 - }, - { - "name": "Sky Kids HD", - "number": 609 - }, - { - "name": "Cartoonito", - "number": 610 - }, - { - "name": "Boomerang +1", - "number": 611 - }, - { - "name": "Nick Jr. Peppa", - "number": 612 - }, - { - "name": "Disney Jr.", - "number": 613 - }, - { - "name": "Nickelodeon +1", - "number": 615 - }, - { - "name": "Nick Jr. +1", - "number": 619 - }, - { - "name": "BabyTV", - "number": 623 - } - ], - "Secondary & Information": [ - { - "name": "Channel Line-up", - "number": 996 - } - ], - "Shopping": [ - { - "name": "QVC HD", - "number": 660 - }, - { - "name": "Must Have Ideas HD", - "number": 661 - }, - { - "name": "TJC HD", - "number": 662 - }, - { - "name": "QVC Beauty", - "number": 663 - }, - { - "name": "QVC Style HD", - "number": 664 - }, - { - "name": "Gemporia HD", - "number": 665 - }, - { - "name": "High Street TV 1", - "number": 666 - }, - { - "name": "High Street TV 2", - "number": 667 - }, - { - "name": "Ideal World HD", - "number": 668 - }, - { - "name": "Best Direct", - "number": 669 - }, - { - "name": "HobbyMaker HD", - "number": 670 - }, - { - "name": "QVC Extra", - "number": 671 - }, - { - "name": "Shop Unlimited", - "number": 672 - }, - { - "name": "TV Warehouse", - "number": 673 - }, - { - "name": "Jewellery Maker", - "number": 674 - }, - { - "name": "Cruise1st.TV", - "number": 675 - } - ], - "International": [ - { - "name": "B4U Movies", - "number": 701 - }, - { - "name": "B4U Music", - "number": 702 - }, - { - "name": "Sony TV HD", - "number": 703 - }, - { - "name": "Utsav Bharat", - "number": 704 - }, - { - "name": "Utsav Plus HD", - "number": 705 - }, - { - "name": "Colors HD", - "number": 706 - }, - { - "name": "Zee TV HD", - "number": 707 - }, - { - "name": "Zee Cinema", - "number": 708 - }, - { - "name": "Apna MA TV", - "number": 709 - }, - { - "name": "Aaj Tak", - "number": 710 - }, - { - "name": "Colors Rishtey", - "number": 712 - }, - { - "name": "Colors Cineplex", - "number": 713 - }, - { - "name": "Sony Max HD", - "number": 714 - }, - { - "name": "Utsav Gold HD", - "number": 715 - }, - { - "name": "Sony SAB", - "number": 716 - }, - { - "name": "Sony Max 2", - "number": 717 - }, - { - "name": "Aastha", - "number": 718 - }, - { - "name": "Sanskar", - "number": 719 - }, - { - "name": "MTA1 World HD", - "number": 731 - }, - { - "name": "Hum News", - "number": 732 - }, - { - "name": "Geo News", - "number": 734 - }, - { - "name": "New Vision TV", - "number": 735 - }, - { - "name": "Islam Channel", - "number": 736 - }, - { - "name": "Geo TV", - "number": 737 - }, - { - "name": "Noor TV", - "number": 738 - }, - { - "name": "IQRA TV", - "number": 739 - }, - { - "name": "Islam TV", - "number": 741 - }, - { - "name": "Ahlebait TV", - "number": 742 - }, - { - "name": "Takbeer TV", - "number": 743 - }, - { - "name": "Hum Europe", - "number": 744 - }, - { - "name": "Islam Channel Urdu", - "number": 745 - }, - { - "name": "Madani Channel", - "number": 746 - }, - { - "name": "Eman Channel", - "number": 747 - }, - { - "name": "ARY Digital", - "number": 748 - }, - { - "name": "QTV Religious", - "number": 751 - }, - { - "name": "PTC Punjabi", - "number": 760 - }, - { - "name": "Panjab TV", - "number": 761 - }, - { - "name": "Sikh Channel", - "number": 762 - }, - { - "name": "Sangat Television", - "number": 763 - }, - { - "name": "Akaal Channel", - "number": 764 - }, - { - "name": "Kanshi TV", - "number": 765 - }, - { - "name": "Pitaara", - "number": 766 - }, - { - "name": "Politics Punjab", - "number": 767 - }, - { - "name": "Panjab Broadcasting Channel", - "number": 768 - }, - { - "name": "Zee Punjabi", - "number": 769 - }, - { - "name": "GTC Punjabi", - "number": 770 - }, - { - "name": "Channel S", - "number": 777 - }, - { - "name": "IQRA Bangla", - "number": 778 - }, - { - "name": "ATN Bangla", - "number": 779 - }, - { - "name": "NTV", - "number": 780 - }, - { - "name": "TV One", - "number": 781 - }, - { - "name": "iON TV", - "number": 782 - }, - { - "name": "Deen TV", - "number": 783 - }, - { - "name": "Islam Channel Bangla", - "number": 784 - }, - { - "name": "Sky News Arabia", - "number": 788 - }, - { - "name": "Phoenix CNE HD", - "number": 789 - }, - { - "name": "Colors Gujarati", - "number": 790 - } - ], - "Adult": [ - { - "name": "Television X", - "number": 900 - }, - { - "name": "The Adult Channel", - "number": 901 - }, - { - "name": "Xpanded TV", - "number": 902 - }, - { - "name": "Babes&Brazzers", - "number": 903 - }, - { - "name": "Babenation", - "number": 904 - }, - { - "name": "TVX 40+", - "number": 907 - }, - { - "name": "XXX Girl Girl", - "number": 908 - } - ], - "Regional & Interactive": [ - { - "name": "BBC One London HD", - "number": 951 - }, - { - "name": "BBC One North East & Cumbria HD", - "number": 952 - }, - { - "name": "BBC One North West HD", - "number": 953 - }, - { - "name": "BBC One Yorkshire HD", - "number": 954 - }, - { - "name": "BBC One East Yorkshire & Lincolnshire HD", - "number": 955 - }, - { - "name": "BBC One West Midlands HD", - "number": 956 - }, - { - "name": "BBC One East Midlands HD", - "number": 957 - }, - { - "name": "BBC One East HD", - "number": 958 - }, - { - "name": "BBC One South East HD", - "number": 959 - }, - { - "name": "BBC One West HD", - "number": 960 - }, - { - "name": "BBC One South HD", - "number": 961 - }, - { - "name": "BBC One South West HD", - "number": 962 - }, - { - "name": "BBC One Channel Islands HD", - "number": 963 - }, - { - "name": "BBC One Scotland HD", - "number": 964 - }, - { - "name": "BBC One Wales HD", - "number": 965 - }, - { - "name": "BBC One Northern Ireland HD", - "number": 966 - }, - { - "name": "BBC Two Wales HD", - "number": 968 - }, - { - "name": "BBC Two Northern Ireland HD", - "number": 969 - }, - { - "name": "BBC Red Button 1 HD", - "number": 970 - } - ] - } +{ + "package": "Sky TV UK (England) Full Lineup", + "date": "2026-05-04", + "description": "Sky TV UK (England) lineup with timeshift, HD, and regional variations", + "source": "Sky Q", + "notes": "This lineup includes the main channels available on Sky TV in the UK (England), categorized by genre. It also includes timeshift channels (+1) and HD channels where available. Regional variations are included in this full lineup.", + "categories": { + "Entertainment": [ + { + "name": "BBC One HD", + "number": 101 + }, + { + "name": "BBC Two HD", + "number": 102 + }, + { + "name": "ITV1 HD", + "number": 103 + }, + { + "name": "Channel 4 HD", + "number": 104 + }, + { + "name": "Channel 5 HD", + "number": 105 + }, + { + "name": "Sky One HD", + "number": 106 + }, + { + "name": "Sky Witness HD", + "number": 107 + }, + { + "name": "Sky Atlantic HD", + "number": 108 + }, + { + "name": "U&Alibi HD", + "number": 109 + }, + { + "name": "U&Gold HD", + "number": 110 + }, + { + "name": "U&Dave HD", + "number": 111 + }, + { + "name": "Comedy Central HD", + "number": 112 + }, + { + "name": "Sky Comedy HD", + "number": 113 + }, + { + "name": "Sky Documentaries HD", + "number": 114 + }, + { + "name": "BBC Three HD", + "number": 115 + }, + { + "name": "BBC Four HD", + "number": 116 + }, + { + "name": "London TV", + "number": 117 + }, + { + "name": "ITV2 HD", + "number": 118 + }, + { + "name": "ITV3 HD", + "number": 119 + }, + { + "name": "ITV4 HD", + "number": 120 + }, + { + "name": "Sky Crime HD", + "number": 121 + }, + { + "name": "Sky Arts HD", + "number": 122 + }, + { + "name": "Sky History HD", + "number": 123 + }, + { + "name": "Sky Nature HD", + "number": 124 + }, + { + "name": "Discovery Channel HD", + "number": 125 + }, + { + "name": "MTV HD", + "number": 126 + }, + { + "name": "Comedy Central Extra", + "number": 127 + }, + { + "name": "5STAR", + "number": 128 + }, + { + "name": "National Geographic HD", + "number": 129 + }, + { + "name": "Challenge", + "number": 130 + }, + { + "name": "ITV Quiz HD", + "number": 131 + }, + { + "name": "U&W", + "number": 132 + }, + { + "name": "TLC HD", + "number": 133 + }, + { + "name": "S4C HD", + "number": 134 + }, + { + "name": "E4 HD", + "number": 135 + }, + { + "name": "More4 HD", + "number": 136 + }, + { + "name": "4seven", + "number": 137 + }, + { + "name": "E4 Extra", + "number": 138 + }, + { + "name": "Crime+Investigation HD", + "number": 139 + }, + { + "name": "Quest HD", + "number": 140 + }, + { + "name": "5USA", + "number": 141 + }, + { + "name": "Really", + "number": 142 + }, + { + "name": "U&Drama", + "number": 143 + }, + { + "name": "Food Network", + "number": 144 + }, + { + "name": "Sky Sci-Fi HD", + "number": 145 + }, + { + "name": "True Crime", + "number": 146 + }, + { + "name": "True Crime Xtra", + "number": 147 + }, + { + "name": "Legend", + "number": 148 + }, + { + "name": "Quest Red", + "number": 149 + }, + { + "name": "5ACTION", + "number": 150 + }, + { + "name": "Sky Mix HD", + "number": 151 + }, + { + "name": "5SELECT", + "number": 153 + }, + { + "name": "Investigation Discovery", + "number": 154 + }, + { + "name": "U&Yesterday", + "number": 155 + }, + { + "name": "Blaze", + "number": 156 + }, + { + "name": "GREAT! TV", + "number": 157 + }, + { + "name": "Discovery Turbo", + "number": 158 + }, + { + "name": "DMAX", + "number": 159 + }, + { + "name": "Discovery History", + "number": 161 + }, + { + "name": "Animal Planet", + "number": 162 + }, + { + "name": "Sky History 2", + "number": 163 + }, + { + "name": "National Geographic Wild HD", + "number": 165 + }, + { + "name": "U&Eden", + "number": 166 + }, + { + "name": "Discovery Science", + "number": 167 + }, + { + "name": "BBC Alba HD", + "number": 169 + }, + { + "name": "Together TV", + "number": 170 + }, + { + "name": "That's TV", + "number": 171 + }, + { + "name": "PBS America", + "number": 173 + }, + { + "name": "Court TV", + "number": 176 + }, + { + "name": "Travelxp", + "number": 178 + }, + { + "name": "BBC Scotland HD", + "number": 180 + }, + { + "name": "That's TV 2", + "number": 181 + }, + { + "name": "Rewind TV", + "number": 182 + }, + { + "name": "That's 20th Century", + "number": 183 + } + ], + "Entertainment & Documentaries +1": [ + { + "name": "ITV1 London +1", + "number": 203 + }, + { + "name": "Channel 4 +1", + "number": 204 + }, + { + "name": "Channel 5 +1", + "number": 205 + }, + { + "name": "Sky Witness +1", + "number": 207 + }, + { + "name": "Sky Atlantic +1", + "number": 208 + }, + { + "name": "U&Alibi +1", + "number": 209 + }, + { + "name": "U&Gold +1", + "number": 210 + }, + { + "name": "U&DaveJaVu", + "number": 211 + }, + { + "name": "Comedy Central +1", + "number": 212 + }, + { + "name": "ITV2 +1", + "number": 218 + }, + { + "name": "ITV3 +1", + "number": 219 + }, + { + "name": "ITV4 +1", + "number": 220 + }, + { + "name": "Sky Crime +1", + "number": 221 + }, + { + "name": "Discovery Channel +1", + "number": 225 + }, + { + "name": "5STAR +1", + "number": 228 + }, + { + "name": "National Geographic +1", + "number": 229 + }, + { + "name": "U&W +1", + "number": 232 + }, + { + "name": "TLC +1", + "number": 233 + }, + { + "name": "E4 +1", + "number": 235 + }, + { + "name": "More4 +1", + "number": 236 + }, + { + "name": "Crime+Investigation +1", + "number": 239 + }, + { + "name": "Quest +1", + "number": 240 + }, + { + "name": "5USA +1", + "number": 241 + }, + { + "name": "Really +1", + "number": 242 + }, + { + "name": "U&Drama +1", + "number": 243 + }, + { + "name": "Food Network +1", + "number": 244 + }, + { + "name": "True Crime +1", + "number": 246 + }, + { + "name": "Quest Red +1", + "number": 249 + }, + { + "name": "Investigation Discovery +1", + "number": 254 + }, + { + "name": "U&Yesterday +1", + "number": 255 + }, + { + "name": "GREAT! TV +1", + "number": 257 + }, + { + "name": "Discovery Turbo +1", + "number": 258 + }, + { + "name": "DMAX +1", + "number": 259 + }, + { + "name": "Discovery History +1", + "number": 261 + }, + { + "name": "Animal Planet +1", + "number": 262 + }, + { + "name": "U&Eden +1", + "number": 266 + }, + { + "name": "Discovery Science +1", + "number": 267 + } + ], + "Movies": [ + { + "name": "Sky Cinema Premiere HD", + "number": 301 + }, + { + "name": "Sky Cinema Decades HD", + "number": 302 + }, + { + "name": "Sky Cinema Fast HD", + "number": 303 + }, + { + "name": "Sky Cinema Family HD", + "number": 304 + }, + { + "name": "Disney+ Cinema HD", + "number": 305 + }, + { + "name": "Sky Cinema Action HD", + "number": 306 + }, + { + "name": "Sky Cinema Greats HD", + "number": 307 + }, + { + "name": "Sky Cinema Comedy HD", + "number": 308 + }, + { + "name": "Sky Cinema Thriller HD", + "number": 309 + }, + { + "name": "Sky Cinema Drama HD", + "number": 310 + }, + { + "name": "Sky Cinema Sci-Fi & Horror HD", + "number": 311 + }, + { + "name": "Movies24", + "number": 312 + }, + { + "name": "Film4 HD", + "number": 313 + }, + { + "name": "Film4 +1", + "number": 314 + }, + { + "name": "Movies24+", + "number": 315 + }, + { + "name": "Legend Xtra", + "number": 316 + }, + { + "name": "Legend Xtra +1", + "number": 317 + }, + { + "name": "GREAT! Action", + "number": 318 + }, + { + "name": "GREAT! Action +1", + "number": 319 + }, + { + "name": "GREAT! Mystery", + "number": 320 + }, + { + "name": "GREAT! Mystery +1", + "number": 321 + }, + { + "name": "GREAT! Romance", + "number": 322 + }, + { + "name": "GREAT! Romance +1", + "number": 323 + }, + { + "name": "Talking Pictures TV", + "number": 324 + } + ], + "Music": [ + { + "name": "Clubland TV", + "number": 354 + }, + { + "name": "Now 70s", + "number": 355 + }, + { + "name": "Now 80s", + "number": 356 + }, + { + "name": "Now 90s & 00s", + "number": 357 + }, + { + "name": "Now Rock", + "number": 358 + } + ], + "Sports": [ + { + "name": "Sky Sports Main Event UHD", + "number": 401 + }, + { + "name": "Sky Sports Premier League HD", + "number": 402 + }, + { + "name": "Sky Sports Football HD", + "number": 403 + }, + { + "name": "Sky Sports+ HD", + "number": 404 + }, + { + "name": "Sky Sports Cricket HD", + "number": 405 + }, + { + "name": "Sky Sports Golf HD", + "number": 406 + }, + { + "name": "Sky Sports F1 UHD", + "number": 407 + }, + { + "name": "Sky Sports Tennis HD", + "number": 408 + }, + { + "name": "Sky Sports News HD", + "number": 409 + }, + { + "name": "TNT Sports 1 HD", + "number": 410 + }, + { + "name": "TNT Sports 2 HD", + "number": 411 + }, + { + "name": "Sky Sports Action HD", + "number": 412 + }, + { + "name": "TNT Sports 3 HD", + "number": 413 + }, + { + "name": "TNT Sports 4 HD", + "number": 414 + }, + { + "name": "Sky Sports Racing HD", + "number": 415 + }, + { + "name": "Sky Sports Mix HD", + "number": 416 + }, + { + "name": "MUTV HD", + "number": 418 + }, + { + "name": "Premier Sports 1 HD", + "number": 419 + }, + { + "name": "Premier Sports 2 HD", + "number": 420 + }, + { + "name": "LFCTV HD", + "number": 423 + }, + { + "name": "Racing TV HD", + "number": 424 + }, + { + "name": "TNT Sports Box Office HD", + "number": 490 + }, + { + "name": "Sky Sports Box Office HD", + "number": 491 + }, + { + "name": "TNT Sports Ultimate UHD", + "number": 493 + }, + { + "name": "TNT Sports Box Office 2 HD", + "number": 494 + }, + { + "name": "TNT Sports 5 HD", + "number": 3006 + }, + { + "name": "TNT Sports 6", + "number": 3007 + }, + { + "name": "TNT Sports 7", + "number": 3008 + }, + { + "name": "TNT Sports 8", + "number": 3009 + }, + { + "name": "TNT Sports 9", + "number": 3010 + }, + { + "name": "TNT Sports 10", + "number": 3011 + } + ], + "News": [ + { + "name": "Sky News HD", + "number": 501 + }, + { + "name": "Bloomberg HD", + "number": 502 + }, + { + "name": "BBC News HD", + "number": 503 + }, + { + "name": "BBC Parliament HD", + "number": 504 + }, + { + "name": "CNBC HD", + "number": 505 + }, + { + "name": "CNN HD", + "number": 506 + }, + { + "name": "NHK World HD", + "number": 507 + }, + { + "name": "Euronews", + "number": 508 + }, + { + "name": "NDTV World", + "number": 509 + }, + { + "name": "France 24 English HD", + "number": 510 + }, + { + "name": "Al Jazeera HD", + "number": 511 + }, + { + "name": "GB News HD", + "number": 512 + }, + { + "name": "TRT World HD", + "number": 513 + }, + { + "name": "Channels 24", + "number": 515 + }, + { + "name": "Arise News", + "number": 516 + }, + { + "name": "Arirang TV HD", + "number": 518 + }, + { + "name": "TVC News", + "number": 520 + }, + { + "name": "NTD", + "number": 521 + }, + { + "name": "DM News English", + "number": 522 + } + ], + "Faith": [ + { + "name": "God Channel", + "number": 580 + }, + { + "name": "Revelation", + "number": 581 + }, + { + "name": "TBN UK", + "number": 582 + }, + { + "name": "Daystar HD", + "number": 583 + }, + { + "name": "LoveWorld HD", + "number": 585 + }, + { + "name": "EWTN Catholic", + "number": 586 + }, + { + "name": "Faith World TV", + "number": 588 + }, + { + "name": "Faith UK", + "number": 590 + }, + { + "name": "Dunamis TV", + "number": 592 + }, + { + "name": "SonLife Broadcasting Network", + "number": 593 + } + ], + "Kids": [ + { + "name": "Cartoon Network HD", + "number": 601 + }, + { + "name": "Cartoon Network +1", + "number": 602 + }, + { + "name": "Boomerang HD", + "number": 603 + }, + { + "name": "Nickelodeon HD", + "number": 604 + }, + { + "name": "Nicktoons", + "number": 605 + }, + { + "name": "Nick Jr. HD", + "number": 606 + }, + { + "name": "CBBC HD", + "number": 607 + }, + { + "name": "CBeebies HD", + "number": 608 + }, + { + "name": "Sky Kids HD", + "number": 609 + }, + { + "name": "Cartoonito", + "number": 610 + }, + { + "name": "Boomerang +1", + "number": 611 + }, + { + "name": "Nick Jr. Peppa", + "number": 612 + }, + { + "name": "Disney Jr.", + "number": 613 + }, + { + "name": "Nickelodeon +1", + "number": 615 + }, + { + "name": "Nick Jr. +1", + "number": 619 + }, + { + "name": "BabyTV", + "number": 623 + } + ], + "Secondary & Information": [ + { + "name": "Channel Line-up", + "number": 996 + } + ], + "Shopping": [ + { + "name": "QVC HD", + "number": 660 + }, + { + "name": "Must Have Ideas HD", + "number": 661 + }, + { + "name": "TJC HD", + "number": 662 + }, + { + "name": "QVC Beauty", + "number": 663 + }, + { + "name": "QVC Style HD", + "number": 664 + }, + { + "name": "Gemporia HD", + "number": 665 + }, + { + "name": "High Street TV 1", + "number": 666 + }, + { + "name": "High Street TV 2", + "number": 667 + }, + { + "name": "Ideal World HD", + "number": 668 + }, + { + "name": "Best Direct", + "number": 669 + }, + { + "name": "HobbyMaker HD", + "number": 670 + }, + { + "name": "QVC Extra", + "number": 671 + }, + { + "name": "Shop Unlimited", + "number": 672 + }, + { + "name": "TV Warehouse", + "number": 673 + }, + { + "name": "Jewellery Maker", + "number": 674 + }, + { + "name": "Cruise1st.TV", + "number": 675 + } + ], + "International": [ + { + "name": "B4U Movies", + "number": 701 + }, + { + "name": "B4U Music", + "number": 702 + }, + { + "name": "Sony TV HD", + "number": 703 + }, + { + "name": "Utsav Bharat", + "number": 704 + }, + { + "name": "Utsav Plus HD", + "number": 705 + }, + { + "name": "Colors HD", + "number": 706 + }, + { + "name": "Zee TV HD", + "number": 707 + }, + { + "name": "Zee Cinema", + "number": 708 + }, + { + "name": "Apna MA TV", + "number": 709 + }, + { + "name": "Aaj Tak", + "number": 710 + }, + { + "name": "Colors Rishtey", + "number": 712 + }, + { + "name": "Colors Cineplex", + "number": 713 + }, + { + "name": "Sony Max HD", + "number": 714 + }, + { + "name": "Utsav Gold HD", + "number": 715 + }, + { + "name": "Sony SAB", + "number": 716 + }, + { + "name": "Sony Max 2", + "number": 717 + }, + { + "name": "Aastha", + "number": 718 + }, + { + "name": "Sanskar", + "number": 719 + }, + { + "name": "MTA1 World HD", + "number": 731 + }, + { + "name": "Hum News", + "number": 732 + }, + { + "name": "Geo News", + "number": 734 + }, + { + "name": "New Vision TV", + "number": 735 + }, + { + "name": "Islam Channel", + "number": 736 + }, + { + "name": "Geo TV", + "number": 737 + }, + { + "name": "Noor TV", + "number": 738 + }, + { + "name": "IQRA TV", + "number": 739 + }, + { + "name": "Islam TV", + "number": 741 + }, + { + "name": "Ahlebait TV", + "number": 742 + }, + { + "name": "Takbeer TV", + "number": 743 + }, + { + "name": "Hum Europe", + "number": 744 + }, + { + "name": "Islam Channel Urdu", + "number": 745 + }, + { + "name": "Madani Channel", + "number": 746 + }, + { + "name": "Eman Channel", + "number": 747 + }, + { + "name": "ARY Digital", + "number": 748 + }, + { + "name": "QTV Religious", + "number": 751 + }, + { + "name": "PTC Punjabi", + "number": 760 + }, + { + "name": "Panjab TV", + "number": 761 + }, + { + "name": "Sikh Channel", + "number": 762 + }, + { + "name": "Sangat Television", + "number": 763 + }, + { + "name": "Akaal Channel", + "number": 764 + }, + { + "name": "Kanshi TV", + "number": 765 + }, + { + "name": "Pitaara", + "number": 766 + }, + { + "name": "Politics Punjab", + "number": 767 + }, + { + "name": "Panjab Broadcasting Channel", + "number": 768 + }, + { + "name": "Zee Punjabi", + "number": 769 + }, + { + "name": "GTC Punjabi", + "number": 770 + }, + { + "name": "Channel S", + "number": 777 + }, + { + "name": "IQRA Bangla", + "number": 778 + }, + { + "name": "ATN Bangla", + "number": 779 + }, + { + "name": "NTV", + "number": 780 + }, + { + "name": "TV One", + "number": 781 + }, + { + "name": "iON TV", + "number": 782 + }, + { + "name": "Deen TV", + "number": 783 + }, + { + "name": "Islam Channel Bangla", + "number": 784 + }, + { + "name": "Sky News Arabia", + "number": 788 + }, + { + "name": "Phoenix CNE HD", + "number": 789 + }, + { + "name": "Colors Gujarati", + "number": 790 + } + ], + "Adult": [ + { + "name": "Television X", + "number": 900 + }, + { + "name": "The Adult Channel", + "number": 901 + }, + { + "name": "Xpanded TV", + "number": 902 + }, + { + "name": "Babes&Brazzers", + "number": 903 + }, + { + "name": "Babenation", + "number": 904 + }, + { + "name": "TVX 40+", + "number": 907 + }, + { + "name": "XXX Girl Girl", + "number": 908 + } + ], + "Regional & Interactive": [ + { + "name": "BBC One London HD", + "number": 951 + }, + { + "name": "BBC One North East & Cumbria HD", + "number": 952 + }, + { + "name": "BBC One North West HD", + "number": 953 + }, + { + "name": "BBC One Yorkshire HD", + "number": 954 + }, + { + "name": "BBC One East Yorkshire & Lincolnshire HD", + "number": 955 + }, + { + "name": "BBC One West Midlands HD", + "number": 956 + }, + { + "name": "BBC One East Midlands HD", + "number": 957 + }, + { + "name": "BBC One East HD", + "number": 958 + }, + { + "name": "BBC One South East HD", + "number": 959 + }, + { + "name": "BBC One West HD", + "number": 960 + }, + { + "name": "BBC One South HD", + "number": 961 + }, + { + "name": "BBC One South West HD", + "number": 962 + }, + { + "name": "BBC One Channel Islands HD", + "number": 963 + }, + { + "name": "BBC One Scotland HD", + "number": 964 + }, + { + "name": "BBC One Wales HD", + "number": 965 + }, + { + "name": "BBC One Northern Ireland HD", + "number": 966 + }, + { + "name": "BBC Two Wales HD", + "number": 968 + }, + { + "name": "BBC Two Northern Ireland HD", + "number": 969 + }, + { + "name": "BBC Red Button 1 HD", + "number": 970 + } + ] + } } \ No newline at end of file diff --git a/plugins/lineuparr/UK_SkyTV_ENG_simple_lineup.json b/plugins/lineuparr/UK_SkyTV_ENG_simple_lineup.json index b7bd3f8..8ae8886 100644 --- a/plugins/lineuparr/UK_SkyTV_ENG_simple_lineup.json +++ b/plugins/lineuparr/UK_SkyTV_ENG_simple_lineup.json @@ -1,1221 +1,1221 @@ -{ - "package": "Sky TV UK (England) Simple Lineup", - "date": "2026-05-04", - "description": "Sky TV UK (England) lineup with timeshift, HD, and no regional variations", - "source": "Sky Q", - "notes": "This lineup includes the main channels available on Sky TV in the UK (England), categorized by genre. It also includes timeshift channels (+1) where available. Regional variations and HD channels are not included in this simple lineup.", - "categories": { - "Entertainment": [ - { - "name": "BBC One", - "number": 101 - }, - { - "name": "BBC Two", - "number": 102 - }, - { - "name": "ITV1", - "number": 103 - }, - { - "name": "Channel 4", - "number": 104 - }, - { - "name": "Channel 5", - "number": 105 - }, - { - "name": "Sky One", - "number": 106 - }, - { - "name": "Sky Witness", - "number": 107 - }, - { - "name": "Sky Atlantic", - "number": 108 - }, - { - "name": "U&Alibi", - "number": 109 - }, - { - "name": "U&Gold", - "number": 110 - }, - { - "name": "U&Dave", - "number": 111 - }, - { - "name": "Comedy Central", - "number": 112 - }, - { - "name": "Sky Comedy", - "number": 113 - }, - { - "name": "Sky Documentaries", - "number": 114 - }, - { - "name": "BBC Three", - "number": 115 - }, - { - "name": "BBC Four", - "number": 116 - }, - { - "name": "London TV", - "number": 117 - }, - { - "name": "ITV2", - "number": 118 - }, - { - "name": "ITV3", - "number": 119 - }, - { - "name": "ITV4", - "number": 120 - }, - { - "name": "Sky Crime", - "number": 121 - }, - { - "name": "Sky Arts", - "number": 122 - }, - { - "name": "Sky History", - "number": 123 - }, - { - "name": "Sky Nature", - "number": 124 - }, - { - "name": "Discovery Channel", - "number": 125 - }, - { - "name": "MTV", - "number": 126 - }, - { - "name": "Comedy Central Extra", - "number": 127 - }, - { - "name": "5STAR", - "number": 128 - }, - { - "name": "National Geographic", - "number": 129 - }, - { - "name": "Challenge", - "number": 130 - }, - { - "name": "ITV Quiz", - "number": 131 - }, - { - "name": "U&W", - "number": 132 - }, - { - "name": "TLC", - "number": 133 - }, - { - "name": "S4C", - "number": 134 - }, - { - "name": "E4", - "number": 135 - }, - { - "name": "More4", - "number": 136 - }, - { - "name": "4seven", - "number": 137 - }, - { - "name": "E4 Extra", - "number": 138 - }, - { - "name": "Crime+Investigation", - "number": 139 - }, - { - "name": "Quest", - "number": 140 - }, - { - "name": "5USA", - "number": 141 - }, - { - "name": "Really", - "number": 142 - }, - { - "name": "U&Drama", - "number": 143 - }, - { - "name": "Food Network", - "number": 144 - }, - { - "name": "Sky Sci-Fi", - "number": 145 - }, - { - "name": "True Crime", - "number": 146 - }, - { - "name": "True Crime Xtra", - "number": 147 - }, - { - "name": "Legend", - "number": 148 - }, - { - "name": "Quest Red", - "number": 149 - }, - { - "name": "5ACTION", - "number": 150 - }, - { - "name": "Sky Mix", - "number": 151 - }, - { - "name": "5SELECT", - "number": 153 - }, - { - "name": "Investigation Discovery", - "number": 154 - }, - { - "name": "U&Yesterday", - "number": 155 - }, - { - "name": "Blaze", - "number": 156 - }, - { - "name": "GREAT! TV", - "number": 157 - }, - { - "name": "Discovery Turbo", - "number": 158 - }, - { - "name": "DMAX", - "number": 159 - }, - { - "name": "Discovery History", - "number": 161 - }, - { - "name": "Animal Planet", - "number": 162 - }, - { - "name": "Sky History 2", - "number": 163 - }, - { - "name": "National Geographic Wild", - "number": 165 - }, - { - "name": "U&Eden", - "number": 166 - }, - { - "name": "Discovery Science", - "number": 167 - }, - { - "name": "BBC Alba", - "number": 169 - }, - { - "name": "Together TV", - "number": 170 - }, - { - "name": "That's TV", - "number": 171 - }, - { - "name": "PBS America", - "number": 173 - }, - { - "name": "Court TV", - "number": 176 - }, - { - "name": "Travelxp", - "number": 178 - }, - { - "name": "BBC Scotland", - "number": 180 - }, - { - "name": "That's TV 2", - "number": 181 - }, - { - "name": "Rewind TV", - "number": 182 - }, - { - "name": "That's 20th Century", - "number": 183 - } - ], - "Entertainment & Documentaries +1": [ - { - "name": "ITV1 +1", - "number": 203 - }, - { - "name": "Channel 4 +1", - "number": 204 - }, - { - "name": "Channel 5 +1", - "number": 205 - }, - { - "name": "Sky Witness +1", - "number": 207 - }, - { - "name": "Sky Atlantic +1", - "number": 208 - }, - { - "name": "U&Alibi +1", - "number": 209 - }, - { - "name": "U&Gold +1", - "number": 210 - }, - { - "name": "U&DaveJaVu", - "number": 211 - }, - { - "name": "Comedy Central +1", - "number": 212 - }, - { - "name": "ITV2 +1", - "number": 218 - }, - { - "name": "ITV3 +1", - "number": 219 - }, - { - "name": "ITV4 +1", - "number": 220 - }, - { - "name": "Sky Crime +1", - "number": 221 - }, - { - "name": "Discovery Channel +1", - "number": 225 - }, - { - "name": "5STAR +1", - "number": 228 - }, - { - "name": "National Geographic +1", - "number": 229 - }, - { - "name": "U&W +1", - "number": 232 - }, - { - "name": "TLC +1", - "number": 233 - }, - { - "name": "E4 +1", - "number": 235 - }, - { - "name": "More4 +1", - "number": 236 - }, - { - "name": "Crime+Investigation +1", - "number": 239 - }, - { - "name": "Quest +1", - "number": 240 - }, - { - "name": "5USA +1", - "number": 241 - }, - { - "name": "Really +1", - "number": 242 - }, - { - "name": "U&Drama +1", - "number": 243 - }, - { - "name": "Food Network +1", - "number": 244 - }, - { - "name": "True Crime +1", - "number": 246 - }, - { - "name": "Quest Red +1", - "number": 249 - }, - { - "name": "Investigation Discovery +1", - "number": 254 - }, - { - "name": "U&Yesterday +1", - "number": 255 - }, - { - "name": "GREAT! TV +1", - "number": 257 - }, - { - "name": "Discovery Turbo +1", - "number": 258 - }, - { - "name": "DMAX +1", - "number": 259 - }, - { - "name": "Discovery History +1", - "number": 261 - }, - { - "name": "Animal Planet +1", - "number": 262 - }, - { - "name": "U&Eden +1", - "number": 266 - }, - { - "name": "Discovery Science +1", - "number": 267 - } - ], - "Movies": [ - { - "name": "Sky Cinema Premiere", - "number": 301 - }, - { - "name": "Sky Cinema Decades", - "number": 302 - }, - { - "name": "Sky Cinema Fast", - "number": 303 - }, - { - "name": "Sky Cinema Family", - "number": 304 - }, - { - "name": "Disney+ Cinema", - "number": 305 - }, - { - "name": "Sky Cinema Action", - "number": 306 - }, - { - "name": "Sky Cinema Greats", - "number": 307 - }, - { - "name": "Sky Cinema Comedy", - "number": 308 - }, - { - "name": "Sky Cinema Thriller ", - "number": 309 - }, - { - "name": "Sky Cinema Drama", - "number": 310 - }, - { - "name": "Sky Cinema Sci-Fi & Horror", - "number": 311 - }, - { - "name": "Movies24", - "number": 312 - }, - { - "name": "Film4", - "number": 313 - }, - { - "name": "Film4 +1", - "number": 314 - }, - { - "name": "Movies24+", - "number": 315 - }, - { - "name": "Legend Xtra", - "number": 316 - }, - { - "name": "Legend Xtra +1", - "number": 317 - }, - { - "name": "GREAT! Action", - "number": 318 - }, - { - "name": "GREAT! Action +1", - "number": 319 - }, - { - "name": "GREAT! Mystery", - "number": 320 - }, - { - "name": "GREAT! Mystery +1", - "number": 321 - }, - { - "name": "GREAT! Romance", - "number": 322 - }, - { - "name": "GREAT! Romance +1", - "number": 323 - }, - { - "name": "Talking Pictures TV", - "number": 324 - } - ], - "Music": [ - { - "name": "Clubland TV", - "number": 354 - }, - { - "name": "Now 70s", - "number": 355 - }, - { - "name": "Now 80s", - "number": 356 - }, - { - "name": "Now 90s & 00s", - "number": 357 - }, - { - "name": "Now Rock", - "number": 358 - } - ], - "Sports": [ - { - "name": "Sky Sports Main Event", - "number": 401 - }, - { - "name": "Sky Sports Premier League", - "number": 402 - }, - { - "name": "Sky Sports Football", - "number": 403 - }, - { - "name": "Sky Sports+", - "number": 404 - }, - { - "name": "Sky Sports Cricket", - "number": 405 - }, - { - "name": "Sky Sports Golf", - "number": 406 - }, - { - "name": "Sky Sports F1", - "number": 407 - }, - { - "name": "Sky Sports Tennis", - "number": 408 - }, - { - "name": "Sky Sports News", - "number": 409 - }, - { - "name": "TNT Sports 1", - "number": 410 - }, - { - "name": "TNT Sports 2", - "number": 411 - }, - { - "name": "Sky Sports Action", - "number": 412 - }, - { - "name": "TNT Sports 3", - "number": 413 - }, - { - "name": "TNT Sports 4", - "number": 414 - }, - { - "name": "Sky Sports Racing", - "number": 415 - }, - { - "name": "Sky Sports Mix", - "number": 416 - }, - { - "name": "MUTV", - "number": 418 - }, - { - "name": "Premier Sports 1", - "number": 419 - }, - { - "name": "Premier Sports 2", - "number": 420 - }, - { - "name": "LFCTV", - "number": 423 - }, - { - "name": "Racing TV", - "number": 424 - }, - { - "name": "TNT Sports Box Office", - "number": 490 - }, - { - "name": "Sky Sports Box Office", - "number": 491 - }, - { - "name": "TNT Sports Ultimate", - "number": 493 - }, - { - "name": "TNT Sports Box Office 2", - "number": 494 - }, - { - "name": "TNT Sports 5", - "number": 3006 - }, - { - "name": "TNT Sports 6", - "number": 3007 - }, - { - "name": "TNT Sports 7", - "number": 3008 - }, - { - "name": "TNT Sports 8", - "number": 3009 - }, - { - "name": "TNT Sports 9", - "number": 3010 - }, - { - "name": "TNT Sports 10", - "number": 3011 - } - ], - "News": [ - { - "name": "Sky News", - "number": 501 - }, - { - "name": "Bloomberg", - "number": 502 - }, - { - "name": "BBC News", - "number": 503 - }, - { - "name": "BBC Parliament", - "number": 504 - }, - { - "name": "CNBC", - "number": 505 - }, - { - "name": "CNN", - "number": 506 - }, - { - "name": "NHK World", - "number": 507 - }, - { - "name": "Euronews", - "number": 508 - }, - { - "name": "NDTV World", - "number": 509 - }, - { - "name": "France 24 English", - "number": 510 - }, - { - "name": "Al Jazeera", - "number": 511 - }, - { - "name": "GB News", - "number": 512 - }, - { - "name": "TRT World", - "number": 513 - }, - { - "name": "Channels 24", - "number": 515 - }, - { - "name": "Arise News", - "number": 516 - }, - { - "name": "Arirang TV", - "number": 518 - }, - { - "name": "TVC News", - "number": 520 - }, - { - "name": "NTD", - "number": 521 - }, - { - "name": "DM News English", - "number": 522 - } - ], - "Faith": [ - { - "name": "God Channel", - "number": 580 - }, - { - "name": "Revelation", - "number": 581 - }, - { - "name": "TBN UK", - "number": 582 - }, - { - "name": "Daystar", - "number": 583 - }, - { - "name": "LoveWorld", - "number": 585 - }, - { - "name": "EWTN Catholic", - "number": 586 - }, - { - "name": "Faith World TV", - "number": 588 - }, - { - "name": "Faith UK", - "number": 590 - }, - { - "name": "Dunamis TV", - "number": 592 - }, - { - "name": "SonLife Broadcasting Network", - "number": 593 - } - ], - "Kids": [ - { - "name": "Cartoon Network", - "number": 601 - }, - { - "name": "Cartoon Network +1", - "number": 602 - }, - { - "name": "Boomerang", - "number": 603 - }, - { - "name": "Nickelodeon", - "number": 604 - }, - { - "name": "Nicktoons", - "number": 605 - }, - { - "name": "Nick Jr.", - "number": 606 - }, - { - "name": "CBBC", - "number": 607 - }, - { - "name": "Cbeebies", - "number": 608 - }, - { - "name": "Sky Kids", - "number": 609 - }, - { - "name": "Cartoonito", - "number": 610 - }, - { - "name": "Boomerang +1", - "number": 611 - }, - { - "name": "Nick Jr. Peppa", - "number": 612 - }, - { - "name": "Disney Jr.", - "number": 613 - }, - { - "name": "Nickelodeon +1", - "number": 615 - }, - { - "name": "Nick Jr. +1", - "number": 619 - }, - { - "name": "BabyTV", - "number": 623 - } - ], - "Shopping": [ - { - "name": "QVC", - "number": 660 - }, - { - "name": "Must Have Ideas", - "number": 661 - }, - { - "name": "TJC", - "number": 662 - }, - { - "name": "QVC Beauty", - "number": 663 - }, - { - "name": "QVC Style", - "number": 664 - }, - { - "name": "Gemporia", - "number": 665 - }, - { - "name": "High Street TV 1", - "number": 666 - }, - { - "name": "High Street TV 2", - "number": 667 - }, - { - "name": "Ideal World", - "number": 668 - }, - { - "name": "Best Direct", - "number": 669 - }, - { - "name": "HobbyMaker", - "number": 670 - }, - { - "name": "QVC Extra", - "number": 671 - }, - { - "name": "Shop Unlimited", - "number": 672 - }, - { - "name": "TV Warehouse", - "number": 673 - }, - { - "name": "Jewellery Maker", - "number": 674 - }, - { - "name": "Cruise1st.TV", - "number": 675 - } - ], - "International": [ - { - "name": "B4U Movies", - "number": 701 - }, - { - "name": "B4U Music", - "number": 702 - }, - { - "name": "Sony TV", - "number": 703 - }, - { - "name": "Utsav Bharat", - "number": 704 - }, - { - "name": "Utsav Plus", - "number": 705 - }, - { - "name": "Colors", - "number": 706 - }, - { - "name": "Zee TV", - "number": 707 - }, - { - "name": "Zee Cinema", - "number": 708 - }, - { - "name": "Apna MA TV", - "number": 709 - }, - { - "name": "Aaj Tak", - "number": 710 - }, - { - "name": "Colors Rishtey", - "number": 712 - }, - { - "name": "Colors Cineplex", - "number": 713 - }, - { - "name": "Sony Max", - "number": 714 - }, - { - "name": "Utsav Gold", - "number": 715 - }, - { - "name": "Sony SAB", - "number": 716 - }, - { - "name": "Sony Max 2", - "number": 717 - }, - { - "name": "Aastha", - "number": 718 - }, - { - "name": "Sanskar", - "number": 719 - }, - { - "name": "MTA1 World", - "number": 731 - }, - { - "name": "Hum News", - "number": 732 - }, - { - "name": "Geo News", - "number": 734 - }, - { - "name": "New Vision TV", - "number": 735 - }, - { - "name": "Islam Channel", - "number": 736 - }, - { - "name": "Geo TV", - "number": 737 - }, - { - "name": "Noor TV", - "number": 738 - }, - { - "name": "IQRA TV", - "number": 739 - }, - { - "name": "Islam TV", - "number": 741 - }, - { - "name": "Ahlebait TV", - "number": 742 - }, - { - "name": "Takbeer TV", - "number": 743 - }, - { - "name": "Hum Europe", - "number": 744 - }, - { - "name": "Islam Channel Urdu", - "number": 745 - }, - { - "name": "Madani Channel", - "number": 746 - }, - { - "name": "Eman Channel", - "number": 747 - }, - { - "name": "ARY Digital", - "number": 748 - }, - { - "name": "QTV Religious", - "number": 751 - }, - { - "name": "PTC Punjabi", - "number": 760 - }, - { - "name": "Panjab TV", - "number": 761 - }, - { - "name": "Sikh Channel", - "number": 762 - }, - { - "name": "Sangat Television", - "number": 763 - }, - { - "name": "Akaal Channel", - "number": 764 - }, - { - "name": "Kanshi TV", - "number": 765 - }, - { - "name": "Pitaara", - "number": 766 - }, - { - "name": "Politics Punjab", - "number": 767 - }, - { - "name": "Panjab Broadcasting Channel", - "number": 768 - }, - { - "name": "Zee Punjabi", - "number": 769 - }, - { - "name": "GTC Punjabi", - "number": 770 - }, - { - "name": "Channel S", - "number": 777 - }, - { - "name": "IQRA Bangla", - "number": 778 - }, - { - "name": "ATN Bangla", - "number": 779 - }, - { - "name": "NTV", - "number": 780 - }, - { - "name": "TV One", - "number": 781 - }, - { - "name": "iON TV", - "number": 782 - }, - { - "name": "Deen TV", - "number": 783 - }, - { - "name": "Islam Channel Bangla", - "number": 784 - }, - { - "name": "Sky News Arabia", - "number": 788 - }, - { - "name": "Phoenix CNE", - "number": 789 - }, - { - "name": "Colors Gujarati", - "number": 790 - } - ], - "Adult": [ - { - "name": "Television X", - "number": 900 - }, - { - "name": "The Adult Channel", - "number": 901 - }, - { - "name": "Xpanded TV", - "number": 902 - }, - { - "name": "Babes&Brazzers", - "number": 903 - }, - { - "name": "Babenation", - "number": 904 - }, - { - "name": "TVX 40+", - "number": 907 - }, - { - "name": "XXX Girl Girl", - "number": 908 - } - ], - "Secondary & Information": [ - { - "name": "Channel Line-up", - "number": 996 - } - ] - } +{ + "package": "Sky TV UK (England) Simple Lineup", + "date": "2026-05-04", + "description": "Sky TV UK (England) lineup with timeshift, HD, and no regional variations", + "source": "Sky Q", + "notes": "This lineup includes the main channels available on Sky TV in the UK (England), categorized by genre. It also includes timeshift channels (+1) where available. Regional variations and HD channels are not included in this simple lineup.", + "categories": { + "Entertainment": [ + { + "name": "BBC One", + "number": 101 + }, + { + "name": "BBC Two", + "number": 102 + }, + { + "name": "ITV1", + "number": 103 + }, + { + "name": "Channel 4", + "number": 104 + }, + { + "name": "Channel 5", + "number": 105 + }, + { + "name": "Sky One", + "number": 106 + }, + { + "name": "Sky Witness", + "number": 107 + }, + { + "name": "Sky Atlantic", + "number": 108 + }, + { + "name": "U&Alibi", + "number": 109 + }, + { + "name": "U&Gold", + "number": 110 + }, + { + "name": "U&Dave", + "number": 111 + }, + { + "name": "Comedy Central", + "number": 112 + }, + { + "name": "Sky Comedy", + "number": 113 + }, + { + "name": "Sky Documentaries", + "number": 114 + }, + { + "name": "BBC Three", + "number": 115 + }, + { + "name": "BBC Four", + "number": 116 + }, + { + "name": "London TV", + "number": 117 + }, + { + "name": "ITV2", + "number": 118 + }, + { + "name": "ITV3", + "number": 119 + }, + { + "name": "ITV4", + "number": 120 + }, + { + "name": "Sky Crime", + "number": 121 + }, + { + "name": "Sky Arts", + "number": 122 + }, + { + "name": "Sky History", + "number": 123 + }, + { + "name": "Sky Nature", + "number": 124 + }, + { + "name": "Discovery Channel", + "number": 125 + }, + { + "name": "MTV", + "number": 126 + }, + { + "name": "Comedy Central Extra", + "number": 127 + }, + { + "name": "5STAR", + "number": 128 + }, + { + "name": "National Geographic", + "number": 129 + }, + { + "name": "Challenge", + "number": 130 + }, + { + "name": "ITV Quiz", + "number": 131 + }, + { + "name": "U&W", + "number": 132 + }, + { + "name": "TLC", + "number": 133 + }, + { + "name": "S4C", + "number": 134 + }, + { + "name": "E4", + "number": 135 + }, + { + "name": "More4", + "number": 136 + }, + { + "name": "4seven", + "number": 137 + }, + { + "name": "E4 Extra", + "number": 138 + }, + { + "name": "Crime+Investigation", + "number": 139 + }, + { + "name": "Quest", + "number": 140 + }, + { + "name": "5USA", + "number": 141 + }, + { + "name": "Really", + "number": 142 + }, + { + "name": "U&Drama", + "number": 143 + }, + { + "name": "Food Network", + "number": 144 + }, + { + "name": "Sky Sci-Fi", + "number": 145 + }, + { + "name": "True Crime", + "number": 146 + }, + { + "name": "True Crime Xtra", + "number": 147 + }, + { + "name": "Legend", + "number": 148 + }, + { + "name": "Quest Red", + "number": 149 + }, + { + "name": "5ACTION", + "number": 150 + }, + { + "name": "Sky Mix", + "number": 151 + }, + { + "name": "5SELECT", + "number": 153 + }, + { + "name": "Investigation Discovery", + "number": 154 + }, + { + "name": "U&Yesterday", + "number": 155 + }, + { + "name": "Blaze", + "number": 156 + }, + { + "name": "GREAT! TV", + "number": 157 + }, + { + "name": "Discovery Turbo", + "number": 158 + }, + { + "name": "DMAX", + "number": 159 + }, + { + "name": "Discovery History", + "number": 161 + }, + { + "name": "Animal Planet", + "number": 162 + }, + { + "name": "Sky History 2", + "number": 163 + }, + { + "name": "National Geographic Wild", + "number": 165 + }, + { + "name": "U&Eden", + "number": 166 + }, + { + "name": "Discovery Science", + "number": 167 + }, + { + "name": "BBC Alba", + "number": 169 + }, + { + "name": "Together TV", + "number": 170 + }, + { + "name": "That's TV", + "number": 171 + }, + { + "name": "PBS America", + "number": 173 + }, + { + "name": "Court TV", + "number": 176 + }, + { + "name": "Travelxp", + "number": 178 + }, + { + "name": "BBC Scotland", + "number": 180 + }, + { + "name": "That's TV 2", + "number": 181 + }, + { + "name": "Rewind TV", + "number": 182 + }, + { + "name": "That's 20th Century", + "number": 183 + } + ], + "Entertainment & Documentaries +1": [ + { + "name": "ITV1 +1", + "number": 203 + }, + { + "name": "Channel 4 +1", + "number": 204 + }, + { + "name": "Channel 5 +1", + "number": 205 + }, + { + "name": "Sky Witness +1", + "number": 207 + }, + { + "name": "Sky Atlantic +1", + "number": 208 + }, + { + "name": "U&Alibi +1", + "number": 209 + }, + { + "name": "U&Gold +1", + "number": 210 + }, + { + "name": "U&DaveJaVu", + "number": 211 + }, + { + "name": "Comedy Central +1", + "number": 212 + }, + { + "name": "ITV2 +1", + "number": 218 + }, + { + "name": "ITV3 +1", + "number": 219 + }, + { + "name": "ITV4 +1", + "number": 220 + }, + { + "name": "Sky Crime +1", + "number": 221 + }, + { + "name": "Discovery Channel +1", + "number": 225 + }, + { + "name": "5STAR +1", + "number": 228 + }, + { + "name": "National Geographic +1", + "number": 229 + }, + { + "name": "U&W +1", + "number": 232 + }, + { + "name": "TLC +1", + "number": 233 + }, + { + "name": "E4 +1", + "number": 235 + }, + { + "name": "More4 +1", + "number": 236 + }, + { + "name": "Crime+Investigation +1", + "number": 239 + }, + { + "name": "Quest +1", + "number": 240 + }, + { + "name": "5USA +1", + "number": 241 + }, + { + "name": "Really +1", + "number": 242 + }, + { + "name": "U&Drama +1", + "number": 243 + }, + { + "name": "Food Network +1", + "number": 244 + }, + { + "name": "True Crime +1", + "number": 246 + }, + { + "name": "Quest Red +1", + "number": 249 + }, + { + "name": "Investigation Discovery +1", + "number": 254 + }, + { + "name": "U&Yesterday +1", + "number": 255 + }, + { + "name": "GREAT! TV +1", + "number": 257 + }, + { + "name": "Discovery Turbo +1", + "number": 258 + }, + { + "name": "DMAX +1", + "number": 259 + }, + { + "name": "Discovery History +1", + "number": 261 + }, + { + "name": "Animal Planet +1", + "number": 262 + }, + { + "name": "U&Eden +1", + "number": 266 + }, + { + "name": "Discovery Science +1", + "number": 267 + } + ], + "Movies": [ + { + "name": "Sky Cinema Premiere", + "number": 301 + }, + { + "name": "Sky Cinema Decades", + "number": 302 + }, + { + "name": "Sky Cinema Fast", + "number": 303 + }, + { + "name": "Sky Cinema Family", + "number": 304 + }, + { + "name": "Disney+ Cinema", + "number": 305 + }, + { + "name": "Sky Cinema Action", + "number": 306 + }, + { + "name": "Sky Cinema Greats", + "number": 307 + }, + { + "name": "Sky Cinema Comedy", + "number": 308 + }, + { + "name": "Sky Cinema Thriller ", + "number": 309 + }, + { + "name": "Sky Cinema Drama", + "number": 310 + }, + { + "name": "Sky Cinema Sci-Fi & Horror", + "number": 311 + }, + { + "name": "Movies24", + "number": 312 + }, + { + "name": "Film4", + "number": 313 + }, + { + "name": "Film4 +1", + "number": 314 + }, + { + "name": "Movies24+", + "number": 315 + }, + { + "name": "Legend Xtra", + "number": 316 + }, + { + "name": "Legend Xtra +1", + "number": 317 + }, + { + "name": "GREAT! Action", + "number": 318 + }, + { + "name": "GREAT! Action +1", + "number": 319 + }, + { + "name": "GREAT! Mystery", + "number": 320 + }, + { + "name": "GREAT! Mystery +1", + "number": 321 + }, + { + "name": "GREAT! Romance", + "number": 322 + }, + { + "name": "GREAT! Romance +1", + "number": 323 + }, + { + "name": "Talking Pictures TV", + "number": 324 + } + ], + "Music": [ + { + "name": "Clubland TV", + "number": 354 + }, + { + "name": "Now 70s", + "number": 355 + }, + { + "name": "Now 80s", + "number": 356 + }, + { + "name": "Now 90s & 00s", + "number": 357 + }, + { + "name": "Now Rock", + "number": 358 + } + ], + "Sports": [ + { + "name": "Sky Sports Main Event", + "number": 401 + }, + { + "name": "Sky Sports Premier League", + "number": 402 + }, + { + "name": "Sky Sports Football", + "number": 403 + }, + { + "name": "Sky Sports+", + "number": 404 + }, + { + "name": "Sky Sports Cricket", + "number": 405 + }, + { + "name": "Sky Sports Golf", + "number": 406 + }, + { + "name": "Sky Sports F1", + "number": 407 + }, + { + "name": "Sky Sports Tennis", + "number": 408 + }, + { + "name": "Sky Sports News", + "number": 409 + }, + { + "name": "TNT Sports 1", + "number": 410 + }, + { + "name": "TNT Sports 2", + "number": 411 + }, + { + "name": "Sky Sports Action", + "number": 412 + }, + { + "name": "TNT Sports 3", + "number": 413 + }, + { + "name": "TNT Sports 4", + "number": 414 + }, + { + "name": "Sky Sports Racing", + "number": 415 + }, + { + "name": "Sky Sports Mix", + "number": 416 + }, + { + "name": "MUTV", + "number": 418 + }, + { + "name": "Premier Sports 1", + "number": 419 + }, + { + "name": "Premier Sports 2", + "number": 420 + }, + { + "name": "LFCTV", + "number": 423 + }, + { + "name": "Racing TV", + "number": 424 + }, + { + "name": "TNT Sports Box Office", + "number": 490 + }, + { + "name": "Sky Sports Box Office", + "number": 491 + }, + { + "name": "TNT Sports Ultimate", + "number": 493 + }, + { + "name": "TNT Sports Box Office 2", + "number": 494 + }, + { + "name": "TNT Sports 5", + "number": 3006 + }, + { + "name": "TNT Sports 6", + "number": 3007 + }, + { + "name": "TNT Sports 7", + "number": 3008 + }, + { + "name": "TNT Sports 8", + "number": 3009 + }, + { + "name": "TNT Sports 9", + "number": 3010 + }, + { + "name": "TNT Sports 10", + "number": 3011 + } + ], + "News": [ + { + "name": "Sky News", + "number": 501 + }, + { + "name": "Bloomberg", + "number": 502 + }, + { + "name": "BBC News", + "number": 503 + }, + { + "name": "BBC Parliament", + "number": 504 + }, + { + "name": "CNBC", + "number": 505 + }, + { + "name": "CNN", + "number": 506 + }, + { + "name": "NHK World", + "number": 507 + }, + { + "name": "Euronews", + "number": 508 + }, + { + "name": "NDTV World", + "number": 509 + }, + { + "name": "France 24 English", + "number": 510 + }, + { + "name": "Al Jazeera", + "number": 511 + }, + { + "name": "GB News", + "number": 512 + }, + { + "name": "TRT World", + "number": 513 + }, + { + "name": "Channels 24", + "number": 515 + }, + { + "name": "Arise News", + "number": 516 + }, + { + "name": "Arirang TV", + "number": 518 + }, + { + "name": "TVC News", + "number": 520 + }, + { + "name": "NTD", + "number": 521 + }, + { + "name": "DM News English", + "number": 522 + } + ], + "Faith": [ + { + "name": "God Channel", + "number": 580 + }, + { + "name": "Revelation", + "number": 581 + }, + { + "name": "TBN UK", + "number": 582 + }, + { + "name": "Daystar", + "number": 583 + }, + { + "name": "LoveWorld", + "number": 585 + }, + { + "name": "EWTN Catholic", + "number": 586 + }, + { + "name": "Faith World TV", + "number": 588 + }, + { + "name": "Faith UK", + "number": 590 + }, + { + "name": "Dunamis TV", + "number": 592 + }, + { + "name": "SonLife Broadcasting Network", + "number": 593 + } + ], + "Kids": [ + { + "name": "Cartoon Network", + "number": 601 + }, + { + "name": "Cartoon Network +1", + "number": 602 + }, + { + "name": "Boomerang", + "number": 603 + }, + { + "name": "Nickelodeon", + "number": 604 + }, + { + "name": "Nicktoons", + "number": 605 + }, + { + "name": "Nick Jr.", + "number": 606 + }, + { + "name": "CBBC", + "number": 607 + }, + { + "name": "Cbeebies", + "number": 608 + }, + { + "name": "Sky Kids", + "number": 609 + }, + { + "name": "Cartoonito", + "number": 610 + }, + { + "name": "Boomerang +1", + "number": 611 + }, + { + "name": "Nick Jr. Peppa", + "number": 612 + }, + { + "name": "Disney Jr.", + "number": 613 + }, + { + "name": "Nickelodeon +1", + "number": 615 + }, + { + "name": "Nick Jr. +1", + "number": 619 + }, + { + "name": "BabyTV", + "number": 623 + } + ], + "Shopping": [ + { + "name": "QVC", + "number": 660 + }, + { + "name": "Must Have Ideas", + "number": 661 + }, + { + "name": "TJC", + "number": 662 + }, + { + "name": "QVC Beauty", + "number": 663 + }, + { + "name": "QVC Style", + "number": 664 + }, + { + "name": "Gemporia", + "number": 665 + }, + { + "name": "High Street TV 1", + "number": 666 + }, + { + "name": "High Street TV 2", + "number": 667 + }, + { + "name": "Ideal World", + "number": 668 + }, + { + "name": "Best Direct", + "number": 669 + }, + { + "name": "HobbyMaker", + "number": 670 + }, + { + "name": "QVC Extra", + "number": 671 + }, + { + "name": "Shop Unlimited", + "number": 672 + }, + { + "name": "TV Warehouse", + "number": 673 + }, + { + "name": "Jewellery Maker", + "number": 674 + }, + { + "name": "Cruise1st.TV", + "number": 675 + } + ], + "International": [ + { + "name": "B4U Movies", + "number": 701 + }, + { + "name": "B4U Music", + "number": 702 + }, + { + "name": "Sony TV", + "number": 703 + }, + { + "name": "Utsav Bharat", + "number": 704 + }, + { + "name": "Utsav Plus", + "number": 705 + }, + { + "name": "Colors", + "number": 706 + }, + { + "name": "Zee TV", + "number": 707 + }, + { + "name": "Zee Cinema", + "number": 708 + }, + { + "name": "Apna MA TV", + "number": 709 + }, + { + "name": "Aaj Tak", + "number": 710 + }, + { + "name": "Colors Rishtey", + "number": 712 + }, + { + "name": "Colors Cineplex", + "number": 713 + }, + { + "name": "Sony Max", + "number": 714 + }, + { + "name": "Utsav Gold", + "number": 715 + }, + { + "name": "Sony SAB", + "number": 716 + }, + { + "name": "Sony Max 2", + "number": 717 + }, + { + "name": "Aastha", + "number": 718 + }, + { + "name": "Sanskar", + "number": 719 + }, + { + "name": "MTA1 World", + "number": 731 + }, + { + "name": "Hum News", + "number": 732 + }, + { + "name": "Geo News", + "number": 734 + }, + { + "name": "New Vision TV", + "number": 735 + }, + { + "name": "Islam Channel", + "number": 736 + }, + { + "name": "Geo TV", + "number": 737 + }, + { + "name": "Noor TV", + "number": 738 + }, + { + "name": "IQRA TV", + "number": 739 + }, + { + "name": "Islam TV", + "number": 741 + }, + { + "name": "Ahlebait TV", + "number": 742 + }, + { + "name": "Takbeer TV", + "number": 743 + }, + { + "name": "Hum Europe", + "number": 744 + }, + { + "name": "Islam Channel Urdu", + "number": 745 + }, + { + "name": "Madani Channel", + "number": 746 + }, + { + "name": "Eman Channel", + "number": 747 + }, + { + "name": "ARY Digital", + "number": 748 + }, + { + "name": "QTV Religious", + "number": 751 + }, + { + "name": "PTC Punjabi", + "number": 760 + }, + { + "name": "Panjab TV", + "number": 761 + }, + { + "name": "Sikh Channel", + "number": 762 + }, + { + "name": "Sangat Television", + "number": 763 + }, + { + "name": "Akaal Channel", + "number": 764 + }, + { + "name": "Kanshi TV", + "number": 765 + }, + { + "name": "Pitaara", + "number": 766 + }, + { + "name": "Politics Punjab", + "number": 767 + }, + { + "name": "Panjab Broadcasting Channel", + "number": 768 + }, + { + "name": "Zee Punjabi", + "number": 769 + }, + { + "name": "GTC Punjabi", + "number": 770 + }, + { + "name": "Channel S", + "number": 777 + }, + { + "name": "IQRA Bangla", + "number": 778 + }, + { + "name": "ATN Bangla", + "number": 779 + }, + { + "name": "NTV", + "number": 780 + }, + { + "name": "TV One", + "number": 781 + }, + { + "name": "iON TV", + "number": 782 + }, + { + "name": "Deen TV", + "number": 783 + }, + { + "name": "Islam Channel Bangla", + "number": 784 + }, + { + "name": "Sky News Arabia", + "number": 788 + }, + { + "name": "Phoenix CNE", + "number": 789 + }, + { + "name": "Colors Gujarati", + "number": 790 + } + ], + "Adult": [ + { + "name": "Television X", + "number": 900 + }, + { + "name": "The Adult Channel", + "number": 901 + }, + { + "name": "Xpanded TV", + "number": 902 + }, + { + "name": "Babes&Brazzers", + "number": 903 + }, + { + "name": "Babenation", + "number": 904 + }, + { + "name": "TVX 40+", + "number": 907 + }, + { + "name": "XXX Girl Girl", + "number": 908 + } + ], + "Secondary & Information": [ + { + "name": "Channel Line-up", + "number": 996 + } + ] + } } \ No newline at end of file diff --git a/plugins/lineuparr/UK_SkyTV_lineup.json b/plugins/lineuparr/UK_SkyTV_lineup.json index cd825ca..ea8ebc4 100644 --- a/plugins/lineuparr/UK_SkyTV_lineup.json +++ b/plugins/lineuparr/UK_SkyTV_lineup.json @@ -1,213 +1,213 @@ -{ - "package": "Sky Ultimate TV", - "date": "2026-04-05", - "description": "Sky Ultimate TV UK lineup with timeshift, HD, and regional variations", - "source": "Sky Channel Lineup", - "notes": "Lineup includes timeshift (+1) channels and high-definition variations. Local network affiliates (BBC One, BBC Two, ITV1, Channel 4, Channel 5) are available in regional versions. Some channels have specific regional variations for Scotland, Wales, and Northern Ireland as noted. Radio channels (0101-0140) and Adult services (900-950) are also included.", - "categories": { - "News": [ - { "name": "Al Jazeera English", "number": 511 }, - { "name": "Arirang TV", "number": 518 }, - { "name": "Arise News", "number": 516 }, - { "name": "BBC NEWS", "number": 503 }, - { "name": "BBC Parliament", "number": 504 }, - { "name": "Bloomberg", "number": 502 }, - { "name": "Channels 24", "number": 515 }, - { "name": "CNBC", "number": 505 }, - { "name": "CNN", "number": 506 }, - { "name": "DM News", "number": 522 }, - { "name": "Euronews", "number": 508 }, - { "name": "FRANCE 24", "number": 510 }, - { "name": "GB News", "number": 512 }, - { "name": "GEO News", "number": 734 }, - { "name": "NDTV World", "number": 509 }, - { "name": "NHK World", "number": 507 }, - { "name": "NTD", "number": 521 }, - { "name": "Sky News", "number": 501 }, - { "name": "Sky News Arabia", "number": 788 }, - { "name": "TRT World", "number": 513 }, - { "name": "TVC News", "number": 520 } - ], - "Sports": [ - { "name": "DAZN PPV", "number": 495, "notes": "part-time" }, - { "name": "LFCTV", "number": 423 }, - { "name": "MUTV", "number": 418 }, - { "name": "Premier Sports 1", "number": 419 }, - { "name": "Premier Sports 2", "number": 420 }, - { "name": "Sky Sports +", "number": 404, "notes": "Some games streaming only" }, - { "name": "Sky Sports Action", "number": 412, "notes": "Sky Sports NFL Sep-Feb" }, - { "name": "Sky Sports Box Office HD", "number": 492, "notes": "part-time" }, - { "name": "Sky Sports Cricket", "number": 405 }, - { "name": "Sky Sports F1", "number": 407, "notes": "Sky Sports Darts Dec-Jan" }, - { "name": "Sky Sports Football", "number": 403 }, - { "name": "Sky Sports Golf", "number": 406 }, - { "name": "Sky Sports Main Event", "number": 401 }, - { "name": "Sky Sports Mix", "number": 416 }, - { "name": "Sky Sports News", "number": 409 }, - { "name": "Sky Sports Premier League", "number": 402 }, - { "name": "Sky Sports Tennis", "number": 408 }, - { "name": "TNT Sports 1", "number": 410 }, - { "name": "TNT Sports 2", "number": 411 }, - { "name": "TNT Sports 3", "number": 413 }, - { "name": "TNT Sports 4", "number": 414 }, - { "name": "TNT Sports Box Office", "number": 490, "notes": "part-time" }, - { "name": "TNT Sports Ultimate (UHD)", "number": 493, "notes": "part-time" } - ], - "Regional Sports": [ - { "name": "Sky Sports Racing", "number": 415 }, - { "name": "Racing TV", "number": 424 } - ], - "Movies": [ - { "name": "Disney+ Cinema", "number": 305 }, - { "name": "GREAT! action", "number": 323 }, - { "name": "GREAT! Mystery", "number": 321 }, - { "name": "GREAT! Romance", "number": 319 }, - { "name": "LEGEND XTRA", "number": 317 }, - { "name": "Movies 24+", "number": 315 }, - { "name": "Sky Cinema Premiere", "number": 301 }, - { "name": "Sky Cinema Select", "number": 302 }, - { "name": "Sky Cinema Hits", "number": 303 }, - { "name": "Sky Cinema Family", "number": 304 }, - { "name": "Sky Cinema Action", "number": 306 }, - { "name": "Sky Cinema Greats", "number": 307 }, - { "name": "Sky Cinema Comedy", "number": 308 }, - { "name": "Sky Cinema Thriller", "number": 309 }, - { "name": "Sky Cinema Drama", "number": 310 }, - { "name": "Sky Cinema SciFi/Horror", "number": 311 }, - { "name": "Sky Cinema Animation", "number": 312 }, - { "name": "Talking Pictures", "number": 328 } - ], - "Kids": [ - { "name": "Baby TV", "number": 623 }, - { "name": "Boomerang", "number": 603 }, - { "name": "Cartoonito", "number": 610 }, - { "name": "Cartoon Network", "number": 601 }, - { "name": "CBBC", "number": 607 }, - { "name": "CBeebies", "number": 608 }, - { "name": "Cúla4", "number": 624, "notes": "In Northern Ireland" }, - { "name": "Disney Jr HD", "number": 613 }, - { "name": "Nickelodeon", "number": 604 }, - { "name": "Nick Jr.", "number": 606 }, - { "name": "Nick Jr. Too", "number": 612 }, - { "name": "Nicktoons", "number": 605 }, - { "name": "RTÉ KIDSjr", "number": 621, "notes": "In Northern Ireland" }, - { "name": "Sky Kids", "number": 609 } - ], - "Entertainment": [ - { "name": "4seven", "number": 137 }, - { "name": "5 USA", "number": 141 }, - { "name": "5ACTION", "number": 150 }, - { "name": "5SELECT", "number": 153 }, - { "name": "5STAR", "number": 128 }, - { "name": "BBC Alba HD", "number": 169 }, - { "name": "BBC Four", "number": 116 }, - { "name": "BBC Scotland", "number": 180 }, - { "name": "BBC Three", "number": 115 }, - { "name": "BLAZE", "number": 156 }, - { "name": "Challenge", "number": 130 }, - { "name": "E4", "number": 135 }, - { "name": "E4 Extra", "number": 138 }, - { "name": "GTC Punjabi", "number": 770 }, - { "name": "ITV2", "number": 118 }, - { "name": "ITV3", "number": 119 }, - { "name": "ITV4", "number": 120 }, - { "name": "More4", "number": 136 }, - { "name": "MTV", "number": 126 }, - { "name": "Rewind TV", "number": 182 }, - { "name": "S4C", "number": 134 }, - { "name": "Sky Arts", "number": 122 }, - { "name": "Sky Atlantic", "number": 108 }, - { "name": "Sky Comedy", "number": 113 }, - { "name": "Sky Mix", "number": 151 }, - { "name": "Sky One", "number": 106 }, - { "name": "Sky Sci-Fi", "number": 145 }, - { "name": "Sky Witness", "number": 107 }, - { "name": "That's TV", "number": 171 }, - { "name": "Together", "number": 170 }, - { "name": "U&alibi", "number": 109 }, - { "name": "U&Dave", "number": 111 }, - { "name": "U&Drama", "number": 143 }, - { "name": "U&GOLD", "number": 110 }, - { "name": "U&W", "number": 132 }, - { "name": "U&YESTERDAY", "number": 155 } - ], - "Reality & Lifestyle": [ - { "name": "DMAX", "number": 159 }, - { "name": "Quest Red", "number": 149 }, - { "name": "Really", "number": 142 } - ], - "Comedy": [ - { "name": "Comedy Central", "number": 112 }, - { "name": "Comedy Central Xtra", "number": 127 } - ], - "Discovery": [ - { "name": "Animal Planet", "number": 162 }, - { "name": "Discovery", "number": 125 }, - { "name": "Discovery History", "number": 161 }, - { "name": "Discovery Turbo", "number": 158 }, - { "name": "Discovery Science", "number": 167 }, - { "name": "GREAT! tv", "number": 157 }, - { "name": "National Geographic", "number": 129 }, - { "name": "Nat Geo Wild", "number": 165 }, - { "name": "Sky Documentaries", "number": 114 }, - { "name": "Sky History", "number": 123 }, - { "name": "Sky History 2", "number": 163 }, - { "name": "Sky Nature", "number": 124 } - ], - "Crime": [ - { "name": "Court TV", "number": 176 }, - { "name": "Crime+Investigation", "number": 139 }, - { "name": "ID", "number": 154 }, - { "name": "LEGEND", "number": 148 }, - { "name": "Sky Crime", "number": 121 }, - { "name": "TRUE CRIME", "number": 146 }, - { "name": "TRUE CRIME XTRA", "number": 147 } - ], - "Faith": [ - { "name": "DAYSTAR", "number": 415 }, - { "name": "Dunamis TV", "number": 417 }, - { "name": "EWTN Catholic", "number": 419 }, - { "name": "Faith UK", "number": 421 }, - { "name": "Faith World TV", "number": 426 }, - { "name": "GOD Channel", "number": 429 }, - { "name": "Good News TV", "number": 431 }, - { "name": "LoveWorld", "number": 434 }, - { "name": "Revelation", "number": 437 }, - { "name": "SonLife", "number": 440 }, - { "name": "TBN UK", "number": 442 }, - { "name": "Word Network", "number": 444 } - ], - "Music": [ - { "name": "B4U Music", "number": 702 }, - { "name": "Clubland TV", "number": 354 }, - { "name": "Music India", "number": 711 }, - { "name": "NOW 70s", "number": 355 }, - { "name": "NOW 80s", "number": 356 }, - { "name": "NOW 90s and 00s", "number": 357 }, - { "name": "NOW ROCK", "number": 358 } - ], - "Shopping": [ - { "name": "Best Direct", "number": 669 }, - { "name": "Cruise1st.tv", "number": 675 }, - { "name": "Direct Store TV", "number": 678 }, - { "name": "Gemporia", "number": 665 }, - { "name": "High Street TV 1", "number": 666 }, - { "name": "Ideal World", "number": 668 }, - { "name": "Jewellery Maker", "number": 674 }, - { "name": "MustHaveIdeas", "number": 661 }, - { "name": "QVC", "number": 660 }, - { "name": "TJC", "number": 662 }, - { "name": "TV Warehouse", "number": 673 } - ], - "Spanish": [], - "Outdoors": [ - { "name": "PBS America", "number": 173 }, - { "name": "Quest", "number": 140 } - ], - "Food & Travel": [ - { "name": "Food Network", "number": 144 }, - { "name": "HUM Masala", "number": 732 }, - { "name": "Travelxp", "number": 178 } - ] - } +{ + "package": "Sky Ultimate TV", + "date": "2026-04-05", + "description": "Sky Ultimate TV UK lineup with timeshift, HD, and regional variations", + "source": "Sky Channel Lineup", + "notes": "Lineup includes timeshift (+1) channels and high-definition variations. Local network affiliates (BBC One, BBC Two, ITV1, Channel 4, Channel 5) are available in regional versions. Some channels have specific regional variations for Scotland, Wales, and Northern Ireland as noted. Radio channels (0101-0140) and Adult services (900-950) are also included.", + "categories": { + "News": [ + { "name": "Al Jazeera English", "number": 511 }, + { "name": "Arirang TV", "number": 518 }, + { "name": "Arise News", "number": 516 }, + { "name": "BBC NEWS", "number": 503 }, + { "name": "BBC Parliament", "number": 504 }, + { "name": "Bloomberg", "number": 502 }, + { "name": "Channels 24", "number": 515 }, + { "name": "CNBC", "number": 505 }, + { "name": "CNN", "number": 506 }, + { "name": "DM News", "number": 522 }, + { "name": "Euronews", "number": 508 }, + { "name": "FRANCE 24", "number": 510 }, + { "name": "GB News", "number": 512 }, + { "name": "GEO News", "number": 734 }, + { "name": "NDTV World", "number": 509 }, + { "name": "NHK World", "number": 507 }, + { "name": "NTD", "number": 521 }, + { "name": "Sky News", "number": 501 }, + { "name": "Sky News Arabia", "number": 788 }, + { "name": "TRT World", "number": 513 }, + { "name": "TVC News", "number": 520 } + ], + "Sports": [ + { "name": "DAZN PPV", "number": 495, "notes": "part-time" }, + { "name": "LFCTV", "number": 423 }, + { "name": "MUTV", "number": 418 }, + { "name": "Premier Sports 1", "number": 419 }, + { "name": "Premier Sports 2", "number": 420 }, + { "name": "Sky Sports +", "number": 404, "notes": "Some games streaming only" }, + { "name": "Sky Sports Action", "number": 412, "notes": "Sky Sports NFL Sep-Feb" }, + { "name": "Sky Sports Box Office HD", "number": 492, "notes": "part-time" }, + { "name": "Sky Sports Cricket", "number": 405 }, + { "name": "Sky Sports F1", "number": 407, "notes": "Sky Sports Darts Dec-Jan" }, + { "name": "Sky Sports Football", "number": 403 }, + { "name": "Sky Sports Golf", "number": 406 }, + { "name": "Sky Sports Main Event", "number": 401 }, + { "name": "Sky Sports Mix", "number": 416 }, + { "name": "Sky Sports News", "number": 409 }, + { "name": "Sky Sports Premier League", "number": 402 }, + { "name": "Sky Sports Tennis", "number": 408 }, + { "name": "TNT Sports 1", "number": 410 }, + { "name": "TNT Sports 2", "number": 411 }, + { "name": "TNT Sports 3", "number": 413 }, + { "name": "TNT Sports 4", "number": 414 }, + { "name": "TNT Sports Box Office", "number": 490, "notes": "part-time" }, + { "name": "TNT Sports Ultimate (UHD)", "number": 493, "notes": "part-time" } + ], + "Regional Sports": [ + { "name": "Sky Sports Racing", "number": 415 }, + { "name": "Racing TV", "number": 424 } + ], + "Movies": [ + { "name": "Disney+ Cinema", "number": 305 }, + { "name": "GREAT! action", "number": 323 }, + { "name": "GREAT! Mystery", "number": 321 }, + { "name": "GREAT! Romance", "number": 319 }, + { "name": "LEGEND XTRA", "number": 317 }, + { "name": "Movies 24+", "number": 315 }, + { "name": "Sky Cinema Premiere", "number": 301 }, + { "name": "Sky Cinema Select", "number": 302 }, + { "name": "Sky Cinema Hits", "number": 303 }, + { "name": "Sky Cinema Family", "number": 304 }, + { "name": "Sky Cinema Action", "number": 306 }, + { "name": "Sky Cinema Greats", "number": 307 }, + { "name": "Sky Cinema Comedy", "number": 308 }, + { "name": "Sky Cinema Thriller", "number": 309 }, + { "name": "Sky Cinema Drama", "number": 310 }, + { "name": "Sky Cinema SciFi/Horror", "number": 311 }, + { "name": "Sky Cinema Animation", "number": 312 }, + { "name": "Talking Pictures", "number": 328 } + ], + "Kids": [ + { "name": "Baby TV", "number": 623 }, + { "name": "Boomerang", "number": 603 }, + { "name": "Cartoonito", "number": 610 }, + { "name": "Cartoon Network", "number": 601 }, + { "name": "CBBC", "number": 607 }, + { "name": "CBeebies", "number": 608 }, + { "name": "Cúla4", "number": 624, "notes": "In Northern Ireland" }, + { "name": "Disney Jr HD", "number": 613 }, + { "name": "Nickelodeon", "number": 604 }, + { "name": "Nick Jr.", "number": 606 }, + { "name": "Nick Jr. Too", "number": 612 }, + { "name": "Nicktoons", "number": 605 }, + { "name": "RTÉ KIDSjr", "number": 621, "notes": "In Northern Ireland" }, + { "name": "Sky Kids", "number": 609 } + ], + "Entertainment": [ + { "name": "4seven", "number": 137 }, + { "name": "5 USA", "number": 141 }, + { "name": "5ACTION", "number": 150 }, + { "name": "5SELECT", "number": 153 }, + { "name": "5STAR", "number": 128 }, + { "name": "BBC Alba HD", "number": 169 }, + { "name": "BBC Four", "number": 116 }, + { "name": "BBC Scotland", "number": 180 }, + { "name": "BBC Three", "number": 115 }, + { "name": "BLAZE", "number": 156 }, + { "name": "Challenge", "number": 130 }, + { "name": "E4", "number": 135 }, + { "name": "E4 Extra", "number": 138 }, + { "name": "GTC Punjabi", "number": 770 }, + { "name": "ITV2", "number": 118 }, + { "name": "ITV3", "number": 119 }, + { "name": "ITV4", "number": 120 }, + { "name": "More4", "number": 136 }, + { "name": "MTV", "number": 126 }, + { "name": "Rewind TV", "number": 182 }, + { "name": "S4C", "number": 134 }, + { "name": "Sky Arts", "number": 122 }, + { "name": "Sky Atlantic", "number": 108 }, + { "name": "Sky Comedy", "number": 113 }, + { "name": "Sky Mix", "number": 151 }, + { "name": "Sky One", "number": 106 }, + { "name": "Sky Sci-Fi", "number": 145 }, + { "name": "Sky Witness", "number": 107 }, + { "name": "That's TV", "number": 171 }, + { "name": "Together", "number": 170 }, + { "name": "U&alibi", "number": 109 }, + { "name": "U&Dave", "number": 111 }, + { "name": "U&Drama", "number": 143 }, + { "name": "U&GOLD", "number": 110 }, + { "name": "U&W", "number": 132 }, + { "name": "U&YESTERDAY", "number": 155 } + ], + "Reality & Lifestyle": [ + { "name": "DMAX", "number": 159 }, + { "name": "Quest Red", "number": 149 }, + { "name": "Really", "number": 142 } + ], + "Comedy": [ + { "name": "Comedy Central", "number": 112 }, + { "name": "Comedy Central Xtra", "number": 127 } + ], + "Discovery": [ + { "name": "Animal Planet", "number": 162 }, + { "name": "Discovery", "number": 125 }, + { "name": "Discovery History", "number": 161 }, + { "name": "Discovery Turbo", "number": 158 }, + { "name": "Discovery Science", "number": 167 }, + { "name": "GREAT! tv", "number": 157 }, + { "name": "National Geographic", "number": 129 }, + { "name": "Nat Geo Wild", "number": 165 }, + { "name": "Sky Documentaries", "number": 114 }, + { "name": "Sky History", "number": 123 }, + { "name": "Sky History 2", "number": 163 }, + { "name": "Sky Nature", "number": 124 } + ], + "Crime": [ + { "name": "Court TV", "number": 176 }, + { "name": "Crime+Investigation", "number": 139 }, + { "name": "ID", "number": 154 }, + { "name": "LEGEND", "number": 148 }, + { "name": "Sky Crime", "number": 121 }, + { "name": "TRUE CRIME", "number": 146 }, + { "name": "TRUE CRIME XTRA", "number": 147 } + ], + "Faith": [ + { "name": "DAYSTAR", "number": 415 }, + { "name": "Dunamis TV", "number": 417 }, + { "name": "EWTN Catholic", "number": 419 }, + { "name": "Faith UK", "number": 421 }, + { "name": "Faith World TV", "number": 426 }, + { "name": "GOD Channel", "number": 429 }, + { "name": "Good News TV", "number": 431 }, + { "name": "LoveWorld", "number": 434 }, + { "name": "Revelation", "number": 437 }, + { "name": "SonLife", "number": 440 }, + { "name": "TBN UK", "number": 442 }, + { "name": "Word Network", "number": 444 } + ], + "Music": [ + { "name": "B4U Music", "number": 702 }, + { "name": "Clubland TV", "number": 354 }, + { "name": "Music India", "number": 711 }, + { "name": "NOW 70s", "number": 355 }, + { "name": "NOW 80s", "number": 356 }, + { "name": "NOW 90s and 00s", "number": 357 }, + { "name": "NOW ROCK", "number": 358 } + ], + "Shopping": [ + { "name": "Best Direct", "number": 669 }, + { "name": "Cruise1st.tv", "number": 675 }, + { "name": "Direct Store TV", "number": 678 }, + { "name": "Gemporia", "number": 665 }, + { "name": "High Street TV 1", "number": 666 }, + { "name": "Ideal World", "number": 668 }, + { "name": "Jewellery Maker", "number": 674 }, + { "name": "MustHaveIdeas", "number": 661 }, + { "name": "QVC", "number": 660 }, + { "name": "TJC", "number": 662 }, + { "name": "TV Warehouse", "number": 673 } + ], + "Spanish": [], + "Outdoors": [ + { "name": "PBS America", "number": 173 }, + { "name": "Quest", "number": 140 } + ], + "Food & Travel": [ + { "name": "Food Network", "number": 144 }, + { "name": "HUM Masala", "number": 732 }, + { "name": "Travelxp", "number": 178 } + ] + } } \ No newline at end of file diff --git a/plugins/lineuparr/US_Verizon-FIOS_lineup.json b/plugins/lineuparr/US_Verizon-FIOS_lineup.json index 8496a7a..13c5468 100644 --- a/plugins/lineuparr/US_Verizon-FIOS_lineup.json +++ b/plugins/lineuparr/US_Verizon-FIOS_lineup.json @@ -1,243 +1,243 @@ -{ - "package": "Verizon The Most Fios TV", - "date": "2026-04-05", - "description": "Verizon Fios TV top-tier package with local, regional sports, and 4K channels", - "source": "Verizon Channel Lineup", - "notes": "Includes local channels (1-49 SD, 501-549 HD) and regional sports networks based on service area. Some channels require compatible 4K equipment.", - "categories": { - "News": [ - { "name": "AccuWeather", "number": 619 }, - { "name": "BBC News", "number": 609 }, - { "name": "Bloomberg", "number": 605 }, - { "name": "Cheddar News", "number": 614 }, - { "name": "CNBC", "number": 602 }, - { "name": "CNBC World", "number": 106 }, - { "name": "CNN", "number": 600 }, - { "name": "CNN International", "number": 105 }, - { "name": "Fox Business Network", "number": 617 }, - { "name": "Fox News", "number": 618 }, - { "name": "Fox Weather", "number": 613 }, - { "name": "HLN Headline News Network", "number": 101 }, - { "name": "i24NEWS", "number": 610 }, - { "name": "MSNBC", "number": 603 }, - { "name": "Newsmax", "number": 616 }, - { "name": "NewsNation", "number": 568 }, - { "name": "The Weather Channel", "number": 611 } - ], - "Sports": [ - { "name": "ACC Network", "number": 571 }, - { "name": "beIN Sports", "number": 805, "addon": "Sports pass" }, - { "name": "Big Ten Network", "number": 585 }, - { "name": "CBS Sports Network", "number": 594 }, - { "name": "Eleven Sports", "number": 597 }, - { "name": "ESPN", "number": 570 }, - { "name": "ESPN2", "number": 574 }, - { "name": "ESPNews", "number": 572 }, - { "name": "ESPN U", "number": 573 }, - { "name": "Fox Sports 1", "number": 583 }, - { "name": "Fox Sports 2", "number": 584 }, - { "name": "Golf Channel", "number": 593 }, - { "name": "MLB Extra Innings", "number": "1461-1474", "addon": "Subscription sports" }, - { "name": "MLB Network", "number": 586 }, - { "name": "MLB Network Strike Zone", "number": 834, "addon": "Sports pass" }, - { "name": "NBA League Pass", "number": "1450-1459", "addon": "Subscription sports" }, - { "name": "NBA TV", "number": 589 }, - { "name": "NFL Network", "number": 588 }, - { "name": "NFL RedZone", "number": 835, "addon": "Sports pass" }, - { "name": "NHL Center Ice", "number": "1461-1474", "addon": "Subscription sports" }, - { "name": "NHL Network", "number": 587 }, - { "name": "SEC Network", "number": 575 }, - { "name": "Tennis Channel", "number": 592 }, - { "name": "TUDN", "number": 1539 }, - { "name": "Willow Cricket", "number": 806, "addon": "Sports pass" } - ], - "Regional Sports": [ - { "name": "NESN National+", "number": 97 }, - { "name": "Sportsnet New York National", "number": 96, "addon": "Sports pass" }, - { "name": "YES National", "number": 595, "addon": "Sports pass" } - ], - "Movies": [ - { "name": "Action Max", "number": 924, "addon": "Cinemax" }, - { "name": "AMC", "number": 731 }, - { "name": "Cinemax", "number": 920, "addon": "Cinemax" }, - { "name": "Five Star Max", "number": 930, "addon": "Cinemax" }, - { "name": "Flix", "number": 390, "addon": "Movie Package" }, - { "name": "FX Movie Channel", "number": 732 }, - { "name": "Hallmark Channel", "number": 740 }, - { "name": "Hallmark Mystery", "number": 739 }, - { "name": "HBO", "number": 899, "addon": "HBO" }, - { "name": "HBO 2", "number": 902, "addon": "HBO" }, - { "name": "HBO Comedy", "number": 908, "addon": "HBO" }, - { "name": "HBO Signature", "number": 904, "addon": "HBO" }, - { "name": "HBO Zone", "number": 910, "addon": "HBO" }, - { "name": "IFC", "number": 734 }, - { "name": "IndiePlex", "number": 848, "addon": "Movie Package" }, - { "name": "MGM+", "number": 895, "addon": "MGM+" }, - { "name": "MGM+ Hits", "number": 896, "addon": "MGM+" }, - { "name": "More Max", "number": 922, "addon": "Cinemax" }, - { "name": "Paramount+ with SHOWTIME", "number": 865, "addon": "Movie Package" }, - { "name": "RetroPlex", "number": 849, "addon": "Movie Package" }, - { "name": "Showtime 2", "number": 869, "addon": "Movie Package" }, - { "name": "Showtime Extreme", "number": 873, "addon": "Movie Package" }, - { "name": "Showtime Family Zone", "number": 379, "addon": "Movie Package" }, - { "name": "Showtime Showcase", "number": 867, "addon": "Movie Package" }, - { "name": "Starz", "number": 840, "addon": "Movie Package" }, - { "name": "Starz Cinema", "number": 846, "addon": "Movie Package" }, - { "name": "Starz Comedy", "number": 847, "addon": "Movie Package" }, - { "name": "Starz Edge", "number": 842, "addon": "Movie Package" }, - { "name": "Starz Encore", "number": 850, "addon": "Movie Package" }, - { "name": "Starz In Black", "number": 844, "addon": "Movie Package" }, - { "name": "Starz Kids & Family", "number": 845, "addon": "Movie Package" }, - { "name": "Sundance TV", "number": 735 }, - { "name": "The Movie Channel", "number": 885, "addon": "Movie Package" }, - { "name": "Turner Classic Movies", "number": 730 } - ], - "Kids": [ - { "name": "BabyFirst", "number": 765 }, - { "name": "Boomerang", "number": 258 }, - { "name": "Cartoon Network", "number": 757 }, - { "name": "Discovery Family Channel", "number": 789 }, - { "name": "Disney Channel", "number": 780 }, - { "name": "Disney Junior", "number": 260 }, - { "name": "Disney XD", "number": 781 }, - { "name": "Nick 2", "number": 253 }, - { "name": "Nick Jr.", "number": 756 }, - { "name": "Nickelodeon", "number": 752 }, - { "name": "Nicktoons", "number": 754 }, - { "name": "TeenNick", "number": 755 } - ], - "Entertainment": [ - { "name": "A&E", "number": 681 }, - { "name": "ASPIRE", "number": 772 }, - { "name": "BBC America", "number": 689 }, - { "name": "BET", "number": 770 }, - { "name": "BET Her", "number": 720 }, - { "name": "Cleo TV", "number": 733 }, - { "name": "FX", "number": 553 }, - { "name": "FXX", "number": 691 }, - { "name": "HBCU GO", "number": 774 }, - { "name": "Lifetime", "number": 640 }, - { "name": "LMN", "number": 641 }, - { "name": "Logo TV", "number": 687 }, - { "name": "OWN: Oprah Winfrey Network", "number": 645 }, - { "name": "Oxygen", "number": 644 }, - { "name": "Paramount Network", "number": 554 }, - { "name": "StellarTV", "number": 776 }, - { "name": "Syfy", "number": 680 }, - { "name": "TNT", "number": 551 }, - { "name": "TV One", "number": 771 }, - { "name": "UP", "number": 738 }, - { "name": "USA Network", "number": 550 }, - { "name": "VICE", "number": 697 } - ], - "Reality & Lifestyle": [ - { "name": "Bravo", "number": 685 }, - { "name": "Destination America", "number": 668 }, - { "name": "Discovery Life", "number": 624 }, - { "name": "E! Entertainment Television", "number": 696 }, - { "name": "FYI", "number": 629 }, - { "name": "HGTV", "number": 665 }, - { "name": "Magnolia Network", "number": 667 }, - { "name": "TLC", "number": 639 }, - { "name": "WE tv", "number": 649 } - ], - "Comedy": [ - { "name": "Comedy Central", "number": 690 }, - { "name": "Comedy.TV", "number": 695 }, - { "name": "Game Show Network", "number": 684 }, - { "name": "POP", "number": 694 }, - { "name": "REELZ", "number": 692 }, - { "name": "TBS", "number": 552 }, - { "name": "truTV", "number": 683 }, - { "name": "TV Land", "number": 741 } - ], - "Discovery": [ - { "name": "American Heroes Channel", "number": 625 }, - { "name": "Animal Planet", "number": 630 }, - { "name": "Discovery Channel", "number": 620 }, - { "name": "History Channel", "number": 628 }, - { "name": "Military History Channel", "number": 126 }, - { "name": "Motor Trend", "number": 631 }, - { "name": "National Geographic", "number": 621 }, - { "name": "Nat Geo Wild", "number": 632 }, - { "name": "Science", "number": 622 }, - { "name": "Smithsonian Channel", "number": 634 } - ], - "Crime": [ - { "name": "Crime & Investigation Network", "number": 679 }, - { "name": "Investigation Discovery", "number": 623 }, - { "name": "JusticeCentral.TV", "number": 677 }, - { "name": "Law & Crime Network", "number": 678 } - ], - "Faith": [ - { "name": "Daystar", "number": 793 }, - { "name": "EWTN", "number": 285 }, - { "name": "Impact Network", "number": 787 }, - { "name": "INSP", "number": 786 }, - { "name": "Living Faith Network HD", "number": 792 }, - { "name": "PTL TV", "number": 795 }, - { "name": "TBN", "number": 295 }, - { "name": "The SonLife Broadcasting Network", "number": 797 }, - { "name": "The Word", "number": 292 }, - { "name": "Three Angels Broadcasting", "number": 291 } - ], - "Music": [ - { "name": "BET Gospel", "number": 225 }, - { "name": "BET Jams", "number": 213 }, - { "name": "BET Soul", "number": 219 }, - { "name": "CMT", "number": 721 }, - { "name": "CMT Music", "number": 222 }, - { "name": "MTV", "number": 710 }, - { "name": "MTV Classic", "number": 218 }, - { "name": "MTV Live", "number": 715 }, - { "name": "MTV2", "number": 711 }, - { "name": "mtvU", "number": 212 }, - { "name": "Music Choice", "number": "1800-1849" }, - { "name": "NickMusic", "number": 214 }, - { "name": "REVOLT", "number": 726 }, - { "name": "VH1", "number": 717 } - ], - "Shopping": [ - { "name": "HSN", "number": 651 }, - { "name": "HSN2", "number": 654 }, - { "name": "Jewelry Television", "number": 652 }, - { "name": "QVC", "number": 650 }, - { "name": "QVC 2", "number": 653 }, - { "name": "QVC 3", "number": 656 } - ], - "Spanish": [ - { "name": "Antena 3", "number": 1515 }, - { "name": "beIN Sports en Español", "number": 1538 }, - { "name": "Canal SUR", "number": 1549 }, - { "name": "CNN en Español", "number": 1540 }, - { "name": "De Película", "number": 1680 }, - { "name": "Discovery En Español", "number": 1558 }, - { "name": "Discovery Familia", "number": 1701 }, - { "name": "ESPN Deportes", "number": 1537 }, - { "name": "Fox Deportes", "number": 1534 }, - { "name": "HBO Latino", "number": 912, "addon": "HBO" }, - { "name": "MAX Latino", "number": 929, "addon": "Cinemax" }, - { "name": "Nat Geo Mundo", "number": 1564 }, - { "name": "Starz Encore Español", "number": 863, "addon": "Movie Package" }, - { "name": "Telemicro Internacional", "number": 1513 }, - { "name": "TVE Internacional", "number": 1560 }, - { "name": "Universo", "number": 775 }, - { "name": "Univision tINovelas", "number": 1584 } - ], - "Outdoors": [ - { "name": "Cars TV", "number": 599 }, - { "name": "FanDuel TV", "number": 815, "addon": "Sports pass" }, - { "name": "MAV TV", "number": 810, "addon": "Sports pass" }, - { "name": "MyDestination TV", "number": 674 }, - { "name": "Pets.TV", "number": 633 }, - { "name": "Pursuit", "number": 818, "addon": "Sports pass" }, - { "name": "The Cowboy Channel", "number": 246 } - ], - "Food & Travel": [ - { "name": "Cooking Channel", "number": 663 }, - { "name": "Food Network", "number": 664 }, - { "name": "Recipe TV", "number": 676 }, - { "name": "Travel Channel", "number": 670 } - ] - } +{ + "package": "Verizon The Most Fios TV", + "date": "2026-04-05", + "description": "Verizon Fios TV top-tier package with local, regional sports, and 4K channels", + "source": "Verizon Channel Lineup", + "notes": "Includes local channels (1-49 SD, 501-549 HD) and regional sports networks based on service area. Some channels require compatible 4K equipment.", + "categories": { + "News": [ + { "name": "AccuWeather", "number": 619 }, + { "name": "BBC News", "number": 609 }, + { "name": "Bloomberg", "number": 605 }, + { "name": "Cheddar News", "number": 614 }, + { "name": "CNBC", "number": 602 }, + { "name": "CNBC World", "number": 106 }, + { "name": "CNN", "number": 600 }, + { "name": "CNN International", "number": 105 }, + { "name": "Fox Business Network", "number": 617 }, + { "name": "Fox News", "number": 618 }, + { "name": "Fox Weather", "number": 613 }, + { "name": "HLN Headline News Network", "number": 101 }, + { "name": "i24NEWS", "number": 610 }, + { "name": "MSNBC", "number": 603 }, + { "name": "Newsmax", "number": 616 }, + { "name": "NewsNation", "number": 568 }, + { "name": "The Weather Channel", "number": 611 } + ], + "Sports": [ + { "name": "ACC Network", "number": 571 }, + { "name": "beIN Sports", "number": 805, "addon": "Sports pass" }, + { "name": "Big Ten Network", "number": 585 }, + { "name": "CBS Sports Network", "number": 594 }, + { "name": "Eleven Sports", "number": 597 }, + { "name": "ESPN", "number": 570 }, + { "name": "ESPN2", "number": 574 }, + { "name": "ESPNews", "number": 572 }, + { "name": "ESPN U", "number": 573 }, + { "name": "Fox Sports 1", "number": 583 }, + { "name": "Fox Sports 2", "number": 584 }, + { "name": "Golf Channel", "number": 593 }, + { "name": "MLB Extra Innings", "number": "1461-1474", "addon": "Subscription sports" }, + { "name": "MLB Network", "number": 586 }, + { "name": "MLB Network Strike Zone", "number": 834, "addon": "Sports pass" }, + { "name": "NBA League Pass", "number": "1450-1459", "addon": "Subscription sports" }, + { "name": "NBA TV", "number": 589 }, + { "name": "NFL Network", "number": 588 }, + { "name": "NFL RedZone", "number": 835, "addon": "Sports pass" }, + { "name": "NHL Center Ice", "number": "1461-1474", "addon": "Subscription sports" }, + { "name": "NHL Network", "number": 587 }, + { "name": "SEC Network", "number": 575 }, + { "name": "Tennis Channel", "number": 592 }, + { "name": "TUDN", "number": 1539 }, + { "name": "Willow Cricket", "number": 806, "addon": "Sports pass" } + ], + "Regional Sports": [ + { "name": "NESN National+", "number": 97 }, + { "name": "Sportsnet New York National", "number": 96, "addon": "Sports pass" }, + { "name": "YES National", "number": 595, "addon": "Sports pass" } + ], + "Movies": [ + { "name": "Action Max", "number": 924, "addon": "Cinemax" }, + { "name": "AMC", "number": 731 }, + { "name": "Cinemax", "number": 920, "addon": "Cinemax" }, + { "name": "Five Star Max", "number": 930, "addon": "Cinemax" }, + { "name": "Flix", "number": 390, "addon": "Movie Package" }, + { "name": "FX Movie Channel", "number": 732 }, + { "name": "Hallmark Channel", "number": 740 }, + { "name": "Hallmark Mystery", "number": 739 }, + { "name": "HBO", "number": 899, "addon": "HBO" }, + { "name": "HBO 2", "number": 902, "addon": "HBO" }, + { "name": "HBO Comedy", "number": 908, "addon": "HBO" }, + { "name": "HBO Signature", "number": 904, "addon": "HBO" }, + { "name": "HBO Zone", "number": 910, "addon": "HBO" }, + { "name": "IFC", "number": 734 }, + { "name": "IndiePlex", "number": 848, "addon": "Movie Package" }, + { "name": "MGM+", "number": 895, "addon": "MGM+" }, + { "name": "MGM+ Hits", "number": 896, "addon": "MGM+" }, + { "name": "More Max", "number": 922, "addon": "Cinemax" }, + { "name": "Paramount+ with SHOWTIME", "number": 865, "addon": "Movie Package" }, + { "name": "RetroPlex", "number": 849, "addon": "Movie Package" }, + { "name": "Showtime 2", "number": 869, "addon": "Movie Package" }, + { "name": "Showtime Extreme", "number": 873, "addon": "Movie Package" }, + { "name": "Showtime Family Zone", "number": 379, "addon": "Movie Package" }, + { "name": "Showtime Showcase", "number": 867, "addon": "Movie Package" }, + { "name": "Starz", "number": 840, "addon": "Movie Package" }, + { "name": "Starz Cinema", "number": 846, "addon": "Movie Package" }, + { "name": "Starz Comedy", "number": 847, "addon": "Movie Package" }, + { "name": "Starz Edge", "number": 842, "addon": "Movie Package" }, + { "name": "Starz Encore", "number": 850, "addon": "Movie Package" }, + { "name": "Starz In Black", "number": 844, "addon": "Movie Package" }, + { "name": "Starz Kids & Family", "number": 845, "addon": "Movie Package" }, + { "name": "Sundance TV", "number": 735 }, + { "name": "The Movie Channel", "number": 885, "addon": "Movie Package" }, + { "name": "Turner Classic Movies", "number": 730 } + ], + "Kids": [ + { "name": "BabyFirst", "number": 765 }, + { "name": "Boomerang", "number": 258 }, + { "name": "Cartoon Network", "number": 757 }, + { "name": "Discovery Family Channel", "number": 789 }, + { "name": "Disney Channel", "number": 780 }, + { "name": "Disney Junior", "number": 260 }, + { "name": "Disney XD", "number": 781 }, + { "name": "Nick 2", "number": 253 }, + { "name": "Nick Jr.", "number": 756 }, + { "name": "Nickelodeon", "number": 752 }, + { "name": "Nicktoons", "number": 754 }, + { "name": "TeenNick", "number": 755 } + ], + "Entertainment": [ + { "name": "A&E", "number": 681 }, + { "name": "ASPIRE", "number": 772 }, + { "name": "BBC America", "number": 689 }, + { "name": "BET", "number": 770 }, + { "name": "BET Her", "number": 720 }, + { "name": "Cleo TV", "number": 733 }, + { "name": "FX", "number": 553 }, + { "name": "FXX", "number": 691 }, + { "name": "HBCU GO", "number": 774 }, + { "name": "Lifetime", "number": 640 }, + { "name": "LMN", "number": 641 }, + { "name": "Logo TV", "number": 687 }, + { "name": "OWN: Oprah Winfrey Network", "number": 645 }, + { "name": "Oxygen", "number": 644 }, + { "name": "Paramount Network", "number": 554 }, + { "name": "StellarTV", "number": 776 }, + { "name": "Syfy", "number": 680 }, + { "name": "TNT", "number": 551 }, + { "name": "TV One", "number": 771 }, + { "name": "UP", "number": 738 }, + { "name": "USA Network", "number": 550 }, + { "name": "VICE", "number": 697 } + ], + "Reality & Lifestyle": [ + { "name": "Bravo", "number": 685 }, + { "name": "Destination America", "number": 668 }, + { "name": "Discovery Life", "number": 624 }, + { "name": "E! Entertainment Television", "number": 696 }, + { "name": "FYI", "number": 629 }, + { "name": "HGTV", "number": 665 }, + { "name": "Magnolia Network", "number": 667 }, + { "name": "TLC", "number": 639 }, + { "name": "WE tv", "number": 649 } + ], + "Comedy": [ + { "name": "Comedy Central", "number": 690 }, + { "name": "Comedy.TV", "number": 695 }, + { "name": "Game Show Network", "number": 684 }, + { "name": "POP", "number": 694 }, + { "name": "REELZ", "number": 692 }, + { "name": "TBS", "number": 552 }, + { "name": "truTV", "number": 683 }, + { "name": "TV Land", "number": 741 } + ], + "Discovery": [ + { "name": "American Heroes Channel", "number": 625 }, + { "name": "Animal Planet", "number": 630 }, + { "name": "Discovery Channel", "number": 620 }, + { "name": "History Channel", "number": 628 }, + { "name": "Military History Channel", "number": 126 }, + { "name": "Motor Trend", "number": 631 }, + { "name": "National Geographic", "number": 621 }, + { "name": "Nat Geo Wild", "number": 632 }, + { "name": "Science", "number": 622 }, + { "name": "Smithsonian Channel", "number": 634 } + ], + "Crime": [ + { "name": "Crime & Investigation Network", "number": 679 }, + { "name": "Investigation Discovery", "number": 623 }, + { "name": "JusticeCentral.TV", "number": 677 }, + { "name": "Law & Crime Network", "number": 678 } + ], + "Faith": [ + { "name": "Daystar", "number": 793 }, + { "name": "EWTN", "number": 285 }, + { "name": "Impact Network", "number": 787 }, + { "name": "INSP", "number": 786 }, + { "name": "Living Faith Network HD", "number": 792 }, + { "name": "PTL TV", "number": 795 }, + { "name": "TBN", "number": 295 }, + { "name": "The SonLife Broadcasting Network", "number": 797 }, + { "name": "The Word", "number": 292 }, + { "name": "Three Angels Broadcasting", "number": 291 } + ], + "Music": [ + { "name": "BET Gospel", "number": 225 }, + { "name": "BET Jams", "number": 213 }, + { "name": "BET Soul", "number": 219 }, + { "name": "CMT", "number": 721 }, + { "name": "CMT Music", "number": 222 }, + { "name": "MTV", "number": 710 }, + { "name": "MTV Classic", "number": 218 }, + { "name": "MTV Live", "number": 715 }, + { "name": "MTV2", "number": 711 }, + { "name": "mtvU", "number": 212 }, + { "name": "Music Choice", "number": "1800-1849" }, + { "name": "NickMusic", "number": 214 }, + { "name": "REVOLT", "number": 726 }, + { "name": "VH1", "number": 717 } + ], + "Shopping": [ + { "name": "HSN", "number": 651 }, + { "name": "HSN2", "number": 654 }, + { "name": "Jewelry Television", "number": 652 }, + { "name": "QVC", "number": 650 }, + { "name": "QVC 2", "number": 653 }, + { "name": "QVC 3", "number": 656 } + ], + "Spanish": [ + { "name": "Antena 3", "number": 1515 }, + { "name": "beIN Sports en Español", "number": 1538 }, + { "name": "Canal SUR", "number": 1549 }, + { "name": "CNN en Español", "number": 1540 }, + { "name": "De Película", "number": 1680 }, + { "name": "Discovery En Español", "number": 1558 }, + { "name": "Discovery Familia", "number": 1701 }, + { "name": "ESPN Deportes", "number": 1537 }, + { "name": "Fox Deportes", "number": 1534 }, + { "name": "HBO Latino", "number": 912, "addon": "HBO" }, + { "name": "MAX Latino", "number": 929, "addon": "Cinemax" }, + { "name": "Nat Geo Mundo", "number": 1564 }, + { "name": "Starz Encore Español", "number": 863, "addon": "Movie Package" }, + { "name": "Telemicro Internacional", "number": 1513 }, + { "name": "TVE Internacional", "number": 1560 }, + { "name": "Universo", "number": 775 }, + { "name": "Univision tINovelas", "number": 1584 } + ], + "Outdoors": [ + { "name": "Cars TV", "number": 599 }, + { "name": "FanDuel TV", "number": 815, "addon": "Sports pass" }, + { "name": "MAV TV", "number": 810, "addon": "Sports pass" }, + { "name": "MyDestination TV", "number": 674 }, + { "name": "Pets.TV", "number": 633 }, + { "name": "Pursuit", "number": 818, "addon": "Sports pass" }, + { "name": "The Cowboy Channel", "number": 246 } + ], + "Food & Travel": [ + { "name": "Cooking Channel", "number": 663 }, + { "name": "Food Network", "number": 664 }, + { "name": "Recipe TV", "number": 676 }, + { "name": "Travel Channel", "number": 670 } + ] + } } \ No newline at end of file diff --git a/plugins/lineuparr/fuzzy_matcher.py b/plugins/lineuparr/fuzzy_matcher.py index 54e1599..b0943ba 100644 --- a/plugins/lineuparr/fuzzy_matcher.py +++ b/plugins/lineuparr/fuzzy_matcher.py @@ -227,6 +227,41 @@ def detect_stream_country(name): return None +def detect_name_country_prefix(channel_name): + """Split a "CC_" country prefix off a lineup channel name. + + A lineup channel may declare its own country by name: "UK_CNN" inside a US + lineup means match this channel against UK streams. Returns + (country_code, stripped_name), or (None, channel_name) unchanged when there + is no recognized prefix. + + Only the curated _KNOWN_COUNTRY_CODES are accepted, so an ordinary name that + happens to contain an underscore ("MTV_Live", "My_Channel") is left alone. + The two letters must be followed by an underscore and by something other + than whitespace, so a name ending at the prefix is not turned into an empty + channel name. + + This lives beside detect_category_country because both answer the same + question from a different string, and both need the country vocabulary that + is private to this module. + """ + if not channel_name: + return None, channel_name + + m = re.match(r'^([A-Za-z]{2})_(.+)$', channel_name) + if not m: + return None, channel_name + + code = m.group(1).upper() + if code not in _KNOWN_COUNTRY_CODES: + return None, channel_name + + stripped = m.group(2).strip() + if not stripped: + return None, channel_name + return code, stripped + + def detect_category_country(category_name): """Detect an ISO-2 country code from a lineup category-name prefix. @@ -307,6 +342,11 @@ def __init__(self, match_threshold=80, logger=None): # match_all_streams calls. Callers reset this before a matching loop # and read it after, so they can log a summary. self.country_filter_drops = 0 + # Compiled whole-token matchers keyed by callsign, built on demand by + # the alias-anchored callsign rescue. The same handful of callsigns is + # re-tested against every stream for every lineup channel, so compiling + # once per callsign matters. + self._alias_callsign_patterns = {} def precompute_normalizations(self, names, user_ignored_tags=None): """ @@ -466,10 +506,100 @@ def alias_match(self, lineup_name, candidate_names, alias_map, user_ignored_tags if score >= effective_threshold: matches.append((candidate, score, "alias")) + # Stage 0b: alias-anchored callsign rescue. + matches.extend( + self._alias_callsign_matches(aliases, candidate_names, + already_matched={m[0] for m in matches}) + ) + # Sort by score descending matches.sort(key=lambda x: x[1], reverse=True) return matches + # A US broadcast callsign is K/W plus 2-4 letters, optionally carrying a + # broadcast suffix. The suffix may be hyphenated (WWOR-TV) or run together + # (WWORDT), and lineup data uses both. + _CALLSIGN_SHAPE = re.compile(r'^[KW][A-Z]{2,4}$') + _CALLSIGN_SUFFIX = re.compile(r'(TV|DT|CD|LP|LD)$') + + def _alias_callsign(self, alias): + """Return the bare callsign an alias declares, or None. + + Only an alias that is ENTIRELY a callsign counts. A phrase that merely + contains a K/W word ("MY NETWORK TV NEW YORK") is not a declaration that + the channel has that callsign, and treating it as one would anchor on an + ordinary word. + + The core callsign denylist is applied, so common English words with + callsign shape (KIDS, WORLD, WOMEN, WEST, KISS, WWE) never anchor. That + denylist exists because regex alone cannot tell "WITH" from "WABC". + """ + compact = re.sub(r'[^A-Za-z0-9]', '', alias or "").upper() + if not compact: + return None + + stem = compact + if not self._CALLSIGN_SHAPE.match(stem): + # Try again without a run-together broadcast suffix (WWORDT -> WWOR). + stripped = self._CALLSIGN_SUFFIX.sub('', compact) + if stripped == compact: + return None + stem = stripped + + if not self._CALLSIGN_SHAPE.match(stem): + return None + if stem in self._CALLSIGN_DENYLIST: + return None + return stem + + def _alias_callsign_pattern(self, callsign): + """Compiled whole-token matcher for a callsign inside a stream name. + + Matched against the ORIGINAL stream name, not the normalized form: + normalize_name strips parenthesized text, which would discard the single + most reliable callsign form there is ("... SECAUCUS NY (WWOR)"). + """ + pattern = self._alias_callsign_patterns.get(callsign) + if pattern is None: + # A run-together suffix is accepted only for TV and DT, the two forms + # lineup data actually uses (WWORTV, WWORDT). Accepting LD run-together + # would make callsign WWOR match the word WWORLD, since the regex would + # read the trailing "LD" as a broadcast suffix. + pattern = re.compile( + r'(?|BBC 1"); they are invisible + # padding that \s does not match and _DECORATOR_CATS does not cover, so they + # would otherwise survive the whole pipeline and poison the match. Removed, not + # spaced, since they are zero-width (a ZWSP inside "BBC" -> "BBC", not "BB C"). + name = ''.join(c for c in name if unicodedata.category(c) != 'Cf') + name = _LEADING_BAR_TAG_RE.sub('', name) # leading "┃CANAL+┃" bouquet tag # Map emoji-as-letters (⚽ = 'o' in "SP⚽RTS") and strip emoji decoration, before @@ -356,8 +365,18 @@ def normalize_name(self, name, user_ignored_tags=None, ignore_quality=True, igno # "SPoRTS" to "3840P" and break the word-boundary anchor. for pattern in RESOLUTION_PATTERNS: name = re.sub(pattern, '', name, flags=re.IGNORECASE) + # Replace with a SPACE, not '' (bug-126). Every QUALITY_PATTERN also + # consumes the whitespace flanking the tag, so deleting the match glues + # the tag's neighbours together whenever a token follows it: + # "SKY NEWS FHD rec" -> "SKY NEWSrec", "CNN [HD] USA" -> "CNNUSA". A + # glued token is also unreachable by a user ignore tag (\brec\b finds no + # boundary inside "NEWSrec"), so the custom-tag escape hatch silently did + # nothing. Tags at the start/end just leave an edge space, which the + # whitespace cleanup at the end of this method strips. The loop still + # terminates: a match always removes >=2 tag chars and adds at most one + # space, so each pass strictly shortens the name. for pattern in QUALITY_PATTERNS: - name = re.sub(pattern, '', name, flags=re.IGNORECASE) + name = re.sub(pattern, ' ', name, flags=re.IGNORECASE) # Normalize spacing around numbers name = re.sub(r'([a-zA-Z])(\d)', r'\1 \2', name) @@ -702,9 +721,21 @@ def _trailing_number(name): 'KIND', 'KING', 'KINGS', 'KISS', 'KITE', 'KNEE', 'KNEW', 'KNOW', 'KNOWN', }) + # OTA branding context that immediately follows a station callsign: a channel + # number (optionally prefixed by a broadcast suffix), e.g. "KING 5", "WAVE 3", + # "WOOD TV8", "WHO 13". Used to rescue a denylisted common-word callsign in a + # loose position WITHOUT also rescuing bare program words ("King of the Hill", + # "Doctor Who"), which never carry this trailing number. bug-098. + _OTA_NUMBER_CONTEXT = re.compile(r'^\s+(?:TV|DT|CD|LP|LD)?\s*\d{1,3}\b', re.IGNORECASE) + def _is_callsign_allowed(self, callsign): """A candidate callsign is allowed if it is not denylisted, OR the plugin - supplied a known-real callsign set that contains it (DB rescue).""" + supplied a known-real callsign set that contains it (DB rescue). + + NOTE: this full rescue is used only at the PARENTHESIZED priorities (1/1b), + where the parentheses are an unambiguous OTA signal. The end-of-name and + loose priorities deliberately do NOT use it for denylisted words -- see + bug-098 hardening in _compute_callsign_with_confidence.""" return (callsign not in self._CALLSIGN_DENYLIST or (self._known_callsigns is not None and callsign in self._known_callsigns)) @@ -746,18 +777,28 @@ def _compute_callsign_with_confidence(self, channel_name): if paren_suffix_match: return paren_suffix_match.group(1).upper(), True - # Priority 3: Callsigns at the end + # Priority 3: Callsigns at the end. A denylisted common word at the end + # ("WOLF KING", "Doctor Who") is NOT rescued here -- end position alone is + # too weak a signal for a word that is also a real callsign. Non-denylisted + # callsigns (and suffixed forms like "KING-TV") still match. bug-098. end_match = re.search(r'\b([KW][A-Z]{2,4}(?:-(?:TV|CD|LP|DT|LD))?)\s*(?:\.[a-z]+)?\s*$', channel_name, re.IGNORECASE) if end_match: callsign = end_match.group(1).upper() - if self._is_callsign_allowed(callsign): + if callsign not in self._CALLSIGN_DENYLIST: return callsign, True - # Priority 4: Any word matching callsign pattern (low confidence) + # Priority 4: Any word matching callsign pattern (low confidence). A + # denylisted common word is rescued here ONLY in OTA branding context -- + # immediately followed by a channel number ("KING 5", "WAVE 3", "WOOD + # TV8", "WHO 13") -- never as a bare program word ("King of the Hill", + # "Doctor Who", "Will Ferrell"). bug-098. word_match = re.search(r'\b([KW][A-Z]{2,4}(?:-(?:TV|CD|LP|DT|LD))?)\b', channel_name, re.IGNORECASE) if word_match: callsign = word_match.group(1).upper() - if self._is_callsign_allowed(callsign): + if callsign not in self._CALLSIGN_DENYLIST: + return callsign, False + if (self._known_callsigns is not None and callsign in self._known_callsigns + and self._OTA_NUMBER_CONTEXT.match(channel_name[word_match.end():])): return callsign, False return None, False diff --git a/plugins/lineuparr/notify_bridge.py b/plugins/lineuparr/notify_bridge.py new file mode 100644 index 0000000..1160fb2 --- /dev/null +++ b/plugins/lineuparr/notify_bridge.py @@ -0,0 +1,177 @@ +"""Lineuparr's emit layer for the Newsflasharr notification service. + +This module owns the guard boundary. The vendored client's notify() never raises, +but the code around it can, and a bug here must never break the matching run that +produced the report. Every public function in this file is written so a failure is +reported rather than thrown. + +Settings are read from the dict passed in on every call and never cached on an +instance. A value primed on one entry path and read back with getattr on another +fails silently, with no crash and no log line, which is a failure this codebase +has shipped before. +""" +import json +import os + +_TRIGGERS = ("never", "every_run") + +# Off by default, deliberately. A plugin that starts emailing after an upgrade +# because a setting happened to be absent is worse than one the operator has to +# switch on. Dispatcharr also never prunes a stored setting when its field is +# removed, so an absent value and a stale value are both possible here. +_DEFAULT_TRIGGER = "never" + +# Which report files are emailed. A notification carries ONE attachment, so +# "both" means two emails per run and either single format means one. +_FORMATS = ("html", "csv", "both") +_DEFAULT_FORMAT = "both" + +# The plugin key. Newsflasharr routing and deduplication both key on it, so it +# must stay stable. +SOURCE = "lineuparr" + +# The event name every report notification carries. Newsflasharr routing rules +# match on it, so it must stay stable. "usage_report" matches the naming the +# other report callers on this installation already use, so an operator reading +# the routing rules sees one convention rather than several. +EVENT = "usage_report" + +# Everything Newsflasharr needs configured before it can send mail at all. Only +# the PRESENCE of each is ever checked or reported: smtp_password is one of them +# and its value must never reach a log line or a toast. +SMTP_REQUIRED = ("smtp_server", "smtp_username", "smtp_password", "smtp_to") + + +def routes_to_smtp(nf_settings, source=SOURCE, event=EVENT): + """Would a report from this plugin actually reach the email channel? + + Newsflasharr sends an event to `default_channels` when no rule matches it, so + a missing routing rule is invisible from this side: the spool write succeeds, + a delivery is recorded, and the mail goes somewhere other than the inbox. + This is the check that makes that visible. + + `routing_rules` is stored as a JSON string, not a list, so it is parsed + defensively; a list is accepted too in case that ever changes. A rule with no + source or no event is a wildcard and matches. Never raises. + """ + nf_settings = nf_settings if isinstance(nf_settings, dict) else {} + raw = nf_settings.get("routing_rules") + rules = raw if isinstance(raw, list) else [] + if isinstance(raw, str): + try: + rules = json.loads(raw) + except (ValueError, TypeError): + rules = [] + for rule in rules if isinstance(rules, list) else []: + if not isinstance(rule, dict): + continue + match = rule.get("match") if isinstance(rule.get("match"), dict) else {} + if match.get("source") not in (None, source): + continue + if match.get("event") not in (None, event): + continue + if any("smtp" in str(c).lower() for c in (rule.get("channels") or [])): + return True + return "smtp" in str(nf_settings.get("default_channels") or "").lower() + + +def is_enabled(settings): + """Is the Newsflasharr master toggle on? + + Public on purpose. The Email Report Now action needs this check to fail fast + before it does any work, and a caller in another module must not reach for a + private helper: nothing would pin that call, so an ordinary rename in this + file would break the action silently. + """ + value = (settings or {}).get("notify_enabled", False) + if isinstance(value, str): + value = value.lower() in ("true", "yes", "1") + return bool(value) + + +def resolve_report_trigger(settings): + """Return "never" or "every_run", never anything else. + + An unrecognised or missing value resolves to "never" rather than raising or + guessing. dict.get cannot distinguish absent from present-but-None. + """ + value = (settings or {}).get("notify_report_on", _DEFAULT_TRIGGER) + if not isinstance(value, str): + return _DEFAULT_TRIGGER + value = value.strip().lower() + return value if value in _TRIGGERS else _DEFAULT_TRIGGER + + +def resolve_report_format(settings): + """Return "html", "csv" or "both", never anything else. + + An unrecognised or missing value resolves to "both". That is the safe + direction: the operator still receives the report rather than silently + receiving nothing, which is what falling back to a single format could do if + that format later failed to render. + """ + value = (settings or {}).get("notify_report_format", _DEFAULT_FORMAT) + if not isinstance(value, str): + return _DEFAULT_FORMAT + value = value.strip().lower() + return value if value in _FORMATS else _DEFAULT_FORMAT + + +def should_emit(settings): + """Return (bool, reason). The reason is operator-readable when False.""" + if not is_enabled(settings): + return False, "notifications to Newsflasharr are switched off" + if resolve_report_trigger(settings) == "never": + return False, "the report trigger is set to never" + return True, None + + +def emit_reports(notify_fn, settings, written): + """Emit one notification per report file. Returns {"sent", "skipped_reason"}. + + `notify_fn` is injected rather than imported so tests can observe the call + without a spool directory. + + Each path must be a caller-owned, never-rewritten timestamped file that + already exists on disk. An email send re-reads the attachment path on every + retry attempt, so a file rewritten in place would be a different file on the + second attempt. A path that is missing is skipped rather than sent, because a + green write result does not prove the artifact exists. + + Two events are emitted, not one, when both formats are wanted: a notification + carries a single attachment, so the HTML page and the CSV arrive as two + separate emails. + """ + result = {"sent": 0, "skipped_reason": None} + try: + allowed, reason = should_emit(settings) + if not allowed: + result["skipped_reason"] = reason + return result + written = written or {} + if written.get("error"): + result["skipped_reason"] = written["error"] + return result + wanted = resolve_report_format(settings) + for key, label in (("html_path", "HTML report"), ("csv_path", "CSV report")): + if wanted != "both" and not key.startswith(wanted): + continue + path = written.get(key) + if not path or not os.path.isfile(path): + continue + ok = notify_fn( + source=SOURCE, + title=f"Lineuparr {label} ready", + body=f"Attached: {os.path.basename(path)}", + event=EVENT, + severity="info", + kind="event", + dedup_key=None, + url=None, + attachment=path, + ) + if ok: + result["sent"] += 1 + except Exception as e: + result["skipped_reason"] = f"the emit path raised and was contained: {e}" + return result diff --git a/plugins/lineuparr/notify_client.py b/plugins/lineuparr/notify_client.py new file mode 100644 index 0000000..ba0e607 --- /dev/null +++ b/plugins/lineuparr/notify_client.py @@ -0,0 +1,211 @@ +"""Notifyarr caller client — vendor this single file into your plugin. + +Writes one redacted JSON event into Notifyarr's spool. Stdlib only, no +imports from Notifyarr. never raises. True = durably spooled (NOT +"delivered"). Strings you pass here are POSTed to external services +(Discord etc.): never place raw stream URLs, provider hostnames or +unfiltered exception text in title/body — redaction is best-effort +shape-matching, not a guarantee. Contract: notifier spec §3. +""" +from __future__ import annotations + +import json +import os +import re +import sys +import time +import types + +SCHEMA_V = 1 +CLIENT_VERSION = "1.2.0" +DEFAULT_BASE = "/data/newsflasharr" +MAX_BODY_BYTES = 65536 +MAX_SPOOL_FILES = 1000 +# Headroom only `critical` may consume, so a backed-up spool cannot +# silently swallow a real incident behind a flood of info events. +CRITICAL_RESERVE = 200 +COUNT_CACHE_S = 5.0 +_STATE_KEY = "_notifyarr_client_state" + +_CREDS_RE = re.compile(r"(/(?:live|movie|series)/)[^/\s?]+/[^/\s?]+(?=[/?\s]|$)", + re.IGNORECASE) +_QUERY_CREDS_RE = re.compile(r"([?&](?:username|user|password|pass)=)[^&\s]+", + re.IGNORECASE) +_BASIC_AUTH_RE = re.compile(r"(://)[^/\s@]+:[^/\s@]+(@)") +_SOURCE_RE = re.compile(r"[^a-z0-9_-]") + +# Stripping the username and password out of a provider URL leaves the edge +# hostname and the numeric stream id, and both identify the operator's provider +# account to anyone reading a Discord message or an email. Those are removed too. +# +# The rewrite is scoped to URLs, never to free text, and that scoping is the +# whole safety argument. A rule that hunts for bare hostnames in prose needs a +# curated top-level-domain allowlist, a file-extension denylist, a two-label +# minimum and public-suffix handling, because .ts, .sh, .zip and .mov are all +# real top-level domains. A host sitting between "://" and the next "/" needs +# none of that, because the URL syntax has already identified it. +_URL_RE = re.compile( + r"(?P[a-z][a-z0-9+.\-]*)://(?P[^/\s?#]*)" + r"(?P[^\s?#]*)(?P[?#][^\s]*)?", + re.IGNORECASE) + +# A URL is treated as media, and therefore rewritten, on either signal: a +# provider path segment, or a streaming file extension. Anything else is left +# alone, because destroying a link to documentation or to an issue makes +# notifications less useful while protecting nothing. +_MEDIA_PATH_RE = re.compile(r"/(?:live|movie|series)/", re.IGNORECASE) +_MEDIA_EXT_RE = re.compile( + r"\.(?:ts|m3u8|m3u|mp4|mkv|avi|flv|m4v|mov|mpd|mpg|mpeg|wmv)$", + re.IGNORECASE) + + +def _redact_media_url(match): + """Rewrite one URL if it looks like media, otherwise return it unchanged. + + The scheme, the /live/ style path segments and the file extension survive, + so a reader can still tell what kind of value was removed. The authority, + which carries any host, port and remaining userinfo, and the final path + segment, which carries the stream id, do not. + """ + whole = match.group(0) + path = match.group("path") or "" + if not (_MEDIA_PATH_RE.search(path) or _MEDIA_EXT_RE.search(path)): + return whole + + segments = path.split("/") + last = segments[-1] + ext = "" + dot = last.rfind(".") + if dot > 0: + ext = last[dot:] + segments[-1] = "" + ext + # The query string on a media URL is where a session token lives, so it is + # dropped rather than kept. On a non-media URL it is left untouched above. + return f'{match.group("scheme")}://{"/".join(segments)}' + + +def redact(text): + if text is None: + return None + out = str(text) + out = _CREDS_RE.sub(r"\1/", out) + out = _QUERY_CREDS_RE.sub(r"\1", out) + out = _BASIC_AUTH_RE.sub(r"\1:\2", out) + return _URL_RE.sub(_redact_media_url, out) + + +def _client_state(): + """Per-process mutable state parked in sys.modules — module globals are + wiped by Dispatcharr's reload ping-pong (bug-136).""" + mod = sys.modules.get(_STATE_KEY) + if mod is None: + mod = types.ModuleType(_STATE_KEY) # no __file__: loader ignores it + mod.cache = {} + sys.modules[_STATE_KEY] = mod + return mod.cache + + +def _spool_has_room(spool_dir, now, severity="info"): + """Backpressure, with a reserve that only `critical` may enter. + + This guard used to be CALLER-BLIND: past MAX_SPOOL_FILES it returned + False for everything, so a critical incident arriving while the spool + was backed up was dropped with no ledger row, no failed/ file and no + trace of any kind. The spool only fills when the collector has fallen + behind, which is exactly when a critical matters most. + + Non-critical events are refused at MAX_SPOOL_FILES; critical may use a + further CRITICAL_RESERVE on top. A critical refused even then leaves a + `spool_full` marker (see notify()), which Newsflasharr's `show_status` + reports as an error naming the age of the marker. + + That last clause used to read "so the loss is at least visible" while NO + production code opened the file -- a docstring asserting a safety property + the code did not implement, which is worse than plain silence because it + stops the next reader checking. + """ + cache = _client_state() + hit = cache.get(spool_dir) + if hit and now - hit[1] < COUNT_CACHE_S: + count = hit[0] + else: + try: + count = sum(1 for f in os.listdir(spool_dir) if f.endswith(".json")) + except OSError: + count = 0 + cache[spool_dir] = (count, now) + limit = MAX_SPOOL_FILES + if str(severity) == "critical": + limit += CRITICAL_RESERVE + return count < limit + + +def _mark_spool_full(spool_dir, severity, now): + """Leave a trace when a CRITICAL is refused. + + Best-effort and deliberately silent on failure: this runs on the + caller's hot path and must never raise. A single marker file is + rewritten rather than appended, so a sustained flood cannot itself + fill the disk. + """ + if str(severity) != "critical": + return + try: + path = os.path.join(os.path.dirname(spool_dir), "spool_full") + with open(path, "w", encoding="utf-8") as f: + f.write(str(int(now))) + except Exception: + pass + + +def _truncate_utf8(text, limit): + data = text.encode("utf-8") + if len(data) <= limit: + return text + return data[:limit].decode("utf-8", errors="ignore") + + +def notify(source, title, *, event=None, body="", severity="info", + kind="event", dedup_key=None, url=None, attachment=None, + base_dir=DEFAULT_BASE, _now_ms=None): + try: + spool_dir = os.path.join(base_dir, "spool") + os.makedirs(spool_dir, exist_ok=True) + now = time.time() + if not _spool_has_room(spool_dir, now, severity): + _mark_spool_full(spool_dir, severity, now) + return False + ts = int(_now_ms) if _now_ms is not None else int(now * 1000) + payload = {"v": SCHEMA_V, "client_v": CLIENT_VERSION, + "source": str(source), "kind": str(kind), + "severity": str(severity), "ts": ts, + "title": _truncate_utf8(redact(str(title)) or "", 1024), + "body": _truncate_utf8(redact(str(body)) or "", MAX_BODY_BYTES)} + if event: + payload["event"] = str(event) + if dedup_key: + payload["dedup_key"] = str(dedup_key) + if url: + payload["url"] = redact(str(url)) + if attachment: + payload["attachment"] = str(attachment) + cache = _client_state() + counter = cache["counter"] = (cache.get("counter", 0) + 1) % 10000 + safe = _SOURCE_RE.sub("_", str(source).lower())[:32] or "unknown" + rand8 = os.urandom(4).hex() + name = f"{ts}-{counter:04d}-{safe}-{rand8}.json" + tmp = os.path.join(spool_dir, f".tmp-{name}") + with open(tmp, "w", encoding="utf-8") as f: + json.dump(payload, f, separators=(",", ":")) + os.replace(tmp, os.path.join(spool_dir, name)) + return True + except Exception: + return False + + +def notifier_alive(base_dir=DEFAULT_BASE, max_age_s=120.0): + try: + return time.time() - os.path.getmtime( + os.path.join(base_dir, "state.json")) < max_age_s + except Exception: + return False diff --git a/plugins/lineuparr/plugin.json b/plugins/lineuparr/plugin.json index 0d718e6..827d43d 100644 --- a/plugins/lineuparr/plugin.json +++ b/plugins/lineuparr/plugin.json @@ -1,19 +1,19 @@ { "name": "Lineuparr", - "version": "1.26.1791747", + "version": "1.26.2142327", "description": "Mirror real-world provider channel lineups by creating channel groups, channels, and fuzzy-matching IPTV streams to them.", "author": "PiratesIRC", "license": "MIT", "repo_url": "https://github.com/PiratesIRC/Dispatcharr-Lineuparr-Plugin", "help_url": "https://github.com/PiratesIRC/Dispatcharr-Lineuparr-Plugin", "min_dispatcharr_version": "v0.20.0", - "_fields_note": "Settings fields are defined dynamically by the Plugin.fields property in plugin.py (the single source of truth \u2014 it builds live dropdowns for lineups, M3U/EPG sources and profiles). Dispatcharr only falls back to a manifest 'fields' array when the plugin provides none, so it is intentionally omitted here to prevent drift.", + "_fields_note": "Settings fields are defined dynamically by the Plugin.fields property in plugin.py (the single source of truth : it builds live dropdowns for lineups, M3U/EPG sources and profiles). Dispatcharr only falls back to a manifest 'fields' array when the plugin provides none, so it is intentionally omitted here to prevent drift.", "fields": [], "actions": [ { "id": "validate_settings", "label": "Validate Settings", - "button_label": "\u2705 Validate", + "button_label": "✅ Validate", "description": "Check all settings, show lineup summary and available profiles", "button_variant": "outline", "button_color": "blue" @@ -29,7 +29,7 @@ { "id": "preview_stream_match", "label": "Preview Stream Match", - "button_label": "\ud83d\udc41\ufe0f Preview", + "button_label": "👁️ Preview", "description": "Dry-run: show stream-to-channel matches with confidence scores (CSV export)", "button_variant": "outline", "button_color": "cyan" @@ -37,52 +37,62 @@ { "id": "full_sync", "label": "Full Sync", - "button_label": "\ud83d\ude80 Full Sync", + "button_label": "🚀 Full Sync", "description": "Create groups and channels from lineup, fuzzy-match streams, assign EPG, assign logos, enable in profiles, and clean up unmatched channels", "button_variant": "filled", "button_color": "violet", - "confirm": { "message": "This will create groups, channels, match streams, assign EPG data, assign logos, enable channels in profiles, and remove unmatched channels. Continue?" } + "confirm": { + "message": "This will create groups, channels, match streams, assign EPG data, assign logos, enable channels in profiles, and remove unmatched channels. Continue?" + } }, { "id": "sync_channels", "label": "Sync Channels Only", - "button_label": "\ud83d\udcfa Sync Channels", + "button_label": "📺 Sync Channels", "description": "Create/update groups and channels from lineup (no stream matching)", "button_variant": "filled", "button_color": "green", - "confirm": { "message": "This will create groups and channels from the lineup. Continue?" } + "confirm": { + "message": "This will create groups and channels from the lineup. Continue?" + } }, { "id": "apply_stream_match", "label": "Apply Stream Match Only", - "button_label": "\ud83c\udfaf Match Streams", + "button_label": "🎯 Match Streams", "description": "Attach matched streams to existing channels with quality ordering", "button_variant": "filled", "button_color": "green", - "confirm": { "message": "This will attach matched streams to channels and remove unmatched channels. Continue?" } + "confirm": { + "message": "This will attach matched streams to channels and remove unmatched channels. Continue?" + } }, { "id": "apply_epg_match", "label": "Apply EPG Match", - "button_label": "\ud83d\udcf0 Match EPG", + "button_label": "📰 Match EPG", "description": "Fuzzy-match EPG data to channels and assign program guides", "button_variant": "filled", "button_color": "green", - "confirm": { "message": "This will assign EPG data to Lineuparr-managed channels. Continue?" } + "confirm": { + "message": "This will assign EPG data to Lineuparr-managed channels. Continue?" + } }, { "id": "apply_logo_match", "label": "Assign Logos", - "button_label": "\ud83c\udfa8 Assign Logos", + "button_label": "🎨 Assign Logos", "description": "Auto-assign channel logos from EPG icons, Logo Manager, or tv-logos GitHub repo", "button_variant": "filled", "button_color": "green", - "confirm": { "message": "This will assign logos to channels that don't have one. Continue?" } + "confirm": { + "message": "This will assign logos to channels that don't have one. Continue?" + } }, { "id": "resort_streams", "label": "Re-sort Streams by Quality", - "button_label": "\u2b50 Re-sort", + "button_label": "⭐ Re-sort", "description": "Re-order already-attached streams using latest quality data. Run this after IPTV Checker scans your streams to use actual resolution/bitrate instead of name-based quality detection.", "button_variant": "outline", "button_color": "orange" @@ -90,11 +100,21 @@ { "id": "clear_csv_exports", "label": "Clear CSV Exports", - "button_label": "\ud83d\uddd1\ufe0f Clear CSV", + "button_label": "🗑️ Clear CSV", "description": "Delete all Lineuparr CSV export files", "button_variant": "outline", "button_color": "red", - "confirm": { "message": "Delete all Lineuparr CSV exports?" } + "confirm": { + "message": "Delete all Lineuparr CSV exports?" + } + }, + { + "id": "email_report", + "label": "Email Report Now", + "description": "Send the newest report to Newsflasharr for delivery. Sends what is already on disk rather than re-running a match.", + "button_label": "✉️ Email Report", + "button_variant": "outline", + "button_color": "cyan" } ] } diff --git a/plugins/lineuparr/plugin.py b/plugins/lineuparr/plugin.py index 480afad..a45a918 100644 --- a/plugins/lineuparr/plugin.py +++ b/plugins/lineuparr/plugin.py @@ -18,9 +18,11 @@ from django.db import transaction -from .fuzzy_matcher import FuzzyMatcher, has_upgrade_quality, detect_category_country, country_codes_in_text +from .fuzzy_matcher import (FuzzyMatcher, has_upgrade_quality, detect_category_country, + detect_name_country_prefix, country_codes_in_text) from .aliases import CHANNEL_ALIASES, COUNTRY_ALIASES from .progress_status import save_progress_atomic, load_progress, build_status_message +from . import notify_bridge, reports from apps.channels.models import Channel, ChannelGroup, ChannelProfile, ChannelProfileMembership, ChannelStream, Stream from apps.m3u.models import M3UAccount @@ -64,7 +66,7 @@ def _clean_json_text(s): class PluginConfig: - PLUGIN_VERSION = "1.26.1791747" + PLUGIN_VERSION = "1.26.2142327" DEFAULT_FUZZY_MATCH_THRESHOLD = 80 DEFAULT_PRIORITIZE_QUALITY = True @@ -499,6 +501,43 @@ def fields(self): "placeholder": "{\"Channel Name\": [\"alias 1\", \"alias 2\"]}", "help_text": "JSON object mapping a lineup channel name to extra alias names (a bare string is accepted as a single alias). Leave blank to use built-in aliases only.", }, + # --- Section: Notifications --- + { + "id": "_sec_notify", + "type": "info", + "label": "Report delivery", + "help_text": "Every run that exports a CSV also writes a shareable HTML report with a sortable table. Newsflasharr can email it. This is off by default and needs the Newsflasharr plugin installed, enabled, and routing this plugin's usage_report event to your email channel.", + }, + { + "id": "notify_enabled", + "label": "Send reports to Newsflasharr", + "type": "boolean", + "default": False, + "help_text": "Master switch. When off, reports are still written to disk and nothing is sent.", + }, + { + "id": "notify_report_on", + "label": "When to send", + "type": "select", + "default": "never", + "options": [ + {"value": "never", "label": "Never - write the report, send nothing"}, + {"value": "every_run", "label": "Every run that produces a report"}, + ], + "help_text": "Applies to the channel sync preview, preview stream match, apply stream match, and EPG match.", + }, + { + "id": "notify_report_format", + "label": "What to attach", + "type": "select", + "default": "both", + "options": [ + {"value": "both", "label": "Both the HTML page and the CSV"}, + {"value": "html", "label": "HTML page only"}, + {"value": "csv", "label": "CSV only"}, + ], + "help_text": "A notification carries one attachment, so choosing both sends two emails per run.", + }, # --- Section: Advanced --- { "id": "_sec_advanced", @@ -538,6 +577,7 @@ def run(self, action, params, context): "apply_logo_match": self._apply_logo_match, "resort_streams": self._resort_streams, "clear_csv_exports": self._clear_csv_exports, + "email_report": self._email_report, # Legacy actions (no UI buttons, kept for API compatibility) "preview_groups": self._preview_groups, "sync_groups": self._sync_groups, @@ -620,6 +660,8 @@ def _load_lineup(self, settings, logger): if "categories" not in data: raise ValueError(f"Invalid lineup file: missing 'categories' key") + self._rewrite_country_prefixes(data, logger) + # Apply category detail level transformation data = self._apply_category_detail(data, detail) @@ -628,6 +670,60 @@ def _load_lineup(self, settings, logger): logger.info(f"{LOG_PREFIX} Loaded lineup: {data.get('package', 'Unknown')} ({sum(len(v) for v in data['categories'].values())} channels, {len(data['categories'])} categories, detail={detail})") return data + @staticmethod + def _rewrite_country_prefixes(data, logger): + """Turn a "CC_" channel-name prefix into the channel's own country. + + "UK_CNN" inside a US lineup becomes name "CNN" with country UK. Done here, + once, before anything else sees the lineup: the name is used for channel + creation, for matching, for reports and for EPG matching, so a prefix left + in place would have to be stripped correctly in several unrelated places. + + Mutates data in place. Runs before _apply_category_detail so every detail + level sees the rewritten entries. + """ + rewritten = 0 + for channels in data.get("categories", {}).values(): + if not isinstance(channels, list): + continue + for entry in channels: + if not isinstance(entry, dict): + continue + country, stripped = detect_name_country_prefix(entry.get("name")) + if not country: + continue + entry["name"] = stripped + entry["_country"] = country + rewritten += 1 + + if rewritten: + logger.info( + f"{LOG_PREFIX} {rewritten} lineup " + f"{'channel carries' if rewritten == 1 else 'channels carry'} a " + f"country prefix in the name" + ) + + @staticmethod + def _resolve_entry_country(entry, category_cc, logger=None): + """Country code to enforce for ONE channel. + + Most specific first: the channel's own country (from its name prefix), + then the category's, then the lineup filename's, which category_cc already + carries. Mirrors _resolve_category_country one level down. + + The override is logged for the same reason the category-level one is: a + channel that does not match is otherwise guesswork. + """ + entry_cc = entry.get("_country") if isinstance(entry, dict) else None + if not entry_cc: + return category_cc + if logger is not None and entry_cc != category_cc: + logger.info( + f"{LOG_PREFIX} Channel '{entry.get('name')}' country: {entry_cc} " + f"(from name prefix, overriding {category_cc or 'lineup default'})" + ) + return entry_cc + def _apply_category_detail(self, data, detail): """Transform lineup categories based on the detail level setting. Always returns a deep copy so callers cannot mutate the cache.""" @@ -888,25 +984,58 @@ def _get_all_streams(self, settings, logger): logger.info(f"{LOG_PREFIX} Loaded {len(streams)} streams" + (f" from {len(valid_ids)} M3U sources" if valid_ids else "")) return streams - def _build_alias_map(self, settings, logger): + def _alias_map_provider(self, settings, logger): + """Return a callable mapping a country code to that country's alias map. + + A lineup can carry channels from several countries (a category named + "UK| Sports" inside a mixed lineup), and each needs the alias table for + ITS country, not for the lineup filename's country. + + Memoized for the life of ONE run and no longer. The map depends on the + custom_aliases setting and on the selected lineup's embedded aliases, + both of which change between runs while the plugin object survives in a + long-lived worker process. Instance-level caching would serve a stale + map after a settings edit, and would carry one lineup's embedded aliases + into another lineup of the same country. + """ + cache = {} + + def alias_map_for(country): + key = (country or "").upper() + if key not in cache: + cache[key] = self._build_alias_map(settings, logger, country=key or None) + return cache[key] + + return alias_map_for + + def _build_alias_map(self, settings, logger, country=None): """Merge built-in aliases with user custom aliases. Built-in aliases are global (keyed by channel name). Country-scoped - overrides from COUNTRY_ALIASES are merged on top for THIS lineup's - country only, so a name that exists in multiple markets (e.g. "TLC", - "MTV") doesn't leak one country's stream-name variants into another - (bug-063). Custom user aliases are merged last and win. + overrides from COUNTRY_ALIASES are merged on top for ONE country only, so + a name that exists in multiple markets (e.g. "TLC", "MTV") does not leak + one country's stream-name variants into another (bug-063). Custom user + aliases are merged last and win. + + The country defaults to the one in the lineup filename. Callers that know + a channel's own country (from its category name or its name prefix) pass + it explicitly, so a UK channel inside a US lineup gets UK aliases. """ alias_map = dict(CHANNEL_ALIASES) - try: - lineup_cc, _ = self._parse_lineup_filename(settings.get("lineup_file", "")) - except Exception: - lineup_cc = None + if country: + lineup_cc = country + else: + try: + lineup_cc, _ = self._parse_lineup_filename(settings.get("lineup_file", "")) + except Exception: + lineup_cc = None if lineup_cc: for k, v in COUNTRY_ALIASES.get(lineup_cc.upper(), {}).items(): alias_map[k] = list(dict.fromkeys(alias_map.get(k, []) + list(v))) + self._merge_embedded_aliases(alias_map, settings, logger) + custom_str = _clean_json_text(settings.get("custom_aliases") or "") if custom_str: try: @@ -958,6 +1087,76 @@ def _build_alias_map(self, settings, logger): return alias_map + def _merge_embedded_aliases(self, alias_map, settings, logger): + """Merge per-channel "aliases" arrays carried inside the lineup JSON. + + A lineup channel entry may declare its own aliases: + + {"name": "My9 New York", "number": 509, + "aliases": ["WWOR", "WWOR-TV", "MY9"]} + + This lets a lineup ship the stream-name variants that are specific to it + (provider callsigns, HDHomeRun and XMLTV display names) without adding + them to the global built-in table, where a name that exists in several + markets would leak across countries. + + Merged after the built-in and country-scoped tables and before the user's + custom aliases, so the user setting still has the last word. A lineup + that cannot be read leaves the alias map untouched: aliases are an + enhancement, and failing the whole matching run over them would trade a + missing alias for no matches at all. + """ + try: + lineup = self._load_lineup(settings, logger) + except Exception as e: + logger.warning(f"{LOG_PREFIX} Could not read embedded lineup aliases: {e}") + return + + categories = lineup.get("categories") if isinstance(lineup, dict) else None + if not isinstance(categories, dict): + return + + merged = 0 + for channels in categories.values(): + if not isinstance(channels, list): + continue + for channel in channels: + if not isinstance(channel, dict): + continue + + name = str(channel.get("name") or "").strip() + raw = channel.get("aliases") + if not name or not raw: + continue + + # Same shapes the custom_aliases setting accepts: a list, or a + # bare string for a single alias. Anything else is a mistake in + # the lineup file and is reported rather than dropped silently. + if isinstance(raw, str): + candidates = [raw] + elif isinstance(raw, list): + candidates = raw + else: + logger.warning( + f"{LOG_PREFIX} Lineup aliases for '{name}' must be a string " + f"or list, got {type(raw).__name__} - ignored" + ) + continue + + clean = [a.strip() for a in candidates + if isinstance(a, str) and a.strip()] + if not clean: + continue + + alias_map[name] = list(dict.fromkeys(alias_map.get(name, []) + clean)) + merged += 1 + + if merged: + logger.info( + f"{LOG_PREFIX} Merged embedded aliases from {merged} lineup " + f"{'channel' if merged == 1 else 'channels'}" + ) + def _build_upgrade_twin_set(self, lineup, matcher): """Return lineup channel names (both tiers) that have a twin in the other tier. @@ -1230,6 +1429,133 @@ def _release_lock(self, logger): except Exception as e: logger.error(f"{LOG_PREFIX} Failed to release lock: {e}") + def _m3u_account_names(self, logger): + """Active M3U account names, or None when the lookup failed. + + None and an empty list mean different things to the report builder: an + installation may legitimately have no active accounts, but a failed + lookup must not be mistaken for one, because these names are the report's + primary redaction input. + """ + try: + return [a['name'] for a in M3UAccount.objects.filter(is_active=True).values('name') + if a.get('name')] + except Exception as e: + logger.warning(f"{LOG_PREFIX} Could not read M3U account names for the report: {e}") + return None + + def _write_html_report(self, title, columns, rows, settings, logger, export_filename=None): + """Write the shareable HTML and CSV report for a run. Returns the HTML + path, or None. + + Never raises and never fails the run that produced the rows: a report is + not the plugin's real work. A failure is logged and the action carries on. + + `columns` is the allow-list of (row key, header) pairs. A key absent from + it never reaches the report, so a column added to a CSV writer later + cannot start appearing in a shared file on its own. + """ + try: + accounts = self._m3u_account_names(logger) + if accounts is None: + logger.warning(f"{LOG_PREFIX} HTML report skipped: M3U account names unavailable, " + f"so stream names could not be scrubbed") + return None + model = reports.build_model( + title, columns, rows, + account_names=accounts, + settings=settings or {}, + lineup=(settings or {}).get("lineup_file", ""), + version=PluginConfig.PLUGIN_VERSION, + now=time.time(), + export_filename=export_filename, + ) + result = reports.write_report(model, reports.REPORT_DIR, time.time()) + if result.get("error"): + logger.warning(f"{LOG_PREFIX} HTML report not written: {result['error']}") + return None + logger.info(f"{LOG_PREFIX} Report written: {result['html_path']}") + self._emit_reports(result, settings, logger) + return result["html_path"] + except Exception as e: + logger.warning(f"{LOG_PREFIX} HTML report failed: {e}") + return None + + @staticmethod + def _notify_client(): + """Import the vendored client lazily. + + Deliberately not a module-scope import: a notification path is never worth + failing the plugin's load over, and the fallback form is what lets the + module be imported by a test harness that has no package parent. + """ + try: + from . import notify_client + except ImportError: + import notify_client + return notify_client + + def _emit_reports(self, written, settings, logger): + """Hand the written report files to Newsflasharr. Returns its result dict. + + Never raises and never fails the run that produced the report. + """ + try: + result = notify_bridge.emit_reports( + self._notify_client().notify, settings or {}, written) + if result.get("sent"): + logger.info(f"{LOG_PREFIX} Report queued for delivery: " + f"{result['sent']} notification(s)") + elif result.get("skipped_reason"): + logger.debug(f"{LOG_PREFIX} Report not sent: {result['skipped_reason']}") + return result + except Exception as e: + logger.warning(f"{LOG_PREFIX} Report delivery failed: {e}") + return {"sent": 0, "skipped_reason": str(e)} + + def _email_report(self, settings, logger): + """Send the newest report on disk, on demand. + + Sends what is already there rather than re-running a match: a match is + minutes of work and the operator asked to send a report, not to produce + one. + """ + if not notify_bridge.is_enabled(settings): + return {"status": "error", + "error": "Sending reports to Newsflasharr is switched off. Turn on " + "'Send reports to Newsflasharr' in the settings above, save, " + "and try again."} + try: + entries = [] + for suffix in (".html", ".csv"): + found = sorted( + (os.path.join(reports.REPORT_DIR, n) + for n in os.listdir(reports.REPORT_DIR) + if n.startswith(reports.FILENAME_PREFIX) and n.endswith(suffix)), + key=os.path.getmtime, reverse=True) + entries.append(found[0] if found else None) + except OSError: + entries = [None, None] + + if not any(entries): + return {"status": "error", + "error": "No report to send yet. Run Preview Stream Match, or any " + "action that exports a CSV, and a report is written " + f"to {reports.REPORT_DIR}."} + + written = {"html_path": entries[0], "csv_path": entries[1], "error": None} + # The on-demand button always sends, whatever the schedule setting says: + # the operator pressing it IS the trigger. + result = self._emit_reports(written, dict(settings or {}, notify_report_on="every_run"), + logger) + if not result.get("sent"): + reason = result.get("skipped_reason") or "no report file could be read" + return {"status": "error", "error": f"Nothing was sent: {reason}"} + names = ", ".join(os.path.basename(p) for p in entries if p) + return {"status": "ok", + "message": f"Queued {result['sent']} notification(s) with Newsflasharr: {names}", + "file": entries[0] or entries[1]} + def _export_csv(self, filename, rows, fieldnames, logger, settings=None): """Export data to CSV in the exports directory with settings header.""" try: @@ -1601,8 +1927,14 @@ def _preview_channels(self, settings, logger): # Export CSV ts = datetime.now().strftime("%Y%m%d_%H%M%S") - self._export_csv(f"lineuparr_preview_channels_{ts}.csv", results, + csv_name = f"lineuparr_preview_channels_{ts}.csv" + self._export_csv(csv_name, results, ["Channel", "Number", "Category", "Group", "Status"], logger, settings) + self._write_html_report( + "Channel sync preview", + [("Channel", "Channel"), ("Number", "Number"), ("Category", "Category"), + ("Group", "Group"), ("Status", "Status")], + results, settings, logger, export_filename=csv_name) return { "status": "ok", @@ -1642,7 +1974,7 @@ def _do_preview_stream_match(self, settings, logger): if lineup.get("status") == "error": return lineup matcher = self._init_fuzzy_matcher(settings, logger) - alias_map = self._build_alias_map(settings, logger) + alias_map_for = self._alias_map_provider(settings, logger) upgrade_twin_set = self._get_upgrade_twin_set(settings, lineup, matcher) streams = self._get_all_streams(settings, logger) assigner = self._init_assigner_state(settings) @@ -1685,13 +2017,14 @@ def _do_preview_stream_match(self, settings, logger): return ch_name = entry["name"] + entry_cc = self._resolve_entry_country(entry, cat_cc, logger) ch_number = self._get_channel_number(settings, entry, assigner) boost_number = self._parse_channel_number(entry.get("number")) if use_number_boost else None matches = matcher.match_all_streams( - ch_name, unique_stream_names, alias_map, + ch_name, unique_stream_names, alias_map_for(entry_cc), channel_number=boost_number, - lineup_country=cat_cc, + lineup_country=entry_cc, quality_aware=(ch_name in upgrade_twin_set), ) @@ -1739,9 +2072,16 @@ def _do_preview_stream_match(self, settings, logger): results.sort(key=lambda r: (0 if r["Score"] == 0 else 1, r["Score"])) ts = datetime.now().strftime("%Y%m%d_%H%M%S") - self._export_csv(f"lineuparr_preview_match_{ts}.csv", results, + csv_name = f"lineuparr_preview_match_{ts}.csv" + self._export_csv(csv_name, results, ["Channel", "Number", "Category", "Best Match", "Score", "Match Type", "Total Matches", "Top Matches"], logger, settings) + report_path = self._write_html_report( + "Preview stream match", + [("Channel", "Channel"), ("Number", "Number"), ("Category", "Category"), + ("Best Match", "Best match"), ("Score", "Score"), ("Match Type", "Match type"), + ("Total Matches", "Total matches")], + results, settings, logger, export_filename=csv_name) total = matched_count + unmatched_count type_breakdown = ", ".join(f"{v} {k}" for k, v in sorted(type_counts.items(), key=lambda x: -x[1])) @@ -1751,6 +2091,9 @@ def _do_preview_stream_match(self, settings, logger): f"{unmatched_count} unmatched. " f"Types: {type_breakdown}. CSV exported." ) + if report_path: + msg += f" Report: {report_path}" + progress.finish(summary=msg) logger.info(f"{LOG_PREFIX} {msg}") send_websocket_update('updates', 'update', { @@ -2312,7 +2655,7 @@ def _do_apply_stream_match(self, settings, logger): return lineup prefix = self._get_group_prefix(settings, lineup) matcher = self._init_fuzzy_matcher(settings, logger) - alias_map = self._build_alias_map(settings, logger) + alias_map_for = self._alias_map_provider(settings, logger) rate_limiter = SmartRateLimiter(settings.get("rate_limiting", PluginConfig.DEFAULT_RATE_LIMITING)) lineup_cc, _ = self._parse_lineup_filename(settings.get("lineup_file", "")) if lineup_cc: @@ -2371,6 +2714,7 @@ def _do_apply_stream_match(self, settings, logger): return {"status": "ok", "message": "Stream matching cancelled by user."} ch_name = entry["name"] + entry_cc = self._resolve_entry_country(entry, cat_cc, logger) ch_number = self._parse_channel_number(entry.get("number")) if use_number_boost else None channel_id = existing_channels.get((ch_name, group_id)) @@ -2382,9 +2726,9 @@ def _do_apply_stream_match(self, settings, logger): # Match streams using deduplicated names matches = matcher.match_all_streams( - ch_name, unique_stream_names, alias_map, + ch_name, unique_stream_names, alias_map_for(entry_cc), channel_number=ch_number, - lineup_country=cat_cc, + lineup_country=entry_cc, quality_aware=(ch_name in upgrade_twin_set), ) @@ -2495,11 +2839,18 @@ def _do_apply_stream_match(self, settings, logger): # Export CSV ts = datetime.now().strftime("%Y%m%d_%H%M%S") mode = "dryrun" if dry_run else "applied" + csv_name = f"lineuparr_match_{mode}_{ts}.csv" self._export_csv( - f"lineuparr_match_{mode}_{ts}.csv", csv_rows, + csv_name, csv_rows, ["Channel", "Number", "Category", "Streams Attached", "Best Match", "Best Score", "Match Type"], logger, settings ) + self._write_html_report( + "Stream match (dry run)" if dry_run else "Stream match applied", + [("Channel", "Channel"), ("Number", "Number"), ("Category", "Category"), + ("Streams Attached", "Streams attached"), ("Best Match", "Best match"), + ("Best Score", "Best score"), ("Match Type", "Match type")], + csv_rows, settings, logger, export_filename=csv_name) # Save state self._save_state({ @@ -2621,7 +2972,7 @@ def _do_apply_epg_match(self, settings, logger): return lineup prefix = self._get_group_prefix(settings, lineup) matcher = self._init_fuzzy_matcher(settings, logger) - alias_map = self._build_alias_map(settings, logger) + alias_map_for = self._alias_map_provider(settings, logger) rate_limiter = SmartRateLimiter(settings.get("rate_limiting", PluginConfig.DEFAULT_RATE_LIMITING)) # Extract lineup country code for EPG country matching @@ -2708,8 +3059,6 @@ def _do_apply_epg_match(self, settings, logger): continue cat_cc = self._resolve_category_country(category, lineup_cc, logger) - # Lineup country first, then relaxed (None) if nothing matched. - countries = (cat_cc, None) if cat_cc else (None,) for entry in channels: if self._stop_event.is_set(): @@ -2717,6 +3066,9 @@ def _do_apply_epg_match(self, settings, logger): return {"status": "ok", "message": "EPG matching cancelled by user."} ch_name = entry["name"] + entry_cc = self._resolve_entry_country(entry, cat_cc, logger) + # Channel country first, then relaxed (None) if nothing matched. + countries = (entry_cc, None) if entry_cc else (None,) ch_number = self._parse_channel_number(entry.get("number")) if use_number_boost else None ch_data = existing_channels.get((ch_name, group_id)) @@ -2746,7 +3098,7 @@ def _do_apply_epg_match(self, settings, logger): if not pool: continue ms = matcher.match_all_streams( - ch_name, pool, alias_map, + ch_name, pool, alias_map_for(entry_cc), channel_number=ch_number, lineup_country=country, ) @@ -2756,13 +3108,13 @@ def _do_apply_epg_match(self, settings, logger): top_entries = lookup.get(top_name, []) if not top_entries: continue - best_epg = self._pick_epg_by_country(top_entries, cat_cc) + best_epg = self._pick_epg_by_country(top_entries, entry_cc) best_score = top_score best_method = top_method has_program_data = has_prog if not has_prog: logger.debug(f"{LOG_PREFIX} EPG fallback (no program data): {ch_name} -> {best_epg['name']}") - if country is None and cat_cc: + if country is None and entry_cc: logger.debug(f"{LOG_PREFIX} EPG country-relaxed match: {ch_name} -> {best_epg['name']}") break if best_epg: @@ -2823,13 +3175,22 @@ def _do_apply_epg_match(self, settings, logger): # Export CSV if csv_rows: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + csv_name = f"lineuparr_epg_match_{timestamp}.csv" self._export_csv( - f"lineuparr_epg_match_{timestamp}.csv", + csv_name, csv_rows, ["channel_name", "channel_number", "channel_group", "matched_epg_name", "epg_source", "confidence_score", "has_program_data", "match_method", "status"], logger, settings, ) + self._write_html_report( + "EPG match", + [("channel_name", "Channel"), ("channel_number", "Number"), + ("channel_group", "Group"), ("matched_epg_name", "Matched EPG entry"), + ("epg_source", "EPG source"), ("confidence_score", "Score"), + ("has_program_data", "Has programme data"), ("match_method", "Match method"), + ("status", "Status")], + csv_rows, settings, logger, export_filename=csv_name) progress.finish() self._trigger_frontend_refresh(logger) diff --git a/plugins/lineuparr/reports.py b/plugins/lineuparr/reports.py new file mode 100644 index 0000000..56150c3 --- /dev/null +++ b/plugins/lineuparr/reports.py @@ -0,0 +1,435 @@ +"""Report model and rendering for Lineuparr. + +Renders one run's results as a self-contained HTML page with a sortable table, +and as a CSV. This is separate from the plugin's own CSV export in +/data/exports: that file opens with a settings header naming the configured M3U +sources, which on a real installation is the provider's hostname, so it is a +local file and not something to hand around. + +Two structural decisions follow from the report being shareable, and both are +pinned by tests: + +The model is built by COPYING a named allow-list of columns out of the row dicts +the actions already hold in memory. It is never built by re-reading an export. +Building from an allow-list also means a column added to a CSV writer later +cannot start appearing in a shared report on its own. + +The M3U account-name scrub is a PRIMARY redaction input here, not a backstop. A +stream name frequently carries its own account label, so build_model refuses when +the account name list is None, which is how a caller reports that the lookup +failed. An empty list is a different thing and is allowed: an installation can +legitimately have no active M3U accounts. +""" +import csv +import datetime +import html +import io +import os +import re +import time + +# Reports are written here, deliberately NOT under /data/logos: Dispatcharr's +# nginx serves that tree unauthenticated to the entire local network. +REPORT_DIR = "/data/lineuparr_reports" + +# Every report file starts with this. The pruner matches on it, so a copied and +# unrenamed prefix would make pruning match nothing and the directory would grow +# forever with no error. +FILENAME_PREFIX = "lineuparr_report_" + +# How many of each file type to keep. +KEEP_REPORTS = 8 + +# A report file younger than this is never pruned, however many newer ones +# exist. A delivery that re-reads the file path on a retry would otherwise find +# it gone. +RETRY_WINDOW_SECONDS = 2400 + +# The row cap, applied ONCE before rendering. A render, measure, drop rows, +# re-render loop is deliberately not used: a large lineup matched against a large +# M3U produces thousands of rows, and these reports are built inside the uWSGI +# request, where a pure Python loop performs no input or output and so never +# yields. Under gevent that freezes the whole worker, not just the request that +# started it. +# +# Anyone raising this constant must measure in UTF-8 BYTES of the written file, +# not characters: the matcher supports Cyrillic and CJK names, which are two to +# three times longer in bytes than in characters. +MAX_REPORT_ROWS = 2000 + +# The container path of the full exports. Named in the report so a reader knows +# where the complete file is. It is a locator, not a browsable link. +EXPORTS_LOCATION = "/data/exports" + +# The only settings that reach the report. Everything else is dropped, including +# m3u_source, which names the operator's provider account, and custom_aliases, +# which is free text the operator typed. +_SAFE_SETTINGS = ( + ("match_sensitivity", "Match sensitivity"), + ("category_detail", "Category detail"), + ("channel_numbering", "Channel numbering"), + ("preserve_existing_streams", "Preserve existing streams"), +) + +# Matches a bare IPv4 address. +_IPV4_RE = re.compile(r"\b\d{1,3}(?:\.\d{1,3}){3}\b") + +# Matches an IPv6 address, including the compressed forms. Deliberately requires +# either a double colon or at least three colon separated groups, so an ordinary +# clock time such as 20:30 is not mistaken for an address. +_IPV6_RE = re.compile( + r"\b(?:[0-9A-Fa-f]{1,4}:){2,7}[0-9A-Fa-f]{1,4}\b" + r"|(?:[0-9A-Fa-f]{1,4}:)+:(?:[0-9A-Fa-f]{1,4})?" + r"|::(?:[0-9A-Fa-f]{1,4}:)*[0-9A-Fa-f]{1,4}" +) + +# Collapses the run of spaces left behind when a value is removed from the middle +# of a name. +_MULTISPACE_RE = re.compile(r"\s{2,}") + + +def sanitise_label(label, account_names): + """Remove an M3U account name wherever it appears, and nothing else. + + Matching is case insensitive and is not limited to the bracketed form, + because an account name on a real installation is a literal provider + hostname, and "ESPN backup provider.tv" leaks exactly as much as + "ESPN [provider.tv]". + + Account names are matched longest first: "provider.tv" is a prefix of + "provider.tv-alt1", and matching the shorter one first would leave a "-alt1" + fragment behind. + + An unknown bracketed value is left alone. Bracketed text in a stream name + usually carries the region or the quality, and removing every bracketed group + would collapse distinct streams into one indistinguishable name. + """ + text = str(label if label is not None else "") + for account in sorted([a for a in (account_names or []) if a], key=len, reverse=True): + escaped = re.escape(str(account)) + text = re.sub(r"\s*\[" + escaped + r"\]", "", text, flags=re.IGNORECASE) + text = re.sub(r"\s*\(" + escaped + r"\)", "", text, flags=re.IGNORECASE) + text = re.sub(escaped, "", text, flags=re.IGNORECASE) + return _MULTISPACE_RE.sub(" ", text).strip() + + +def _scrub(value, account_names): + """Apply every content rule to one free text value.""" + cleaned = sanitise_label(value, account_names) + cleaned = _IPV4_RE.sub("", cleaned) + cleaned = _IPV6_RE.sub("", cleaned) + return _MULTISPACE_RE.sub(" ", cleaned).strip() + + +def build_model(title, columns, rows, *, account_names, settings, lineup, + version, now, export_filename=None): + """Build the report model from in-memory rows. + + `columns` is a list of (row key, display header) pairs and IS the allow list. + A key absent from it is never copied, whatever the row carries. + + `account_names` of None means the M3U account lookup failed, and this raises + rather than producing an unscrubbed report. An empty list is allowed. + """ + if account_names is None: + raise ValueError( + "account_names is None, which means the M3U account lookup failed. " + "The report is not built, because the account names are the primary " + "redaction input and an empty scrub would ship unredacted names.") + + rows = list(rows or []) + total_rows = len(rows) + kept = rows[:MAX_REPORT_ROWS] + + entries = [] + for row in kept: + entries.append([_scrub(row.get(key), account_names) for key, _ in columns]) + + settings = settings if isinstance(settings, dict) else {} + summary = [("Plugin version", str(version)), + ("Generated", _fmt_ts(now)), + ("Lineup", str(lineup or "unknown"))] + for key, label in _SAFE_SETTINGS: + if key in settings: + summary.append((label, str(settings.get(key)))) + summary.append(("Rows", f"{len(entries)} of {total_rows}")) + + return { + "title": str(title), + "generated_ts": float(now or 0), + "summary": summary, + "headers": [header for _, header in columns], + "entries": entries, + "total_rows": total_rows, + "shown_rows": len(entries), + "truncated": total_rows > len(entries), + "export_filename": export_filename, + } + + +def truncation_notice(model): + """The one line stating that rows were dropped, or None when none were. + + It names a count and a filename only. Never the M3U source. + """ + if not model.get("truncated"): + return None + name = model.get("export_filename") or "the export file" + return (f"Showing the first {model['shown_rows']} of {model['total_rows']} rows. " + f"The complete file is {name} in {EXPORTS_LOCATION} inside the container.") + + +# --------------------------------------------------------------------------- # +# Rendering +# --------------------------------------------------------------------------- # + +# Styling is inlined because the page is read from a file path or inside an email +# client, where an external stylesheet would not resolve. +_CSS = """ +:root { color-scheme: light dark; --accent: #2a78d6; } +body { font: 15px/1.5 system-ui, -apple-system, Segoe UI, sans-serif; + margin: 0; padding: 24px; background: #fbfbfd; color: #16181d; } +@media (prefers-color-scheme: dark) { + :root { --accent: #3987e5; } + body { background: #14161a; color: #e8eaed; } + th { background: #1e2127 !important; } + tr:nth-child(even) td { background: #191c21; } + .card { background: #1a1d22 !important; border-color: #2a2e35 !important; } + .notice { background: #2a2410 !important; border-color: #5a4a18 !important; } +} +h1 { font-size: 22px; margin: 0 0 4px; } +.sub { opacity: .7; font-size: 15px; margin-bottom: 20px; } +.card { background: #fff; border: 1px solid #e3e5ea; border-radius: 10px; + padding: 14px 16px; margin-bottom: 18px; } +.notice { background: #fff8e1; border: 1px solid #e8d9a0; border-radius: 8px; + padding: 10px 14px; margin-bottom: 18px; font-size: 14px; } +table { border-collapse: collapse; width: 100%; font-size: 15px; } +.scroll { overflow-x: auto; } +th, td { text-align: left; padding: 6px 10px; border-bottom: 1px solid #e6e8ec; + vertical-align: top; } +th { background: #f2f3f6; } +th.sortable { cursor: pointer; user-select: none; white-space: nowrap; } +th.sortable::after { content: " \\2195"; opacity: .35; font-size: 12px; } +th.sortable[aria-sort="ascending"]::after { content: " \\2191"; opacity: 1; } +th.sortable[aria-sort="descending"]::after { content: " \\2193"; opacity: 1; } +.empty { opacity: .7; font-style: italic; } +.note { font-size: 14px; opacity: .7; margin-top: 20px; } +dl.meta { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 2px 14px; } +dl.meta dt { opacity: .7; } +dl.meta dd { margin: 0; } +""" + + +def _esc(value): + return html.escape(str(value if value is not None else ""), quote=True) + + +def _fmt_ts(ts): + """Render a time in UTC, labelled as such. + + Deliberately not local time: this module has no access to Dispatcharr's + configured timezone, and a bare unlabelled clock that silently means UTC is + how a reader gets the day wrong. + """ + try: + moment = datetime.datetime.fromtimestamp(float(ts or 0), datetime.timezone.utc) + return moment.strftime("%Y-%m-%d %H:%M UTC") + except (TypeError, ValueError, OSError, OverflowError): + return "unknown" + + +# Click to sort, embedded in the page rather than loaded from anywhere. The page +# is opened from a file path or from a mail attachment, so an external request +# would not resolve and would disclose that the report had been opened. +# +# This is an ADDITION, not a requirement. Every row is present in the markup, so +# a reader whose mail client strips scripts still sees the whole table; they +# simply cannot reorder it. +# +# The comparison reads each cell's data-v attribute, which holds the same value +# the cell displays. Two values that both parse as numbers are compared as +# numbers, because comparing them as text puts 10 before 2, which matters here: +# every report has a channel number column and most have a score column. +_SORT_SCRIPT = """ +(function () { + var table = document.querySelector('table'); + if (!table || !table.tBodies.length) { return; } + var headers = [].slice.call(table.querySelectorAll('th.sortable')); + function value(row, index) { + var cell = row.children[index]; + if (!cell) { return ''; } + var raw = cell.getAttribute('data-v'); + return raw === null ? cell.textContent : raw; + } + function compare(a, b, index) { + var x = value(a, index), y = value(b, index); + var nx = Number(x), ny = Number(y); + if (x !== '' && y !== '' && !isNaN(nx) && !isNaN(ny)) { return nx - ny; } + return String(x).localeCompare(String(y), undefined, + { numeric: true, sensitivity: 'base' }); + } + function apply(header, index) { + var ascending = header.getAttribute('aria-sort') !== 'ascending'; + headers.forEach(function (other) { other.setAttribute('aria-sort', 'none'); }); + header.setAttribute('aria-sort', ascending ? 'ascending' : 'descending'); + var body = table.tBodies[0]; + var rows = [].slice.call(body.rows); + rows.sort(function (a, b) { + return ascending ? compare(a, b, index) : compare(b, a, index); + }); + rows.forEach(function (row) { body.appendChild(row); }); + } + headers.forEach(function (header, index) { + header.addEventListener('click', function () { apply(header, index); }); + header.addEventListener('keydown', function (event) { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + apply(header, index); + } + }); + }); +})(); +""" + + +def render_html(model): + """Render the model to one self contained HTML page. + + The table is sortable by clicking or keyboard-activating a column header. + See _SORT_SCRIPT for what that does and what it deliberately does not do. + """ + rows = [] + for entry in model.get("entries") or []: + cells = "".join(f"{_esc(cell)}" for cell in entry) + rows.append(f"{cells}") + headers = "".join( + "{_esc(h)}" + for h in model.get("headers") or []) + if rows: + table = ("
" + "" + headers + "" + "" + "".join(rows) + "" + "
") + else: + table = "

This run produced no rows.

" + + meta = "".join(f"
{_esc(k)}
{_esc(v)}
" + for k, v in model.get("summary") or []) + + notice = truncation_notice(model) + notice_html = f"
{_esc(notice)}
\n" if notice else "" + + return ( + "\n\n\n" + "\n" + "\n" + f"Lineuparr: {_esc(model.get('title'))}\n" + f"\n\n\n" + f"

Lineuparr: {_esc(model.get('title'))}

\n" + f"
{_esc(model.get('shown_rows', 0))} row(s) shown
\n" + + notice_html + + f"
{meta}
\n" + f"
{table}
\n" + "

Click a column heading to sort by it, or focus it and " + "press Enter. Sorting needs the page open in a browser; a mail client " + "previewing this file shows every row but cannot reorder them.

\n" + "

Stream names here are shown without their M3U source " + "label, and the plugin settings that name your M3U sources are not " + f"included. The complete export, which does include them, stays in " + f"{EXPORTS_LOCATION} inside the container.

\n" + f"\n" + "\n\n" + ) + + +# A cell beginning with any of these is evaluated as a formula by Excel, +# LibreOffice and Google Sheets when the file is opened. +_FORMULA_LEADS = ("=", "+", "-", "@") + + +def _csv_safe(value): + """Prefix a formula shaped cell with an apostrophe so it stays text.""" + text = str(value if value is not None else "") + if text[:1] in _FORMULA_LEADS: + return "'" + text + return text + + +def render_csv(model): + """Render the model to CSV text, with the same content rules as the HTML. + + Deliberately carries no settings header. The plugin's own export in + /data/exports does carry one, which names the configured M3U sources. + """ + buf = io.StringIO() + writer = csv.writer(buf, lineterminator="\n") + writer.writerow(model.get("headers") or []) + for entry in model.get("entries") or []: + writer.writerow([_csv_safe(cell) for cell in entry]) + return buf.getvalue() + + +def _atomic_write(path, text): + """Write through a temporary file and rename, so no partial file is ever + visible at the destination path.""" + tmp = f"{path}.tmp-{os.getpid()}" + try: + with open(tmp, "w", encoding="utf-8", newline="") as handle: + handle.write(text) + os.replace(tmp, path) + except Exception: + try: + os.unlink(tmp) + except OSError: + pass + raise + + +def _prune(dirpath, suffix, keep=KEEP_REPORTS, now=None): + """Keep the newest `keep` report files with this suffix and delete the rest, + except any file still young enough that a delivery retry could need it. + + Never raises: losing an old report must not fail the run that produced a new + one. + """ + try: + moment = time.time() if now is None else now + entries = [os.path.join(dirpath, name) for name in os.listdir(dirpath) + if name.startswith(FILENAME_PREFIX) and name.endswith(suffix)] + entries.sort(key=os.path.getmtime, reverse=True) + for stale in entries[keep:]: + try: + if moment - os.path.getmtime(stale) < RETRY_WINDOW_SECONDS: + continue # a queued delivery may still re-read this path + os.unlink(stale) + except OSError: + pass + except OSError: + pass + + +def write_report(model, report_dir, now): + """Write the HTML and CSV reports and return their paths. + + Returns {"html_path", "csv_path", "error"}. Never raises: reporting is not + the plugin's real work, and a failure here is reported rather than thrown. + + Both files carry the run's timestamp in their name and are never rewritten, + because a delivery re-reads the path on every retry attempt. + """ + result = {"html_path": None, "csv_path": None, "error": None} + try: + os.makedirs(report_dir, exist_ok=True) + stamp = datetime.datetime.fromtimestamp( + float(now or 0), datetime.timezone.utc).strftime("%Y%m%d_%H%M%S") + base = os.path.join(report_dir, f"{FILENAME_PREFIX}{stamp}") + html_path, csv_path = base + ".html", base + ".csv" + _atomic_write(html_path, render_html(model)) + _atomic_write(csv_path, render_csv(model)) + result["html_path"], result["csv_path"] = html_path, csv_path + _prune(report_dir, ".html") + _prune(report_dir, ".csv") + except Exception as error: + result["error"] = f"could not write the report: {error}" + return result