diff --git a/sites/vrt.be/__data__/channels.json b/sites/vrt.be/__data__/channels.json new file mode 100644 index 00000000..5b58b846 --- /dev/null +++ b/sites/vrt.be/__data__/channels.json @@ -0,0 +1 @@ +{"data":{"page":{"channelNavigation":{"items":[{"title":"VRT 1","brandLogos":[{"primary":"https://images.vrt.be/orig/2025/11/04/fffbd9ab-0953-4f60-95b6-5075a4744739.svg","type":"svg"},{"primary":"https://images.vrt.be/orig/2023/04/28/c448d669-e5c1-11ed-91d7-02b7b76bf47f.png","type":"png"}],"action":{"link":"/vrtmax/tv-gids/vrt1/","linkTokens":[{"placeholder":":livestreamName","value":"vrt1"}]}},{"title":"VRT CANVAS","brandLogos":[{"primary":"https://images.vrt.be/orig/2025/11/04/fb012713-f86b-475a-bcb4-35704a429b25.svg","type":"svg"},{"primary":"https://images.vrt.be/orig/2023/11/09/1ae4c23e-7ef9-11ee-91d7-02b7b76bf47f.png","type":"png"}],"action":{"link":"/vrtmax/tv-gids/vrt-canvas/","linkTokens":[{"placeholder":":livestreamName","value":"vrt-canvas"}]}},{"title":"Ketnet","brandLogos":[{"primary":"https://images.vrt.be/orig/2025/11/04/3ecce5ad-bb71-49e1-9313-dcd3caf483c0.svg","type":"svg"},{"primary":"https://images.vrt.be/orig/2024/10/22/03aa85a7-302c-4b2e-8028-71b52ae0a38d.png","type":"png"}],"action":{"link":"/vrtmax/tv-gids/ketnet/","linkTokens":[{"placeholder":":livestreamName","value":"ketnet"}]}},{"title":"Radio 1","brandLogos":[{"primary":"https://images.vrt.be/orig/2025/06/19/d2b27cba-863f-44f3-abee-3a262e593348.svg","type":"svg"},{"primary":"https://images.vrt.be/orig/2025/06/19/2a99563b-7503-4906-81fb-aa6bc91bfa08.png","type":"png"}],"action":{"link":"/vrtmax/tv-gids/radio1/","linkTokens":[{"placeholder":":livestreamName","value":"radio1"}]}},{"title":"Radio 2","brandLogos":[{"primary":"https://images.vrt.be/orig/2025/06/19/cde32167-4963-4a61-8430-0c7f45d77ff4.svg","type":"svg"},{"primary":"https://images.vrt.be/orig/2025/06/19/11b3ccce-bc76-43cf-aa15-b6c93e75f6db.png","type":"png"}],"action":{"link":"/vrtmax/tv-gids/radio2/","linkTokens":[{"placeholder":":livestreamName","value":"radio2"}]}},{"title":"Studio Brussel","brandLogos":[{"primary":"https://images.vrt.be/orig/2025/11/04/1850af4d-d93a-4465-bdec-8d3e82cafe17.svg","type":"svg"},{"primary":"https://images.vrt.be/orig/2023/12/08/a6d153f0-95cb-11ee-b483-02b7b76bf47f.png","type":"png"}],"action":{"link":"/vrtmax/tv-gids/studio-brussel/","linkTokens":[{"placeholder":":livestreamName","value":"studio-brussel"}]}},{"title":"MNM","brandLogos":[{"primary":"https://images.vrt.be/orig/2025/06/19/f2cafaab-b969-440e-82ff-9817aefffbc2.svg","type":"svg"},{"primary":"https://images.vrt.be/orig/2025/06/19/f0be1c65-3f98-43d1-b962-ded0f3bca602.png","type":"png"}],"action":{"link":"/vrtmax/tv-gids/mnm/","linkTokens":[{"placeholder":":livestreamName","value":"mnm"}]}},{"title":"Klara","brandLogos":[{"primary":"https://images.vrt.be/orig/2025/06/19/0ae49c2d-c218-4bc1-aee8-1040b2551a98.svg","type":"svg"},{"primary":"https://images.vrt.be/orig/2025/06/19/1434fa63-eb65-4f65-b465-26919982d6fc.png","type":"png"}],"action":{"link":"/vrtmax/tv-gids/klara/","linkTokens":[{"placeholder":":livestreamName","value":"klara"}]}},{"title":"De Tijdloze","brandLogos":[{"primary":"https://images.vrt.be/orig/2025/06/19/44d3870a-c533-4e6b-aa5a-e4535d77f12c.svg","type":"svg"},{"primary":"https://images.vrt.be/orig/2025/06/19/a8150bd0-4af0-4c4f-bdaf-193f579adba6.png","type":"png"}],"action":{"link":"/vrtmax/tv-gids/tijdloze/","linkTokens":[{"placeholder":":livestreamName","value":"tijdloze"}]}},{"title":"Radio Bene","brandLogos":[{"primary":"https://images.vrt.be/orig/2025/06/19/653473de-e808-4532-9370-1302bf379a4e.svg","type":"svg"},{"primary":"https://images.vrt.be/orig/2025/06/19/d7fc517e-2fc8-4467-b1d0-90a32b1c7334.png","type":"png"}],"action":{"link":"/vrtmax/tv-gids/radio-bene/","linkTokens":[{"placeholder":":livestreamName","value":"radio-bene"}]}}]}}}} diff --git a/sites/vrt.be/__data__/content.json b/sites/vrt.be/__data__/content.json new file mode 100644 index 00000000..cafe2b4d --- /dev/null +++ b/sites/vrt.be/__data__/content.json @@ -0,0 +1 @@ +{"data":{"page":{"previous":{"listId":"$byUzMXxzY2hlZHVsZVByZXZpb3VzfE84fGQlMTc3MzAxNDQwMDAwMHx8JQ==","paginatedItems":{"edges":[{"cursor":"epg#O8#2026-03-09T05:00:08.000Z","node":{"title":"Mr. Magoo","description":"Rondleiding door de stad","indexMeta":[{"value":"06:00u"}],"progress":null,"status":{"accessibilityLabel":"8 minuten","text":{"small":"8 min","default":"8 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2024/12/07/7c0854d3-67e3-4271-9e4f-43ca80b63c87.jpg"},"action":{"link":"/vrtmax/a-z/mr-magoo/2/mr-magoo-rondleiding-door-de-stad/"}}},{"cursor":"epg#O8#2026-03-09T05:07:43.760Z","node":{"title":"Galactic Agency","description":"Croc love","indexMeta":[{"value":"06:07u"}],"progress":null,"status":{"accessibilityLabel":"12 minuten","text":{"small":"12 min","default":"12 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2023/01/09/22dff54c-901c-11ed-b07d-02b7b76bf47f.jpg?gravity=50p%2C50p"},"action":{"link":"/vrtmax/a-z/galactic-agency/1/galactic-agency-s1a12/"}}},{"cursor":"epg#O8#2026-03-09T05:19:22.680Z","node":{"title":"Mega Maffe Monsterfreaks","description":"Het boek der vampiers","indexMeta":[{"value":"06:19u"}],"progress":null,"status":{"accessibilityLabel":"11 minuten","text":{"small":"11 min","default":"11 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2024/10/19/44c9f12f-fa45-4506-a897-b8dc7ef03762.jpg"},"action":{"link":"/vrtmax/a-z/mega-maffe-monsterfreaks/1/mega-maffe-monsterfreaks-s1a12/"}}},{"cursor":"epg#O8#2026-03-09T05:30:21.080Z","node":{"title":"Lou","description":"U hebt één bericht","indexMeta":[{"value":"06:30u"}],"progress":null,"status":{"accessibilityLabel":"13 minuten","text":{"small":"13 min","default":"13 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2024/02/03/1a9ff874-c21f-11ee-b483-02b7b76bf47f.jpg"},"action":{"link":"/vrtmax/a-z/lou/1/lou-s1a14/"}}},{"cursor":"epg#O8#2026-03-09T05:42:49.360Z","node":{"title":"Dodo","description":"Poëziewedstrijd","indexMeta":[{"value":"06:42u"}],"progress":null,"status":{"accessibilityLabel":"11 minuten","text":{"small":"11 min","default":"11 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2024/12/20/7d65c544-419b-4897-bf54-bbecb0e67cc8.jpg"},"action":{"link":"/vrtmax/a-z/dodo/1/dodo-s1a28/"}}},{"cursor":"epg#O8#2026-03-09T05:53:24.960Z","node":{"title":"Bernie","description":"Bernie's got talent","indexMeta":[{"value":"06:53u"}],"progress":null,"status":{"accessibilityLabel":"4 minuten","text":{"small":"4 min","default":"4 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2023/06/17/9f32d099-0c92-11ee-91d7-02b7b76bf47f.jpg"},"action":{"link":"/vrtmax/a-z/bernie/1/bernie-s1a9/"}}},{"cursor":"epg#O8#2026-03-09T05:57:22.200Z","node":{"title":"Videoclips #likeme seizoen 5","description":"","indexMeta":[{"value":"06:57u"}],"progress":null,"status":{"accessibilityLabel":"Ma 09/03 om 06:57","text":{"small":null,"default":"Ma 09/03 om 06:57"}},"image":{"templateUrl":"https://images.vrt.be/orig/2025/01/07/a5beb727-6d46-4dcf-93bd-d587da4bd7bc.jpg"},"action":null}},{"cursor":"epg#O8#2026-03-09T05:59:24.280Z","node":{"title":"De week van Karrewiet met VGT","description":"Het nieuws van 2 tot 6 maart met Vlaamse Gebarentaal","indexMeta":[{"value":"06:59u"}],"progress":null,"status":{"accessibilityLabel":"13 minuten","text":{"small":"13 min","default":"13 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/03/06/f46fbe74-929d-476f-8b37-5f1420023918.jpg"},"action":{"link":"/vrtmax/a-z/de-week-van-karrewiet-met-vgt/2026/de-week-van-karrewiet-vgt-d20260307/"}}},{"cursor":"epg#O8#2026-03-09T06:12:30.040Z","node":{"title":"HALLO KROKET!","description":"Zo word jij een echte schrijver!","indexMeta":[{"value":"07:12u"}],"progress":null,"status":{"accessibilityLabel":"15 minuten","text":{"small":"15 min","default":"15 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/03/06/8759fcfc-b4ab-407c-9aab-be4894e9d306.jpg"},"action":{"link":"/vrtmax/a-z/hallo-kroket/2/hallo-kroket-d20260306/"}}},{"cursor":"epg#O8#2026-03-09T06:27:54.520Z","node":{"title":"Athleticus","description":"Marathon","indexMeta":[{"value":"07:27u"}],"progress":null,"status":{"accessibilityLabel":"3 minuten","text":{"small":"3 min","default":"3 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2024/12/07/79293e2e-2386-4910-9aa0-005f6e2d9b9e.jpg"},"action":{"link":"/vrtmax/a-z/athleticus/3/athleticus-s3a22/"}}},{"cursor":"epg#O8#2026-03-09T06:30:15.520Z","node":{"title":"Athleticus","description":"Parkour","indexMeta":[{"value":"07:30u"}],"progress":null,"status":{"accessibilityLabel":"3 minuten","text":{"small":"3 min","default":"3 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2024/12/07/83dd8541-90a2-45c3-ab01-f85d1d74e5b4.jpg"},"action":{"link":"/vrtmax/a-z/athleticus/3/athleticus-s3a23/"}}},{"cursor":"epg#O8#2026-03-09T06:32:38.520Z","node":{"title":"De Faker","description":"Wie is de faker bij de familie Sekvan?","indexMeta":[{"value":"07:32u"}],"progress":null,"status":{"accessibilityLabel":"15 minuten","text":{"small":"15 min","default":"15 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2022/12/07/1a257954-762e-11ed-b07d-02b7b76bf47f.jpg"},"action":{"link":"/vrtmax/a-z/de-faker/1/de-faker-s1a10/"}}},{"cursor":"epg#O8#2026-03-09T06:47:45.520Z","node":{"title":"Dance academy","description":"Legendes","indexMeta":[{"value":"07:47u"}],"progress":null,"status":{"accessibilityLabel":"24 minuten","text":{"small":"24 min","default":"24 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2025/04/15/74bf9eb9-c356-47fa-858c-3dec59b584d3.jpg"},"action":{"link":"/vrtmax/a-z/dance-academy/2/dance-academy-s2a4/"}}},{"cursor":"epg#O8#2026-03-09T06:47:45.520Z","node":{"title":"Dance academy","description":"Legendes","indexMeta":[{"value":"07:47u"}],"progress":null,"status":{"accessibilityLabel":"24 minuten","text":{"small":"24 min","default":"24 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2025/04/15/74bf9eb9-c356-47fa-858c-3dec59b584d3.jpg"},"action":{"link":"/vrtmax/a-z/dance-academy/2/dance-academy-s2a4/"}}},{"cursor":"epg#O8#2026-03-09T07:11:48.560Z","node":{"title":"Prank Squad","description":"De schuimballon","indexMeta":[{"value":"08:11u"}],"progress":null,"status":{"accessibilityLabel":"9 minuten","text":{"small":"9 min","default":"9 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2024/11/06/83590ee9-a8ba-47d6-bdde-6ec32fb92dae.jpg"},"action":{"link":"/vrtmax/a-z/prank-squad/1/prank-squad-s1a7/"}}},{"cursor":"epg#O8#2026-03-09T07:21:18.400Z","node":{"title":"De Raad van Soekie","description":"Nieuwe Brollers","indexMeta":[{"value":"08:21u"}],"progress":null,"status":{"accessibilityLabel":"16 minuten","text":{"small":"16 min","default":"16 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2024/12/10/814f992f-5e44-45ce-8b31-e88fa229a6bb.jpg"},"action":{"link":"/vrtmax/a-z/de-raad-van-soekie/2/de-raad-van-soekie-s2a4/"}}},{"cursor":"epg#O8#2026-03-09T07:36:32.400Z","node":{"title":"De Hoppers","description":"Worstelen met kangoeroes","indexMeta":[{"value":"08:36u"}],"progress":null,"status":{"accessibilityLabel":"16 minuten","text":{"small":"16 min","default":"16 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2022/12/12/83bf6e7f-7a1b-11ed-b07d-02b7b76bf47f.jpg"},"action":{"link":"/vrtmax/a-z/de-hoppers/2/de-hoppers-s2a25/"}}},{"cursor":"epg#O8#2026-03-09T07:52:20.440Z","node":{"title":"Shaun het schaap","description":"Jongens, wat is het heet","indexMeta":[{"value":"08:52u"}],"progress":null,"status":{"accessibilityLabel":"7 minuten","text":{"small":"7 min","default":"7 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2023/07/19/f397b2c3-2623-11ee-91d7-02b7b76bf47f.jpg"},"action":{"link":"/vrtmax/a-z/shaun-het-schaap/3/shaun-het-schaap-s3a17-jongens-wat-is-het-heet/"}}},{"cursor":"epg#O8#2026-03-09T08:00:00.000Z","node":{"title":"Radio 2 op VRT 1: WinWin","description":"","indexMeta":[{"value":"09:00u"}],"progress":null,"status":{"accessibilityLabel":"Ma 09/03 om 09:00","text":{"small":null,"default":"Ma 09/03 om 09:00"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/02/23/06378eaf-5a32-4f82-9fb5-9cc3bf19a86b.jpg"},"action":null}},{"cursor":"epg#O8#2026-03-09T09:00:00.000Z","node":{"title":"Radio 2 op VRT 1: Ann & Daan","description":"","indexMeta":[{"value":"10:00u"}],"progress":null,"status":{"accessibilityLabel":"Ma 09/03 om 10:00","text":{"small":null,"default":"Ma 09/03 om 10:00"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/02/23/88706755-2f1b-48c5-a91d-288da0011abd.jpg"},"action":null}},{"cursor":"epg#O8#2026-03-09T10:00:20.000Z","node":{"title":"Winterbeelden","description":"","indexMeta":[{"value":"11:00u"}],"progress":null,"status":{"accessibilityLabel":"Ma 09/03 om 11:00","text":{"small":null,"default":"Ma 09/03 om 11:00"}},"image":{"templateUrl":"https://images.vrt.be/orig/2025/12/11/9d468ebe-a3a9-40ea-ab7d-70f0930163fd.jpg"},"action":null}},{"cursor":"epg#O8#2026-03-09T11:11:31.200Z","node":{"title":"Dagelijkse kost","description":"Ovenschotel met zalm, geitenkaas en broccolipuree","indexMeta":[{"value":"12:11u"}],"progress":null,"status":{"accessibilityLabel":"16 minuten","text":{"small":"16 min","default":"16 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/02/24/c763b65d-a3cd-403f-b374-53a15e64b082.jpg"},"action":{"link":"/vrtmax/a-z/dagelijkse-kost/16/dagelijkse-kost-d20260306/"}}},{"cursor":"epg#O8#2026-03-09T11:29:07.480Z","node":{"title":"Blokken","description":"Aflevering 6689","indexMeta":[{"value":"12:29u"}],"progress":null,"status":{"accessibilityLabel":"Ma 09/03 om 12:29","text":{"small":null,"default":"Ma 09/03 om 12:29"}},"image":{"templateUrl":"https://images.vrt.be/orig/2025/11/07/5951fe0f-02a1-4393-b42f-5741cb76f7c1.jpg"},"action":null}},{"cursor":"epg#O8#2026-03-09T12:00:00.000Z","node":{"title":"VRT NWS journaal","description":"VRT NWS journaal 13u","indexMeta":[{"value":"13:00u"}],"progress":null,"status":{"accessibilityLabel":"29 minuten","text":{"small":"29 min","default":"29 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/03/09/959f4833-1c38-4ef9-b776-0256adf74aee.jpg"},"action":{"link":"/vrtmax/a-z/vrt-nws-journaal/2026/vrt-nws-journaal-vrt-nws-journaal-13u-20260309/"}}},{"cursor":"epg#O8#2026-03-09T12:29:49.960Z","node":{"title":"Het weer","description":"","indexMeta":[{"value":"13:29u"}],"progress":null,"status":{"accessibilityLabel":"Ma 09/03 om 13:29","text":{"small":null,"default":"Ma 09/03 om 13:29"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/02/23/5fc0ca32-3657-41aa-b65f-1d9b38c1f86c.jpg"},"action":null}},{"cursor":"epg#O8#2026-03-09T12:35:10.920Z","node":{"title":"Man bijt hond","description":"Open beet op het toilet","indexMeta":[{"value":"13:35u"}],"progress":null,"status":{"accessibilityLabel":"22 minuten","text":{"small":"22 min","default":"22 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/03/06/46d2ef28-5be2-4d10-8386-23f4fd8e460d.jpg"},"action":{"link":"/vrtmax/a-z/man-bijt-hond/2026/man-bijt-hond-s2026a23/"}}},{"cursor":"epg#O8#2026-03-09T12:59:08.760Z","node":{"title":"Thuis","description":"Dirk zet Angèle met beide voeten op de grond","indexMeta":[{"value":"13:59u"}],"progress":null,"status":{"accessibilityLabel":"26 minuten","text":{"small":"26 min","default":"26 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2023/03/22/528ba69e-c83c-11ed-91d7-02b7b76bf47f.jpg"},"action":{"link":"/vrtmax/a-z/thuis/28/thuis-s28a5413/"}}},{"cursor":"epg#O8#2026-03-09T13:25:19.840Z","node":{"title":"De inzichten","description":"De inzichten van Gaea Schoeters","indexMeta":[{"value":"14:25u"}],"progress":null,"status":{"accessibilityLabel":"41 minuten","text":{"small":"41 min","default":"41 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/02/19/ff0b75cf-c17a-463b-828c-3de766f2442c.jpg"},"action":{"link":"/vrtmax/a-z/de-inzichten/8/de-inzichten-van-s8a2-de-inzichten-van-gaea-schoeters/"}}},{"cursor":"epg#O8#2026-03-09T14:07:21.480Z","node":{"title":"Peter Van de Veire & De Zandloper","description":"Mentissa","indexMeta":[{"value":"15:07u"}],"progress":null,"status":{"accessibilityLabel":"30 minuten","text":{"small":"30 min","default":"30 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/03/04/6641caeb-33a5-46d1-a309-8346e13dd14d.jpg"},"action":{"link":"/vrtmax/a-z/peter-van-de-veire-en-de-zandloper/8/peter-van-de-veire-en-de-zandloper-s8a2/"}}},{"cursor":"epg#O8#2026-03-09T14:40:06.800Z","node":{"title":"Sporza: wielrennen","description":"Parijs-Nice: rit 2: Épône - Montargis","indexMeta":[{"value":"15:40u"}],"progress":null,"status":{"accessibilityLabel":"121 minuten","text":{"small":"121 min","default":"121 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/03/09/13e5de1a-c2b9-4a26-b1e4-f4ce9d0adfac.jpg"},"action":{"link":"/vrtmax/a-z/sporza--wielrennen/2026/sporza--wielrennen-d20260309-parijs-nice--rit-2--epone---montargis/"}}},{"cursor":"epg#O8#2026-03-09T16:41:51.720Z","node":{"title":"Clips","description":"","indexMeta":[{"value":"17:41u"}],"progress":null,"status":{"accessibilityLabel":"Ma 09/03 om 17:41","text":{"small":null,"default":"Ma 09/03 om 17:41"}},"image":{"templateUrl":"https://images.vrt.be/orig/2025/10/15/dfa2c6d9-86fe-4ebb-bc6c-009beada7c98.jpg"},"action":null}},{"cursor":"epg#O8#2026-03-09T17:04:03.440Z","node":{"title":"VRT NWS Update","description":"","indexMeta":[{"value":"18:04u"}],"progress":null,"status":{"accessibilityLabel":"Ma 09/03 om 18:04","text":{"small":null,"default":"Ma 09/03 om 18:04"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/02/23/46542f91-20ad-4eb0-8eda-fc85eed348ec.jpg"},"action":null}},{"cursor":"epg#O8#2026-03-09T17:05:39.360Z","node":{"title":"Het weer","description":"","indexMeta":[{"value":"18:05u"}],"progress":null,"status":{"accessibilityLabel":"Ma 09/03 om 18:05","text":{"small":null,"default":"Ma 09/03 om 18:05"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/02/23/57b36aea-3719-4470-8894-b6417e280de9.jpg"},"action":null}},{"cursor":"epg#O8#2026-03-09T17:10:11.800Z","node":{"title":"Dagelijkse kost","description":"Kip piri piri met geroosterde groenten en rijst","indexMeta":[{"value":"18:10u"}],"progress":null,"status":{"accessibilityLabel":"16 minuten","text":{"small":"16 min","default":"16 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/03/03/bb8f8729-43a1-482e-9229-f3df3422f106.jpg"},"action":{"link":"/vrtmax/a-z/dagelijkse-kost/16/dagelijkse-kost-d20260309/"}}},{"cursor":"epg#O8#2026-03-09T17:28:32.200Z","node":{"title":"Blokken","description":"Aflevering 6764","indexMeta":[{"value":"18:28u"}],"progress":null,"status":{"accessibilityLabel":"29 minuten","text":{"small":"29 min","default":"29 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/02/23/d2cfdfd6-02b0-4bb4-be8c-a3976d89e51b.jpg"},"action":{"link":"/vrtmax/a-z/blokken/2025-2026/blokken-d20260309/"}}},{"cursor":"epg#O8#2026-03-09T18:00:00.120Z","node":{"title":"VRT NWS journaal","description":"VRT NWS journaal 19u","indexMeta":[{"value":"19:00u"}],"progress":null,"status":{"accessibilityLabel":"47 minuten","text":{"small":"47 min","default":"47 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/03/09/b1586d23-2302-46b0-be23-a255983494d6.jpg"},"action":{"link":"/vrtmax/a-z/vrt-nws-journaal/2026/vrt-nws-journaal-vrt-nws-journaal-19u-20260309/"}}},{"cursor":"epg#O8#2026-03-09T18:49:51.640Z","node":{"title":"Man bijt hond","description":"Ik heb nog nooit een bus of trein genomen","indexMeta":[{"value":"19:49u"}],"progress":null,"status":{"accessibilityLabel":"24 minuten","text":{"small":"24 min","default":"24 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/03/09/2f4e297c-5ab4-48b8-800a-17f0dad4898e.jpg"},"action":{"link":"/vrtmax/a-z/man-bijt-hond/2026/man-bijt-hond-s2026a25/"}}},{"cursor":"epg#O8#2026-03-09T19:15:28.800Z","node":{"title":"Het weer","description":"Het weerbericht van maandag 9 maart","indexMeta":[{"value":"20:15u"}],"progress":null,"status":{"accessibilityLabel":"3 minuten","text":{"small":"3 min","default":"3 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2025/02/18/4f206a3f-ea26-462f-9a49-e819ed83e76c.jpg"},"action":{"link":"/vrtmax/a-z/het-weer/2026/het-weer-d20260309/"}}},{"cursor":"epg#O8#2026-03-09T19:21:50.400Z","node":{"title":"Thuis","description":"Nancy doet Femke een voorstel","indexMeta":[{"value":"20:21u"}],"progress":null,"status":{"accessibilityLabel":"25 minuten","text":{"small":"25 min","default":"25 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/02/23/37e6617a-d235-442f-9d75-5a6e9e3ddfe1.jpg"},"action":{"link":"/vrtmax/a-z/thuis/31/thuis-s31a6030/"}}},{"cursor":"epg#O8#2026-03-09T19:48:41.560Z","node":{"title":"Alles in orde!","description":"Familie Peckstadt","indexMeta":[{"value":"20:48u"}],"progress":null,"status":{"accessibilityLabel":"58 minuten","text":{"small":"58 min","default":"58 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/01/08/b77ba82f-c869-4b1f-a8b4-1eed325934a1.jpg"},"action":{"link":"/vrtmax/a-z/alles-in-orde-/1/alles-in-orde--s1a6/"}}},{"cursor":"epg#O8#2026-03-09T20:48:53.920Z","node":{"title":"De dag van vandaag","description":"Stephanie Planckaert, Sarah Vandeursen & Clara Cleymans","indexMeta":[{"value":"21:48u"}],"progress":null,"status":{"accessibilityLabel":"55 minuten","text":{"small":"55 min","default":"55 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/03/09/bf896495-075a-42b6-8760-78e7945565f4.jpg"},"action":{"link":"/vrtmax/a-z/de-dag-van-vandaag/5/de-dag-van-vandaag-s5a32/"}}},{"cursor":"epg#O8#2026-03-09T21:46:12.280Z","node":{"title":"VRT NWS laat","description":"VRT NWS laat van maandag 9 maart","indexMeta":[{"value":"22:46u"}],"progress":null,"status":{"accessibilityLabel":"27 minuten","text":{"small":"27 min","default":"27 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/03/09/302ff997-1ef0-4f47-a3ba-773adf2e8bbd.jpg"},"action":{"link":"/vrtmax/a-z/vrt-nws-laat/2026/vrt-nws-laat-d20260309/"}}},{"cursor":"epg#O8#2026-03-09T22:13:38.920Z","node":{"title":"Het weer","description":"Het weerbericht van maandag 9 maart","indexMeta":[{"value":"23:13u"}],"progress":null,"status":{"accessibilityLabel":"3 minuten","text":{"small":"3 min","default":"3 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2025/02/18/4f206a3f-ea26-462f-9a49-e819ed83e76c.jpg"},"action":{"link":"/vrtmax/a-z/het-weer/2026/het-weer-d20260309/"}}},{"cursor":"epg#O8#2026-03-09T22:19:16.560Z","node":{"title":"Ik vraag het aan","description":"Met o.a. Camille, Average Rob en Marc Coucke","indexMeta":[{"value":"23:19u"}],"progress":null,"status":{"accessibilityLabel":"66 minuten","text":{"small":"66 min","default":"66 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/02/12/bb2d1845-072c-4fae-9191-a106a3b2d0ed.jpg"},"action":{"link":"/vrtmax/a-z/ik-vraag-het-aan/3/ik-vraag-het-aan-s3a4/"}}},{"cursor":"epg#O8#2026-03-09T23:25:16.920Z","node":{"title":"Eurodreams","description":"","indexMeta":[{"value":"00:25u"}],"progress":null,"status":{"accessibilityLabel":"Di 10/03 om 00:25","text":{"small":null,"default":"Di 10/03 om 00:25"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/02/23/65ee610d-e8a1-4369-8a71-f54e67e5df7e.jpg"},"action":null}},{"cursor":"epg#O8#2026-03-09T23:27:47.720Z","node":{"title":"Thuis","description":"Nancy doet Femke een voorstel","indexMeta":[{"value":"00:27u"}],"progress":null,"status":{"accessibilityLabel":"25 minuten","text":{"small":"25 min","default":"25 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/02/23/37e6617a-d235-442f-9d75-5a6e9e3ddfe1.jpg"},"action":{"link":"/vrtmax/a-z/thuis/31/thuis-s31a6030/"}}},{"cursor":"epg#O8#2026-03-09T23:52:32.760Z","node":{"title":"Dagelijkse kost","description":"Kip piri piri met geroosterde groenten en rijst","indexMeta":[{"value":"00:52u"}],"progress":null,"status":{"accessibilityLabel":"16 minuten","text":{"small":"16 min","default":"16 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/03/03/bb8f8729-43a1-482e-9229-f3df3422f106.jpg"},"action":{"link":"/vrtmax/a-z/dagelijkse-kost/16/dagelijkse-kost-d20260309/"}}},{"cursor":"epg#O8#2026-03-10T00:08:59.000Z","node":{"title":"VRT NWS laat","description":"VRT NWS laat van maandag 9 maart","indexMeta":[{"value":"01:08u"}],"progress":null,"status":{"accessibilityLabel":"27 minuten","text":{"small":"27 min","default":"27 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2026/03/09/302ff997-1ef0-4f47-a3ba-773adf2e8bbd.jpg"},"action":{"link":"/vrtmax/a-z/vrt-nws-laat/2026/vrt-nws-laat-d20260309/"}}},{"cursor":"epg#O8#2026-03-10T00:37:00.120Z","node":{"title":"Het weer","description":"Het weerbericht van maandag 9 maart","indexMeta":[{"value":"01:37u"}],"progress":null,"status":{"accessibilityLabel":"3 minuten","text":{"small":"3 min","default":"3 minuten"}},"image":{"templateUrl":"https://images.vrt.be/orig/2025/02/18/4f206a3f-ea26-462f-9a49-e819ed83e76c.jpg"},"action":{"link":"/vrtmax/a-z/het-weer/2026/het-weer-d20260309/"}}}],"pageInfo":{"hasNextPage":false,"endCursor":"epg#O8#2026-03-10T00:37:00.120Z"}}},"next":{"listId":"$byUzMXxzY2hlZHVsZU5leHR8Tzh8ZCUxNzczMDE0NDAwMDAwfHwl","paginatedItems":{"edges":[],"pageInfo":{"hasNextPage":false,"endCursor":null}}}}}} diff --git a/sites/vrt.be/readme.md b/sites/vrt.be/readme.md new file mode 100644 index 00000000..1feaa432 --- /dev/null +++ b/sites/vrt.be/readme.md @@ -0,0 +1,21 @@ +# vrt.be + +https://www.vrt.be/vrtmax/tv-gids/ + +### Download the guide + +```sh +npm run grab --- --site=vrt.be +``` + +### Update channel list + +```sh +npm run channels:parse --- --config=./sites/vrt.be/vrt.be.config.js --output=./sites/vrt.be/vrt.be.channels.xml +``` + +### Test + +```sh +npm test --- vrt.be +``` diff --git a/sites/vrt.be/vrt.be.channels.xml b/sites/vrt.be/vrt.be.channels.xml new file mode 100644 index 00000000..60a05fab --- /dev/null +++ b/sites/vrt.be/vrt.be.channels.xml @@ -0,0 +1,13 @@ + + + VRT CANVAS + De Tijdloze + Ketnet + Klara + MNM + Radio 1 + Radio 2 + Radio Bene + Studio Brussel + VRT 1 + diff --git a/sites/vrt.be/vrt.be.config.js b/sites/vrt.be/vrt.be.config.js new file mode 100644 index 00000000..16e901d7 --- /dev/null +++ b/sites/vrt.be/vrt.be.config.js @@ -0,0 +1,228 @@ +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') + +dayjs.extend(utc) + +const EPG_QUERY = ` +query EpgPage($pageId: ID!, $lazyItemCount: Int = 100) { + page(id: $pageId) { + ... on ElectronicProgramGuidePage { + previous { + ...epgListFragment + } + next { + ...epgListFragment + } + } + } +} + +fragment epgListFragment on PaginatedTileList { + listId + paginatedItems(first: $lazyItemCount) { + edges { + cursor + node { + ...epgTileFragment + } + } + pageInfo { + hasNextPage + endCursor + } + } +} + +fragment epgTileFragment on Tile { + ... on ITile { + title + description + primaryMeta { + value + shortValue + } + indexMeta { + value + } + progress { + durationInSeconds + } + status { + accessibilityLabel + text { + small + default + } + } + image { + templateUrl + } + action { + ... on LinkAction { + link + } + } + } +} +` + +const CHANNELS_QUERY = ` +query ProgramGuidePage($pageId: ID!) { + page(id: $pageId) { + ... on ElectronicProgramGuidePage { + channelNavigation { + items { + ... on ContentTile { + title + brandLogos { + primary + type + } + action { + ... on LinkAction { + link + linkTokens { + placeholder + value + } + } + } + } + } + } + } + } +} +` +const API_ENDPOINT = 'https://www.vrt.be/vrtnu-api/graphql/public/v1' +const API_HEADERS = { + 'content-type': 'application/json', + 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0', + 'x-vrt-client-name': 'WEB' +} + +module.exports = { + site: 'vrt.be', + days: 2, + url: API_ENDPOINT, + request: { + method: 'POST', + headers: API_HEADERS, + data({ channel, date }) { + return { + query: EPG_QUERY, + variables: { + pageId: `/vrtmax/tv-gids/${channel.site_id}/${date.format('YYYY-MM-DD')}/` + } + } + } + }, + parser({ content }) { + let data + try { + data = JSON.parse(content) + } catch { + return [] + } + if (!data.data?.page) return [] + + const page = data.data?.page + const previousEdges = page.previous?.paginatedItems?.edges || [] + const nextEdges = page.next?.paginatedItems?.edges || [] + const edges = [...previousEdges, ...nextEdges] + + const programs = [] + edges.forEach((edge, index) => { + const node = edge.node + if (!node || !node.title) return + + const start = parseCursor(edge.cursor) + if (!start) return + + const nextEdge = edges[index + 1] + const stop = nextEdge ? parseCursor(nextEdge.cursor) : parseFallbackStop(start, node) + if (!stop || !stop.isAfter(start)) return + + programs.push({ + title: node.title, + description: node.description || null, + season: parseSeason(node.primaryMeta), + episode: parseEpisode(node.primaryMeta), + image: node.image?.templateUrl || null, + start, + stop + }) + }) + + return programs + }, + async channels() { + const data = await axios + .post( + API_ENDPOINT, + { + query: CHANNELS_QUERY, + variables: { pageId: '/vrtmax/tv-gids/' } + }, + { headers: API_HEADERS } + ) + .then(r => r.data) + .catch(console.error) + + if (!data) return [] + + const items = data.data?.page?.channelNavigation?.items || [] + return items + .map(item => { + const siteId = item.action?.linkTokens?.find( + t => t.placeholder === ':livestreamName' + )?.value + if (!siteId) return null + + const logo = item.brandLogos?.find(l => l.type === 'png')?.primary || null + + return { + lang: 'nl', + site_id: siteId, + name: item.title, + logo + } + }) + .filter(Boolean) + } +} + +function parseSeason(primaryMeta) { + if (!Array.isArray(primaryMeta)) return null + const item = primaryMeta.find(m => /^S\d+$/.test(m.shortValue)) + return item ? parseInt(item.shortValue.slice(1), 10) : null +} + +function parseEpisode(primaryMeta) { + if (!Array.isArray(primaryMeta)) return null + const item = primaryMeta.find(m => /^Afl\.\d+$/.test(m.shortValue)) + return item ? parseInt(item.shortValue.replace('Afl.', ''), 10) : null +} + +function parseCursor(cursor) { + if (!cursor) return null + const iso = cursor.replace(/^epg#[^#]+#/, '') + const d = dayjs.utc(iso) + return d.isValid() ? d : null +} + +function parseFallbackStop(start, node) { + // Try progress.durationInSeconds (radio) + const durationS = node.progress?.durationInSeconds + if (durationS) return start.add(durationS, 'second') + + // Try status.text.small e.g. "16 min" + const statusSmall = node.status?.text?.small + if (statusSmall) { + const match = statusSmall.match(/(\d+)\s*min/) + if (match) return start.add(parseInt(match[1], 10), 'minute') + } + + return null +} diff --git a/sites/vrt.be/vrt.be.test.js b/sites/vrt.be/vrt.be.test.js new file mode 100644 index 00000000..31a04ef1 --- /dev/null +++ b/sites/vrt.be/vrt.be.test.js @@ -0,0 +1,109 @@ +const { parser, url, request } = require('./vrt.be.config.js') +const axios = require('axios') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +dayjs.extend(utc) + +jest.mock('axios') + +const channelsContent = fs.readFileSync(path.resolve(__dirname, '__data__/channels.json'), 'utf8') +axios.post.mockResolvedValue({ data: JSON.parse(channelsContent) }) + +const date = dayjs.utc('2026-03-09').startOf('d') +const channel = { + lang: 'nl', + site_id: 'vrt1' +} + +it('can generate valid url', () => { + expect(url).toBe('https://www.vrt.be/vrtnu-api/graphql/public/v1') +}) + +it('can generate valid request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ 'content-type': 'application/json', 'x-vrt-client-name': 'WEB' }) +}) + +it('can generate valid request data', () => { + const data = request.data({ channel, date }) + expect(data.variables).toMatchObject({ + pageId: '/vrtmax/tv-gids/vrt1/2026-03-09/' + }) + expect(typeof data.query).toBe('string') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') + const result = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result[0]).toMatchObject({ + title: 'Mr. Magoo', + description: 'Rondleiding door de stad', + image: 'https://images.vrt.be/orig/2024/12/07/7c0854d3-67e3-4271-9e4f-43ca80b63c87.jpg', + start: '2026-03-09T05:00:08.000Z', + stop: '2026-03-09T05:07:43.760Z' + }) + + const last = result[result.length - 1] + expect(last.title).toBe('Het weer') + expect(last.start).toBe('2026-03-10T00:37:00.120Z') + expect(last.stop).toBe('2026-03-10T00:40:00.120Z') +}) + +it('can parse cursor with any channel prefix', () => { + const content = JSON.stringify({ + data: { + page: { + previous: { paginatedItems: { edges: [] } }, + next: { + paginatedItems: { + edges: [ + { + cursor: 'epg#1H#2026-03-16T11:00:00.000Z', + node: { title: 'Test', description: null, indexMeta: [], progress: null, status: { text: { small: '30 min' } }, image: null, action: null } + }, + { + cursor: 'epg#1H#2026-03-16T11:30:00.000Z', + node: { title: 'Test 2', description: null, indexMeta: [], progress: null, status: null, image: null, action: null } + } + ] + } + } + } + } + }) + const result = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + expect(result[0]).toMatchObject({ + title: 'Test', + start: '2026-03-16T11:00:00.000Z', + stop: '2026-03-16T11:30:00.000Z' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ content: '', channel, date }) + expect(result).toMatchObject([]) +}) + +it('can load channels', async () => { + const result = await require('./vrt.be.config.js').channels() + expect(result[0]).toMatchObject({ + lang: 'nl', + site_id: 'vrt1', + name: 'VRT 1' + }) + expect(result.length).toBe(10) +})