diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..749aa1e --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/docs/README-images/logo.png b/docs/README-images/logo.png new file mode 100644 index 0000000..c6e9aaf Binary files /dev/null and b/docs/README-images/logo.png differ diff --git a/docs/README.md b/docs/README.md index 69b5db9..878ba15 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,7 @@ -Playfff -============= +[![SRG Logger logo](README-images/logo.png)](https://github.com/SRGSSR/pfff) + +[![Build Status](https://travis-ci.org/SRGSSR/pfff.svg?branch=master)](https://travis-ci.org/SRGSSR/pfff) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/SRGSSR/pfff)](https://github.com/SRGSSR/pfff/releases) [![GitHub license](https://img.shields.io/github/license/SRGSSR/pfff)](https://github.com/SRGSSR/pfff/blob/master/LICENSE) + ## About @@ -50,7 +52,8 @@ A wide list of parameters are available. #### Deep link -* `/api/v1/deeplink/parsePlayUrl.js` (GET): Get the Play web URL to mobile application scheme URL script (deep link script). The HTTP ETag caching is supported. +* `/api/v1/deeplink/parsePlayUrl.js` (GET): Get the Play web URL to mobile application scheme URL (v1) script (deep link script). The HTTP ETag caching is supported. +* `/api/v2/deeplink/parsePlayUrl.js` (GET): Get the Play web URL to mobile application scheme URL (v2) script (deep link script). The HTTP ETag caching is supported. * `/api/v1/deeplink/report` (POST) : create or update a new deep link report object from the JSON body object. Send a report only if the script returns `[scheme]://unsupported`. The JSON object must contains: * `clientTime` (string): date of the parsing execution in `yyyy-MM-dd'T'HH:mm:ssXXX` format. * `clientId` (string): Bundle id or package name. @@ -93,4 +96,4 @@ Private APIs need a user authentification. ## License -To be defined. +See the [LICENSE](../LICENSE) file for more information. diff --git a/pom.xml b/pom.xml index 6c96d07..eeff725 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ ch.srgssr playfff - 16 + 17 jar pfff diff --git a/portal-app/src/app/deeplink/deeplink.component.html b/portal-app/src/app/deeplink/deeplink.component.html index d06811d..c07c194 100644 --- a/portal-app/src/app/deeplink/deeplink.component.html +++ b/portal-app/src/app/deeplink/deeplink.component.html @@ -14,8 +14,13 @@

- - {{deeplinkReport[col]}} + + + {{deeplinkReport[column]}} + + + {{deeplinkReport[column]}} + diff --git a/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java b/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java index a910dd3..1ffbcce 100644 --- a/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java +++ b/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java @@ -67,7 +67,7 @@ protected void configure(HttpSecurity http) throws Exception { public void configure(WebSecurity web) throws Exception { web .ignoring() - .antMatchers("/api/v1/deeplink/parsePlayUrl.js") + .antMatchers("/api/v{[0-9]+}/deeplink/parsePlayUrl.js") .antMatchers(HttpMethod.POST, "/api/v1/deeplink/report"); } diff --git a/src/main/java/ch/srgssr/playfff/controller/DeepLinkController.java b/src/main/java/ch/srgssr/playfff/controller/DeepLinkController.java index d8072ef..2d9687f 100644 --- a/src/main/java/ch/srgssr/playfff/controller/DeepLinkController.java +++ b/src/main/java/ch/srgssr/playfff/controller/DeepLinkController.java @@ -56,17 +56,31 @@ public ResponseEntity create(@RequestBody DeepLinkReport deepLin } // Public API - @RequestMapping(value="/api/v1/deeplink/parsePlayUrl.js") + @RequestMapping(value="/api/v{version}/deeplink/parsePlayUrl.js") @ResponseBody - public ResponseEntity parsePlayUrlJavascript() { + public ResponseEntity parsePlayUrlJavascript(@PathVariable("version") int version) { DeepLinkJSContent deepLinkJSContent = service.getParsePlayUrlJSContent(); - if (deepLinkJSContent != null) { + String content = null; + String hash = null; + + switch (version) { + case 1: + content = deepLinkJSContent.getContentV1(); + hash = deepLinkJSContent.getHashV1(); + break; + case 2: + content = deepLinkJSContent.getContentV2(); + hash = deepLinkJSContent.getHashV2(); + break; + } + + if (content != null && hash != null) { return ResponseEntity.ok() .cacheControl(CacheControl.empty().cachePublic()) - .eTag(deepLinkJSContent.getHash()) - .body(deepLinkJSContent.getContent()); + .eTag(hash) + .body(content); } else { return ResponseEntity.notFound().build(); } diff --git a/src/main/java/ch/srgssr/playfff/model/DeepLinkJSContent.java b/src/main/java/ch/srgssr/playfff/model/DeepLinkJSContent.java index c49c9df..e46a55c 100644 --- a/src/main/java/ch/srgssr/playfff/model/DeepLinkJSContent.java +++ b/src/main/java/ch/srgssr/playfff/model/DeepLinkJSContent.java @@ -7,19 +7,32 @@ */ public class DeepLinkJSContent { - private String content; - private String hash; + private String contentV1; + private String hashV1; - public DeepLinkJSContent(String content, String hash) { - this.content = content; - this.hash = hash; + private String contentV2; + private String hashV2; + + public DeepLinkJSContent(String contentV1, String hashV1, String contentV2, String hashV2) { + this.contentV1 = contentV1; + this.hashV1 = hashV1; + this.contentV2 = contentV2; + this.hashV2 = hashV2; + } + + public String getContentV1() { + return contentV1; + } + + public String getHashV1() { + return hashV1; } - public String getContent() { - return content; + public String getContentV2() { + return contentV2; } - public String getHash() { - return hash; + public String getHashV2() { + return hashV2; } } diff --git a/src/main/java/ch/srgssr/playfff/service/DeepLinkService.java b/src/main/java/ch/srgssr/playfff/service/DeepLinkService.java index 74c92e9..74db868 100644 --- a/src/main/java/ch/srgssr/playfff/service/DeepLinkService.java +++ b/src/main/java/ch/srgssr/playfff/service/DeepLinkService.java @@ -69,7 +69,8 @@ public DeepLinkJSContent getParsePlayUrlJSContent() { @CachePut(DeepLinkCacheName) public synchronized DeepLinkJSContent refreshParsePlayUrlJSContent() { - String javascript = BaseResourceString.getString(applicationContext, "parsePlayUrl.js"); + String javascriptV1 = BaseResourceString.getString(applicationContext, "deeplink/v1/parsePlayUrl.js"); + String javascriptV2 = BaseResourceString.getString(applicationContext, "deeplink/v2/parsePlayUrl.js"); Map buProdMap = new HashMap<>(); buProdMap.put("srf", "www.srf.ch"); @@ -125,7 +126,8 @@ public synchronized DeepLinkJSContent refreshParsePlayUrlJSContent() { } if (tvTopics != null) { - javascript = javascript.replaceAll("\\/\\* INJECT TVTOPICS OBJECT \\*\\/", "var tvTopics = " + tvTopics + ";"); + javascriptV1 = javascriptV1.replaceAll("\\/\\* INJECT TVTOPICS OBJECT \\*\\/", "var tvTopics = " + tvTopics + ";"); + javascriptV2 = javascriptV2.replaceAll("\\/\\* INJECT TVTOPICS OBJECT \\*\\/", "var tvTopics = " + tvTopics + ";"); } String tvEvents = null; @@ -136,23 +138,31 @@ public synchronized DeepLinkJSContent refreshParsePlayUrlJSContent() { } if (tvEvents != null) { - javascript = javascript.replaceAll("\\/\\* INJECT TVEVENTS OBJECT \\*\\/", "var tvEvents = " + tvEvents + ";"); + javascriptV1 = javascriptV1.replaceAll("\\/\\* INJECT TVEVENTS OBJECT \\*\\/", "var tvEvents = " + tvEvents + ";"); + javascriptV2 = javascriptV2.replaceAll("\\/\\* INJECT TVEVENTS OBJECT \\*\\/", "var tvEvents = " + tvEvents + ";"); } - String buildHash = "NO_SHA1"; + String buildHashV1 = "NO_SHA1"; try { - buildHash = Sha1.sha1(javascript); + buildHashV1 = Sha1.sha1(javascriptV1); } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { - logger.warn("sha1", e); + logger.warn("sha1 v1", e); + } + String buildHashV2 = "NO_SHA1"; + try { + buildHashV2 = Sha1.sha1(javascriptV2); + } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { + logger.warn("sha1 v2", e); } Date buildDate = new Date(); DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); dateFormat.setTimeZone(TimeZone.getTimeZone("Europe/Zurich")); String strDate = dateFormat.format(buildDate); - javascript = javascript.replaceAll("var parsePlayUrlBuild = \"mmf\";", "var parsePlayUrlBuild = \"" + buildHash + "\";\nvar parsePlayUrlBuildDate = \"" + strDate + "\";"); + javascriptV1 = javascriptV1.replaceAll("var parsePlayUrlBuild = \"mmf\";", "var parsePlayUrlBuild = \"" + buildHashV1 + "\";\nvar parsePlayUrlBuildDate = \"" + strDate + "\";"); + javascriptV2 = javascriptV2.replaceAll("var parsePlayUrlBuild = \"mmf\";", "var parsePlayUrlBuild = \"" + buildHashV2 + "\";\nvar parsePlayUrlBuildDate = \"" + strDate + "\";"); - return new DeepLinkJSContent(javascript, buildHash); + return new DeepLinkJSContent(javascriptV1, buildHashV1, javascriptV2, buildHashV2); } private Map> getTvTopicMap(Map buMap) { diff --git a/src/main/resources/parsePlayUrl.js b/src/main/resources/deeplink/v1/parsePlayUrl.js similarity index 98% rename from src/main/resources/parsePlayUrl.js rename to src/main/resources/deeplink/v1/parsePlayUrl.js index f68cd17..f2ead3c 100644 --- a/src/main/resources/parsePlayUrl.js +++ b/src/main/resources/deeplink/v1/parsePlayUrl.js @@ -3,22 +3,10 @@ var parsePlayUrlVersion = 21; var parsePlayUrlBuild = "mmf"; -function parsePlayUrl(urlString) { - var url = urlString; - try { - url = new URL(urlString); - } - catch(error) { - console.log("Can't read URL: " + error); - return null; - } - - var queryParams = {}; - for (var queryItem of url.searchParams) { - queryParams[queryItem[0]] = queryItem[1]; - } - - return parseForPlayApp(url.protocol.replace(':', ''), url.hostname, url.pathname, queryParams, url.hash.replace('#', '')); +if(! console) { + var console = { + log:function(){} + } } function parseForPlayApp(scheme, hostname, pathname, queryParams, anchor) { diff --git a/src/main/resources/deeplink/v2/parsePlayUrl.js b/src/main/resources/deeplink/v2/parsePlayUrl.js new file mode 100644 index 0000000..35e0fe1 --- /dev/null +++ b/src/main/resources/deeplink/v2/parsePlayUrl.js @@ -0,0 +1,833 @@ +// parsePlayUrl + +var parsePlayUrlVersion = 21; +var parsePlayUrlBuild = "mmf"; + +if(! console) { + var console = { + log:function(){} + } +} + +function parseForPlayApp(scheme, hostname, pathname, queryParams, anchor) { + // fix path issue + pathname = pathname.replace("//", "/"); + + // Get BU + var bu = getBuFromHostname(hostname,pathname); + if (! bu) { + console.log("This URL is not a Play SRG URL."); + return null; + } + + // Get server + var server = serverForUrl(hostname, pathname, queryParams); + + /** + * Catch special case: Player web + * + * Ex: https://tp.srgssr.ch/p/rts/default?urn=urn:rts:video:6735513 + * Ex: https://player.rts.ch/p/rts/default?urn=urn:rts:video:6735513&start=60 + */ + if (bu == "tp") { + if (pathname.startsWith("/p/")) { + var mediaURN = queryParams["urn"]; + if (mediaURN) { + var redirectBu = "tp"; + switch (true) { + case pathname.startsWith("/p/srf/"): + redirectBu = "srf"; + break; + case pathname.startsWith("/p/rts/"): + redirectBu = "rts"; + break; + case pathname.startsWith("/p/rsi/"): + redirectBu = "rsi"; + break; + case pathname.startsWith("/p/rtr/"): + redirectBu = "rtr"; + break; + case pathname.startsWith("/p/swi/"): + redirectBu = "swi"; + break; + } + var startTime = queryParams["start"]; + return openMediaURN(server, redirectBu, mediaURN, startTime); + } + } + } + + /** + * Catch special case: radio swiss + * + * Ex: http://www.radioswisspop.ch/de/webplayer + * Ex: http://www.radioswissclassic.ch/fr/webplayer + * Ex: http://www.radioswissjazz.ch/it/webplayer + */ + if (bu == "radioswiss") { + var redirectBu = null; + switch (true) { + case pathname.startsWith("/de"): + redirectBu = "srf"; + break; + case pathname.startsWith("/fr"): + redirectBu = "rts"; + break; + case pathname.startsWith("/it"): + redirectBu = "rsi"; + break; + } + if (redirectBu) { + return openURL(server, redirectBu, scheme, hostname, pathname, queryParams, anchor); + } + } + + /** + * Catch special case: Play MMF + * + * Ex: https://play-mmf.herokuapp.com/mmf/ + * Ex: https://play-mmf.herokuapp.com/mmf/media/urn:rts:video:_rts_info_delay + */ + if (bu == "mmf") { + if (pathname.includes("/media/")) { + var lastPathComponent = pathname.split("/").slice(-1)[0]; + return openMediaURN(server, bu, lastPathComponent, null); + } + + // Returns default TV homepage + return openTvHomePage(server,bu); + } + + if (hostname.includes("play-mmf") && ! pathname.startsWith("/mmf/")) { + pathname = pathname.substring(4); + } + + /** + * Catch special case: shared RTS media urls built by RTS MAM. + * + * Ex: https://www.rts.ch/video + * Ex: https://www.rts.ch/video/emissions/signes/9901229-la-route-de-lexil-en-langue-des-signes.html + * Ex: https://www.rts.ch/audio/la-1ere + */ + if (bu == "rts" && (pathname.startsWith("/video") || pathname.startsWith("/audio"))) { + var mediaId = null; + + if (pathname.endsWith(".html")) { + var lastPath = pathname.substr(pathname.lastIndexOf('/') + 1); + mediaId = lastPath.split('.')[0].split('-')[0]; + } + + if (mediaId) { + var mediaType = (pathname.startsWith("/video")) ? "video" : "audio"; + return openMedia(server, bu, mediaType, mediaId, null); + } + else if (pathname.startsWith("/video")) { + // Returns default TV homepage + return openTvHomePage(server,bu); + } + else { + var channelId = null; + var paths = pathname.split('/'); + if (paths.length > 2) { + var radioId = paths[2]; + switch (radioId) { + case "la-1ere": + channelId = "a9e7621504c6959e35c3ecbe7f6bed0446cdf8da"; + break; + case "espace-2": + channelId = "a83f29dee7a5d0d3f9fccdb9c92161b1afb512db"; + break; + case "couleur3": + channelId = "8ceb28d9b3f1dd876d1df1780f908578cbefc3d7"; + break; + case "option-musique": + channelId = "f8517e5319a515e013551eea15aa114fa5cfbc3a"; + break; + } + } + // Returns default radio homepage + return openRadioHomePage(server, bu, channelId); + } + } + + if (! pathname.startsWith("/play")) { + console.log("No /play path in url."); + return null; + } + + /** + * Catch classic media urls + * + * Ex: https://www.rts.ch/play/tv/faut-pas-croire/video/exportations-darmes--la-suisse-vend-t-elle-la-guerre-ou-la-paix-?id=9938530 + */ + var mediaType = null; + switch (true) { + case pathname.includes("/video/"): + mediaType = "video"; + break; + case pathname.includes("/audio/"): + mediaType = "audio"; + break; + } + + if (mediaType) { + var mediaId = queryParams["id"]; + if (mediaId) { + var startTime = queryParams["startTime"]; + return openMedia(server, bu, mediaType, mediaId, startTime); + } + else { + mediaType = null; + } + } + + /** + * Catch redirect media urls + * + * Ex: https://www.rts.ch/play/tv/redirect/detail/9938530 + */ + switch (true) { + case pathname.includes("/tv/redirect/detail/"): + mediaType = "video"; + break; + case pathname.includes("/radio/redirect/detail/"): + mediaType = "audio"; + break; + } + + if (mediaType) { + var mediaId = pathname.substr(pathname.lastIndexOf('/') + 1); + if (mediaId) { + var startTime = queryParams["startTime"]; + return openMedia(server, bu, mediaType, mediaId, startTime); + } + else { + mediaType = null; + } + } + + /** + * Catch live TV urls + * + * Ex: https://www.srf.ch/play/tv/live?tvLiveId=c49c1d73-2f70-0001-138a-15e0c4ccd3d0 + */ + if (pathname.endsWith("/tv/live") || pathname.endsWith("/tv/direct")) { + var mediaId = queryParams["tvLiveId"]; + if (mediaId) { + return openMedia(server, bu, "video", mediaId, null); + } + else { + // Returns default TV homepage + return openTvHomePage(server,bu); + } + } + + /** + * Catch live radio urls + * + * Ex: https://www.rsi.ch/play/radio/livepopup/rete-uno + */ + if (pathname.includes("/radio/livepopup/")) { + var mediaBu = null; + var mediaId = null; + switch (pathname.substr(pathname.lastIndexOf('/') + 1)) { + case "radio-srf-1": + mediaBu = "srf"; + mediaId = "69e8ac16-4327-4af4-b873-fd5cd6e895a7"; + break; + case "radio-srf-2-kultur": + mediaBu = "srf"; + mediaId = "c8537421-c9c5-4461-9c9c-c15816458b46"; + break; + case "radio-srf-3": + mediaBu = "srf"; + mediaId = "dd0fa1ba-4ff6-4e1a-ab74-d7e49057d96f"; + break; + case "radio-srf-4-news": + mediaBu = "srf"; + mediaId = "ee1fb348-2b6a-4958-9aac-ec6c87e190da"; + break; + case "radio-srf-musikwelle": + mediaBu = "srf"; + mediaId = "a9c5c070-8899-46c7-ac27-f04f1be902fd"; + break; + case "radio-srf-virus": + mediaBu = "srf"; + mediaId = "66815fe2-9008-4853-80a5-f9caaffdf3a9"; + break; + case "la-1ere": + mediaBu = "rts"; + mediaId = "3262320"; + break; + case "espace-2": + mediaBu = "rts"; + mediaId = "3262362"; + break; + case "couleur-3": + mediaBu = "rts"; + mediaId = "3262363"; + break; + case "option-musique": + mediaBu = "rts"; + mediaId = "3262364"; + break; + case "rete-uno": + mediaBu = "rsi"; + mediaId = "livestream_ReteUno"; + break; + case "rete-due": + mediaBu = "rsi"; + mediaId = "livestream_ReteDue"; + break; + case "rete-tre": + mediaBu = "rsi"; + mediaId = "livestream_ReteTre"; + break; + case "rtr": + mediaBu = "rtr"; + mediaId = "a029e818-77a5-4c2e-ad70-d573bb865e31"; + break; + } + + if (mediaBu && mediaId) { + return openMedia(server, mediaBu, "audio", mediaId, null); + } + else { + // Returns default radio homepage + return openRadioHomePage(server, bu, null); + } + } + + /** + * Catch live tv popup urls + * + * Ex: https://www.srf.ch/play/tv/popupvideoplayer?id=b833a5af-63c6-4310-bb80-05341310a4f5 + */ + if (pathname.includes("/tv/popupvideoplayer")) { + var mediaId = queryParams["id"]; + if (mediaId) { + return openMedia(server, bu, "video", mediaId, null); + } + else { + // Returns default TV homepage + return openTvHomePage(server,bu); + } + } + + /** + * Catch classic show urls + * + * Ex: https://www.rts.ch/play/tv/emission/faut-pas-croire?id=6176 + */ + var showTransmission = null; + switch (true) { + case pathname.includes("/tv/sendung") || pathname.includes("/tv/emission") || pathname.includes("/tv/programma") || pathname.includes("/tv/emissiuns"): + showTransmission = "tv"; + break; + case pathname.includes("/radio/sendung") || pathname.includes("/radio/emission") || pathname.includes("/radio/programma") || pathname.includes("/radio/emissiuns"): + showTransmission = "radio"; + break; + } + + if (showTransmission) { + var showId = queryParams["id"]; + if (showId) { + return openShow(server, bu, showTransmission, showId); + } + else { + showTransmission = null; + } + } + + /** + * Catch redirect show urls + * + * Ex: https://www.rts.ch/play/tv/quicklink/6176 + */ + switch (true) { + case pathname.includes("/tv/quicklink/"): + showTransmission = "tv"; + break; + case pathname.includes("/radio/quicklink/"): + showTransmission = "radio"; + break; + } + + if (showTransmission) { + var showId = pathname.substr(pathname.lastIndexOf('/') + 1); + if (showId) { + return openShow(server, bu, showTransmission, showId); + } + else { + showTransmission = null; + } + } + + /** + * Catch home TV urls + * + * Ex: https://www.srf.ch/play/tv + */ + if (pathname.endsWith("/tv")) { + return openTvHomePage(server,bu); + } + + /** + * Catch home radio urls + * + * Ex: https://www.srf.ch/play/radio?station=ee1fb348-2b6a-4958-9aac-ec6c87e190da + */ + if (pathname.endsWith("/radio")) { + var channelId = queryParams["station"]; + return openRadioHomePage(server, bu,channelId); + } + + /** + * Catch AZ TV urls + * + * Ex: https://www.rts.ch/play/tv/emissions?index=G + */ + if (pathname.endsWith("/tv/sendungen") || pathname.endsWith("/tv/emissions") || pathname.endsWith("/tv/programmi") || pathname.endsWith("/tv/emissiuns")) { + var index = queryParams["index"]; + if (index) { + index = index.toLowerCase(); + index = (index.length > 1) ? null : index; + } + return openAtoZ(server, bu, null, index); + } + + /** + * Catch AZ radio urls + * + * Ex: https://www.rts.ch/play/radio/emissions?index=S&station=8ceb28d9b3f1dd876d1df1780f908578cbefc3d7 + */ + if (pathname.endsWith("/radio/sendungen") || pathname.endsWith("/radio/emissions") || pathname.endsWith("/radio/programmi") || pathname.endsWith("/radio/emissiuns")) { + var channelId = queryParams["station"]; + var index = queryParams["index"]; + if (index) { + index = index.toLowerCase(); + index = (index.length > 1) ? null : index; + } + return openRadioAtoZ(server, bu, channelId, index); + } + + /** + * Catch by date TV urls + * + * Ex: https://www.rtr.ch/play/tv/emissiuns-tenor-data?date=07-03-2019 + */ + if (pathname.endsWith("/tv/sendungen-nach-datum") || pathname.endsWith("/tv/emissions-par-dates") || pathname.endsWith("/tv/programmi-per-data") || pathname.endsWith("/tv/emissiuns-tenor-data")) { + var date = queryParams["date"]; + if (date) { + // Returns an ISO format + var dateArray = date.split("-"); + if (dateArray.length == 3 && dateArray[2].length == 4 && dateArray[1].length == 2 && dateArray[0].length == 2) { + date = dateArray[2] + "-" + dateArray[1] + "-" + dateArray[0]; + } + else { + date = null; + } + } + return openByDate(server, bu, null, date); + } + + /** + * Catch by date radio urls + * + * Ex: https://www.rts.ch/play/radio/emissions-par-dates?date=07-03-2019&station=8ceb28d9b3f1dd876d1df1780f908578cbefc3d7 + */ + if (pathname.endsWith("/radio/sendungen-nach-datum") || pathname.endsWith("/radio/emissions-par-dates") || pathname.endsWith("/radio/programmi-per-data") || pathname.endsWith("/radio/emissiuns-tenor-data")) { + var channelId = queryParams["station"]; + var date = queryParams["date"]; + if (date) { + // Returns an ISO format + var dateArray = date.split("-"); + if (dateArray.length == 3 && dateArray[2].length == 4 && dateArray[1].length == 2 && dateArray[0].length == 2) { + date = dateArray[2] + "-" + dateArray[1] + "-" + dateArray[0]; + } + else { + date = null; + } + } + return openRadioByDate(server, bu, channelId, date); + } + + /** + * Catch search urls + * + * Ex: https://www.rsi.ch/play/ricerca?query=federer%20finale + * Ex: https://www.rtr.ch/play/retschertga?query=Federer%20tennis&mediaType=video + */ + if (pathname.endsWith("/suche") || pathname.endsWith("/recherche") || pathname.endsWith("/ricerca") || pathname.endsWith("/retschertga") || pathname.endsWith("/search")) { + var query = queryParams["query"]; + var mediaType = queryParams["mediaType"]; + if (mediaType) { + mediaType = mediaType.toLowerCase(); + if (mediaType != "video" && mediaType != "audio") { + mediaType = null; + } + } + return openSearch(server, bu, query, mediaType); + } + + /** + * Catch TV topics urls + * + * Ex: https://www.rts.ch/play/tv/categories/info + */ + if (pathname.endsWith("/tv/themen") || pathname.endsWith("/tv/categories") || pathname.endsWith("/tv/categorie") || pathname.endsWith("/tv/tematicas") || pathname.endsWith("/tv/topics")) { + return openTvHomePage(server,bu); + } + else if (pathname.includes("/tv/themen") || pathname.includes("/tv/categories") || pathname.includes("/tv/categorie") || pathname.includes("/tv/tematicas") || pathname.includes("/tv/topics")) { + var lastPathComponent = pathname.split("/").slice(-1)[0]; + + var topicId = null; + + /* INJECT TVTOPICS OBJECT */ + + if (typeof tvTopics !== 'undefined' && lastPathComponent.length > 0) { + topicId = tvTopics[server][bu][lastPathComponent]; + } + + if (topicId) { + return openTopic(server, bu, "tv", topicId); + } + else { + return openTvHomePage(server,bu); + } + } + + /** + * Catch TV event urls + * + * Ex: https://www.srf.ch/play/tv/event/10-jahre-auf-und-davon + *. Ex: https://www.rsi.ch/play/tv/event/event-playrsi-8858482 + */ + if (pathname.endsWith("/tv/event")) { + return openTvHomePage(server,bu); + } + else if (pathname.includes("/tv/event")) { + var lastPathComponent = pathname.split("/").slice(-1)[0]; + + var eventId = null; + + /* INJECT TVEVENTS OBJECT */ + + if (typeof tvEvents !== 'undefined' && lastPathComponent.length > 0) { + eventId = tvEvents[server][bu][lastPathComponent]; + } + + if (eventId) { + return openModule(server, bu, "event", eventId); + } + else { + return openTvHomePage(server,bu); + } + } + + /** + * Catch base play urls + * + * Ex: https://www.srf.ch/play/ + *. Ex: https://www.rsi.ch/play + */ + if (pathname.endsWith("/play/") || pathname.endsWith("/play")) { + return openTvHomePage(server,bu); + } + + /** + * Catch play help urls + * + * Ex: https://www.srf.ch/play/tv/hilfe + * Ex: https://www.rts.ch/play/tv/aide + * Ex: https://www.rsi.ch/play/tv/guida + * Ex: https://www.rtr.ch/play/tv/agid + * Ex: https://play.swissinfo.ch/play/tv/help + */ + if (pathname.endsWith("/hilfe") || pathname.endsWith("/aide") || pathname.endsWith("/guida") || pathname.endsWith("/agid") || pathname.endsWith("/help")) { + return openURL(server, bu, scheme, hostname, pathname, queryParams, anchor); + } + + // Redirect fallback. + console.log("Can't parse Play URL. Unsupported URL."); + return schemeForBu(bu) + "://unsupported?server=" + server; +}; + +// ---- Open functions + +function openMedia(server, bu, mediaType, mediaId, startTime) { + var urn="urn:" + bu + ":" + mediaType + ":" + mediaId; + return openMediaURN(server,bu,urn,startTime); +} + +function openMediaURN(server, bu, mediaURN, startTime) { + var options = {}; + if (startTime) { + options['start_time'] = startTime; + } + if (server) { + options['server'] = server; + } + return buildBuUri(bu,"media",mediaURN,options); +} + +function openShow(server, bu, showTransmission, showId) { + var showUrn="urn:" + bu + ":show:" + showTransmission + ":" + showId; + var options = {}; + if (server) { + options['server'] = server; + } + return buildBuUri(bu,"show",showUrn,options); +} + +function openTopic(server, bu, topicTransmission, topicId) { + var topicUrn="urn:" + bu + ":topic:" + topicTransmission + ":" + topicId; + var options = {}; + if (server) { + options['server'] = server; + } + return buildBuUri(bu,"topic",topicUrn,options); +} + +function openModule(server, bu, moduleType, moduleId) { + var topicUrn="urn:" + bu + ":module:" + moduleType + ":" + moduleId; + var options = {}; + if (server) { + options['server'] = server; + } + return buildBuUri(bu,"module",topicUrn,options); +} + +function openTvHomePage(server,bu){ + var options = {}; + if (server) { + options['server'] = server; + } + return buildBuUri(bu,"home",null,options); +} + +function openRadioHomePage(server,bu,channelId){ + if (!channelId) { + channelId = primaryChannelUidForBu(bu); + } + var options={}; + if(channelId){ + options["channel_id"] = channelId; + } + if (server) { + options['server'] = server; + } + return buildBuUri(bu,"home",null,options); +} + +function openAtoZ(server,bu,channelId,index){ + var options = {}; + if(channelId) { + options['channel_id'] = channelId; + } + if(index) { + options['index'] = index; + } + if (server) { + options['server'] = server; + } + return buildBuUri(bu,"az",null,options); +} + +function openRadioAtoZ(server,bu,channelId,index){ + if (!channelId) { + channelId = primaryChannelUidForBu(bu); + } + return openAtoZ(server,bu,channelId,index); +} + +function openByDate(server,bu,channelId,date){ + var options = {}; + if(channelId) { + options['channel_id'] = channelId; + } + + if(date) { + options['date'] = date; + } + if (server) { + options['server'] = server; + } + return buildBuUri(bu, "bydate", null, options); +} + +function openRadioByDate(server,bu,channelId,date) { + if (!channelId) { + channelId = primaryChannelUidForBu(bu); + } + return openByDate(server,bu,channelId,date); +} + +function openSearch(server, bu, query, mediaType){ + var options = {}; + if(query) { + options['query'] = query; + } + if(mediaType) { + options['media_type'] = mediaType; + } + if (server) { + options['server'] = server; + } + return buildBuUri(bu,"search", null, options); +} + +function openURL(server, bu, scheme, hostname, pathname, queryParams, anchor) { + if (! scheme) { + scheme = "http"; + } + + var queryParamsString = ""; + if (queryParams) { + for (var key in queryParams) { + queryParamsString = queryParamsString + "&" + key + "=" + encodeURIComponent(queryParams[key]); + } + } + if (queryParamsString.length > 0) { + queryParamsString = queryParamsString.replace('&','?'); + } + + var anchorString = ""; + if (anchor) { + anchorString = "#" + anchor; + } + + var url = scheme + "://" + hostname + pathname + queryParamsString + anchorString; + var options = {}; + options['url'] = url; + if (server) { + options['server'] = server; + } + return buildBuUri(bu,"link",null,options) +} + +// --- parsing functions + +function primaryChannelUidForBu(bu) { + switch (bu) { + case "srf": + return "69e8ac16-4327-4af4-b873-fd5cd6e895a7"; + break; + case "rts": + return "a9e7621504c6959e35c3ecbe7f6bed0446cdf8da"; + break; + case "rsi": + return "rete-uno"; + break; + case "rtr": + return "12fb886e-b7aa-4e55-beb2-45dbc619f3c4"; + break; + default: + return null; + } +} + +function schemeForBu(bu) { + switch (bu) { + case "srf": + return "playsrf"; + break; + case "rts": + return "playrts"; + break; + case "rsi": + return "playrsi"; + break; + case "rtr": + return "playrtr"; + break; + case "swi": + return "playswi"; + break; + case "mmf": + case "tp": + return "letterbox"; + break; + default: + return null; + } +} + +function serverForUrl(hostname, pathname, queryParams) { + var server = "production"; + if (hostname.includes("stage")) { + server = "stage"; + } + else if (hostname.includes("test")) { + server = "test"; + } + else if (hostname.includes("play-mmf")) { + if (pathname.startsWith("/mmf/")) { + server = "play mmf"; + } + else { + var serverParam = queryParams["server"]; + switch (serverParam) { + case "stage": + server = "stage"; + break; + case "test": + server = "test"; + break; + } + } + } + return server; +} + +function getBuFromHostname(hostname, pathname) { + switch (true) { + case hostname.endsWith("tp.srgssr.ch") || hostname.endsWith("player.rts.ch") || hostname.endsWith("player.rsi.ch") || hostname.endsWith("player.rtr.ch") || hostname.endsWith("player.swissinfo.ch") || hostname.endsWith("player.srf.ch"): + return "tp"; + case hostname.includes("rts.ch") || hostname.includes("srgplayer-rts") || (hostname.includes("play-mmf") && pathname.startsWith("/rts/")): + return "rts"; + case hostname.includes("rsi.ch") || hostname.includes("srgplayer-rsi") || (hostname.includes("play-mmf") && pathname.startsWith("/rsi/")): + return "rsi"; + case hostname.includes("rtr.ch") || hostname.includes("srgplayer-rtr") || (hostname.includes("play-mmf") && pathname.startsWith("/rtr/")): + return "rtr"; + case hostname.includes("swissinfo.ch") || hostname.includes("srgplayer-swi") || (hostname.includes("play-mmf") && pathname.startsWith("/swi/")): + return "swi"; + case hostname.includes("srf.ch") || hostname.includes("srgplayer-srf") || (hostname.includes("play-mmf") && pathname.startsWith("/srf/")): + return "srf"; + case hostname.includes("play-mmf") && pathname.startsWith("/mmf/"): + return "mmf"; + case hostname.includes("radioswisspop.ch") || hostname.includes("radioswissclassic.ch") || hostname.includes("radioswissjazz.ch"): + return "radioswiss"; + } + return null; + } + +/** +* Build scheme://host[/path][?queryParams[0]&...&queryParams[n-1]] +* Sample: +* playrts://media/urn:xxx?position=0&server=mmf +*/ +function buildUri(scheme, host, path, queryParams) { + var uri = scheme + "://" + host; + if (path) { + uri = uri + "/" + path; + } + if (queryParams && queryParams !== {}) { + uri = uri + "?"; + var optionIndex = 0; + for (var option in queryParams) { + if(queryParams[option]) { + if(optionIndex > 0) { + uri = uri + "&"; + } + uri = uri + option + "=" + encodeURIComponent(queryParams[option]); + optionIndex++; + } + } + } + return uri; +} + +function buildBuUri(bu, host, path, queryParams) { + return buildUri(schemeForBu(bu), host, path, queryParams); +} diff --git a/src/test/java/ch/srgssr/playfff/controller/DeepLinkIntegrationTest.java b/src/test/java/ch/srgssr/playfff/controller/DeepLinkIntegrationTest.java index f69385d..e45014e 100644 --- a/src/test/java/ch/srgssr/playfff/controller/DeepLinkIntegrationTest.java +++ b/src/test/java/ch/srgssr/playfff/controller/DeepLinkIntegrationTest.java @@ -55,7 +55,7 @@ public void tearDown() { } @Test - public void getParsePlayUrl() throws Exception { + public void getParsePlayUrlV1() throws Exception { MvcResult mvcResult = mvc.perform(get("/api/v1/deeplink/parsePlayUrl.js")) .andExpect(status().isOk()) .andExpect(header().string("ETag", IsNull.notNullValue())) @@ -67,6 +67,19 @@ public void getParsePlayUrl() throws Exception { .andExpect(content().string("")); } + @Test + public void getParsePlayUrlV2() throws Exception { + MvcResult mvcResult = mvc.perform(get("/api/v2/deeplink/parsePlayUrl.js")) + .andExpect(status().isOk()) + .andExpect(header().string("ETag", IsNull.notNullValue())) + .andExpect(content().string(IsNull.notNullValue())).andReturn(); + String eTag = mvcResult.getResponse().getHeader("ETag"); + + mvc.perform(get("/api/v2/deeplink/parsePlayUrl.js").header("If-None-Match", eTag)) + .andExpect(status().isNotModified()) + .andExpect(content().string("")); + } + @Test public void reportNotAcceptable() throws Exception { mvc.perform(post("/api/v1/deeplink/report")).andExpect(status().isBadRequest()); diff --git a/src/test/java/ch/srgssr/playfff/controller/DeepLinkServiceTests.java b/src/test/java/ch/srgssr/playfff/controller/DeepLinkServiceTests.java index c105cf9..bb3fd5f 100644 --- a/src/test/java/ch/srgssr/playfff/controller/DeepLinkServiceTests.java +++ b/src/test/java/ch/srgssr/playfff/controller/DeepLinkServiceTests.java @@ -20,19 +20,29 @@ public class DeepLinkServiceTests { public void getParsePlayUrlContentTest() { DeepLinkJSContent deepLinkJSContent = deepLinkService.getParsePlayUrlJSContent(); - Assert.assertNotNull(deepLinkJSContent.getContent()); - Assert.assertNotNull(deepLinkJSContent.getHash()); - Assert.assertTrue(deepLinkJSContent.getContent().contains(deepLinkJSContent.getHash())); + Assert.assertNotNull(deepLinkJSContent.getContentV1()); + Assert.assertNotNull(deepLinkJSContent.getHashV1()); + Assert.assertTrue(deepLinkJSContent.getContentV1().contains(deepLinkJSContent.getHashV1())); + + Assert.assertNotNull(deepLinkJSContent.getContentV2()); + Assert.assertNotNull(deepLinkJSContent.getHashV2()); + Assert.assertTrue(deepLinkJSContent.getContentV2().contains(deepLinkJSContent.getHashV2())); } @Test public void refreshParsePlayUrlContentTest() { DeepLinkJSContent deepLinkJSContent = deepLinkService.refreshParsePlayUrlJSContent(); - Assert.assertNotNull(deepLinkJSContent.getContent()); - Assert.assertNotNull(deepLinkJSContent.getHash()); - Assert.assertTrue(deepLinkJSContent.getContent().contains(deepLinkJSContent.getHash())); - Assert.assertFalse(deepLinkJSContent.getContent().contains("INJECT TVTOPICS OBJECT")); - Assert.assertFalse(deepLinkJSContent.getContent().contains("INJECT TVEVENTS OBJECT")); + Assert.assertNotNull(deepLinkJSContent.getContentV1()); + Assert.assertNotNull(deepLinkJSContent.getHashV1()); + Assert.assertTrue(deepLinkJSContent.getContentV1().contains(deepLinkJSContent.getHashV1())); + Assert.assertFalse(deepLinkJSContent.getContentV1().contains("INJECT TVTOPICS OBJECT")); + Assert.assertFalse(deepLinkJSContent.getContentV1().contains("INJECT TVEVENTS OBJECT")); + + Assert.assertNotNull(deepLinkJSContent.getContentV2()); + Assert.assertNotNull(deepLinkJSContent.getHashV2()); + Assert.assertTrue(deepLinkJSContent.getContentV2().contains(deepLinkJSContent.getHashV2())); + Assert.assertFalse(deepLinkJSContent.getContentV2().contains("INJECT TVTOPICS OBJECT")); + Assert.assertFalse(deepLinkJSContent.getContentV2().contains("INJECT TVEVENTS OBJECT")); } }