From afb7522bdc1a86f28788af6d9613f79d6b245b56 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Thu, 22 Aug 2019 17:21:45 +0200 Subject: [PATCH 01/10] Bump version number --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b3a3ace..6c96d07 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ ch.srgssr playfff - 15 + 16 jar pfff From d1fd0d80cd8d80606ef374e5e9eff1eec9b15852 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Thu, 29 Aug 2019 23:34:15 +0200 Subject: [PATCH 02/10] Add hyperlinks on deep link report view --- portal-app/src/app/deeplink/deeplink.component.html | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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]}} + From 3aa655a40e6fd318e7504534ca2598dae1060138 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Fri, 30 Aug 2019 14:48:40 +0200 Subject: [PATCH 03/10] Bump version number --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 9beeaafb7e3a0d4a69ddb9ee0d574109f1b6fd58 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Fri, 23 Aug 2019 18:59:59 +0200 Subject: [PATCH 04/10] Add parse play url v2 --- docs/README.md | 3 +- .../playfff/config/AuthenticationConfig.java | 2 +- .../controller/DeepLinkController.java | 24 +- .../playfff/model/DeepLinkJSContent.java | 31 +- .../playfff/service/DeepLinkService.java | 26 +- .../{parsePlayUrl.js => parsePlayUrl_v1.js} | 0 src/main/resources/parsePlayUrl_v2.js | 833 ++++++++++++++++++ .../controller/DeepLinkIntegrationTest.java | 15 +- .../controller/DeepLinkServiceTests.java | 26 +- 9 files changed, 927 insertions(+), 33 deletions(-) rename src/main/resources/{parsePlayUrl.js => parsePlayUrl_v1.js} (100%) create mode 100644 src/main/resources/parsePlayUrl_v2.js diff --git a/docs/README.md b/docs/README.md index 69b5db9..9a45256 100644 --- a/docs/README.md +++ b/docs/README.md @@ -50,7 +50,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. 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..db55996 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, "parsePlayUrl_v1.js"); + String javascriptV2 = BaseResourceString.getString(applicationContext, "parsePlayUrl_v2.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/parsePlayUrl_v1.js similarity index 100% rename from src/main/resources/parsePlayUrl.js rename to src/main/resources/parsePlayUrl_v1.js diff --git a/src/main/resources/parsePlayUrl_v2.js b/src/main/resources/parsePlayUrl_v2.js new file mode 100644 index 0000000..c760a9e --- /dev/null +++ b/src/main/resources/parsePlayUrl_v2.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 optionI = 0; + for (var option in queryParams) { + if(queryParams[option]){ + if(optionI > 0){ + uri = uri + "&"; + } + uri = uri + option + "=" + encodeURIComponent(queryParams[option]); + optionI++; + } + } + } + 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")); } } From 29c1e055740774ea19c20f934c7e1863cb8923cf Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sat, 31 Aug 2019 19:32:41 +0200 Subject: [PATCH 05/10] Move js files --- src/main/java/ch/srgssr/playfff/service/DeepLinkService.java | 4 ++-- .../{parsePlayUrl_v1.js => deeplink/v1/parsePlayUrl.js} | 0 .../{parsePlayUrl_v2.js => deeplink/v2/parsePlayUrl.js} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/main/resources/{parsePlayUrl_v1.js => deeplink/v1/parsePlayUrl.js} (100%) rename src/main/resources/{parsePlayUrl_v2.js => deeplink/v2/parsePlayUrl.js} (100%) diff --git a/src/main/java/ch/srgssr/playfff/service/DeepLinkService.java b/src/main/java/ch/srgssr/playfff/service/DeepLinkService.java index db55996..74db868 100644 --- a/src/main/java/ch/srgssr/playfff/service/DeepLinkService.java +++ b/src/main/java/ch/srgssr/playfff/service/DeepLinkService.java @@ -69,8 +69,8 @@ public DeepLinkJSContent getParsePlayUrlJSContent() { @CachePut(DeepLinkCacheName) public synchronized DeepLinkJSContent refreshParsePlayUrlJSContent() { - String javascriptV1 = BaseResourceString.getString(applicationContext, "parsePlayUrl_v1.js"); - String javascriptV2 = BaseResourceString.getString(applicationContext, "parsePlayUrl_v2.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"); diff --git a/src/main/resources/parsePlayUrl_v1.js b/src/main/resources/deeplink/v1/parsePlayUrl.js similarity index 100% rename from src/main/resources/parsePlayUrl_v1.js rename to src/main/resources/deeplink/v1/parsePlayUrl.js diff --git a/src/main/resources/parsePlayUrl_v2.js b/src/main/resources/deeplink/v2/parsePlayUrl.js similarity index 100% rename from src/main/resources/parsePlayUrl_v2.js rename to src/main/resources/deeplink/v2/parsePlayUrl.js From ca51102dc51338fa2f035c5ebbd93be304fe78de Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sat, 31 Aug 2019 19:40:21 +0200 Subject: [PATCH 06/10] Harmonize JS v1 nd v2 --- .../resources/deeplink/v1/parsePlayUrl.js | 20 +++---------- .../resources/deeplink/v2/parsePlayUrl.js | 28 +++++++++---------- 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/src/main/resources/deeplink/v1/parsePlayUrl.js b/src/main/resources/deeplink/v1/parsePlayUrl.js index f68cd17..f2ead3c 100644 --- a/src/main/resources/deeplink/v1/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 index c760a9e..35e0fe1 100644 --- a/src/main/resources/deeplink/v2/parsePlayUrl.js +++ b/src/main/resources/deeplink/v2/parsePlayUrl.js @@ -3,7 +3,7 @@ var parsePlayUrlVersion = 21; var parsePlayUrlBuild = "mmf"; -if(!console){ +if(! console) { var console = { log:function(){} } @@ -780,7 +780,7 @@ function serverForUrl(hostname, pathname, queryParams) { return server; } -function getBuFromHostname(hostname,pathname) { +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"; @@ -803,31 +803,31 @@ function getBuFromHostname(hostname,pathname) { } /** -* build scheme://host[/path][?queryParams[0]&...&queryParams[n-1]] -* Sample : +* 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){ +function buildUri(scheme, host, path, queryParams) { + var uri = scheme + "://" + host; + if (path) { uri = uri + "/" + path; } - if(queryParams && queryParams!=={}){ + if (queryParams && queryParams !== {}) { uri = uri + "?"; - var optionI = 0; + var optionIndex = 0; for (var option in queryParams) { - if(queryParams[option]){ - if(optionI > 0){ + if(queryParams[option]) { + if(optionIndex > 0) { uri = uri + "&"; } uri = uri + option + "=" + encodeURIComponent(queryParams[option]); - optionI++; + optionIndex++; } } } return uri; } -function buildBuUri(bu,host,path,queryParams){ - return buildUri(schemeForBu(bu),host,path,queryParams); +function buildBuUri(bu, host, path, queryParams) { + return buildUri(schemeForBu(bu), host, path, queryParams); } From 51cef2ef134dc5f12a19a6f9ab5efa7024962625 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sun, 1 Sep 2019 11:36:27 +0200 Subject: [PATCH 07/10] Add shields labels in read me --- docs/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/README.md b/docs/README.md index 9a45256..5b3d15c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,9 @@ Playfff ============= +[![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) + + ## About Playfff is a SRG micro service to serve extra datas to Play applications. Playfff means "Play Features and Functionalities with Flair". From cacf94e65b36e292eeaaab3f4f169365dd8fb87a Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sun, 1 Sep 2019 11:37:13 +0200 Subject: [PATCH 08/10] Add MIT license --- LICENSE | 19 +++++++++++++++++++ docs/README.md | 4 ++-- 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 LICENSE 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.md b/docs/README.md index 5b3d15c..6954d42 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,7 +1,7 @@ Playfff ============= -[![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) +[![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 @@ -97,4 +97,4 @@ Private APIs need a user authentification. ## License -To be defined. +See the [LICENSE](../LICENSE) file for more information. From 54f10edf487b912d403e3bd7e16a1eb5cb388717 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sun, 1 Sep 2019 12:04:55 +0200 Subject: [PATCH 09/10] Add read me logo --- docs/README-images/logo.png | Bin 0 -> 45645 bytes docs/README.md | 3 +-- 2 files changed, 1 insertion(+), 2 deletions(-) create mode 100644 docs/README-images/logo.png diff --git a/docs/README-images/logo.png b/docs/README-images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c6e9aafe5ea0b65a55cc285e9a829e7c89b1217d GIT binary patch literal 45645 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 z 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 O{F9MXkSG!}@cl1LxQosJ literal 0 HcmV?d00001 diff --git a/docs/README.md b/docs/README.md index 6954d42..63e975d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,4 @@ -Playfff -============= +![SRG Logger logo](README-images/logo.png) [![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) From 25adf478a43db9e2af9e66eed11ebe5e8014146f Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Mon, 2 Sep 2019 15:42:07 +0200 Subject: [PATCH 10/10] Add link on logo in read me --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 63e975d..878ba15 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,4 +1,4 @@ -![SRG Logger logo](README-images/logo.png) +[![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)