@@ -15,7 +19,7 @@ Update Details
{{update[col]}}
- Delete Update
+ Delete
diff --git a/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java b/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java
index ddfda57..a910dd3 100644
--- a/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java
+++ b/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java
@@ -3,7 +3,9 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
@@ -11,6 +13,7 @@
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import java.util.Collections;
@@ -30,7 +33,7 @@ public AuthenticationConfig(
@Value("${PFFF_USER:}") String user,
@Value("${PFFF_PASSWORD:}") String password) {
- this.user = user;;
+ this.user = user;
this.password = password;
}
@@ -43,12 +46,29 @@ protected void configure(HttpSecurity http) throws Exception {
.and()
.formLogin()
.loginPage("/login")
+ .defaultSuccessUrl("/", true)
.permitAll()
.and()
.logout()
+ .deleteCookies("JSESSIONID")
+ .invalidateHttpSession(true)
+ .logoutUrl("/logout")
+ .logoutSuccessUrl("/login")
.permitAll();
http
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
+
+ http
+ .logout()
+ .logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
+ }
+
+ @Override
+ public void configure(WebSecurity web) throws Exception {
+ web
+ .ignoring()
+ .antMatchers("/api/v1/deeplink/parsePlayUrl.js")
+ .antMatchers(HttpMethod.POST, "/api/v1/deeplink/report");
}
@Bean
diff --git a/src/main/java/ch/srgssr/playfff/config/DeeplinkConfig.java b/src/main/java/ch/srgssr/playfff/config/DeeplinkConfig.java
new file mode 100644
index 0000000..c01dfb1
--- /dev/null
+++ b/src/main/java/ch/srgssr/playfff/config/DeeplinkConfig.java
@@ -0,0 +1,30 @@
+package ch.srgssr.playfff.config;
+
+import ch.srgssr.playfff.service.DeepLinkService;
+import ch.srgssr.playfff.service.DeepLinkReportService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+
+/**
+ * Copyright (c) SRG SSR. All rights reserved.
+ *
+ * License information is available from the LICENSE file.
+ */
+@Configuration
+@EnableScheduling
+public class DeeplinkConfig {
+
+ @Autowired
+ private DeepLinkService deepLinkService;
+
+ @Autowired
+ private DeepLinkReportService deepLinkReportService;
+
+ @Scheduled(fixedDelayString = "${DEEP_LINK_REFRESH_DELAY_MS:300000}")
+ public void DeepLinkRefresh() {
+ deepLinkService.refreshParsePlayUrlJSContent();
+ deepLinkReportService.purgeOlderReports();
+ }
+}
diff --git a/src/main/java/ch/srgssr/playfff/config/WebMvcConfig.java b/src/main/java/ch/srgssr/playfff/config/WebMvcConfig.java
new file mode 100644
index 0000000..84d7b4a
--- /dev/null
+++ b/src/main/java/ch/srgssr/playfff/config/WebMvcConfig.java
@@ -0,0 +1,22 @@
+package ch.srgssr.playfff.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
+
+/**
+ * Copyright (c) SRG SSR. All rights reserved.
+ *
+ * License information is available from the LICENSE file.
+ */
+@Configuration
+public class WebMvcConfig extends WebMvcConfigurerAdapter {
+
+ @Override
+ public void addViewControllers(final ViewControllerRegistry registry) {
+ super.addViewControllers(registry);
+ registry.addViewController("/updates").setViewName("forward:/");
+ registry.addViewController("/add_update").setViewName("forward:/");
+ registry.addViewController("/deeplink").setViewName("forward:/");
+ }
+}
diff --git a/src/main/java/ch/srgssr/playfff/controller/DeepLinkController.java b/src/main/java/ch/srgssr/playfff/controller/DeepLinkController.java
new file mode 100644
index 0000000..d8072ef
--- /dev/null
+++ b/src/main/java/ch/srgssr/playfff/controller/DeepLinkController.java
@@ -0,0 +1,74 @@
+package ch.srgssr.playfff.controller;
+
+import ch.srgssr.playfff.model.DeepLinkJSContent;
+import ch.srgssr.playfff.model.DeepLinkReport;
+import ch.srgssr.playfff.service.DeepLinkService;
+import ch.srgssr.playfff.service.DeepLinkReportService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.CacheControl;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.context.request.WebRequest;
+
+/**
+ * Copyright (c) SRG SSR. All rights reserved.
+ *
+ * License information is available from the LICENSE file.
+ */
+@Controller
+@CrossOrigin(origins = "*")
+public class DeepLinkController {
+
+ @Autowired
+ private DeepLinkService service;
+
+ @Autowired
+ private DeepLinkReportService deepLinkReportService;
+
+ @GetMapping(path = {"/api/v1/deeplink/report/{id}"})
+ public ResponseEntity findOne(@PathVariable("id") int id) {
+ return new ResponseEntity<>(deepLinkReportService.findById(id), HttpStatus.OK);
+ }
+
+ @DeleteMapping(path = {"/api/v1/deeplink/report/{id}"})
+ public ResponseEntity delete(@PathVariable("id") int id) {
+ return new ResponseEntity<>(deepLinkReportService.delete(id), HttpStatus.OK);
+ }
+
+ @GetMapping("/api/v1/deeplink/report")
+ public ResponseEntity> findAll() {
+ return new ResponseEntity<>(deepLinkReportService.findAllByOrderByJsVersionDescCountDesc(), HttpStatus.OK);
+ }
+
+ // Public API
+ @PostMapping("/api/v1/deeplink/report")
+ public ResponseEntity create(@RequestBody DeepLinkReport deepLinkReport, WebRequest webRequest) {
+
+ if (deepLinkReport == null
+ || deepLinkReport.clientTime == null || deepLinkReport.clientId == null
+ || deepLinkReport.jsVersion == 0 || deepLinkReport.url == null) {
+ return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE);
+ } else {
+ return new ResponseEntity<>(deepLinkReportService.save(deepLinkReport), HttpStatus.CREATED);
+ }
+ }
+
+ // Public API
+ @RequestMapping(value="/api/v1/deeplink/parsePlayUrl.js")
+ @ResponseBody
+ public ResponseEntity parsePlayUrlJavascript() {
+
+ DeepLinkJSContent deepLinkJSContent = service.getParsePlayUrlJSContent();
+
+ if (deepLinkJSContent != null) {
+ return ResponseEntity.ok()
+ .cacheControl(CacheControl.empty().cachePublic())
+ .eTag(deepLinkJSContent.getHash())
+ .body(deepLinkJSContent.getContent());
+ } else {
+ return ResponseEntity.notFound().build();
+ }
+ }
+}
diff --git a/src/main/java/ch/srgssr/playfff/controller/UpdateController.java b/src/main/java/ch/srgssr/playfff/controller/UpdateController.java
index d153922..a7c72d6 100644
--- a/src/main/java/ch/srgssr/playfff/controller/UpdateController.java
+++ b/src/main/java/ch/srgssr/playfff/controller/UpdateController.java
@@ -20,53 +20,11 @@ public class UpdateController {
@Autowired
UpdateService updateService;
- @RequestMapping(value = "/update/admin", method = RequestMethod.GET)
- public String updateAdmin(@RequestParam(value = "name", required = false, defaultValue = "World") String name, Model model) {
- model.addAttribute("name", name);
- return "update/entry";
- }
-
- @RequestMapping(value = "/update/admin", method = RequestMethod.POST)
- @ResponseBody
- public String updateSave(@RequestParam(value = "package") String packageName, @RequestParam(value = "version") String version, @RequestParam(value = "text") String text, @RequestParam(value = "mandatory", required = false) boolean mandatory) {
- Update note = new Update();
- note.packageName = packageName;
- note.text = text;
- note.version = version;
- note.mandatory = mandatory;
- updateService.save(note);
- return "pushed";
- }
-
- @RequestMapping(value = "/update/remove", method = RequestMethod.POST)
- @ResponseBody
- public String updateRemove(@RequestParam(value = "package") String packageName, @RequestParam(value = "version") String version) {
- updateService.remove(packageName, version);
- return "removed";
- }
-
- @RequestMapping("/api/v1/update/check")
- public ResponseEntity updateText(@RequestParam(value = "package") String packageName, @RequestParam(value = "version") String version) {
- Update update = updateService.getUpdate(packageName, version);
- return new ResponseEntity<>(new UpdateResult(update), HttpStatus.OK);
- }
-
-
- @PostMapping("/api/v1/update")
- public ResponseEntity create(@RequestBody Update update) {
- return new ResponseEntity<>(updateService.create(update), HttpStatus.OK);
- }
-
@GetMapping(path = {"/api/v1/update/{id}"})
public ResponseEntity findOne(@PathVariable("id") int id) {
return new ResponseEntity<>(updateService.findById(id), HttpStatus.OK);
}
- @PutMapping("/api/v1/update")
- public ResponseEntity update(@RequestBody Update update) {
- return new ResponseEntity<>(updateService.update(update), HttpStatus.OK);
- }
-
@DeleteMapping(path = {"/api/v1/update/{id}"})
public ResponseEntity delete(@PathVariable("id") int id) {
return new ResponseEntity<>(updateService.delete(id), HttpStatus.OK);
@@ -76,4 +34,23 @@ public ResponseEntity delete(@PathVariable("id") int id) {
public ResponseEntity> findAllDesc() {
return new ResponseEntity<>(updateService.findAllDesc(), HttpStatus.OK);
}
+
+ @PostMapping("/api/v1/update")
+ public ResponseEntity create(@RequestBody Update update) {
+ if (update == null
+ || update.packageName == null || update.packageName.length() == 0
+ || update.version == null || update.version.length() == 0
+ || update.text == null || update.text.length() == 0) {
+ return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE);
+ } else {
+ return new ResponseEntity<>(updateService.save(update), HttpStatus.CREATED);
+ }
+ }
+
+ // Public API
+ @RequestMapping("/api/v1/update/check")
+ public ResponseEntity updateText(@RequestParam(value = "package") String packageName, @RequestParam(value = "version") String version) {
+ Update update = updateService.getUpdate(packageName, version);
+ return new ResponseEntity<>(new UpdateResult(update), HttpStatus.OK);
+ }
}
diff --git a/src/main/java/ch/srgssr/playfff/controller/WhatIsNew.java b/src/main/java/ch/srgssr/playfff/controller/WhatIsNewController.java
similarity index 96%
rename from src/main/java/ch/srgssr/playfff/controller/WhatIsNew.java
rename to src/main/java/ch/srgssr/playfff/controller/WhatIsNewController.java
index 46bebc9..64d14f2 100644
--- a/src/main/java/ch/srgssr/playfff/controller/WhatIsNew.java
+++ b/src/main/java/ch/srgssr/playfff/controller/WhatIsNewController.java
@@ -17,7 +17,7 @@
* License information is available from the LICENSE file.
*/
@Controller
-public class WhatIsNew {
+public class WhatIsNewController {
@Autowired
ReleaseNoteService releaseNoteService;
@@ -38,12 +38,14 @@ public String whatisnewSave(@RequestParam(value = "package") String packageName,
return "pushed";
}
+ // Public API
@RequestMapping("/api/v1/whatisnew/text")
@ResponseBody
public WhatIsNewResult whatisnewText(@RequestParam(value = "package") String packageName, @RequestParam(value = "version") String version) {
return new WhatIsNewResult(releaseNoteService.getDisplayableText(packageName, version));
}
+ // Public API
@RequestMapping("/api/v1/whatisnew/html")
@ResponseBody
public String whatisnewHtml(@RequestParam(value = "package") String packageName, @RequestParam(value = "version") String version) {
diff --git a/src/test/java/ch/srgssr/playfff/helper/BaseResourceString.java b/src/main/java/ch/srgssr/playfff/helper/BaseResourceString.java
similarity index 66%
rename from src/test/java/ch/srgssr/playfff/helper/BaseResourceString.java
rename to src/main/java/ch/srgssr/playfff/helper/BaseResourceString.java
index 8c66e34..ae7fc10 100644
--- a/src/test/java/ch/srgssr/playfff/helper/BaseResourceString.java
+++ b/src/main/java/ch/srgssr/playfff/helper/BaseResourceString.java
@@ -6,26 +6,20 @@
import java.io.IOException;
import java.nio.charset.StandardCharsets;
-import java.util.Map;
/**
- * Created by seb on 10/08/16.
+ * Copyright (c) SRG SSR. All rights reserved.
+ *
+ * License information is available from the LICENSE file.
*/
public class BaseResourceString {
- public static String getString(ApplicationContext applicationContext, String name, Map variables) {
+ public static String getString(ApplicationContext applicationContext, String name) {
try {
Resource resource = applicationContext.getResource("classpath:" + name);
if (resource == null) {
throw new RuntimeException("No resource: " + name);
}
String s = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
- for (String k : variables.keySet()) {
- String value = variables.get(k);
- if (value == null) {
- throw new IllegalArgumentException(k + " has null value (" + name + ")");
- }
- s = s.replaceAll(k, value);
- }
return s;
} catch (IOException e) {
throw new RuntimeException("IO Exception for " + name, e);
diff --git a/src/main/java/ch/srgssr/playfff/model/DeepLinkJSContent.java b/src/main/java/ch/srgssr/playfff/model/DeepLinkJSContent.java
new file mode 100644
index 0000000..c49c9df
--- /dev/null
+++ b/src/main/java/ch/srgssr/playfff/model/DeepLinkJSContent.java
@@ -0,0 +1,25 @@
+package ch.srgssr.playfff.model;
+
+/**
+ * Copyright (c) SRG SSR. All rights reserved.
+ *
+ * License information is available from the LICENSE file.
+ */
+public class DeepLinkJSContent {
+
+ private String content;
+ private String hash;
+
+ public DeepLinkJSContent(String content, String hash) {
+ this.content = content;
+ this.hash = hash;
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public String getHash() {
+ return hash;
+ }
+}
diff --git a/src/main/java/ch/srgssr/playfff/model/DeepLinkReport.java b/src/main/java/ch/srgssr/playfff/model/DeepLinkReport.java
new file mode 100644
index 0000000..09574eb
--- /dev/null
+++ b/src/main/java/ch/srgssr/playfff/model/DeepLinkReport.java
@@ -0,0 +1,33 @@
+package ch.srgssr.playfff.model;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+import javax.persistence.*;
+import java.util.Date;
+
+/**
+ * Copyright (c) SRG SSR. All rights reserved.
+ *
+ * License information is available from the LICENSE file.
+ */
+@Entity
+@Table(name = "deeplink_reports")
+public class DeepLinkReport {
+ private static final long serialVersionUID = -3009157732242241606L;
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ public Long id;
+
+ @Temporal(TemporalType.TIMESTAMP)
+ @JsonFormat(shape = JsonFormat.Shape.STRING, locale = "en_US_POSIX", pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", timezone = "Europe/Zurich")
+ public Date clientTime;
+ public String clientId;
+
+ public int jsVersion;
+
+ @Column(length = 512)
+ public String url;
+
+ public int count;
+}
diff --git a/src/main/java/ch/srgssr/playfff/model/playportal/PlayTopic.java b/src/main/java/ch/srgssr/playfff/model/playportal/PlayTopic.java
new file mode 100755
index 0000000..6ee6007
--- /dev/null
+++ b/src/main/java/ch/srgssr/playfff/model/playportal/PlayTopic.java
@@ -0,0 +1,109 @@
+
+package ch.srgssr.playfff.model.playportal;
+
+import java.util.HashMap;
+import java.util.Map;
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonPropertyOrder({
+ "id",
+ "title",
+ "urlEncodedTitle",
+ "url",
+ "latestModuleUrl",
+ "mostClickedModuleUrl"
+})
+public class PlayTopic {
+
+ @JsonProperty("id")
+ private String id;
+ @JsonProperty("title")
+ private String title;
+ @JsonProperty("urlEncodedTitle")
+ private String urlEncodedTitle;
+ @JsonProperty("url")
+ private String url;
+ @JsonProperty("latestModuleUrl")
+ private String latestModuleUrl;
+ @JsonProperty("mostClickedModuleUrl")
+ private String mostClickedModuleUrl;
+ @JsonIgnore
+ private Map additionalProperties = new HashMap();
+
+ @JsonProperty("id")
+ public String getId() {
+ return id;
+ }
+
+ @JsonProperty("id")
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @JsonProperty("title")
+ public String getTitle() {
+ return title;
+ }
+
+ @JsonProperty("title")
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ @JsonProperty("urlEncodedTitle")
+ public String getUrlEncodedTitle() {
+ return urlEncodedTitle;
+ }
+
+ @JsonProperty("urlEncodedTitle")
+ public void setUrlEncodedTitle(String urlEncodedTitle) {
+ this.urlEncodedTitle = urlEncodedTitle;
+ }
+
+ @JsonProperty("url")
+ public String getUrl() {
+ return url;
+ }
+
+ @JsonProperty("url")
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ @JsonProperty("latestModuleUrl")
+ public String getLatestModuleUrl() {
+ return latestModuleUrl;
+ }
+
+ @JsonProperty("latestModuleUrl")
+ public void setLatestModuleUrl(String latestModuleUrl) {
+ this.latestModuleUrl = latestModuleUrl;
+ }
+
+ @JsonProperty("mostClickedModuleUrl")
+ public String getMostClickedModuleUrl() {
+ return mostClickedModuleUrl;
+ }
+
+ @JsonProperty("mostClickedModuleUrl")
+ public void setMostClickedModuleUrl(String mostClickedModuleUrl) {
+ this.mostClickedModuleUrl = mostClickedModuleUrl;
+ }
+
+ @JsonAnyGetter
+ public Map getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+
+ @JsonAnySetter
+ public void setAdditionalProperty(String name, Object value) {
+ this.additionalProperties.put(name, value);
+ }
+
+}
diff --git a/src/main/java/ch/srgssr/playfff/repository/DeepLinkReportRepository.java b/src/main/java/ch/srgssr/playfff/repository/DeepLinkReportRepository.java
new file mode 100644
index 0000000..3d7f4a0
--- /dev/null
+++ b/src/main/java/ch/srgssr/playfff/repository/DeepLinkReportRepository.java
@@ -0,0 +1,22 @@
+package ch.srgssr.playfff.repository;
+
+import ch.srgssr.playfff.model.DeepLinkReport;
+import org.springframework.data.repository.CrudRepository;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Copyright (c) SRG SSR. All rights reserved.
+ *
+ * License information is available from the LICENSE file.
+ */
+public interface DeepLinkReportRepository extends CrudRepository {
+ DeepLinkReport findFirstByClientIdAndJsVersionAndUrl(String clientId, int jsVersion, String url);
+
+ List findAllByOrderByClientTimeDesc();
+
+ List findAllByClientTimeLessThan(Date date);
+
+ List findAllByOrderByJsVersionDescCountDesc();
+}
diff --git a/src/main/java/ch/srgssr/playfff/service/DeepLinkReportService.java b/src/main/java/ch/srgssr/playfff/service/DeepLinkReportService.java
new file mode 100644
index 0000000..235308e
--- /dev/null
+++ b/src/main/java/ch/srgssr/playfff/service/DeepLinkReportService.java
@@ -0,0 +1,75 @@
+package ch.srgssr.playfff.service;
+
+import ch.srgssr.playfff.model.DeepLinkReport;
+import ch.srgssr.playfff.repository.DeepLinkReportRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Repository;
+
+import javax.transaction.Transactional;
+import java.util.List;
+
+/**
+ * Copyright (c) SRG SSR. All rights reserved.
+ *
+ * License information is available from the LICENSE file.
+ */
+@Repository
+public class DeepLinkReportService {
+ @Autowired
+ private DeepLinkReportRepository repository;
+
+ private int maxDeepLinkReports;
+
+ public DeepLinkReportService(
+ @Value("${MAX_DEEP_LINK_REPORTS:2500}") String maxDeepLinkReportsString) {
+
+ this.maxDeepLinkReports = Integer.valueOf(maxDeepLinkReportsString);
+ }
+
+ @Transactional
+ public DeepLinkReport save(DeepLinkReport deepLinkReport) {
+ DeepLinkReport currentDeepLinkReport = getParsingReport(deepLinkReport.clientId, deepLinkReport.jsVersion, deepLinkReport.url);
+ if (currentDeepLinkReport != null) {
+ currentDeepLinkReport.count += 1;
+ currentDeepLinkReport.clientTime = deepLinkReport.clientTime;
+ return repository.save(currentDeepLinkReport);
+ } else {
+ deepLinkReport.count = 1;
+ return repository.save(deepLinkReport);
+ }
+ }
+
+ @Transactional
+ public synchronized void purgeOlderReports() {
+ // Keep only latest reports
+ if (repository.count() > maxDeepLinkReports) {
+ List allReports = repository.findAllByOrderByClientTimeDesc();
+ DeepLinkReport report = allReports.get(maxDeepLinkReports - 1);
+ List olderReports = repository.findAllByClientTimeLessThan(report.clientTime);
+ repository.delete(olderReports);
+ }
+ }
+
+ private DeepLinkReport getParsingReport(String clientId, int jsVersion, String url) {
+ return repository.findFirstByClientIdAndJsVersionAndUrl(clientId, jsVersion, url);
+ }
+
+ public DeepLinkReport findById(long id) {
+ return repository.findOne(id);
+ }
+
+ @Transactional
+ public DeepLinkReport delete(long id) {
+ DeepLinkReport deepLinkReport = findById(id);
+ if (deepLinkReport != null) {
+ repository.delete(deepLinkReport);
+ return deepLinkReport;
+ }
+ return null;
+ }
+
+ public Iterable findAllByOrderByJsVersionDescCountDesc() {
+ return repository.findAllByOrderByJsVersionDescCountDesc();
+ }
+}
diff --git a/src/main/java/ch/srgssr/playfff/service/DeepLinkService.java b/src/main/java/ch/srgssr/playfff/service/DeepLinkService.java
new file mode 100644
index 0000000..141e843
--- /dev/null
+++ b/src/main/java/ch/srgssr/playfff/service/DeepLinkService.java
@@ -0,0 +1,167 @@
+package ch.srgssr.playfff.service;
+
+import ch.srg.il.domain.v2_0.ModuleConfig;
+import ch.srg.il.domain.v2_0.ModuleConfigList;
+import ch.srgssr.playfff.helper.BaseResourceString;
+import ch.srgssr.playfff.model.DeepLinkJSContent;
+import ch.srgssr.playfff.model.Environment;
+import ch.srgssr.playfff.model.playportal.PlayTopic;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.cache.annotation.CachePut;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.context.ApplicationContext;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import javax.xml.bind.DatatypeConverter;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TimeZone;
+
+/**
+ * Copyright (c) SRG SSR. All rights reserved.
+ *
+ * License information is available from the LICENSE file.
+ */
+@Service
+public class DeepLinkService {
+ private static final Logger logger = LoggerFactory.getLogger(DeepLinkService.class);
+
+ private DeepLinkJSContent parsePlayUrlContent;
+
+ private RestTemplate restTemplate;
+
+ @Autowired
+ protected ApplicationContext applicationContext;
+
+ @Autowired
+ private IntegrationLayerRequest integrationLayerRequest;
+
+ public DeepLinkService(RestTemplateBuilder restTemplateBuilder) {
+ restTemplate = restTemplateBuilder.build();
+ }
+
+ @Cacheable("DeeplinkParsePlayUrlJSContent")
+ public DeepLinkJSContent getParsePlayUrlJSContent() {
+ if (parsePlayUrlContent == null) {
+ refreshParsePlayUrlJSContent();
+ }
+
+ return parsePlayUrlContent;
+ }
+
+ @CachePut("DeeplinkParsePlayUrlJSContent")
+ public synchronized DeepLinkJSContent refreshParsePlayUrlJSContent() {
+ String javascript = BaseResourceString.getString(applicationContext, "parsePlayUrl.js");
+
+ Map buMap = new HashMap();
+ buMap.put("srf", "www.srf.ch");
+ buMap.put("rts", "www.rts.ch");
+ buMap.put("rsi", "www.rsi.ch");
+ buMap.put("rtr", "www.rtr.ch");
+ buMap.put("swi", "play.swissinfo.ch");
+
+ ObjectMapper mapperObj = new ObjectMapper();
+
+ // Get tv topic list
+ Map> tvTopicsMap = new HashMap<>();
+
+ for (Map.Entry bu : buMap.entrySet()) {
+ URI tvTopicListUri = null;
+ try {
+ tvTopicListUri = new URI("https", null, bu.getValue(), 443, "/play/tv/topicList",
+ null, null);
+ } catch (URISyntaxException e) {
+ e.printStackTrace();
+ }
+ ResponseEntity tvTopicListResponseEntity = restTemplate.exchange(tvTopicListUri, HttpMethod.GET, null, PlayTopic[].class);
+ if (tvTopicListResponseEntity.getBody() != null) {
+ PlayTopic[] tvTopicList = tvTopicListResponseEntity.getBody();
+ Map tvTopicsSubMap = new HashMap<>();
+
+ for (PlayTopic playTopic : tvTopicList) {
+ tvTopicsSubMap.put(playTopic.getUrlEncodedTitle(), playTopic.getId());
+ }
+
+ tvTopicsMap.put(bu.getKey(), tvTopicsSubMap);
+ }
+ }
+
+ String tvTopics = null;
+ try {
+ tvTopics = mapperObj.writeValueAsString(tvTopicsMap);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ if (tvTopics != null) {
+ javascript = javascript.replaceAll("\\/\\* INJECT TVTOPICS OBJECT \\*\\/", "var tvTopics = " + tvTopics + ";");
+ }
+
+ // Get event module list
+ Map> tvEventsMap = new HashMap<>();
+
+ for (Map.Entry bu : buMap.entrySet()) {
+ ModuleConfigList moduleConfigList = integrationLayerRequest.getEvents(bu.getKey(), Environment.PROD);
+ if (moduleConfigList != null) {
+ Map tvEventsSubMap = new HashMap<>();
+
+ for (int i = 0; i < moduleConfigList.getModuleConfigList().size(); i++) {
+ ModuleConfig moduleConfig = moduleConfigList.getModuleConfigList().get(i);
+ tvEventsSubMap.put(moduleConfig.getSeoName(), moduleConfig.getId());
+ }
+
+ tvEventsMap.put(bu.getKey(), tvEventsSubMap);
+ }
+ }
+
+ String tvEvents = null;
+ try {
+ tvEvents = mapperObj.writeValueAsString(tvEventsMap);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ if (tvEvents != null) {
+ javascript = javascript.replaceAll("\\/\\* INJECT TVEVENTS OBJECT \\*\\/", "var tvEvents = " + tvEvents + ";");
+ }
+
+ String buildHash = sha1(javascript);
+ 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 + "\";");
+
+ parsePlayUrlContent = new DeepLinkJSContent(javascript, buildHash);
+ return parsePlayUrlContent;
+ }
+
+ private String sha1(String input) {
+ String sha1 = null;
+ try {
+ MessageDigest msdDigest = MessageDigest.getInstance("SHA-1");
+ msdDigest.update(input.getBytes("UTF-8"), 0, input.length());
+ sha1 = DatatypeConverter.printHexBinary(msdDigest.digest());
+ } catch (UnsupportedEncodingException | NoSuchAlgorithmException e) {
+ logger.warn(e.getMessage());
+ }
+ return sha1;
+ }
+}
diff --git a/src/main/java/ch/srgssr/playfff/service/IntegrationLayerRequest.java b/src/main/java/ch/srgssr/playfff/service/IntegrationLayerRequest.java
index 7a30d73..d0640ac 100644
--- a/src/main/java/ch/srgssr/playfff/service/IntegrationLayerRequest.java
+++ b/src/main/java/ch/srgssr/playfff/service/IntegrationLayerRequest.java
@@ -101,6 +101,19 @@ public MediaComposition getMediaComposition(String mediaURN, Environment environ
}
}
+ public ModuleConfigList getEvents(String bu, Environment environment) {
+ String path = "/integrationlayer/2.0/" + bu + "/moduleConfigList/event.json";
+ try {
+ URI uri = new URI("http", null, environment.getBaseUrl(), PORT, path, null, null);
+ ResponseEntity responseEntity = restTemplate.getForEntity(uri, String.class);
+ SrgUnmarshaller unmarshaller = new SrgUnmarshaller();
+ return unmarshaller.unmarshal(responseEntity.getBody(), MediaType.APPLICATION_JSON, ModuleConfigList.class);
+ } catch (Exception e) {
+ logger.warn("http://{}{} : {}", environment.getBaseUrl(), path, e.getMessage());
+ return null;
+ }
+ }
+
@VisibleForTesting
public RestTemplate getRestTemplate() {
return restTemplate;
diff --git a/src/main/java/ch/srgssr/playfff/service/UpdateService.java b/src/main/java/ch/srgssr/playfff/service/UpdateService.java
index b4f562c..f44816c 100644
--- a/src/main/java/ch/srgssr/playfff/service/UpdateService.java
+++ b/src/main/java/ch/srgssr/playfff/service/UpdateService.java
@@ -39,32 +39,20 @@ public Update getUpdate(String packageName, String version) {
}
}
- public Update create(Update update) {
- return repository.save(update);
- }
-
public Update findById(long id) {
return repository.findOne(id);
}
- public Update update(Update update) {
- return repository.save(update);
- }
-
+ @Transactional
public Update delete(long id) {
Update update = findById(id);
if (update != null) {
- repository.delete(update);
+ repository.delete(update);
return update;
}
return null;
}
-
- public Iterable findAll() {
- return repository.findAll();
- }
-
public Iterable findAllDesc() {
return repository.findAllByOrderByIdDesc();
}
diff --git a/src/main/resources/parsePlayUrl.js b/src/main/resources/parsePlayUrl.js
new file mode 100644
index 0000000..80d913e
--- /dev/null
+++ b/src/main/resources/parsePlayUrl.js
@@ -0,0 +1,710 @@
+// parsePlayUrl
+
+var parsePlayUrlVersion = 16;
+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.hostname, url.pathname, queryParams, url.hash);
+}
+
+function parseForPlayApp(hostname, pathname, queryParams, anchor) {
+
+ // fix path issue
+ pathname = pathname.replace("//", "/");
+
+ // Get BU
+ var bu = null;
+ 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"):
+ bu = "tp";
+ break;
+ case hostname.includes("rts.ch") || hostname.includes("srgplayer-rts") || (hostname.includes("play-mmf") && pathname.startsWith("/rts/")):
+ bu = "rts";
+ break;
+ case hostname.includes("rsi.ch") || hostname.includes("srgplayer-rsi") || (hostname.includes("play-mmf") && pathname.startsWith("/rsi/")):
+ bu = "rsi";
+ break;
+ case hostname.includes("rtr.ch") || hostname.includes("srgplayer-rtr") || (hostname.includes("play-mmf") && pathname.startsWith("/rtr/")):
+ bu = "rtr";
+ break;
+ case hostname.includes("swissinfo.ch") || hostname.includes("srgplayer-swi") || (hostname.includes("play-mmf") && pathname.startsWith("/swi/")):
+ bu = "swi";
+ break;
+ case hostname.includes("srf.ch") || hostname.includes("srgplayer-srf") || (hostname.includes("play-mmf") && pathname.startsWith("/srf/")):
+ bu = "srf";
+ break;
+ case hostname.includes("play-mmf") && pathname.startsWith("/mmf/"):
+ bu = "mmf";
+ break;
+ }
+
+ 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: 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 openPage(server, bu, "tv:home", null, null);
+ }
+
+ 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 openPage(server, bu, "tv:home", null, null);
+ }
+ 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 openPage(server, bu, "radio:home", channelId, null);
+ }
+ }
+
+ 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 openPage(server, bu, "tv:home", null, null);
+ }
+ }
+
+ /**
+ * 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 openPage(server, bu, "radio:home", null, 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 openPage(server, bu, "tv:home", null, null);
+ }
+ }
+
+ /**
+ * 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 openPage(server, bu, "tv:home", null, null);
+ }
+
+ /**
+ * 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 openPage(server, bu, "radio:home", channelId, null);
+ }
+
+ /**
+ * 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;
+ }
+ var options = new Array( { key: "index", value: index } );
+ return openPage(server, bu, "tv:az", null, options);
+ }
+
+ /**
+ * 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;
+ }
+ var options = new Array( { key: "index", value: index } );
+ return openPage(server, bu, "radio:az", channelId, options);
+ }
+
+ /**
+ * 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;
+ }
+ }
+ var options = new Array( { key: "date", value: date } );
+ return openPage(server, bu, "tv:bydate", null, options);
+ }
+
+ /**
+ * 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;
+ }
+ }
+ var options = new Array( { key: "date", value: date } );
+ return openPage(server, bu, "radio:bydate", channelId, options);
+ }
+
+ /**
+ * 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"];
+ var transmission = null;
+ if (mediaType) {
+ mediaType = mediaType.toLowerCase();
+ if (mediaType != "video" && mediaType != "audio") {
+ mediaType = null;
+ }
+ }
+ var transmissionComponent = (transmission != null) ? transmission + ":" : "";
+ var options = new Array( { key: "query", value: query }, { key: "mediaType", value: mediaType } );
+ return openPage(server, bu, transmissionComponent + "search", null, options);
+ }
+
+ /**
+ * 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 openPage(server, bu, "tv:home", null, null);
+ }
+ 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[bu][lastPathComponent];
+ }
+
+ if (topicId) {
+ return openTopic(server, bu, "tv", topicId);
+ }
+ else {
+ return openPage(server, bu, "tv:home", null, null);
+ }
+ }
+
+ /**
+ * 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 openPage(server, bu, "tv:home", null, null);
+ }
+ 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[bu][lastPathComponent];
+ }
+
+ if (eventId) {
+ return openModule(server, bu, "event", eventId);
+ }
+ else {
+ return openPage(server, bu, "tv:home", null, null);
+ }
+ }
+
+ /**
+ * Catch base play urls
+ *
+ * Ex: https://www.srf.ch/play/
+ *. Ex: https://www.rsi.ch/play
+ */
+ if (pathname.endsWith("/play/") || pathname.endsWith("/play")) {
+ return openPage(server, bu, "tv:home", null, null);
+ }
+
+ // Redirect fallback.
+ console.log("Can't parse Play URL. Redirect.");
+ return schemeForBu(bu) + "://redirect";
+};
+function openMedia(server, bu, mediaType, mediaId, startTime) {
+ var redirect = schemeForBu(bu) + "://open?media=urn:" + bu + ":" + mediaType + ":" + mediaId;
+ if (startTime) {
+ redirect = redirect + "&start-time=" + startTime;
+ }
+ if (server) {
+ redirect = redirect + "&server=" + encodeURIComponent(server);
+ }
+ return redirect;
+}
+
+function openMediaURN(server, bu, mediaURN, startTime) {
+ var redirect = schemeForBu(bu) + "://open?media=" + mediaURN;
+ if (startTime) {
+ redirect = redirect + "&start-time=" + startTime;
+ }
+ if (server) {
+ redirect = redirect + "&server=" + encodeURIComponent(server);
+ }
+ return redirect;
+}
+
+function openShow(server, bu, showTransmission, showId) {
+ var redirect = schemeForBu(bu) + "://open?show=urn:" + bu + ":show:" + showTransmission + ":" + showId;
+ if (server) {
+ redirect = redirect + "&server=" + encodeURIComponent(server);
+ }
+ return redirect;
+}
+
+function openTopic(server, bu, topicTransmission, topicId) {
+ var redirect = schemeForBu(bu) + "://open?topic=urn:" + bu + ":topic:" + topicTransmission + ":" + topicId;
+ if (server) {
+ redirect = redirect + "&server=" + encodeURIComponent(server);
+ }
+ return redirect;
+}
+
+function openModule(server, bu, moduleType, moduleId) {
+ var redirect = schemeForBu(bu) + "://open?module=urn:" + bu + ":module:" + moduleType + ":" + moduleId;
+ if (server) {
+ redirect = redirect + "&server=" + encodeURIComponent(server);
+ }
+ return redirect;
+}
+
+function openPage(server, bu, page, channelId, options) {
+ if (! page) {
+ page = "tv:home";
+ }
+
+ var pageUid = page.split(":").slice(-1)[0];
+
+ if (page.startsWith("radio:") && ! channelId) {
+ channelId = primaryChannelUidForBu(bu);
+ }
+
+ var redirect = schemeForBu(bu) + "://open?page=urn:" + bu + ":page:" + page + "&page-id=" + pageUid;
+ if (channelId) {
+ redirect = redirect + "&channel-id=" + channelId;
+ }
+ if (options && Array.isArray(options)) {
+ options.forEach(function(option) {
+ if (option.key && option.value) {
+ redirect = redirect + "&" + option.key + "=" + encodeURIComponent(option.value);
+ }
+ });
+ }
+ if (server) {
+ redirect = redirect + "&server=" + encodeURIComponent(server);
+ }
+ return redirect;
+}
+
+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 server = queryParams["server"];
+ switch (server) {
+ case "production":
+ server = "production";
+ break;
+ case "stage":
+ server = "stage";
+ break;
+ case "test":
+ server = "test";
+ break;
+ default:
+ server = null;
+ }
+ }
+ }
+ return server;
+}
diff --git a/src/main/resources/templates/error.html b/src/main/resources/templates/error.html
index 2fc03bd..5350d62 100644
--- a/src/main/resources/templates/error.html
+++ b/src/main/resources/templates/error.html
@@ -2,7 +2,7 @@
- Erreur
+ Error