v2.0.0.0000
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.DS_Store
|
||||||
|
compiler
|
||||||
|
files
|
||||||
|
update_xteve*.sh
|
||||||
|
xteve
|
||||||
|
xteve.exe
|
||||||
|
de.json
|
||||||
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2019 xteve-project
|
Copyright (c) 2019 marmei@xteve-project
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
1
README-DEV.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Information for the developers will come soon
|
||||||
126
README.md
@@ -1 +1,127 @@
|
|||||||
|
<div align="center" style="background-color: #111; padding: 100;">
|
||||||
|
<a href="https://github.com/xteve-project/xTeVe"><img width="600" height="200" src="html/img/logo_w_600x200.png" alt="xTeVe" /></a>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
|
||||||
# xTeVe
|
# xTeVe
|
||||||
|
## M3U Proxy for Plex DVR and Emby Live TV.
|
||||||
|
|
||||||
|
Documentation for setup and configuration is [here](https://github.com/xteve-project/xTeVe-Documentation/blob/master/en/configuration.md).
|
||||||
|
|
||||||
|
#### Donation
|
||||||
|
* **Bitcoin:** 1c1iCe4CJPfNUXtqxKBbW2Qd2EtqRPWme
|
||||||
|

|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
### Plex
|
||||||
|
* Plex Media Server (1.11.1.4730 or newer)
|
||||||
|
* Plex Client with DVR support
|
||||||
|
* Plex Pass
|
||||||
|
|
||||||
|
### Emby
|
||||||
|
* Emby Server (3.5.3.0 or newer)
|
||||||
|
* Emby Client with Live-TV support
|
||||||
|
* Emby Premiere
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
#### Files
|
||||||
|
* Merge external M3U files
|
||||||
|
* Merge external XMLTV files
|
||||||
|
* Automatic M3U and XMLTV update
|
||||||
|
* M3U und XMLTV export
|
||||||
|
|
||||||
|
#### Channel management
|
||||||
|
* Filtering streams
|
||||||
|
* Channel mapping
|
||||||
|
* Channel order
|
||||||
|
* Channel logos
|
||||||
|
* Channel categories
|
||||||
|
|
||||||
|
#### Streaming
|
||||||
|
* Buffer with HLS / M3U8 support
|
||||||
|
* Re-streaming
|
||||||
|
* Number of tuners adjustable
|
||||||
|
* Compatible with Plex / Emby EPG
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Downloads v2 | 64 Bit only
|
||||||
|
#### 64 Bit Intel / AMD
|
||||||
|
|
||||||
|
* [Windows](https://github.com/xteve-project/xTeVe-Downloads/blob/master/xteve_windows_amd64.zip?raw=true)
|
||||||
|
* [OS X](https://github.com/xteve-project/xTeVe-Downloads/blob/master/xteve_darwin_amd64.zip?raw=true)
|
||||||
|
* [Linux](https://github.com/xteve-project/xTeVe-Downloads/blob/master/xteve_linux_amd64.zip?raw=true)
|
||||||
|
* [FreeBSD](https://github.com/xteve-project/xTeVe-Downloads/blob/master/xteve_freebsd_amd64.zip?raw=true)
|
||||||
|
|
||||||
|
#### 64 Bit ARM
|
||||||
|
* [Linux](https://github.com/xteve-project/xTeVe-Downloads/blob/master/xteve_linux_arm64.zip?raw=true)
|
||||||
|
|
||||||
|
#### Recommended Docker Image (Linux 64 Bit)
|
||||||
|
Thanks to @alturismo and @LeeD for creating the Docker Images.
|
||||||
|
|
||||||
|
**Created by alturismo:**
|
||||||
|
[xTeVe](https://hub.docker.com/r/alturismo/xteve)
|
||||||
|
[xTeVe / Guide2go](https://hub.docker.com/r/alturismo/xteve_guide2go)
|
||||||
|
|
||||||
|
Including:
|
||||||
|
- Guide2go: XMLTV grabber for Schedules Direct
|
||||||
|
|
||||||
|
|
||||||
|
**Created by LeeD:**
|
||||||
|
[xTeVe / Guide2go / Zap2XML](https://hub.docker.com/r/dnsforge/xteve)
|
||||||
|
|
||||||
|
Including:
|
||||||
|
- Guide2go: XMLTV grabber for Schedules Direct
|
||||||
|
- Zap2XML: Perl based zap2it XMLTV grabber
|
||||||
|
- Bash: A Unix / Linux shell
|
||||||
|
- Crond: Daemon to execute scheduled commands
|
||||||
|
- Perl: Programming language
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build from source code [Go / Golang]
|
||||||
|
|
||||||
|
#### Requirements
|
||||||
|
* Go (go1.12.4 or newer)
|
||||||
|
|
||||||
|
#### Dependancys
|
||||||
|
* [go-ssdp](https://github.com/koron/go-ssdp)
|
||||||
|
* [websocket](https://github.com/gorilla/websocket)
|
||||||
|
* [osext](https://github.com/kardianos/osext)
|
||||||
|
|
||||||
|
#### Build
|
||||||
|
1. Download source code
|
||||||
|
2. Install dependancys
|
||||||
|
```
|
||||||
|
go get github.com/koron/go-ssdp
|
||||||
|
go get github.com/gorilla/websocket
|
||||||
|
go get github.com/kardianos/osext
|
||||||
|
```
|
||||||
|
3. Build xTeVe
|
||||||
|
```
|
||||||
|
go build xteve.go
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fork without pull request :mega:
|
||||||
|
When creating a fork, the xTeVe GitHub account must be changed from the sorce code or the update function disabled.
|
||||||
|
Future updates of the xteve-project would update your fork. :wink:
|
||||||
|
|
||||||
|
xteve.go - Line: 29
|
||||||
|
```Go
|
||||||
|
var GitHub = GitHubStruct{Branch: "master", User: "xteve-project", Repo: "xTeVe-Downloads", Update: true}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Branch: GitHub Branch
|
||||||
|
User: GitHub Username
|
||||||
|
Repo: GitHub Repository
|
||||||
|
Update: Automatic updates from the GitHub repository [true|false]
|
||||||
|
*/
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
58
html/configuration.html
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>xTeVe</title>
|
||||||
|
<link rel="stylesheet" href="css/screen.css" type="text/css">
|
||||||
|
<link rel="stylesheet" href="css/base.css" type="text/css">
|
||||||
|
<script language="javascript" type="text/javascript" src="js/configuration_ts.js"></script>
|
||||||
|
<script language="javascript" type="text/javascript" src="js/network_ts.js"></script>
|
||||||
|
<script language="javascript" type="text/javascript" src="js/menu_ts.js"></script>
|
||||||
|
<script language="javascript" type="text/javascript" src="js/settings_ts.js"></script>
|
||||||
|
<script language="javascript" type="text/javascript" src="js/base_ts.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body onload="javascript: readyForConfiguration(0);">
|
||||||
|
|
||||||
|
<div id="loading" class="block">
|
||||||
|
<div class="loader"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="header" class="imgCenter"></div>
|
||||||
|
<div id="box">
|
||||||
|
|
||||||
|
<table id="clientInfo" class="visible">
|
||||||
|
<tr>
|
||||||
|
<td class="tdKey">Version:</td>
|
||||||
|
<td id="version" class="tdVal"> </td>
|
||||||
|
<td class="tdKey">OS:</td>
|
||||||
|
<td id="os" class="tdVal"> </td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="tdKey">UUID:</td>
|
||||||
|
<td id="uuid" class="tdVal"> </td>
|
||||||
|
<td class="tdKey">Arch:</td>
|
||||||
|
<td id="arch" class="tdVal"> </td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="tdKey">Streams:</td>
|
||||||
|
<td id="streams" class="tdVal"> </td>
|
||||||
|
<td class="tdKey">DVR:</td>
|
||||||
|
<td id="DVR" class="tdVal"> </td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div id="headline">
|
||||||
|
<h1 id="head-text" class="center">Configuration</h1>
|
||||||
|
</div>
|
||||||
|
<p id="err" class="errorMsg center"></p>
|
||||||
|
<div id="content">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div id="box-footer">
|
||||||
|
<input id="next" class="" type="button" name="next" value="Next" onclick="javascript: saveWizard();">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
47
html/create-first-user.html
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>xTeVe</title>
|
||||||
|
<link rel="stylesheet" href="css/screen.css" type="text/css">
|
||||||
|
<link rel="stylesheet" href="css/base.css" type="text/css">
|
||||||
|
<script language="javascript" type="text/javascript" src="js/network_ts.js"></script>
|
||||||
|
<script language="javascript" type="text/javascript" src="js/authentication_ts.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="header" class="imgCenter"></div>
|
||||||
|
|
||||||
|
<div id="box">
|
||||||
|
|
||||||
|
<div id="headline">
|
||||||
|
<h1 id="head-text" class="center">{{.account.headline}}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p id="err" class="errorMsg center"></p>
|
||||||
|
|
||||||
|
<div id="content">
|
||||||
|
|
||||||
|
<form id="authentication" action="/web/" method="post">
|
||||||
|
|
||||||
|
<h5>{{.account.username.title}}:</h5>
|
||||||
|
<input id="username" type="text" name="username" placeholder="Username" value="">
|
||||||
|
<h5>{{.account.password.title}}:</h5>
|
||||||
|
<input id="password" type="password" name="password" placeholder="Password" value="">
|
||||||
|
<h5>{{.account.confirm.title}}:</h5>
|
||||||
|
<input id="confirm" type="password" name="confirm" placeholder="Confirm" value="">
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="box-footer">
|
||||||
|
<input id="submit" class="" type="button" value="{{.button.craeteAccount}}" onclick="javascript: login();">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
448
html/css/base.css
Normal file
@@ -0,0 +1,448 @@
|
|||||||
|
* {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
-ms-appearance: none;
|
||||||
|
font-family: "Arial", sans-serif;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
border-radius: 5px;
|
||||||
|
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0,0.6);
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-corner {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #00E6FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
color: #fff;
|
||||||
|
margin: 0px auto;
|
||||||
|
height: 100%;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 22px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 20px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
line-height: 1.5em;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 16px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
line-height: 1.2em;
|
||||||
|
margin: 25px 0px 10px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: 0;
|
||||||
|
height: 1px;
|
||||||
|
background: #333;
|
||||||
|
margin: 10px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 2px;
|
||||||
|
padding: 2px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin: 0px 0px 5px 0px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #ddd;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: normal;
|
||||||
|
font-variant: normal;
|
||||||
|
line-height: 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
list-style-type: none;
|
||||||
|
background-color: #111;
|
||||||
|
padding: 10px 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-left: solid 2px #111;
|
||||||
|
transition: all 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
li:hover {
|
||||||
|
border-color: #00E6FF
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
cursor: pointer;
|
||||||
|
width: calc(100% + 2px);
|
||||||
|
border: solid 0px #00E6FF;
|
||||||
|
border-radius: 0px;
|
||||||
|
outline: none;
|
||||||
|
color: #fff;
|
||||||
|
padding: 9px 10px;
|
||||||
|
display:block;
|
||||||
|
background-color: #333;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 5px 0px 5px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
select:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 5px 0px;
|
||||||
|
padding: 2.5px 10px;
|
||||||
|
outline: none;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=button], input[type=submit] {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #000;
|
||||||
|
margin: 10px 10px;
|
||||||
|
padding: 10px 25px;
|
||||||
|
border: solid 0px;
|
||||||
|
border-color: #000;
|
||||||
|
border-radius: 3px;
|
||||||
|
outline: none;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=button]:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=button]:hover {
|
||||||
|
background-color: #00E6FF;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=button]:hover.delete {
|
||||||
|
background-color: red;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=text], input[type=search], input[type=password] {
|
||||||
|
color: #fff;
|
||||||
|
width: -webkit-calc(100% - 0px);
|
||||||
|
width: -moz-calc(100% - 0px);
|
||||||
|
width: calc(100% - 0px);
|
||||||
|
outline: none;
|
||||||
|
border: solid 1px transparent;
|
||||||
|
background-color: transparent;
|
||||||
|
border-bottom-color: #555;
|
||||||
|
border-radius: 0px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
border: solid 1px #00E6FF;
|
||||||
|
background-color: #333;
|
||||||
|
height: 25px;
|
||||||
|
width: 25px;
|
||||||
|
cursor: pointer;
|
||||||
|
/*
|
||||||
|
-webkit-appearance: checkbox;
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]:checked {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #00E6FF;
|
||||||
|
/*display: inline-block;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]:before {
|
||||||
|
position: initial;
|
||||||
|
left: 0px;
|
||||||
|
margin-left: -4px;
|
||||||
|
content: " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]:checked:before {
|
||||||
|
position: initial;
|
||||||
|
left: 0px;
|
||||||
|
margin-left: -3px;
|
||||||
|
content: "✓";
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
input[type=button].cancel {
|
||||||
|
|
||||||
|
background-color: transparent;
|
||||||
|
border-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=button].save{
|
||||||
|
background-color: #111;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
input[type=button].black, input[type=submit].black{
|
||||||
|
background-color: #000;
|
||||||
|
border-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=button].center{
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
background-color: #000;
|
||||||
|
border-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pointer:hover {
|
||||||
|
color: #00E6FF;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sortThis {
|
||||||
|
color: #00E6FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w40px {
|
||||||
|
max-width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w50px {
|
||||||
|
max-width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w80px {
|
||||||
|
max-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w150px {
|
||||||
|
max-width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w200px {
|
||||||
|
max-width: 200px;
|
||||||
|
min-width: 100px;
|
||||||
|
width: 200px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w300px {
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w220px {
|
||||||
|
max-width: 220px;
|
||||||
|
cursor: alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.screenLogHidden {
|
||||||
|
transform: translate(0px, -110px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.borderSpace {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.none {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.notVisible {
|
||||||
|
height: 0px;
|
||||||
|
display: none;
|
||||||
|
opacity: 0;
|
||||||
|
border-bottom: #000 solid 0px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.visible {
|
||||||
|
opacity: 1;
|
||||||
|
display: block;
|
||||||
|
border-bottom: #444 solid 1px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floatRight {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floatLeft {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-active {
|
||||||
|
background-color: #00E6FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-notActive {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#branch {
|
||||||
|
display: table;
|
||||||
|
margin: auto;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
#interaction {
|
||||||
|
margin-bottom: 100px;
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: solid 0px #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.half {
|
||||||
|
display: block;
|
||||||
|
width: 45%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
border: solid 1px #00E6FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoMsg {
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.errorMsg {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warningMsg {
|
||||||
|
color: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debugMsg {
|
||||||
|
color: magenta;
|
||||||
|
}
|
||||||
|
|
||||||
|
.News, .Movie, .Series, .Sports, .Kids {
|
||||||
|
border-left: solid 2px
|
||||||
|
}
|
||||||
|
|
||||||
|
.News {
|
||||||
|
border-color: tomato
|
||||||
|
}
|
||||||
|
|
||||||
|
.Movie {
|
||||||
|
border-color: royalblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Series {
|
||||||
|
border-color: gold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Sports {
|
||||||
|
border-color: yellowgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Kids {
|
||||||
|
border-color: mediumpurple;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading */
|
||||||
|
#loading {
|
||||||
|
left: 0px;
|
||||||
|
top: 0px;
|
||||||
|
z-index: 10000;
|
||||||
|
position: absolute;
|
||||||
|
background-color: rgba(0,0,0, 0.8);
|
||||||
|
margin: auto;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.loader {
|
||||||
|
border: 5px solid transparent;
|
||||||
|
border-radius: 50%;
|
||||||
|
border-top: 5px solid #00E6FF;
|
||||||
|
border-bottom: 5px solid #00E6FF;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
-webkit-animation: spin 1.2s linear infinite;
|
||||||
|
animation: spin 1.2s linear infinite;
|
||||||
|
|
||||||
|
position: fixed;
|
||||||
|
margin: auto;
|
||||||
|
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes spin {
|
||||||
|
0% { -webkit-transform: rotate(0deg); }
|
||||||
|
100% { -webkit-transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
540
html/css/screen.css
Normal file
@@ -0,0 +1,540 @@
|
|||||||
|
nav img {
|
||||||
|
display: block;
|
||||||
|
max-height: 20px;
|
||||||
|
max-width: 20px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav p {
|
||||||
|
text-align: left;
|
||||||
|
padding: 0px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#layout {
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.layout-left {
|
||||||
|
display: block;
|
||||||
|
min-width: 150px;
|
||||||
|
max-width: 20%;
|
||||||
|
background-color: #111;
|
||||||
|
height: inherit;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-right {
|
||||||
|
display: block;
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu-wrapper {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#logo {
|
||||||
|
display: block;
|
||||||
|
min-width: 180px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100px;
|
||||||
|
background: url("../img/logo_w_600x200.png");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
background-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#page {
|
||||||
|
max-width: 950px;
|
||||||
|
margin: auto;
|
||||||
|
background-color: #444;
|
||||||
|
|
||||||
|
/*
|
||||||
|
height: -webkit-calc(100% - 130px);
|
||||||
|
height: -moz-calc(100% - 130px);
|
||||||
|
height: calc(100% - 130px);
|
||||||
|
*/
|
||||||
|
|
||||||
|
min-height: -webkit-calc(100% - 120px);
|
||||||
|
min-height: -moz-calc(100% - 120px);
|
||||||
|
min-height: calc(100% - 120px);
|
||||||
|
|
||||||
|
|
||||||
|
box-shadow: 0px 5px 5px #222;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#uiSetting {
|
||||||
|
float: right;
|
||||||
|
margin-right: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#box input[type=text], #box input[type=password] {
|
||||||
|
width: -webkit-calc(100% - 20px);
|
||||||
|
width: -moz-calc(100% - 20px);
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#box input[type=submit]{
|
||||||
|
margin: 50px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings {
|
||||||
|
display: block;
|
||||||
|
padding: 10px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings h5 {
|
||||||
|
margin: 50px 0px 10px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content-interaction .search {
|
||||||
|
width: 200px;
|
||||||
|
border: 1px solid #000;
|
||||||
|
padding: 9px;
|
||||||
|
background-color: #333;
|
||||||
|
margin: 10px;
|
||||||
|
float: right;
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#myStreams {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0px;
|
||||||
|
background-color: #111;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 950px;
|
||||||
|
|
||||||
|
/*
|
||||||
|
max-height: 100px;
|
||||||
|
*/
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#myStreams img {
|
||||||
|
width: 4%;
|
||||||
|
padding: 2px 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings-footer {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Wizard*/
|
||||||
|
#box {
|
||||||
|
background-color: #444;
|
||||||
|
min-height: 400px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
#box p{
|
||||||
|
padding: 10px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#box-footer {
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#box-footer {
|
||||||
|
margin: auto;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#headline {
|
||||||
|
background-color: #222;
|
||||||
|
border-bottom: solid 2px #222;
|
||||||
|
transition: all 0.5s;
|
||||||
|
padding: 10px 0px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
display: block;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- */
|
||||||
|
|
||||||
|
|
||||||
|
#clientInfo, #activeStreams, #inactiveStreams {
|
||||||
|
font-family: monospace;
|
||||||
|
display: block;
|
||||||
|
font-size: 9px;
|
||||||
|
background-color: #111;
|
||||||
|
color: #00E6FF;
|
||||||
|
border-bottom: solid 0px;;
|
||||||
|
padding: 0px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
border-spacing: 4px 4px;
|
||||||
|
border-bottom: solid 1px #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
#myStreamsBox {
|
||||||
|
position: relative;
|
||||||
|
padding: 0px;
|
||||||
|
/*height: 100px;*/
|
||||||
|
max-height: 150px;
|
||||||
|
background-color: #111;
|
||||||
|
color: white;
|
||||||
|
display:flex;
|
||||||
|
justify-content:center;
|
||||||
|
align-items:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#openStreams {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
float: right;
|
||||||
|
position: absolute;
|
||||||
|
right: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
background: url("../img/touch.png");
|
||||||
|
background-color: #111;
|
||||||
|
|
||||||
|
background-position: bottom right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#allStreams {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#activeStreams, #inactiveStreams {
|
||||||
|
overflow-y: scroll;
|
||||||
|
width: 50%;
|
||||||
|
max-height: 100px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#activeStreams .tdKey, #inactiveStreams .tdKey {
|
||||||
|
width: 75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#inactiveStreams .tdKey {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
#clientInfo .tdVal, #logInfo .tdVal, #activeStreams .tdVal, #inactiveStreams .tdVal, #mappingInfo .tdVal{
|
||||||
|
color: #aaa;
|
||||||
|
white-space: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
#box-wrapper {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content_table, #mapping-detail-table, #content_table {
|
||||||
|
display: table;
|
||||||
|
|
||||||
|
border-collapse: collapse;
|
||||||
|
overflow-y: scroll;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#content_table .content_table_header {
|
||||||
|
background-color: #333;
|
||||||
|
height: 50px;
|
||||||
|
border-bottom: solid 1px #111;
|
||||||
|
border-left: solid 3px #333;
|
||||||
|
cursor: auto;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
tbody {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.tableEllipsis {
|
||||||
|
width: 150px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content_table img {
|
||||||
|
display: block;
|
||||||
|
max-height: 28px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
max-width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content_table tr{
|
||||||
|
border-left: solid 3px 444;
|
||||||
|
border-bottom: solid 1px #333;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content_table tr:hover {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content_table td {
|
||||||
|
|
||||||
|
padding: 0px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content_table input[type=text]{
|
||||||
|
width: 80%;
|
||||||
|
border: 0px;
|
||||||
|
background-color: #333;
|
||||||
|
margin-left: 5px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content_table input[type=checkbox]{
|
||||||
|
max-width: 25px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.showBulk {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hideBulk {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noBulk {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#content_table tr.activeEPG{
|
||||||
|
border-left: solid 3px lawngreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content_table tr.notActiveEPG{
|
||||||
|
border-left: solid 3px red;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#logScreen p{
|
||||||
|
white-space: pre;
|
||||||
|
font-size: 10px;
|
||||||
|
/*
|
||||||
|
line-height: 1.6em;
|
||||||
|
font-family: "Arial", sans-serif;
|
||||||
|
*/
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: normal;
|
||||||
|
font-variant: normal;
|
||||||
|
line-height: 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup {
|
||||||
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
|
position: fixed;
|
||||||
|
left: 0px;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 100;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mapping-detail, #user-detail, #file-detail, #popup-custom {
|
||||||
|
box-shadow: 0px 5px 40px #000;
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
|
||||||
|
max-width: 600px;
|
||||||
|
background-color: #222;
|
||||||
|
padding: 10px;
|
||||||
|
overflow:auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-custom h3 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#file-detail input[type=text] {
|
||||||
|
width: -webkit-calc(100% - 20px);
|
||||||
|
width: -moz-calc(100% - 20px);
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#mapping-detail img {
|
||||||
|
display: block;
|
||||||
|
max-height: 30px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-custom input[type=text], #popup-custom input[type=password], #mapping-detail input[type=text], #content_settings input[type=text], #content_settings input[type=password]{
|
||||||
|
border: solid 1px;
|
||||||
|
border-color: transparent;
|
||||||
|
background-color: #333;
|
||||||
|
text-align: left;
|
||||||
|
width: -webkit-calc(100% - 20px);
|
||||||
|
width: -moz-calc(100% - 20px);
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-custom input[type=text].notAvailable {
|
||||||
|
border-color: red;
|
||||||
|
color: #666;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mapping-detail-table, #user-detail-table {
|
||||||
|
display: inline-table;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-custom table, #content_settings table {
|
||||||
|
display: inline-table;
|
||||||
|
table-layout: fixed;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#mapping-detail-table td, #user-detail-table td {
|
||||||
|
padding: 10px 0px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#mapping-detail-table td.left, #user-detail-table td.left, #popup-custom td.left {
|
||||||
|
width: 38%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interaction, #interaction {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: inline-flex;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interaction input[type=button], .interaction input[type=submit] {
|
||||||
|
background-color: #000;
|
||||||
|
min-width: 100px;
|
||||||
|
margin: 0px 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#notification {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
right: 0px;
|
||||||
|
height: 100%;
|
||||||
|
width: 250px;
|
||||||
|
|
||||||
|
background-color: #222;
|
||||||
|
box-shadow: 0px 0px 20px #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#notification h5 {
|
||||||
|
background-color: #121212;
|
||||||
|
padding: 5px 10px 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#notification pre {
|
||||||
|
padding: 0px 10px 0px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#notification p {
|
||||||
|
font-size: 10 px;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px 10px 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#notification .element {
|
||||||
|
/*padding: 0px 5px;*/
|
||||||
|
margin: 5px 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #181818;
|
||||||
|
border-left: 10px solid green;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media only screen and (min-width: 620px){
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 26px;
|
||||||
|
letter-spacing: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav p {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#header_config {
|
||||||
|
display: block;
|
||||||
|
height: 100px;
|
||||||
|
background: url("../img/logo_w_600x200.png");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
|
background-size: 300px 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#screenLog {
|
||||||
|
margin-left: 300px;
|
||||||
|
|
||||||
|
transition: none;
|
||||||
|
background-color: transparent;
|
||||||
|
border-bottom: solid 1px transparent;
|
||||||
|
box-shadow: 0px 0px 0px #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings {
|
||||||
|
/*
|
||||||
|
height: -webkit-calc(100% - 100px);
|
||||||
|
height: -moz-calc(100% - 100px);
|
||||||
|
height: calc(100% - 100px);
|
||||||
|
*/
|
||||||
|
position: relative;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.screenLogHidden {
|
||||||
|
transform: translate(0px, 0px);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#box {
|
||||||
|
display: block;
|
||||||
|
min-height: 500px;
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 10px auto;
|
||||||
|
background-color: #444;
|
||||||
|
box-shadow: 0px 5px 5px #222;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings, #settings-footer {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
3
html/css/screen2.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
h1 {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
BIN
html/img/BC-QR.jpg
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
html/img/filter.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
html/img/log.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
html/img/logo_w_600x200.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
html/img/logout.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
html/img/m3u.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
html/img/mapping.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
html/img/settings.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
html/img/stream-limit.jpg
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
html/img/users.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
html/img/x_ transparent.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
html/img/x_black.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
html/img/x_white.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
html/img/xmltv.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
108
html/index.html
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<!---
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
-->
|
||||||
|
<title>xTeVe</title>
|
||||||
|
<link rel="stylesheet" href="css/screen.css" type="text/css">
|
||||||
|
<link rel="stylesheet" href="css/base.css" type="text/css">
|
||||||
|
|
||||||
|
<script language="javascript" type="text/javascript" src="js/network_ts.js"></script>
|
||||||
|
<script language="javascript" type="text/javascript" src="js/menu_ts.js"></script>
|
||||||
|
<script language="javascript" type="text/javascript" src="js/settings_ts.js"></script>
|
||||||
|
<script language="javascript" type="text/javascript" src="js/logs_ts.js"></script>
|
||||||
|
<script language="javascript" type="text/javascript" src="js/base_ts.js"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body onload="javascript: PageReady();">
|
||||||
|
|
||||||
|
<div id="loading" class="none">
|
||||||
|
<div class="loader"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="popup" class="none">
|
||||||
|
<div id="popup-custom"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="layout">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<div id="notification">
|
||||||
|
<div class="element">
|
||||||
|
<h5>XEPG</h5>
|
||||||
|
<pre>11.05.2019 - 20:21</pre>
|
||||||
|
<hr>
|
||||||
|
<p>Hallo das ist ein Test. Und noch mehr Text.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<div id="menu-wrapper" class="layout-left">
|
||||||
|
<div id= "branch"></div>
|
||||||
|
<div id="logo"></div>
|
||||||
|
<nav id="main-menu"></nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="layout-right">
|
||||||
|
|
||||||
|
<table id="clientInfo" class="">
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="tdKey">xTeVe:</td>
|
||||||
|
<td id="version" class="tdVal"> </td>
|
||||||
|
<td class="tdKey">OS:</td>
|
||||||
|
<td id="os" class="tdVal"> </td>
|
||||||
|
<td class="tdKey phone">DVR IP:</td>
|
||||||
|
<td id="DVR" class="tdVal phone"> </td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="tdKey">UUID:</td>
|
||||||
|
<td id="uuid" class="tdVal"> </td>
|
||||||
|
<td class="tdKey">Arch:</td>
|
||||||
|
<td id="arch" class="tdVal"> </td>
|
||||||
|
<td class="tdKey phone">M3U URL:</td>
|
||||||
|
<td id="m3u-url" class="tdVal phone"> </td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="tdKey">Available Streams:</td>
|
||||||
|
<td id="streams" class="tdVal"> </td>
|
||||||
|
<td class="tdKey">EPG Source:</td>
|
||||||
|
<td id="epgSource" class="tdVal"> </td>
|
||||||
|
<td class="tdKey phone">XEPG URL:</td>
|
||||||
|
<td id="xepg-url" class="tdVal phone"> </td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="tdKey">XEPG Channels:</td>
|
||||||
|
<td id="xepg" class="tdVal"> </td>
|
||||||
|
<td class="tdKey">Errors:</td>
|
||||||
|
<td id="errors" class="tdVal"> </td>
|
||||||
|
<td class="tdKey">Warnings:</td>
|
||||||
|
<td id="warnings" class="tdVal"> </td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div id="myStreamsBox" class="notVisible">
|
||||||
|
|
||||||
|
<div id="allStreams">
|
||||||
|
<table id="activeStreams"></table>
|
||||||
|
<table id="inactiveStreams"></table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="content" class=""></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
42
html/js/authentication.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
function createFirstAccount(elm) {
|
||||||
|
var err = false;
|
||||||
|
var div = document.getElementById(elm);
|
||||||
|
console.log(div);
|
||||||
|
|
||||||
|
var form = document.getElementById('authentication');
|
||||||
|
|
||||||
|
const username = document.getElementById('username');
|
||||||
|
const password = document.getElementById('password');
|
||||||
|
const confirm = document.getElementById('confirm');
|
||||||
|
|
||||||
|
var inputs = div.getElementsByTagName('INPUT')
|
||||||
|
console.log(confirm);
|
||||||
|
|
||||||
|
switch(confirm) {
|
||||||
|
case null: break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
for (var i = 0; i < inputs.length; i++) {
|
||||||
|
if (inputs[i].value.length == 0) {
|
||||||
|
inputs[i].style.borderColor = 'red';
|
||||||
|
err = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(err) {
|
||||||
|
case true: return; break;
|
||||||
|
case false:
|
||||||
|
if (password.value != confirm.value) {
|
||||||
|
confirm.style.borderColor = 'red';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
form.submit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
32
html/js/authentication_ts.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
function login() {
|
||||||
|
var err = false;
|
||||||
|
var data = new Object();
|
||||||
|
var div = document.getElementById("content");
|
||||||
|
var form = document.getElementById("authentication");
|
||||||
|
var inputs = div.getElementsByTagName("INPUT");
|
||||||
|
console.log(inputs);
|
||||||
|
for (var i = inputs.length - 1; i >= 0; i--) {
|
||||||
|
var key = inputs[i].name;
|
||||||
|
var value = inputs[i].value;
|
||||||
|
if (value.length == 0) {
|
||||||
|
inputs[i].style.borderColor = "red";
|
||||||
|
err = true;
|
||||||
|
}
|
||||||
|
data[key] = value;
|
||||||
|
}
|
||||||
|
if (err == true) {
|
||||||
|
data = new Object();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.hasOwnProperty("confirm")) {
|
||||||
|
if (data["confirm"] != data["password"]) {
|
||||||
|
alert("sdafsd");
|
||||||
|
document.getElementById('password').style.borderColor = "red";
|
||||||
|
document.getElementById('confirm').style.borderColor = "red";
|
||||||
|
document.getElementById("err").innerHTML = "{{.account.failed}}";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(data);
|
||||||
|
form.submit();
|
||||||
|
}
|
||||||
331
html/js/base.js
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
var config = new Object();
|
||||||
|
var menu = new Object();
|
||||||
|
var subMenu = new Object();
|
||||||
|
var activeStreams = new Object();
|
||||||
|
var xEPG = new Object();
|
||||||
|
var users = new Object();
|
||||||
|
var log = new Object();
|
||||||
|
var undo = new Object();
|
||||||
|
var webSockets = true;
|
||||||
|
var closeLog, version, activeMenu;
|
||||||
|
var columnToSort = 0
|
||||||
|
|
||||||
|
|
||||||
|
if (window.WebSocket === undefined) {
|
||||||
|
alert("Your browser does not support WebSockets");
|
||||||
|
webSockets = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pageReady() {
|
||||||
|
var data = new Object();
|
||||||
|
data["cmd"] = "getServerConfig";
|
||||||
|
xTeVe(data);
|
||||||
|
//showLoadingScreen(false);
|
||||||
|
|
||||||
|
var resizeHandle = document.getElementById("openStreams");
|
||||||
|
var box = document.getElementById("myStreamsBox");
|
||||||
|
resizeHandle.addEventListener("mousedown", initialiseResize, false);
|
||||||
|
|
||||||
|
function initialiseResize(e) {
|
||||||
|
window.addEventListener("mousemove", startResizing, false);
|
||||||
|
window.addEventListener("mouseup", stopResizing, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startResizing(e) {
|
||||||
|
box.style.height = (e.clientY - box.offsetTop) + "px";
|
||||||
|
|
||||||
|
var elm = document.getElementById("allStreams");
|
||||||
|
if (e.clientY > 120) {
|
||||||
|
elm.className = "visible";
|
||||||
|
} else {
|
||||||
|
elm.className = "notVisible";
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateWrapperHeight();
|
||||||
|
|
||||||
|
}
|
||||||
|
function stopResizing(e) {
|
||||||
|
window.removeEventListener('mousemove', startResizing, false);
|
||||||
|
window.removeEventListener('mouseup', stopResizing, false);
|
||||||
|
calculateWrapperHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("resize", function(){
|
||||||
|
calculateWrapperHeight();
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getObjKeys(obj) {
|
||||||
|
var keys = new Array();
|
||||||
|
|
||||||
|
for (var i in obj) {
|
||||||
|
if (obj.hasOwnProperty(i)) {
|
||||||
|
keys.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function createElement(item) {
|
||||||
|
//console.log(item);
|
||||||
|
var element = document.createElement(item["_element"]);
|
||||||
|
if (item.hasOwnProperty("_text")) {
|
||||||
|
//element.innerHTML = "<p>" + item["_text"] + "</p>";
|
||||||
|
element.innerHTML = item["_text"];
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = getObjKeys(item);
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
if (keys[i].charAt(0) != "_") {
|
||||||
|
//console.log(keys[i], item[keys[i]]);
|
||||||
|
element.setAttribute(keys[i], item[keys[i]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(element);
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
function modifyOption(id, options, values) {
|
||||||
|
var select = document.getElementById(id);
|
||||||
|
select.innerHTML = "";
|
||||||
|
|
||||||
|
for (var i = 0; i < options.length; i++) {
|
||||||
|
|
||||||
|
var element = document.createElement("OPTION")
|
||||||
|
|
||||||
|
element.value = values[i];
|
||||||
|
element.innerHTML = options[i];
|
||||||
|
|
||||||
|
document.getElementById(id).appendChild(element);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function startWebSocket() {
|
||||||
|
if (webSockets == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//ws.send('{"cmd": "getServerConfig1"}');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkErr(obj) {
|
||||||
|
//alert(obj["err"])
|
||||||
|
//screenLog(obj["err"], "error")
|
||||||
|
console.log(obj);
|
||||||
|
var newObj = new Object();
|
||||||
|
var newErr = new Object();
|
||||||
|
newErr["key"] = "Error";
|
||||||
|
newErr["value"] = obj["err"];
|
||||||
|
newErr["type"] = "error";
|
||||||
|
|
||||||
|
newObj[0] = newErr
|
||||||
|
showLog(newObj);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function screenLog(msg, msgType, show) {
|
||||||
|
return
|
||||||
|
clearTimeout(closeLog)
|
||||||
|
var div = document.getElementById("screenLog");
|
||||||
|
var newMsg = new Object();
|
||||||
|
|
||||||
|
newMsg["_element"] = "P";
|
||||||
|
|
||||||
|
switch(msgType) {
|
||||||
|
case "error": newMsg["class"] = "errorMsg"; break;
|
||||||
|
case "warning": newMsg["class"] = "warningMsg"; break;
|
||||||
|
//default: newMsg["class"] = "infoMsg"
|
||||||
|
}
|
||||||
|
|
||||||
|
newMsg["_text"] = msg;
|
||||||
|
|
||||||
|
div.appendChild(createElement(newMsg));
|
||||||
|
|
||||||
|
div.scrollTop = div.scrollHeight;
|
||||||
|
|
||||||
|
if (show == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.className = ""
|
||||||
|
closeLog = setTimeout(closeScreenLog, 10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function closeScreenLog() {
|
||||||
|
var div = document.getElementById("screenLog");
|
||||||
|
div.className = "screenLogHidden"
|
||||||
|
}
|
||||||
|
|
||||||
|
function showScreenLog() {
|
||||||
|
clearTimeout(closeLog)
|
||||||
|
var div = document.getElementById("screenLog");
|
||||||
|
var currentClass = div.className;
|
||||||
|
div.className = "screenLogHidden"
|
||||||
|
|
||||||
|
switch(currentClass) {
|
||||||
|
case "screenLogHidden": div.className = ""; break;
|
||||||
|
case "": div.className = "screenLogHidden"; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showLoadingScreen(elm) {
|
||||||
|
var div = document.getElementById("loading");
|
||||||
|
switch(elm) {
|
||||||
|
case true: div.className = "block"; break;
|
||||||
|
case false: div.className = "none"; break;
|
||||||
|
|
||||||
|
/*
|
||||||
|
case true: div.style.display = "block"; break;
|
||||||
|
case false: div.style.display = "none"; break;
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createClintInfo(obj) {
|
||||||
|
//console.log(obj);
|
||||||
|
var keys = getObjKeys(obj);
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
if(document.getElementById(keys[i])){
|
||||||
|
document.getElementById(keys[i]).innerHTML = obj[keys[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//document.getElementById("clientInfo").className = "visible";
|
||||||
|
}
|
||||||
|
|
||||||
|
function showElement(elmID, type) {
|
||||||
|
switch(type) {
|
||||||
|
case true: cssClass = "block"; break;
|
||||||
|
case false: cssClass = "none"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById(elmID).className = cssClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showPopUpElement(elm) {
|
||||||
|
var allElements = new Array("deleteUserDetail", "mapping-detail", "user-detail", "file-detail");
|
||||||
|
|
||||||
|
for (var i = 0; i < allElements.length; i++) {
|
||||||
|
showElement(allElements[i], false)
|
||||||
|
}
|
||||||
|
|
||||||
|
showElement(elm, true)
|
||||||
|
|
||||||
|
setTimeout(function(){
|
||||||
|
showElement("popup", true);
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// body...
|
||||||
|
|
||||||
|
function showStreams(force) {
|
||||||
|
|
||||||
|
var elmBox = document.getElementById("myStreamsBox");
|
||||||
|
var elm = document.getElementById("allStreams");
|
||||||
|
//console.log(elm);
|
||||||
|
show = elm.className;
|
||||||
|
|
||||||
|
switch(force) {
|
||||||
|
case true: show = "notVisible"; break;
|
||||||
|
case false: show = "visible"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(show) {
|
||||||
|
case "notVisible":
|
||||||
|
elm.className = "visible";
|
||||||
|
elmBox.style.height = "100px";
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
elm.className = "notVisible";
|
||||||
|
elmBox.style.height = "20px";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var show = elm.style.display; {
|
||||||
|
//console.log(elm.style.display);
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateWrapperHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
function xteveBackup() {
|
||||||
|
console.log("xteveBackup");
|
||||||
|
var data = new Object();
|
||||||
|
data["cmd"] = "xteveBackup";
|
||||||
|
|
||||||
|
xTeVe(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function xteveRestore(elm) {
|
||||||
|
var restore = document.createElement("INPUT");
|
||||||
|
restore.setAttribute("type", "file");
|
||||||
|
restore.setAttribute("class", "notVisible");
|
||||||
|
restore.setAttribute("name", "");
|
||||||
|
restore.id = "upload";
|
||||||
|
|
||||||
|
document.body.appendChild(restore);
|
||||||
|
restore.click();
|
||||||
|
|
||||||
|
restore.onchange = function() {
|
||||||
|
var filename = restore.files[0].name
|
||||||
|
//console.log(restore.srcElement.files[0]);
|
||||||
|
var check = confirm("File: " + filename + "\nAll data will be replaced with those from the backup.\nShould the files be restored?");
|
||||||
|
if (check == true) {
|
||||||
|
var reader = new FileReader();
|
||||||
|
var file = document.querySelector('input[type=file]').files[0];
|
||||||
|
if (file) {
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
reader.onload = function() {
|
||||||
|
console.log(reader.result);
|
||||||
|
var data = new Object();
|
||||||
|
data["cmd"] = "xteveRestore"
|
||||||
|
data["base64"] = reader.result
|
||||||
|
|
||||||
|
xTeVe(data);
|
||||||
|
return
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
alert("File could not be loaded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBase64(file) {
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
reader.onload = function() {
|
||||||
|
console.log(reader.result);
|
||||||
|
};
|
||||||
|
reader.onerror = function(error) {
|
||||||
|
console.log('Error: ', error);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
document.cookie.split(';').forEach(function(c) {
|
||||||
|
document.cookie = c.trim().split('=')[0] + '=;' + 'expires=Thu, 01 Jan 1970 00:00:00 UTC;';
|
||||||
|
});
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCookie(name) {
|
||||||
|
var value = "; " + document.cookie;
|
||||||
|
var parts = value.split("; " + name + "=");
|
||||||
|
if (parts.length == 2) return parts.pop().split(";").shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCookie(token) {
|
||||||
|
//console.log(token);
|
||||||
|
document.cookie = "Token=" + token
|
||||||
|
}
|
||||||
|
|
||||||
473
html/js/base_ts.js
Normal file
@@ -0,0 +1,473 @@
|
|||||||
|
var SERVER = new Object();
|
||||||
|
var BULK_EDIT = false;
|
||||||
|
var COLUMN_TO_SORT;
|
||||||
|
var SEARCH_MAPPING = new Object();
|
||||||
|
var UNDO = new Object();
|
||||||
|
var SERVER_CONNECTION = false;
|
||||||
|
var WS_AVAILABLE = false;
|
||||||
|
// Menü
|
||||||
|
var menuItems = new Array();
|
||||||
|
menuItems.push(new MainMenuItem("playlist", "{{.mainMenu.item.playlist}}", "m3u.png", "{{.mainMenu.headline.playlist}}"));
|
||||||
|
//menuItems.push(new MainMenuItem("pmsID", "{{.mainMenu.item.pmsID}}", "number.png", "{{.mainMenu.headline.pmsID}}"))
|
||||||
|
menuItems.push(new MainMenuItem("filter", "{{.mainMenu.item.filter}}", "filter.png", "{{.mainMenu.headline.filter}}"));
|
||||||
|
menuItems.push(new MainMenuItem("xmltv", "{{.mainMenu.item.xmltv}}", "xmltv.png", "{{.mainMenu.headline.xmltv}}"));
|
||||||
|
menuItems.push(new MainMenuItem("mapping", "{{.mainMenu.item.mapping}}", "mapping.png", "{{.mainMenu.headline.mapping}}"));
|
||||||
|
menuItems.push(new MainMenuItem("users", "{{.mainMenu.item.users}}", "users.png", "{{.mainMenu.headline.users}}"));
|
||||||
|
menuItems.push(new MainMenuItem("settings", "{{.mainMenu.item.settings}}", "settings.png", "{{.mainMenu.headline.settings}}"));
|
||||||
|
menuItems.push(new MainMenuItem("log", "{{.mainMenu.item.log}}", "log.png", "{{.mainMenu.headline.log}}"));
|
||||||
|
menuItems.push(new MainMenuItem("logout", "{{.mainMenu.item.logout}}", "logout.png", "{{.mainMenu.headline.logout}}"));
|
||||||
|
// Kategorien für die Einstellungen
|
||||||
|
var settingsCategory = new Array();
|
||||||
|
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.general}}", "xteveAutoUpdate,tuner,epgSource,api"));
|
||||||
|
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.files}}", "update,files.update,temp.path,cache.images,xepg.replace.missing.images"));
|
||||||
|
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.streaming}}", "buffer,buffer.size.kb,buffer.timeout,user.agent"));
|
||||||
|
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.backup}}", "backup.path,backup.keep"));
|
||||||
|
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.authentication}}", "authentication.web,authentication.pms,authentication.m3u,authentication.xml,authentication.api"));
|
||||||
|
function showPopUpElement(elm) {
|
||||||
|
var allElements = new Array("popup-custom");
|
||||||
|
for (var i = 0; i < allElements.length; i++) {
|
||||||
|
showElement(allElements[i], false);
|
||||||
|
}
|
||||||
|
showElement(elm, true);
|
||||||
|
setTimeout(function () {
|
||||||
|
showElement("popup", true);
|
||||||
|
}, 10);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function showElement(elmID, type) {
|
||||||
|
var cssClass;
|
||||||
|
switch (type) {
|
||||||
|
case true:
|
||||||
|
cssClass = "block";
|
||||||
|
break;
|
||||||
|
case false:
|
||||||
|
cssClass = "none";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
document.getElementById(elmID).className = cssClass;
|
||||||
|
}
|
||||||
|
function changeButtonAction(element, buttonID, attribute) {
|
||||||
|
var value = element.options[element.selectedIndex].value;
|
||||||
|
document.getElementById(buttonID).setAttribute(attribute, value);
|
||||||
|
}
|
||||||
|
function getLocalData(dataType, id) {
|
||||||
|
var data = new Object();
|
||||||
|
switch (dataType) {
|
||||||
|
case "m3u":
|
||||||
|
data = SERVER["settings"]["files"][dataType][id];
|
||||||
|
break;
|
||||||
|
case "hdhr":
|
||||||
|
data = SERVER["settings"]["files"][dataType][id];
|
||||||
|
break;
|
||||||
|
case "filter":
|
||||||
|
case "custom-filter":
|
||||||
|
case "group-title":
|
||||||
|
if (id == -1) {
|
||||||
|
data["active"] = true;
|
||||||
|
data["caseSensitive"] = false;
|
||||||
|
data["description"] = "";
|
||||||
|
data["exclude"] = "";
|
||||||
|
data["filter"] = "";
|
||||||
|
data["include"] = "";
|
||||||
|
data["name"] = "";
|
||||||
|
data["type"] = "group-title";
|
||||||
|
SERVER["settings"]["filter"][id] = data;
|
||||||
|
}
|
||||||
|
data = SERVER["settings"]["filter"][id];
|
||||||
|
break;
|
||||||
|
case "xmltv":
|
||||||
|
data = SERVER["settings"]["files"][dataType][id];
|
||||||
|
break;
|
||||||
|
case "users":
|
||||||
|
data = SERVER["users"][id]["data"];
|
||||||
|
break;
|
||||||
|
case "mapping":
|
||||||
|
data = SERVER["xepg"]["epgMapping"][id];
|
||||||
|
break;
|
||||||
|
case "m3uGroups":
|
||||||
|
data = SERVER["data"]["playlist"]["m3u"]["groups"];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
function getObjKeys(obj) {
|
||||||
|
var keys = new Array();
|
||||||
|
for (var i in obj) {
|
||||||
|
if (obj.hasOwnProperty(i)) {
|
||||||
|
keys.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
function getAllSelectedChannels() {
|
||||||
|
var channels = new Array();
|
||||||
|
if (BULK_EDIT == false) {
|
||||||
|
return channels;
|
||||||
|
}
|
||||||
|
var trs = document.getElementById("content_table").getElementsByTagName("TR");
|
||||||
|
for (var i = 1; i < trs.length; i++) {
|
||||||
|
if (trs[i].style.display != "none") {
|
||||||
|
if (trs[i].firstChild.firstChild.checked == true) {
|
||||||
|
channels.push(trs[i].id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return channels;
|
||||||
|
}
|
||||||
|
function selectAllChannels() {
|
||||||
|
var bulk = false;
|
||||||
|
var trs = document.getElementById("content_table").getElementsByTagName("TR");
|
||||||
|
if (trs[0].firstChild.firstChild.checked == true) {
|
||||||
|
bulk = true;
|
||||||
|
}
|
||||||
|
for (var i = 1; i < trs.length; i++) {
|
||||||
|
if (trs[i].style.display != "none") {
|
||||||
|
switch (bulk) {
|
||||||
|
case true:
|
||||||
|
trs[i].firstChild.firstChild.checked = true;
|
||||||
|
break;
|
||||||
|
case false:
|
||||||
|
trs[i].firstChild.firstChild.checked = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function bulkEdit() {
|
||||||
|
BULK_EDIT = !BULK_EDIT;
|
||||||
|
var className;
|
||||||
|
var rows = document.getElementsByClassName("bulk");
|
||||||
|
switch (BULK_EDIT) {
|
||||||
|
case true:
|
||||||
|
className = "bulk showBulk";
|
||||||
|
break;
|
||||||
|
case false:
|
||||||
|
className = "bulk hideBulk";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (var i = 0; i < rows.length; i++) {
|
||||||
|
rows[i].className = className;
|
||||||
|
rows[i].checked = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function sortTable(column) {
|
||||||
|
//console.log(columm);
|
||||||
|
if (column == COLUMN_TO_SORT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var table = document.getElementById("content_table");
|
||||||
|
var tableHead = table.getElementsByTagName("TR")[0];
|
||||||
|
var tableItems = tableHead.getElementsByTagName("TD");
|
||||||
|
var sortObj = new Object();
|
||||||
|
var x, xValue;
|
||||||
|
var tableHeader;
|
||||||
|
var sortByString = false;
|
||||||
|
if (column > 0 && COLUMN_TO_SORT > 0) {
|
||||||
|
tableItems[COLUMN_TO_SORT].className = "pointer";
|
||||||
|
tableItems[column].className = "sortThis";
|
||||||
|
}
|
||||||
|
COLUMN_TO_SORT = column;
|
||||||
|
var rows = table.rows;
|
||||||
|
if (rows[1] != undefined) {
|
||||||
|
tableHeader = rows[0];
|
||||||
|
x = rows[1].getElementsByTagName("TD")[column];
|
||||||
|
for (i = 1; i < rows.length; i++) {
|
||||||
|
x = rows[i].getElementsByTagName("TD")[column];
|
||||||
|
switch (x.childNodes[0].tagName.toLowerCase()) {
|
||||||
|
case "input":
|
||||||
|
xValue = x.getElementsByTagName("INPUT")[0].value.toLowerCase();
|
||||||
|
break;
|
||||||
|
case "p":
|
||||||
|
xValue = x.getElementsByTagName("P")[0].innerText.toLowerCase();
|
||||||
|
break;
|
||||||
|
default: console.log(x.childNodes[0].tagName);
|
||||||
|
}
|
||||||
|
if (xValue == "" || xValue == NaN) {
|
||||||
|
xValue = i;
|
||||||
|
sortObj[i] = rows[i];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
switch (isNaN(xValue)) {
|
||||||
|
case false:
|
||||||
|
xValue = parseFloat(xValue);
|
||||||
|
sortObj[xValue] = rows[i];
|
||||||
|
break;
|
||||||
|
case true:
|
||||||
|
sortByString = true;
|
||||||
|
sortObj[xValue.toLowerCase() + i] = rows[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (table.firstChild) {
|
||||||
|
table.removeChild(table.firstChild);
|
||||||
|
}
|
||||||
|
var sortValues = getObjKeys(sortObj);
|
||||||
|
if (sortByString == true) {
|
||||||
|
sortValues.sort();
|
||||||
|
console.log(sortValues);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
function sortFloat(a, b) {
|
||||||
|
return a - b;
|
||||||
|
}
|
||||||
|
sortValues.sort(sortFloat);
|
||||||
|
}
|
||||||
|
table.appendChild(tableHeader);
|
||||||
|
for (var i = 0; i < sortValues.length; i++) {
|
||||||
|
table.appendChild(sortObj[sortValues[i]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function createSearchObj() {
|
||||||
|
SEARCH_MAPPING = new Object();
|
||||||
|
var data = SERVER["xepg"]["epgMapping"];
|
||||||
|
var channels = getObjKeys(data);
|
||||||
|
var channelKeys = ["x-active", "x-channelID", "x-name", "_file.m3u.name", "x-group-title"];
|
||||||
|
channels.forEach(function (id) {
|
||||||
|
channelKeys.forEach(function (key) {
|
||||||
|
if (key == "x-active") {
|
||||||
|
switch (data[id][key]) {
|
||||||
|
case true:
|
||||||
|
SEARCH_MAPPING[id] = "online ";
|
||||||
|
break;
|
||||||
|
case false:
|
||||||
|
SEARCH_MAPPING[id] = "offline ";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SEARCH_MAPPING[id] = SEARCH_MAPPING[id] + data[id][key] + " ";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function searchInMapping() {
|
||||||
|
var searchValue = document.getElementById("searchMapping").value;
|
||||||
|
var trs = document.getElementById("content_table").getElementsByTagName("TR");
|
||||||
|
for (var i = 1; i < trs.length; ++i) {
|
||||||
|
var id = trs[i].getAttribute("id");
|
||||||
|
var element = SEARCH_MAPPING[id];
|
||||||
|
switch (element.toLowerCase().includes(searchValue.toLowerCase())) {
|
||||||
|
case true:
|
||||||
|
document.getElementById(id).style.display = "";
|
||||||
|
break;
|
||||||
|
case false:
|
||||||
|
document.getElementById(id).style.display = "none";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function calculateWrapperHeight() {
|
||||||
|
if (document.getElementById("box-wrapper")) {
|
||||||
|
var elm = document.getElementById("box-wrapper");
|
||||||
|
var divs = new Array("myStreamsBox", "clientInfo", "content");
|
||||||
|
var elementsHeight = 0 - elm.offsetHeight;
|
||||||
|
for (var i = 0; i < divs.length; i++) {
|
||||||
|
elementsHeight = elementsHeight + document.getElementById(divs[i]).offsetHeight;
|
||||||
|
}
|
||||||
|
elm.style.height = window.innerHeight - elementsHeight + "px";
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function changeChannelNumber(element) {
|
||||||
|
var dbID = element.parentNode.parentNode.id;
|
||||||
|
var newNumber = parseFloat(element.value);
|
||||||
|
var channelNumbers = [];
|
||||||
|
var data = SERVER["xepg"]["epgMapping"];
|
||||||
|
var channels = getObjKeys(data);
|
||||||
|
if (isNaN(newNumber)) {
|
||||||
|
alert("{{.alert.invalidChannelNumber}}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
channels.forEach(function (id) {
|
||||||
|
var channelNumber = parseFloat(data[id]["x-channelID"]);
|
||||||
|
channelNumbers.push(channelNumber);
|
||||||
|
});
|
||||||
|
for (var i = 0; i < channelNumbers.length; i++) {
|
||||||
|
if (channelNumbers.indexOf(newNumber) == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (Math.floor(newNumber) == newNumber) {
|
||||||
|
newNumber = newNumber + 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newNumber = newNumber + 0.1;
|
||||||
|
newNumber.toFixed(1);
|
||||||
|
newNumber = Math.round(newNumber * 10) / 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data[dbID]["x-channelID"] = newNumber.toString();
|
||||||
|
element.value = newNumber;
|
||||||
|
console.log(data[dbID]["x-channelID"]);
|
||||||
|
if (COLUMN_TO_SORT == 1) {
|
||||||
|
COLUMN_TO_SORT = -1;
|
||||||
|
sortTable(1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function backup() {
|
||||||
|
var data = new Object();
|
||||||
|
console.log("Backup data");
|
||||||
|
var cmd = "xteveBackup";
|
||||||
|
console.log("SEND TO SERVER");
|
||||||
|
console.log(data);
|
||||||
|
var server = new Server(cmd);
|
||||||
|
server.request(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function toggleChannelStatus(id) {
|
||||||
|
var element;
|
||||||
|
var status;
|
||||||
|
if (document.getElementById("active")) {
|
||||||
|
var checkbox = document.getElementById("active");
|
||||||
|
status = (checkbox).checked;
|
||||||
|
}
|
||||||
|
var ids = getAllSelectedChannels();
|
||||||
|
if (ids.length == 0) {
|
||||||
|
ids.push(id);
|
||||||
|
}
|
||||||
|
ids.forEach(function (id) {
|
||||||
|
var channel = SERVER["xepg"]["epgMapping"][id];
|
||||||
|
channel["x-active"] = status;
|
||||||
|
switch (channel["x-active"]) {
|
||||||
|
case true:
|
||||||
|
if (channel["x-xmltv-file"] == "-" || channel["x-mapping"] == "-") {
|
||||||
|
if (BULK_EDIT == false) {
|
||||||
|
alert(channel["x-name"] + ": Missing XMLTV file / channel");
|
||||||
|
checkbox.checked = false;
|
||||||
|
}
|
||||||
|
channel["x-active"] = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case false:
|
||||||
|
// code...
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (channel["x-active"] == false) {
|
||||||
|
document.getElementById(id).className = "notActiveEPG";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
document.getElementById(id).className = "activeEPG";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function restore() {
|
||||||
|
if (document.getElementById('upload')) {
|
||||||
|
document.getElementById('upload').remove();
|
||||||
|
}
|
||||||
|
var restore = document.createElement("INPUT");
|
||||||
|
restore.setAttribute("type", "file");
|
||||||
|
restore.setAttribute("class", "notVisible");
|
||||||
|
restore.setAttribute("name", "");
|
||||||
|
restore.id = "upload";
|
||||||
|
document.body.appendChild(restore);
|
||||||
|
restore.click();
|
||||||
|
restore.onchange = function () {
|
||||||
|
var filename = restore.files[0].name;
|
||||||
|
var check = confirm("File: " + filename + "\n{{.confirm.restore}}");
|
||||||
|
if (check == true) {
|
||||||
|
var reader = new FileReader();
|
||||||
|
var file = document.querySelector('input[type=file]').files[0];
|
||||||
|
if (file) {
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
reader.onload = function () {
|
||||||
|
console.log(reader.result);
|
||||||
|
var data = new Object();
|
||||||
|
var cmd = "xteveRestore";
|
||||||
|
data["base64"] = reader.result;
|
||||||
|
var server = new Server(cmd);
|
||||||
|
server.request(data);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alert("File could not be loaded");
|
||||||
|
}
|
||||||
|
restore.remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function uploadLogo() {
|
||||||
|
if (document.getElementById('upload')) {
|
||||||
|
document.getElementById('upload').remove();
|
||||||
|
}
|
||||||
|
var upload = document.createElement("INPUT");
|
||||||
|
upload.setAttribute("type", "file");
|
||||||
|
upload.setAttribute("class", "notVisible");
|
||||||
|
upload.setAttribute("name", "");
|
||||||
|
upload.id = "upload";
|
||||||
|
document.body.appendChild(upload);
|
||||||
|
upload.click();
|
||||||
|
upload.onblur = function () {
|
||||||
|
alert();
|
||||||
|
};
|
||||||
|
upload.onchange = function () {
|
||||||
|
var filename = upload.files[0].name;
|
||||||
|
var reader = new FileReader();
|
||||||
|
var file = document.querySelector('input[type=file]').files[0];
|
||||||
|
if (file) {
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
reader.onload = function () {
|
||||||
|
console.log(reader.result);
|
||||||
|
var data = new Object();
|
||||||
|
var cmd = "uploadLogo";
|
||||||
|
data["base64"] = reader.result;
|
||||||
|
data["filename"] = file.name;
|
||||||
|
var server = new Server(cmd);
|
||||||
|
server.request(data);
|
||||||
|
var updateLogo = document.getElementById('update-icon');
|
||||||
|
updateLogo.checked = false;
|
||||||
|
updateLogo.className = "changed";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alert("File could not be loaded");
|
||||||
|
}
|
||||||
|
upload.remove();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function checkUndo(key) {
|
||||||
|
switch (key) {
|
||||||
|
case "epgMapping":
|
||||||
|
if (UNDO.hasOwnProperty(key)) {
|
||||||
|
SERVER["xepg"][key] = JSON.parse(JSON.stringify(UNDO[key]));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
UNDO[key] = JSON.parse(JSON.stringify(SERVER["xepg"][key]));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function sortSelect(elem) {
|
||||||
|
var tmpAry = [];
|
||||||
|
var selectedValue = elem[elem.selectedIndex].value;
|
||||||
|
for (var i = 0; i < elem.options.length; i++)
|
||||||
|
tmpAry.push(elem.options[i]);
|
||||||
|
tmpAry.sort(function (a, b) { return (a.text < b.text) ? -1 : 1; });
|
||||||
|
while (elem.options.length > 0)
|
||||||
|
elem.options[0] = null;
|
||||||
|
var newSelectedIndex = 0;
|
||||||
|
for (var i = 0; i < tmpAry.length; i++) {
|
||||||
|
elem.options[i] = tmpAry[i];
|
||||||
|
if (elem.options[i].value == selectedValue)
|
||||||
|
newSelectedIndex = i;
|
||||||
|
}
|
||||||
|
elem.selectedIndex = newSelectedIndex; // Set new selected index after sorting
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function updateLog() {
|
||||||
|
console.log("TOKEN");
|
||||||
|
var server = new Server("updateLog");
|
||||||
|
server.request(new Object());
|
||||||
|
}
|
||||||
40
html/js/classes_ts.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
var __extends = (this && this.__extends) || (function () {
|
||||||
|
var extendStatics = function (d, b) {
|
||||||
|
extendStatics = Object.setPrototypeOf ||
|
||||||
|
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||||
|
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||||
|
return extendStatics(d, b);
|
||||||
|
};
|
||||||
|
return function (d, b) {
|
||||||
|
extendStatics(d, b);
|
||||||
|
function __() { this.constructor = d; }
|
||||||
|
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
var MainMenu = /** @class */ (function () {
|
||||||
|
function MainMenu() {
|
||||||
|
this.DocumentID = "main-menu";
|
||||||
|
this.HTMLTag = "LI";
|
||||||
|
}
|
||||||
|
MainMenu.prototype.create = function () {
|
||||||
|
console.log(this.DocumentID);
|
||||||
|
};
|
||||||
|
return MainMenu;
|
||||||
|
}());
|
||||||
|
var MainMenuItem = /** @class */ (function (_super) {
|
||||||
|
__extends(MainMenuItem, _super);
|
||||||
|
function MainMenuItem() {
|
||||||
|
return _super !== null && _super.apply(this, arguments) || this;
|
||||||
|
}
|
||||||
|
MainMenuItem.prototype.create2 = function () {
|
||||||
|
var element = document.createElement(this.HTMLTag);
|
||||||
|
element.innerText = this.Value;
|
||||||
|
console.log(element);
|
||||||
|
};
|
||||||
|
return MainMenuItem;
|
||||||
|
}(MainMenu));
|
||||||
|
function pageReady() {
|
||||||
|
var item = new MainMenuItem();
|
||||||
|
item.Value = "Test";
|
||||||
|
item.create2();
|
||||||
|
}
|
||||||
294
html/js/configuaration.js
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
var configMenu = new Object();
|
||||||
|
var wizard = new Array("key", "tuner", "epgSource", "m3u", "complete");
|
||||||
|
var activeWizard;
|
||||||
|
var dvrIP
|
||||||
|
|
||||||
|
var configMenu_tuner = new Object();
|
||||||
|
configMenu_tuner["_element"] = "SELECT";
|
||||||
|
configMenu_tuner["_menuType"] = "singleInput";
|
||||||
|
configMenu_tuner["_configKey"] = "tuner";
|
||||||
|
configMenu_tuner["_label"] = "Available tuners";
|
||||||
|
configMenu_tuner["name"] = "tuner";
|
||||||
|
configMenu_tuner["id"] = "Tuner";
|
||||||
|
configMenu_tuner["placeholder"] = "Tuner";
|
||||||
|
configMenu_tuner["_usage"] = "This setting is only used by Plex and Emby.<br>The number of concurrent streams allowed by the IPTV provider."
|
||||||
|
|
||||||
|
|
||||||
|
var optionValues = new Array();
|
||||||
|
for (var i = 1; i <= 100; i++) {
|
||||||
|
optionValues.push(i)
|
||||||
|
}
|
||||||
|
configMenu_tuner["_optionValues"] = optionValues;
|
||||||
|
|
||||||
|
var configMenu_epg = new Object();
|
||||||
|
configMenu_epg["_element"] = "SELECT";
|
||||||
|
configMenu_epg["_menuType"] = "singleInput";
|
||||||
|
configMenu_epg["_configKey"] = "epgSource";
|
||||||
|
configMenu_epg["_label"] = "Selection of the EPG source";
|
||||||
|
configMenu_epg["name"] = "epgSource";
|
||||||
|
configMenu_epg["id"] = "EPG source";
|
||||||
|
configMenu_epg["placeholder"] = "EPG source";
|
||||||
|
configMenu_epg["_optionValues"] = new Array("PMS", "XEPG");
|
||||||
|
configMenu_epg["_usage"] = "PMS: Use EPG data from Plex or Emby<br>XEPG: Use of external EPG data (XMLTV)<br> Several XMLTV sources possible<br> Allows editing and order channels<br> M3U / XMLTV export (HTTP link for IPTV apps)"
|
||||||
|
|
||||||
|
var configMenu_m3u = new Object();
|
||||||
|
configMenu_m3u["_element"] = "INPUT";
|
||||||
|
configMenu_m3u["_menuType"] = "inputArray";
|
||||||
|
configMenu_m3u["_configKey"] = "file";
|
||||||
|
configMenu_m3u["_label"] = "M3U File: local or remote";
|
||||||
|
configMenu_m3u["name"] = "file";
|
||||||
|
configMenu_m3u["id"] = "m3u";
|
||||||
|
configMenu_m3u["type"] = "text";
|
||||||
|
configMenu_m3u["placeholder"] = "M3U File";
|
||||||
|
configMenu_m3u["_usage"] = "Remote playlist: http://your.provider.com/file.m3u<br>Local playlist: /path/to/file.m3u"
|
||||||
|
|
||||||
|
|
||||||
|
configMenu_m3u["value"] = "http://websrv.local:8080/kabel.m3u";
|
||||||
|
|
||||||
|
var configMenu_complete = new Object();
|
||||||
|
configMenu_complete["_element"] = "H2";
|
||||||
|
configMenu_complete["_menuType"] = "inputArray";
|
||||||
|
configMenu_complete["_configKey"] = "file";
|
||||||
|
configMenu_complete["_text"] = "xTeVe was successfully set up";
|
||||||
|
configMenu_complete["name"] = "complete";
|
||||||
|
configMenu_complete["id"] = "complete";
|
||||||
|
configMenu_complete["type"] = "text";
|
||||||
|
configMenu_complete["class"] = "center";
|
||||||
|
|
||||||
|
configMenu["tuner"] = configMenu_tuner;
|
||||||
|
configMenu["epgSource"] = configMenu_epg;
|
||||||
|
configMenu["m3u"] = configMenu_m3u;
|
||||||
|
configMenu["complete"] = configMenu_complete;
|
||||||
|
|
||||||
|
function readyForConfiguration() {
|
||||||
|
var data = new Object();
|
||||||
|
data["cmd"] = "getServerConfig";
|
||||||
|
xTeVe(data);
|
||||||
|
showLoadingScreen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createConfiguration(elm) {
|
||||||
|
|
||||||
|
activeWizard = elm;
|
||||||
|
var item = configMenu[elm];
|
||||||
|
|
||||||
|
var div = document.getElementById("content");
|
||||||
|
div.innerHTML = "";
|
||||||
|
div.setAttribute("data-configKey", item["_configKey"]);
|
||||||
|
div.setAttribute("data-menuType", item["_menuType"]);
|
||||||
|
|
||||||
|
switch(item.hasOwnProperty("_label")) {
|
||||||
|
case true:
|
||||||
|
var newItem = new Object();
|
||||||
|
newItem["_element"] = "LABEL";
|
||||||
|
newItem["_text"] = item["_label"];
|
||||||
|
newItem["for"] = item["id"];
|
||||||
|
div.appendChild(createElement(newItem));
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(item["_element"]) {
|
||||||
|
case "SELECT":
|
||||||
|
div.appendChild(createElement(item));
|
||||||
|
var selectElement = div.getElementsByTagName("SELECT")[0];
|
||||||
|
var values = item["_optionValues"];
|
||||||
|
for (var i = 0; i < values.length; i++) {
|
||||||
|
var newEntry = new Object;
|
||||||
|
newEntry["_element"] = "OPTION";
|
||||||
|
newEntry["_text"] = item["id"] + ": " + values[i];
|
||||||
|
newEntry["value"] = values[i];
|
||||||
|
selectElement.appendChild(createElement(newEntry));
|
||||||
|
}
|
||||||
|
//return
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
div.appendChild(createElement(item));
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
//alert()
|
||||||
|
|
||||||
|
switch(item.hasOwnProperty("_usage")) {
|
||||||
|
case true:
|
||||||
|
var usageItem = new Object();
|
||||||
|
usageItem["_element"] = "PRE"
|
||||||
|
usageItem["_text"] = item["_usage"];
|
||||||
|
div.appendChild(createElement(usageItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeWizard == "complete") {
|
||||||
|
document.getElementById("next").value = "Finished"
|
||||||
|
//document.getElementById("next").setAttribute("onclick", "javascript: location.reload();")
|
||||||
|
}
|
||||||
|
|
||||||
|
//div.appendChild(createElement(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveData() {
|
||||||
|
|
||||||
|
var div = document.getElementById("content");
|
||||||
|
var inputs = div.getElementsByTagName("INPUT");
|
||||||
|
var selects = div.getElementsByTagName("SELECT");
|
||||||
|
var value;
|
||||||
|
var data = new Object();
|
||||||
|
var valueArr = new Array();
|
||||||
|
var newData = false;
|
||||||
|
|
||||||
|
if (activeWizard == "complete") {
|
||||||
|
data["cmd"] = "wizardCompleted";
|
||||||
|
showLoadingScreen(true)
|
||||||
|
xTeVe(data);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < inputs.length; i++) {
|
||||||
|
var menuType = inputs[i].parentElement.getAttribute("data-menutype");
|
||||||
|
if (inputs[i].value != undefined && inputs[i].value != "" ) {
|
||||||
|
newData = true;
|
||||||
|
|
||||||
|
console.log(inputs[i].id)
|
||||||
|
switch(inputs[i].id) {
|
||||||
|
case "m3u":
|
||||||
|
var newPlaylist = new Object();
|
||||||
|
newPlaylist["file.source"] = inputs[i].value;
|
||||||
|
//newPlaylist["name"] = inputs[i].value;
|
||||||
|
newPlaylist["type"] = "m3u";
|
||||||
|
newPlaylist["new"] = true;
|
||||||
|
|
||||||
|
data["files"] = new Object();
|
||||||
|
data["files"]["m3u"] = new Object();
|
||||||
|
data["files"]["m3u"]["-"] = newPlaylist;
|
||||||
|
|
||||||
|
data["cmd"] = "saveFilesM3U";
|
||||||
|
xTeVe(data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
switch(menuType) {
|
||||||
|
case "singleInput":
|
||||||
|
data[inputs[i].name] = inputs[i].value; break;
|
||||||
|
case "inputArray":
|
||||||
|
valueArr.push(inputs[i].value);
|
||||||
|
data[inputs[i].name] = valueArr; break
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
} else {
|
||||||
|
inputs[i].style.borderBottomColor = "red";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (var i = 0; i < selects.length; i++) {
|
||||||
|
var value = selects[i].options[selects[i].selectedIndex].value;
|
||||||
|
if (isNaN(value) == false) {
|
||||||
|
value = parseInt(value);
|
||||||
|
data[selects[i].name] = value;
|
||||||
|
newData = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
data[selects[i].name] = value;
|
||||||
|
newData = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//console.log(data, newData);
|
||||||
|
if (newData == true) {
|
||||||
|
config = data
|
||||||
|
data["cmd"] = "saveConfig";
|
||||||
|
xTeVe(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function xTeVe(data) {
|
||||||
|
|
||||||
|
if (webSockets == false) {
|
||||||
|
alert("Your browser does not support WebSockets");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeWizard == "m3u" || activeWizard == "epgSource") {
|
||||||
|
showLoadingScreen(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var protocolWS
|
||||||
|
switch(window.location.protocol) {
|
||||||
|
case "http:": protocolWS = "ws://"; break;
|
||||||
|
case "https:": protocolWS = "wss://"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ws = new WebSocket(protocolWS + window.location.hostname + ":" + window.location.port + "/data/" + "?Token=" + getCookie("Token"));
|
||||||
|
|
||||||
|
ws.onopen = function() {
|
||||||
|
ws.send(JSON.stringify(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onmessage = function (e) {
|
||||||
|
|
||||||
|
var response = JSON.parse(e.data);
|
||||||
|
|
||||||
|
if (response.hasOwnProperty("clientInfo")) {
|
||||||
|
createClintInfo(response["clientInfo"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.hasOwnProperty("status")) {
|
||||||
|
if (response["status"] == false) {
|
||||||
|
document.getElementById("headline").style.borderColor = "red";
|
||||||
|
showErr(response["err"]);
|
||||||
|
showLoadingScreen(false)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
document.getElementById("err").innerHTML = "";
|
||||||
|
document.getElementById("headline").style.borderColor = "lawngreen";
|
||||||
|
}
|
||||||
|
|
||||||
|
dvrIP = response["DVR"]
|
||||||
|
switch(response["configurationWizard"]) {
|
||||||
|
case true:
|
||||||
|
if (activeWizard == undefined) {
|
||||||
|
activeWizard = wizard[0]
|
||||||
|
}
|
||||||
|
var n = wizard.indexOf(activeWizard);
|
||||||
|
n++;
|
||||||
|
activeWizard = wizard[n]
|
||||||
|
|
||||||
|
if (activeWizard == undefined) {
|
||||||
|
data["cmd"] = "wizardCompleted";
|
||||||
|
xTeVe(data)
|
||||||
|
} else {
|
||||||
|
//console.log(activeWizard);
|
||||||
|
createConfiguration(activeWizard);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(response["reload"]) {
|
||||||
|
|
||||||
|
|
||||||
|
case true:
|
||||||
|
|
||||||
|
setTimeout(function(){
|
||||||
|
location.reload();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
//location.reload();
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(function(){ showLoadingScreen(false); }, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function showErr(elm) {
|
||||||
|
document.getElementById("err").innerHTML = elm;
|
||||||
|
}
|
||||||
140
html/js/configuration_ts.js
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
var __extends = (this && this.__extends) || (function () {
|
||||||
|
var extendStatics = function (d, b) {
|
||||||
|
extendStatics = Object.setPrototypeOf ||
|
||||||
|
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||||
|
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||||
|
return extendStatics(d, b);
|
||||||
|
};
|
||||||
|
return function (d, b) {
|
||||||
|
extendStatics(d, b);
|
||||||
|
function __() { this.constructor = d; }
|
||||||
|
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
var WizardCategory = /** @class */ (function () {
|
||||||
|
function WizardCategory() {
|
||||||
|
this.DocumentID = "content";
|
||||||
|
}
|
||||||
|
WizardCategory.prototype.createCategoryHeadline = function (value) {
|
||||||
|
var element = document.createElement("H4");
|
||||||
|
element.innerHTML = value;
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
return WizardCategory;
|
||||||
|
}());
|
||||||
|
var WizardItem = /** @class */ (function (_super) {
|
||||||
|
__extends(WizardItem, _super);
|
||||||
|
function WizardItem(key, headline) {
|
||||||
|
var _this = _super.call(this) || this;
|
||||||
|
_this.headline = headline;
|
||||||
|
_this.key = key;
|
||||||
|
return _this;
|
||||||
|
}
|
||||||
|
WizardItem.prototype.createWizard = function () {
|
||||||
|
var headline = this.createCategoryHeadline(this.headline);
|
||||||
|
var key = this.key;
|
||||||
|
var content = new PopupContent();
|
||||||
|
var description;
|
||||||
|
var doc = document.getElementById(this.DocumentID);
|
||||||
|
doc.innerHTML = "";
|
||||||
|
doc.appendChild(headline);
|
||||||
|
switch (key) {
|
||||||
|
case "tuner":
|
||||||
|
var text = new Array();
|
||||||
|
var values = new Array();
|
||||||
|
for (var i = 1; i <= 100; i++) {
|
||||||
|
text.push(i);
|
||||||
|
values.push(i);
|
||||||
|
}
|
||||||
|
var select = content.createSelect(text, values, "1", key);
|
||||||
|
select.setAttribute("class", "wizard");
|
||||||
|
select.id = key;
|
||||||
|
doc.appendChild(select);
|
||||||
|
description = "{{.wizard.tuner.description}}";
|
||||||
|
break;
|
||||||
|
case "epgSource":
|
||||||
|
var text = ["PMS", "XEPG"];
|
||||||
|
var values = ["PMS", "XEPG"];
|
||||||
|
var select = content.createSelect(text, values, "XEPG", key);
|
||||||
|
select.setAttribute("class", "wizard");
|
||||||
|
select.id = key;
|
||||||
|
doc.appendChild(select);
|
||||||
|
description = "{{.wizard.epgSource.description}}";
|
||||||
|
break;
|
||||||
|
case "m3u":
|
||||||
|
var input = content.createInput("text", key, "");
|
||||||
|
input.setAttribute("class", "wizard");
|
||||||
|
input.id = key;
|
||||||
|
doc.appendChild(input);
|
||||||
|
description = "{{.wizard.m3u.description}}";
|
||||||
|
break;
|
||||||
|
case "xmltv":
|
||||||
|
var input = content.createInput("text", key, "");
|
||||||
|
input.setAttribute("class", "wizard");
|
||||||
|
input.id = key;
|
||||||
|
doc.appendChild(input);
|
||||||
|
description = "{{.wizard.xmltv.description}}";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log(key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
var pre = document.createElement("PRE");
|
||||||
|
pre.innerHTML = description;
|
||||||
|
doc.appendChild(pre);
|
||||||
|
console.log(headline, key);
|
||||||
|
};
|
||||||
|
return WizardItem;
|
||||||
|
}(WizardCategory));
|
||||||
|
function readyForConfiguration(wizard) {
|
||||||
|
var server = new Server("getServerConfig");
|
||||||
|
server.request(new Object());
|
||||||
|
showElement("loading", false);
|
||||||
|
configurationWizard[wizard].createWizard();
|
||||||
|
}
|
||||||
|
function saveWizard() {
|
||||||
|
var cmd = "saveWizard";
|
||||||
|
var div = document.getElementById("content");
|
||||||
|
var config = div.getElementsByClassName("wizard");
|
||||||
|
var wizard = new Object();
|
||||||
|
for (var i = 0; i < config.length; i++) {
|
||||||
|
var name;
|
||||||
|
var value;
|
||||||
|
switch (config[i].tagName) {
|
||||||
|
case "SELECT":
|
||||||
|
name = config[i].name;
|
||||||
|
value = config[i].value;
|
||||||
|
// Wenn der Wert eine Zahl ist, wird dieser als Zahl gespeichert
|
||||||
|
if (isNaN(value)) {
|
||||||
|
wizard[name] = value;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
wizard[name] = parseInt(value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "INPUT":
|
||||||
|
switch (config[i].type) {
|
||||||
|
case "text":
|
||||||
|
name = config[i].name;
|
||||||
|
value = config[i].value;
|
||||||
|
wizard[name] = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// code...
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var data = new Object();
|
||||||
|
data["wizard"] = wizard;
|
||||||
|
var server = new Server(cmd);
|
||||||
|
server.request(data);
|
||||||
|
console.log(data);
|
||||||
|
}
|
||||||
|
// Wizard
|
||||||
|
var configurationWizard = new Array();
|
||||||
|
configurationWizard.push(new WizardItem("tuner", "{{.wizard.tuner.title}}"));
|
||||||
|
configurationWizard.push(new WizardItem("epgSource", "{{.wizard.epgSource.title}}"));
|
||||||
|
configurationWizard.push(new WizardItem("m3u", "{{.wizard.m3u.title}}"));
|
||||||
|
configurationWizard.push(new WizardItem("xmltv", "{{.wizard.xmltv.title}}"));
|
||||||
329
html/js/data.js
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
function showConfig(obj) {
|
||||||
|
config = obj;
|
||||||
|
//setMenuItem();
|
||||||
|
createMenu();
|
||||||
|
//document.getElementById("page").className = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
showMyStreams
|
||||||
|
|
||||||
|
function showMyStreams(allStreamsObj) {
|
||||||
|
|
||||||
|
var streamTypeKeys = getObjKeys(allStreamsObj)
|
||||||
|
|
||||||
|
for (var s = 0; s < streamTypeKeys.length; s++) {
|
||||||
|
var streamType = streamTypeKeys[s];
|
||||||
|
var obj = new Object();
|
||||||
|
obj = allStreamsObj[streamType];
|
||||||
|
switch(streamType) {
|
||||||
|
case "activeStreams": activeStreams = obj; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById(streamType).innerHTML = "";
|
||||||
|
|
||||||
|
var streamsObj = new Object();
|
||||||
|
var streamsNames = new Array();
|
||||||
|
|
||||||
|
var keys = getObjKeys(obj)
|
||||||
|
|
||||||
|
// Create Object (streamsObj) for the streams and sort by name (streamsNames)
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
var name = obj[keys[i]]["name"];
|
||||||
|
var tmp = new Object();
|
||||||
|
var streamKey = getObjKeys(obj[keys[i]]);
|
||||||
|
|
||||||
|
for (var j = 0; j < streamKey.length; j++) {
|
||||||
|
tmp[streamKey[j]] = obj[keys[i]][streamKey[j]];
|
||||||
|
}
|
||||||
|
|
||||||
|
streamsObj[name] = tmp;
|
||||||
|
streamsNames.push(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
streamsNames.sort();
|
||||||
|
|
||||||
|
// Create Table for activeStreams
|
||||||
|
var table = document.getElementById(streamType);
|
||||||
|
|
||||||
|
for (var i = 0; i < streamsNames.length; i++) {
|
||||||
|
var newEntry = new Object();
|
||||||
|
newEntry["_element"] = "TR";
|
||||||
|
table.appendChild(createElement(newEntry));
|
||||||
|
var line = table.lastChild;
|
||||||
|
|
||||||
|
var tmp = streamsObj[streamsNames[i]]
|
||||||
|
var keys = getObjKeys(tmp)
|
||||||
|
|
||||||
|
var newKey = new Object()
|
||||||
|
newKey["_element"] = "TD";
|
||||||
|
//newKey["_text"] = streamsNames[i];
|
||||||
|
switch(streamType) {
|
||||||
|
case "activeStreams": newKey["_text"] = "Channel (+):"; break;
|
||||||
|
case "inactiveStreams": newKey["_text"] = "Channel (-):"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
newKey["class"] = "tdKey";
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
|
||||||
|
var newVal = new Object()
|
||||||
|
newVal["_element"] = "TD";
|
||||||
|
newVal["_text"] = streamsNames[i];
|
||||||
|
newVal["class"] = "tdVal";
|
||||||
|
//newVal["_text"] = value;
|
||||||
|
|
||||||
|
line.appendChild(createElement(newKey));
|
||||||
|
line.appendChild(createElement(newVal));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function showActiveStreams(obj) {
|
||||||
|
document.getElementById("activeStreams").innerHTML = "";
|
||||||
|
activeStreams = obj;
|
||||||
|
var streamsObj = new Object();
|
||||||
|
var streamsNames = new Array();
|
||||||
|
|
||||||
|
var keys = getObjKeys(obj)
|
||||||
|
|
||||||
|
// Create Object (streamsObj) for the streams and sort by name (streamsNames)
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
var name = obj[keys[i]]["name"];
|
||||||
|
var tmp = new Object();
|
||||||
|
var streamKey = getObjKeys(obj[keys[i]]);
|
||||||
|
|
||||||
|
for (var j = 0; j < streamKey.length; j++) {
|
||||||
|
tmp[streamKey[j]] = obj[keys[i]][streamKey[j]];
|
||||||
|
}
|
||||||
|
|
||||||
|
streamsObj[name] = tmp;
|
||||||
|
streamsNames.push(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
streamsNames.sort();
|
||||||
|
|
||||||
|
// Create Table for activeStreams
|
||||||
|
var table = document.getElementById("activeStreams");
|
||||||
|
|
||||||
|
for (var i = 0; i < streamsNames.length; i++) {
|
||||||
|
var newEntry = new Object();
|
||||||
|
newEntry["_element"] = "TR";
|
||||||
|
table.appendChild(createElement(newEntry));
|
||||||
|
var line = table.lastChild;
|
||||||
|
|
||||||
|
var tmp = streamsObj[streamsNames[i]]
|
||||||
|
var keys = getObjKeys(tmp)
|
||||||
|
|
||||||
|
var newKey = new Object()
|
||||||
|
newKey["_element"] = "TD";
|
||||||
|
//newKey["_text"] = streamsNames[i];
|
||||||
|
newKey["_text"] = "Channel:";
|
||||||
|
newKey["class"] = "tdKey";
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
|
||||||
|
var newVal = new Object()
|
||||||
|
newVal["_element"] = "TD";
|
||||||
|
newVal["_text"] = streamsNames[i];
|
||||||
|
newVal["class"] = "tdVal";
|
||||||
|
//newVal["_text"] = value;
|
||||||
|
|
||||||
|
line.appendChild(createElement(newKey));
|
||||||
|
line.appendChild(createElement(newVal));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseLogs(obj) {
|
||||||
|
log = obj
|
||||||
|
var keys = getObjKeys(obj)
|
||||||
|
|
||||||
|
var msgType;
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
switch(keys[i]) {
|
||||||
|
case "warnings": msgType = "warningMsg"; break;
|
||||||
|
case "errors": msgType = "errorMsg"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(obj[keys[i]]) {
|
||||||
|
case 0: msgType = "tdVal"; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(document.getElementById(keys[i])){
|
||||||
|
document.getElementById(keys[i]).className = msgType;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function cancelData(element) {
|
||||||
|
createMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveData(element) {
|
||||||
|
var data = new Object();
|
||||||
|
var div = element.parentNode.parentNode;
|
||||||
|
var inputs = div.getElementsByTagName("INPUT");
|
||||||
|
|
||||||
|
var configKey = div.getAttribute("data-configkey");
|
||||||
|
var menuType = div.getAttribute("data-menutype");
|
||||||
|
var value;
|
||||||
|
var valueArr = new Array();
|
||||||
|
|
||||||
|
for (var i = 0; i < inputs.length; i++) {
|
||||||
|
if (inputs[i].type == "text" && inputs[i].value != undefined && inputs[i].value != "" ) {
|
||||||
|
console.log(inputs[i].value, menuType)
|
||||||
|
switch(menuType) {
|
||||||
|
case "inputArray": valueArr.push(inputs[i].value); break;
|
||||||
|
case "singleInput": value = inputs[i].value; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(menuType) {
|
||||||
|
case "inputArray": data[configKey] = valueArr; break;
|
||||||
|
case "singleInput":
|
||||||
|
if (isNaN(value) == false) {
|
||||||
|
value = parseInt(value);
|
||||||
|
data[configKey] = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == undefined) {
|
||||||
|
data["delete"] = configKey;
|
||||||
|
} else {
|
||||||
|
data[configKey] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
data["cmd"] = "saveConfig";
|
||||||
|
console.log(data);
|
||||||
|
xTeVe(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
function xTeVe(data) {
|
||||||
|
if (webSockets == false) {
|
||||||
|
alert("Your browser does not support WebSockets");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
if (data["cmd"] != "getLog") {
|
||||||
|
showLoadingScreen(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete undo["epgMapping"];
|
||||||
|
|
||||||
|
var protocolWS
|
||||||
|
switch(window.location.protocol) {
|
||||||
|
case "http:": protocolWS = "ws://"; break;
|
||||||
|
case "https:": protocolWS = "wss://"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var ws = new WebSocket(protocolWS + window.location.hostname + ":" + window.location.port + "/data/" + "?Token=" + getCookie("Token"));
|
||||||
|
ws.onopen = function() {
|
||||||
|
console.log(data)
|
||||||
|
ws.send(JSON.stringify(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onmessage = function (e) {
|
||||||
|
var response = JSON.parse(e.data);
|
||||||
|
console.log(response);
|
||||||
|
|
||||||
|
if (response.hasOwnProperty("clientInfo")) {
|
||||||
|
createClintInfo(response["clientInfo"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.hasOwnProperty("log")) {
|
||||||
|
createClintInfo(response["log"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.hasOwnProperty("status")) {
|
||||||
|
if (response["status"] == false) {
|
||||||
|
alert(response["err"])
|
||||||
|
if(response.hasOwnProperty("reload")) {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
//checkErr(response)
|
||||||
|
console.log(response);
|
||||||
|
updateXteveStatus(response);
|
||||||
|
setTimeout(function(){ showLoadingScreen(false); }, 300);
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updateXteveStatus(response)
|
||||||
|
|
||||||
|
//console.log(data["cmd"]);
|
||||||
|
switch(data["cmd"]) {
|
||||||
|
case "saveUserData": createMenu(); break;
|
||||||
|
case "saveNewUser": createMenu(); break;
|
||||||
|
case "saveFilesXMLTV": //createMenu(); break;
|
||||||
|
case "saveFilesM3U": //createMenu(); return; break;
|
||||||
|
case "saveConfig":
|
||||||
|
data = new Object();
|
||||||
|
data["cmd"] = "checkToken";
|
||||||
|
xTeVe(data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "emptyLog": writeLogInDiv(); break;
|
||||||
|
case "getLog": return; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config["files"] == undefined || config["files"].length == 0) {
|
||||||
|
createMenu();
|
||||||
|
document.getElementById(10).click()
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(function(){ showLoadingScreen(false); }, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateXteveStatus(response) {
|
||||||
|
var keys = getObjKeys(response);
|
||||||
|
//console.log(keys);
|
||||||
|
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
switch(keys[i]) {
|
||||||
|
case "alert": alert(response[keys[i]]); break;
|
||||||
|
case "config": showConfig(response[keys[i]]); break;
|
||||||
|
case "log": parseLogs(response[keys[i]]); break;
|
||||||
|
case "myStreams": showMyStreams(response[keys[i]]); break;
|
||||||
|
case "xEPG": xEPG = response[keys[i]]; break;
|
||||||
|
case "users": users = response[keys[i]]; break;
|
||||||
|
case "token": document.cookie = "Token=" + response[keys[i]]; break;
|
||||||
|
case "reload": location.reload(); break;
|
||||||
|
case "openLink": window.location = response["openLink"]; break;
|
||||||
|
//case "version": version = response[keys[i]]; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValueFromProviderFile(xXmltvFile, fileType, key) {
|
||||||
|
|
||||||
|
var fileID = xXmltvFile.substring(0, xXmltvFile.lastIndexOf('.'))
|
||||||
|
|
||||||
|
if (config["files"][fileType].hasOwnProperty(fileID) == true) {
|
||||||
|
var data = config["files"][fileType][fileID];
|
||||||
|
return data[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
379
html/js/files.js
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
function openFiles(elm, fileType) {
|
||||||
|
//document.getElementById("settings").innerHTML = "Test";
|
||||||
|
|
||||||
|
columnToSort = 0;
|
||||||
|
var newDiv = document.getElementById("settings");
|
||||||
|
|
||||||
|
var newEntry = new Object();
|
||||||
|
newEntry["_element"] = "HR";
|
||||||
|
newDiv.appendChild(createElement(newEntry));
|
||||||
|
|
||||||
|
var newEntry = new Object();
|
||||||
|
newEntry["_element"] = "INPUT";
|
||||||
|
newEntry["type"] = "button";
|
||||||
|
newEntry["class"] = "button";
|
||||||
|
newEntry["value"] = "New";
|
||||||
|
newEntry["onclick"] = 'fileDetail("-", "' + fileType + '")';
|
||||||
|
newDiv.appendChild(createElement(newEntry));
|
||||||
|
|
||||||
|
var newEntry = new Object();
|
||||||
|
newEntry["_element"] = "INPUT";
|
||||||
|
newEntry["type"] = "button";
|
||||||
|
newEntry["class"] = "button";
|
||||||
|
newEntry["value"] = "Update";
|
||||||
|
newEntry["onclick"] = "fileDetail(0)";
|
||||||
|
//newDiv.appendChild(createElement(newEntry));
|
||||||
|
|
||||||
|
var div = document.getElementById("settings");
|
||||||
|
|
||||||
|
// Build table
|
||||||
|
var newTable = new Object();
|
||||||
|
newTable["_element"] = "TABLE";
|
||||||
|
newTable["id"] = "id_mapping";
|
||||||
|
newTable["class"] = "table-mapping";
|
||||||
|
div.appendChild(createElement(newTable));
|
||||||
|
|
||||||
|
setTimeout(function(){
|
||||||
|
createFilesTable(fileType);
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFilesTable(fileType) {
|
||||||
|
var table = document.getElementById("id_mapping");
|
||||||
|
var availableFileTypes = new Array();
|
||||||
|
|
||||||
|
table.innerHTML = "";
|
||||||
|
var newTR = new Object();
|
||||||
|
newTR["_element"] = "TR";
|
||||||
|
newTR["class"] = "table-mapping-header";
|
||||||
|
table.appendChild(createElement(newTR));
|
||||||
|
|
||||||
|
var tr = table.lastChild;
|
||||||
|
|
||||||
|
switch(fileType) {
|
||||||
|
case "xmltv":
|
||||||
|
availableFileTypes = new Array("xmltv");
|
||||||
|
var trHeadlines = new Array("Guide", "Last Update", "Availability %", "Channels", "Programs")
|
||||||
|
var compatibilityKeys = new Array("xmltv.channels", "xmltv.programs")
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "m3u":
|
||||||
|
availableFileTypes = new Array("m3u", "hdhr");
|
||||||
|
var trHeadlines = new Array("Playlist", "Last Update", "Availability %", "Type", "Streams", "group-title %", "tvg-id %", "Unique ID %");
|
||||||
|
var compatibilityKeys = new Array("streams", "group.title", "tvg.id", "stream.id");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < trHeadlines.length; i++) {
|
||||||
|
var newTD = new Object();
|
||||||
|
newTD["_element"] = "TD";
|
||||||
|
newTD["_text"] = trHeadlines[i];
|
||||||
|
tr.appendChild(createElement(newTD));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < availableFileTypes.length; i++) {
|
||||||
|
|
||||||
|
var fileType = availableFileTypes[i]
|
||||||
|
|
||||||
|
var data = config["files"][fileType];
|
||||||
|
|
||||||
|
var allFiles = getObjKeys(data)
|
||||||
|
|
||||||
|
for (var f = 0; f < allFiles.length; f++) {
|
||||||
|
var elm = data[allFiles[f]];
|
||||||
|
var table = document.getElementById("id_mapping");
|
||||||
|
var fileID = elm["id.provider"];
|
||||||
|
var name = elm["name"];
|
||||||
|
var lastUpdate = elm["last.update"];
|
||||||
|
var availability = elm["provider.availability"];
|
||||||
|
var type = elm["type"].toUpperCase();
|
||||||
|
var compatibility = elm["compatibility"];
|
||||||
|
|
||||||
|
// Create TR
|
||||||
|
var newTR = new Object();
|
||||||
|
newTR["_element"] = "TR";
|
||||||
|
newTR["class"] = "";
|
||||||
|
newTR["id"] = fileID;
|
||||||
|
newTR["onclick"] = 'javascript: fileDetail("' + fileID + '","' + fileType + '");';
|
||||||
|
table.appendChild(createElement(newTR));
|
||||||
|
|
||||||
|
var tr = table.lastChild;
|
||||||
|
|
||||||
|
// Create file name TD
|
||||||
|
var newTD = new Object();
|
||||||
|
newTD["_element"] = "P";
|
||||||
|
newTD["_text"] = name;
|
||||||
|
createNewTD(newTD, tr);
|
||||||
|
|
||||||
|
// Create last update TD
|
||||||
|
var newTD = new Object();
|
||||||
|
newTD["_element"] = "P";
|
||||||
|
newTD["_text"] = lastUpdate;
|
||||||
|
createNewTD(newTD, tr);
|
||||||
|
|
||||||
|
// Create availability TD
|
||||||
|
var newTD = new Object();
|
||||||
|
newTD["_element"] = "P";
|
||||||
|
newTD["_text"] = availability;
|
||||||
|
createNewTD(newTD, tr);
|
||||||
|
|
||||||
|
if (fileType == "m3u" || fileType == "hdhr") {
|
||||||
|
|
||||||
|
// Create Type TD
|
||||||
|
var newTD = new Object();
|
||||||
|
newTD["_element"] = "P";
|
||||||
|
newTD["_text"] = type;
|
||||||
|
createNewTD(newTD, tr);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create all compatibility TDs
|
||||||
|
|
||||||
|
for (var j = 0; j < compatibilityKeys.length; j++) {
|
||||||
|
var newTD = new Object();
|
||||||
|
newTD["_element"] = "P";
|
||||||
|
newTD["_text"] = compatibility[compatibilityKeys[j]];
|
||||||
|
createNewTD(newTD, tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sortTable(0)
|
||||||
|
|
||||||
|
// usage Info
|
||||||
|
var div = document.getElementById("settings");
|
||||||
|
switch(menu[activeMenu.id].hasOwnProperty("_usage")) {
|
||||||
|
case true:
|
||||||
|
var usageItem = new Object();
|
||||||
|
usageItem["_element"] = "PRE"
|
||||||
|
usageItem["_text"] = menu[activeMenu.id]["_usage"];
|
||||||
|
|
||||||
|
var newHR = new Object();
|
||||||
|
newHR["_element"] = "HR"
|
||||||
|
div.appendChild(createElement(newHR));
|
||||||
|
div.appendChild(createElement(usageItem));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateWrapperHeight();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function fileDetail(fileID, fileType) {
|
||||||
|
|
||||||
|
optionsText = new Array("M3U", "HDHomeRun - [Experimental]")
|
||||||
|
optionsValue = new Array("m3u", "hdhr")
|
||||||
|
|
||||||
|
switch (fileType) {
|
||||||
|
|
||||||
|
case "m3u":
|
||||||
|
document.getElementById("name").setAttribute("placeholder", "Playlist name");
|
||||||
|
document.getElementById("description").setAttribute("placeholder", "Description of this playlist");
|
||||||
|
document.getElementById("file-detail-headline").innerHTML = "M3U Playlist";
|
||||||
|
document.getElementById("file-path").innerHTML = "M3U File:";
|
||||||
|
document.getElementById("file.source").setAttribute("placeholder", "Local or remote");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "hdhr":
|
||||||
|
document.getElementById("name").setAttribute("placeholder", "HDHomeRun name");
|
||||||
|
document.getElementById("description").setAttribute("placeholder", "Description of this HDHomeRun tuner");
|
||||||
|
document.getElementById("file-detail-headline").innerHTML = "HDHomeRun";
|
||||||
|
document.getElementById("file-path").innerHTML = "HDHomeRun IP:";
|
||||||
|
document.getElementById("file.source").setAttribute("placeholder", "IP address and port of the tuner (192.168.1.10:5004)");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "xmltv":
|
||||||
|
document.getElementById("name").setAttribute("placeholder", "XMLTV name");
|
||||||
|
document.getElementById("description").setAttribute("placeholder", "Description of this XMLTV file");
|
||||||
|
document.getElementById("file-detail-headline").innerHTML = "XMLTV File";
|
||||||
|
document.getElementById("file-path").innerHTML = "XMLTV File:";
|
||||||
|
document.getElementById("file.source").setAttribute("placeholder", "Local or remote");
|
||||||
|
|
||||||
|
optionsText = new Array("XMLTV")
|
||||||
|
optionsValue = new Array("xmltv")
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifyOption("type", optionsText, optionsValue)
|
||||||
|
|
||||||
|
showPopUpElement('file-detail');
|
||||||
|
|
||||||
|
document.getElementById("saveFileDetail").setAttribute("onclick", 'javascript: saveFileDetail("' + fileID + '","' + fileType + '", false)');
|
||||||
|
document.getElementById("updateFileDetail").setAttribute("onclick", 'javascript: updateFile("' + fileID + '","' + fileType + '", false)');
|
||||||
|
document.getElementById("deleteFileDetail").setAttribute("onclick", 'javascript: saveFileDetail("' + fileID + '","' + fileType + '", true)');
|
||||||
|
|
||||||
|
var data = new Object();
|
||||||
|
|
||||||
|
switch(fileID) {
|
||||||
|
|
||||||
|
case "-": // New file
|
||||||
|
data["name"] = "";
|
||||||
|
data["description"] = "";
|
||||||
|
data["file.source"] = "";
|
||||||
|
data["type"] = fileType;
|
||||||
|
|
||||||
|
document.getElementById("deleteFileDetail").className = "delete";
|
||||||
|
document.getElementById("type").setAttribute("onchange", "changeFileType(this);")
|
||||||
|
document.getElementById("type").setAttribute("data-id", fileID)
|
||||||
|
|
||||||
|
showElement("deleteFileDetail", false);
|
||||||
|
showElement("updateFileDetail", false);
|
||||||
|
|
||||||
|
if (fileType == "xmltv") {
|
||||||
|
showElement("type", false);
|
||||||
|
showElement("file-type", false);
|
||||||
|
} else {
|
||||||
|
showElement("type", true);
|
||||||
|
showElement("file-type", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
data = config["files"][fileType][fileID];
|
||||||
|
document.getElementById("deleteFileDetail").className = "delete";
|
||||||
|
|
||||||
|
showElement("updateFileDetail", true);
|
||||||
|
showElement("type", false);
|
||||||
|
showElement("file-type", false);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = getObjKeys(data);
|
||||||
|
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
|
||||||
|
if(document.getElementById(keys[i])){
|
||||||
|
document.getElementById(keys[i]).value = data[keys[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeFileType(elm) {
|
||||||
|
|
||||||
|
var fileID = elm.getAttribute("data-id");
|
||||||
|
var fileType = elm.options[elm.selectedIndex].value;
|
||||||
|
|
||||||
|
fileDetail(fileID, fileType)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function saveFileDetail(fileID, fileType, deleteFile) {
|
||||||
|
|
||||||
|
if (fileID == undefined) {
|
||||||
|
alert("ID is missing!!!");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var inputs = document.getElementById("file-detail").getElementsByTagName("INPUT");
|
||||||
|
var selects = document.getElementById("file-detail").getElementsByTagName("SELECT");
|
||||||
|
var newFileData = new Object();
|
||||||
|
var data = new Object();
|
||||||
|
|
||||||
|
for (var i = 0; i < inputs.length; i++) {
|
||||||
|
switch(inputs[i].type) {
|
||||||
|
case "text": newFileData[inputs[i].name] = inputs[i].value; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < selects.length; i++) {
|
||||||
|
newFileData[selects[i].id] = selects[i].options[selects[i].selectedIndex].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleteFile == true) {
|
||||||
|
switch(fileType) {
|
||||||
|
case "m3u": var alertText = "Delete this playlist?"; break;
|
||||||
|
case "hdhr": var alertText = "Delete this HDHomeRun tuner?"; break;
|
||||||
|
case "xmltv": var alertText = "Delete this XMLTV file?"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (confirm(alertText)) {
|
||||||
|
newFileData["delete"] = true
|
||||||
|
data = buildFilesObj(fileType, fileID, newFileData);
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
showElement("popup", false);
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
switch(config["files"][fileType].hasOwnProperty(fileID)) {
|
||||||
|
|
||||||
|
case true:
|
||||||
|
data = config["files"][fileType][fileID];
|
||||||
|
if (data["file.source"] != newFileData["file.source"]) {
|
||||||
|
data["update"] = true
|
||||||
|
} else {
|
||||||
|
data["updatePlaylistName"] = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case false:
|
||||||
|
newFileData["new"] = true;
|
||||||
|
data = buildFilesObj(fileType, fileID, newFileData);
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(fileType) {
|
||||||
|
|
||||||
|
case "m3u": data["cmd"] = "saveFilesM3U"; break;
|
||||||
|
case "hdhr": data["cmd"] = "saveFilesHDHR"; break;
|
||||||
|
case "xmltv": data["cmd"] = "saveFilesXMLTV"; break;
|
||||||
|
|
||||||
|
}
|
||||||
|
//console.log(data);
|
||||||
|
xTeVe(data);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFile(fileID, fileType, allFiles) {
|
||||||
|
|
||||||
|
switch(config["files"][fileType].hasOwnProperty(fileID)) {
|
||||||
|
|
||||||
|
case true:
|
||||||
|
|
||||||
|
var data = new Object();
|
||||||
|
var data = buildFilesObj(fileType, fileID, config["files"][fileType][fileID])
|
||||||
|
data["new"] = true
|
||||||
|
|
||||||
|
switch(fileType) {
|
||||||
|
|
||||||
|
case "m3u": data["cmd"] = "updateFileM3U"; break;
|
||||||
|
case "hdhr": data["cmd"] = "updateFileHDHR"; break;
|
||||||
|
case "xmltv": data["cmd"] = "updateFileXMLTV"; break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
xTeVe(data);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildFilesObj(fileType, fileID, obj) {
|
||||||
|
|
||||||
|
var data = new Object();
|
||||||
|
data["files"] = new Object();
|
||||||
|
data["files"][fileType] = new Object();
|
||||||
|
data["files"][fileType][fileID] = obj
|
||||||
|
return data
|
||||||
|
|
||||||
|
}
|
||||||
114
html/js/log.js
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
var logInterval
|
||||||
|
|
||||||
|
|
||||||
|
function updateLog() {
|
||||||
|
var data = new Object();
|
||||||
|
data["cmd"] = "getLog";
|
||||||
|
xTeVe(data);
|
||||||
|
writeLogInDiv();
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeLogInDiv() {
|
||||||
|
var logs = log["log"];
|
||||||
|
var div = document.getElementById("settings").lastChild.lastChild;
|
||||||
|
div.innerHTML = "";
|
||||||
|
|
||||||
|
var max = 50;
|
||||||
|
|
||||||
|
|
||||||
|
for (var i = 0; i < logs.length; i++) {
|
||||||
|
var newEntry = new Object();
|
||||||
|
newEntry["_element"] = "P";
|
||||||
|
|
||||||
|
if (logs[i].includes("ERROR")) {
|
||||||
|
// case "warnings": msgType = "warningMsg"; break;
|
||||||
|
newEntry["class"] = "errorMsg";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logs[i].includes("WARNING")) {
|
||||||
|
// case "warnings": msgType = "warningMsg"; break;
|
||||||
|
newEntry["class"] = "warningMsg";
|
||||||
|
}
|
||||||
|
|
||||||
|
newEntry["_text"] = logs[i];
|
||||||
|
|
||||||
|
div.appendChild(createElement(newEntry));
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateWrapperHeight();
|
||||||
|
var scrollDiv = document.getElementById("box-wrapper");
|
||||||
|
scrollDiv.scrollTop = scrollDiv.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showLog(obj) {
|
||||||
|
//logInterval = setInterval(updateLog, 5000);
|
||||||
|
|
||||||
|
var logs = log["log"];
|
||||||
|
|
||||||
|
var div = document.getElementById("settings");
|
||||||
|
|
||||||
|
var newEntry = new Object();
|
||||||
|
newEntry["_element"] = "HR";
|
||||||
|
div.appendChild(createElement(newEntry));
|
||||||
|
//div = div.lastChild;
|
||||||
|
|
||||||
|
var newEntry = new Object();
|
||||||
|
newEntry["_element"] = "INPUT";
|
||||||
|
newEntry["type"] = "button";
|
||||||
|
newEntry["class"] = "button";
|
||||||
|
newEntry["value"] = "Empty Log";
|
||||||
|
newEntry["onclick"] = "emptyLog()";
|
||||||
|
div.appendChild(createElement(newEntry));
|
||||||
|
|
||||||
|
var newEntry = new Object();
|
||||||
|
newEntry["_element"] = "code";
|
||||||
|
newEntry["_text"] = "Update Log: ";
|
||||||
|
div.appendChild(createElement(newEntry));
|
||||||
|
|
||||||
|
var newEntry = new Object();
|
||||||
|
newEntry["_element"] = "INPUT";
|
||||||
|
newEntry["type"] = "checkbox";
|
||||||
|
//newEntry["checked"] = "checkbox";
|
||||||
|
newEntry["onclick"] = "logUpdates(this)";
|
||||||
|
div.appendChild(createElement(newEntry));
|
||||||
|
|
||||||
|
var newEntry = new Object();
|
||||||
|
newEntry["_element"] = "HR";
|
||||||
|
div.appendChild(createElement(newEntry));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var newWrapper = new Object();
|
||||||
|
newWrapper["_element"] = "DIV";
|
||||||
|
newWrapper["id"] = "box-wrapper";
|
||||||
|
div.appendChild(createElement(newWrapper));
|
||||||
|
div = div.lastChild;
|
||||||
|
|
||||||
|
var newPre = new Object();
|
||||||
|
newPre["_element"] = "PRE";
|
||||||
|
newPre["id"] = "logScreen";
|
||||||
|
div.appendChild(createElement(newPre));
|
||||||
|
|
||||||
|
div = div.lastChild;
|
||||||
|
|
||||||
|
writeLogInDiv()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function emptyLog() {
|
||||||
|
var data = new Object();
|
||||||
|
data["cmd"] = "emptyLog";
|
||||||
|
xTeVe(data);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function logUpdates(elm) {
|
||||||
|
switch(elm.checked) {
|
||||||
|
case false: clearInterval(logInterval); break;
|
||||||
|
case true: logInterval = setInterval(updateLog, 5000); break;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
42
html/js/logs_ts.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
var Log = /** @class */ (function () {
|
||||||
|
function Log() {
|
||||||
|
}
|
||||||
|
Log.prototype.createLog = function (entry) {
|
||||||
|
var element = document.createElement("PRE");
|
||||||
|
if (entry.indexOf("WARNING") != -1) {
|
||||||
|
element.className = "warningMsg";
|
||||||
|
}
|
||||||
|
if (entry.indexOf("ERROR") != -1) {
|
||||||
|
element.className = "errorMsg";
|
||||||
|
}
|
||||||
|
if (entry.indexOf("DEBUG") != -1) {
|
||||||
|
element.className = "debugMsg";
|
||||||
|
}
|
||||||
|
element.innerHTML = entry;
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
return Log;
|
||||||
|
}());
|
||||||
|
function showLogs(bottom) {
|
||||||
|
var log = new Log();
|
||||||
|
var logs = SERVER["log"]["log"];
|
||||||
|
var div = document.getElementById("content_log");
|
||||||
|
div.innerHTML = "";
|
||||||
|
var keys = getObjKeys(logs);
|
||||||
|
keys.forEach(function (logID) {
|
||||||
|
var entry = log.createLog(logs[logID]);
|
||||||
|
div.append(entry);
|
||||||
|
});
|
||||||
|
setTimeout(function () {
|
||||||
|
if (bottom == true) {
|
||||||
|
var wrapper = document.getElementById("box-wrapper");
|
||||||
|
wrapper.scrollTop = wrapper.scrollHeight;
|
||||||
|
}
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
function resetLogs() {
|
||||||
|
var cmd = "resetLogs";
|
||||||
|
var data = new Object();
|
||||||
|
var server = new Server(cmd);
|
||||||
|
server.request(data);
|
||||||
|
}
|
||||||
1466
html/js/mapping-editor.js
Normal file
754
html/js/menu.js
Normal file
@@ -0,0 +1,754 @@
|
|||||||
|
|
||||||
|
function setMenuItem() {
|
||||||
|
|
||||||
|
menu = new Object();
|
||||||
|
subMenu = new Object();
|
||||||
|
|
||||||
|
var menu_m3u = new Object();
|
||||||
|
menu_m3u["_menuType"] = "inputArray";
|
||||||
|
menu_m3u["_element"] = "LI";
|
||||||
|
menu_m3u["_configKey"] = "files.m3u";
|
||||||
|
menu_m3u["_text"] = "Playlist";
|
||||||
|
menu_m3u["_icon"] = "img/m3u.png";
|
||||||
|
menu_m3u["_headline"] = "Playlists: Local or remote";
|
||||||
|
menu_m3u["_usage"] = "<b>Info</b><br>Availability: File availability in percent<br>Streams: Number of streams in the file.<br>group-title: Streams that are assigned to a group. Simplifies filtering streams<br>tvg-id: This ID is used for automatic mapping, must match with the channel ID in the XMLTV file.<br>Unique ID: Streams with a unique ID to identify them. Allows channel name changes in the M3U without losing the XMLTV mapping (PPV / live events).<br><br><b>Usage M3U:</b><br>Remote playlist: http://your.iptv.provider.com/file.m3u<br>Local playlist: /path/to/file.m3u<br><br><b>Usage HDHomeRun:</b><br>IP: 192.168.1.10:5004<br>"
|
||||||
|
menu_m3u["name"] = "file";
|
||||||
|
menu_m3u["id"] = "file";
|
||||||
|
menu_m3u["value"] = menu_m3u["name"];
|
||||||
|
menu_m3u["placeholder"] = "Playlist: local or remote";
|
||||||
|
menu_m3u["onclick"] = "javascript: toggleMenu(this);";
|
||||||
|
menu_m3u["class"] = "menu-notActive";
|
||||||
|
|
||||||
|
|
||||||
|
var menu_filter = new Object();
|
||||||
|
menu_filter["_menuType"] = "inputArray";
|
||||||
|
menu_filter["_element"] = "LI";
|
||||||
|
menu_filter["_configKey"] = "filter";
|
||||||
|
menu_filter["_text"] = "Filter";
|
||||||
|
menu_filter["_icon"] = "img/filter.png";
|
||||||
|
menu_filter["_headline"] = "Filter by M3U parameters, e.g. group-title";
|
||||||
|
menu_filter["_usage"] = "<b>Usage:</b><br>Sport - All sports channels<br>Sport {HD} - All HD sports channels<br>Sport {HD} !{ES,DE} - All HD sports channels, but no Spanish and German<br><br>To filter the streams of a HDHomeRun, the playlist name can be entered:<br>My tuner {HD}"
|
||||||
|
//menu_filter["_usage"] = "<b>Usage:</b><br>All sports channels: Sport<br>All HD sports channels: Sport {HD}<br>All HD sports channels, but no Spanish and German: Sport {HD} !{ES,DE}"
|
||||||
|
menu_filter["name"] = "filter";
|
||||||
|
menu_filter["id"] = "M3U";
|
||||||
|
menu_filter["value"] = menu_filter["name"];
|
||||||
|
menu_filter["placeholder"] = "Filter streams: Sport";
|
||||||
|
menu_filter["onclick"] = "javascript: toggleMenu(this);";
|
||||||
|
menu_filter["class"] = "menu-notActive";
|
||||||
|
|
||||||
|
var menu_id = new Object();
|
||||||
|
menu_id["_menuType"] = "inputArray";
|
||||||
|
menu_id["_element"] = "LI";
|
||||||
|
menu_id["_configKey"] = "id";
|
||||||
|
menu_id["_text"] = "PMS ID";
|
||||||
|
menu_id["_icon"] = "img/number.png";
|
||||||
|
menu_id["_headline"] = "Setup PMS guide number";
|
||||||
|
menu_id["_usage"] = 'Some playlists have unique channel IDs.<br>Enter the keyword of the ID. The channel assignment in PMS will change as a result.<br><br>e.g. channelID<br>#EXTINF:0 type="stream" <b>channelId</b>="81", My Streaming Channel HD<br><br>Only enter here if you know what you are doing!'
|
||||||
|
menu_id["name"] = "id";
|
||||||
|
menu_id["id"] = "id";
|
||||||
|
menu_id["value"] = menu_id["name"];
|
||||||
|
menu_id["placeholder"] = "Unique ID from the M3U file";
|
||||||
|
menu_id["onclick"] = "javascript: toggleMenu(this);";
|
||||||
|
menu_id["class"] = "menu-notActive";
|
||||||
|
|
||||||
|
|
||||||
|
var menu_xmltv = new Object();
|
||||||
|
menu_xmltv["_menuType"] = "inputArray";
|
||||||
|
menu_xmltv["_element"] = "LI";
|
||||||
|
menu_xmltv["_configKey"] = "files.xmltv";
|
||||||
|
menu_xmltv["_text"] = "XMLTV";
|
||||||
|
menu_xmltv["_icon"] = "img/xmltv.png";
|
||||||
|
menu_xmltv["_headline"] = "XMLTV files: Local or remote";
|
||||||
|
menu_xmltv["_usage"] = "<b>Info:</b><br>Availability: File availability in percent<br>Channels: Number of channels in the file<br>Programs: Number of EPG data<br><br><b>Usage:</b><br>Remote XMLTV file: http://your.epg.provider.com/guide.xml<br>Local XMLTV file: /path/to/guide.xml"
|
||||||
|
menu_xmltv["name"] = "xmltv";
|
||||||
|
menu_xmltv["id"] = "xmltv";
|
||||||
|
menu_xmltv["value"] = menu_xmltv["name"];
|
||||||
|
menu_xmltv["placeholder"] = "XMLTV File: local or remote";
|
||||||
|
menu_xmltv["onclick"] = "javascript: toggleMenu(this);";
|
||||||
|
menu_xmltv["class"] = "menu-notActive";
|
||||||
|
|
||||||
|
menu_mapping = new Object();
|
||||||
|
menu_mapping["_element"] = "LI";
|
||||||
|
menu_mapping["_text"] = "Mapping";
|
||||||
|
menu_mapping["_icon"] = "img/mapping.png";
|
||||||
|
menu_mapping["_configKey"] = "mapping";
|
||||||
|
menu_mapping["_headline"] = "XMLTV assignment and sorting of channels";
|
||||||
|
menu_mapping["id"] = "mapping";
|
||||||
|
menu_mapping["onclick"] = "javascript: toggleMenu(this);";
|
||||||
|
menu_mapping["class"] = "menu-notActive phone";
|
||||||
|
|
||||||
|
menu_users = new Object();
|
||||||
|
menu_users["_element"] = "LI";
|
||||||
|
menu_users["_text"] = "Users";
|
||||||
|
menu_users["_icon"] = "img/users.png";
|
||||||
|
menu_users["_configKey"] = "users";
|
||||||
|
menu_users["_headline"] = "Administration of users and permissions";
|
||||||
|
menu_users["id"] = "users";
|
||||||
|
menu_users["onclick"] = "javascript: toggleMenu(this);";
|
||||||
|
menu_users["class"] = "menu-notActive";
|
||||||
|
menu_users["_usage"] = "<b>Authorization groups:</b><br>WEB: Users can log in to the web interface<br>PMS: Programs like Plex can access the channel list. Login via DVR IP: username:password@xteve.ip:port<br>M3U: Allows clients to download the M3U playlist.<br>XML: Allows clients to download the XMLTV file.<br>API: Allows clients to use the API interface.<br><br>!!! For PMS authentication, only the following special characters are valid: !$()=.,-:;<br><br>The individual authentication groups can be activated / deactivated in the settings menu."
|
||||||
|
|
||||||
|
menu_settings = new Object();
|
||||||
|
menu_settings["_element"] = "LI";
|
||||||
|
menu_settings["_text"] = "Settings";
|
||||||
|
menu_settings["_icon"] = "img/settings.png";
|
||||||
|
menu_settings["_configKey"] = "settings";
|
||||||
|
menu_settings["_headline"] = "Settings";
|
||||||
|
menu_settings["_subMenu"] = "701,702,703,704,705,706,707,708,799,710,711,712,713,714";
|
||||||
|
menu_settings["id"] = "settings";
|
||||||
|
menu_settings["onclick"] = "javascript: toggleMenu(this);";
|
||||||
|
menu_settings["class"] = "menu-notActive";
|
||||||
|
|
||||||
|
menu_log = new Object();
|
||||||
|
menu_log["_element"] = "LI";
|
||||||
|
menu_log["_text"] = "Log";
|
||||||
|
menu_log["_icon"] = "img/log.png";
|
||||||
|
menu_log["_headline"] = "Log";
|
||||||
|
menu_log["_configKey"] = "log";
|
||||||
|
menu_log["id"] = "log";
|
||||||
|
menu_log["onclick"] = "javascript: toggleMenu(this);";
|
||||||
|
menu_log["class"] = "menu-notActive";
|
||||||
|
|
||||||
|
menu_logout = new Object();
|
||||||
|
menu_logout["_element"] = "LI";
|
||||||
|
menu_logout["_text"] = "Logout";
|
||||||
|
menu_logout["_icon"] = "img/logout.png";
|
||||||
|
menu_logout["id"] = "logout";
|
||||||
|
menu_logout["onclick"] = "javascript: logout();";
|
||||||
|
menu_logout["class"] = "menu-notActive";
|
||||||
|
|
||||||
|
var menu_schedule = new Object();
|
||||||
|
menu_schedule["_menuType"] = "inputArray";
|
||||||
|
menu_schedule["_element"] = "LI";
|
||||||
|
menu_schedule["_configKey"] = "update";
|
||||||
|
menu_schedule["_text"] = "Schedule";
|
||||||
|
menu_schedule["_icon"] = "img/schedule.png";
|
||||||
|
menu_schedule["_headline"] = "Schedule for updating M3U, XMLTV files and creating a local backup";
|
||||||
|
menu_schedule["_usage"] = "<b>Usage:</b><br>0815 = 8:15 am<br>1930 = 7:30 pm"
|
||||||
|
menu_schedule["name"] = "update";
|
||||||
|
menu_schedule["id"] = "update";
|
||||||
|
menu_schedule["value"] = menu_id["name"];
|
||||||
|
menu_schedule["placeholder"]= "time of day (24-hour clock)";
|
||||||
|
menu_schedule["onclick"] = "javascript: toggleMenu(this);";
|
||||||
|
menu_schedule["class"] = "menu-notActive";
|
||||||
|
|
||||||
|
var menu_filesUpdate = new Object();
|
||||||
|
menu_filesUpdate["_element"] = "LI";
|
||||||
|
menu_filesUpdate["_menuType"] = "checkbox";
|
||||||
|
menu_filesUpdate["_configKey"] = "files.update";
|
||||||
|
menu_filesUpdate["_label"] = "Update the provider files at system startup";
|
||||||
|
menu_filesUpdate["_headline"] = "Update the provider files at system startup";
|
||||||
|
menu_filesUpdate["_usage"] = "Playlists and XMLTV files are updated by xTeVe at system startup."
|
||||||
|
menu_filesUpdate["name"] = "files.update";
|
||||||
|
menu_filesUpdate["id"] = "files.update";
|
||||||
|
menu_filesUpdate["value"] = menu_filesUpdate["name"];
|
||||||
|
menu_filesUpdate["onclick"] = "javascript: toggleMenu(this);";
|
||||||
|
menu_filesUpdate["class"] = "menu-notActive";
|
||||||
|
|
||||||
|
var menu_tuner = new Object();
|
||||||
|
menu_tuner["_element"] = "LI";
|
||||||
|
menu_tuner["_menuType"] = "select";
|
||||||
|
menu_tuner["_configKey"] = "tuner";
|
||||||
|
menu_tuner["_label"] = "Available tuners";
|
||||||
|
menu_tuner["_text"] = "Tuner";
|
||||||
|
menu_tuner["_icon"] = "img/tuner.png";
|
||||||
|
menu_tuner["_headline"] = "Number of tuners";
|
||||||
|
menu_tuner["_usage"] = "This setting is only used by Plex and Emby.<br>The number of concurrent streams allowed by the IPTV provider.<br>After a change, xTeVe must be delete in the PMS DVR settings and set up again."
|
||||||
|
menu_tuner["name"] = "tuner";
|
||||||
|
menu_tuner["id"] = "tuner";
|
||||||
|
menu_tuner["value"] = menu_tuner["name"];
|
||||||
|
menu_tuner["placeholder"] = "Number of tuners";
|
||||||
|
menu_tuner["onclick"] = "javascript: toggleMenu(this);";
|
||||||
|
menu_tuner["class"] = "menu-notActive";
|
||||||
|
|
||||||
|
var optionValues = new Array();
|
||||||
|
for (var i = 1; i <= 100; i++) {
|
||||||
|
optionValues.push(i)
|
||||||
|
}
|
||||||
|
menu_tuner["_optionValues"] = optionValues;
|
||||||
|
|
||||||
|
var menu_epg = new Object();
|
||||||
|
menu_epg["_element"] = "LI";
|
||||||
|
menu_epg["_menuType"] = "select";
|
||||||
|
menu_epg["_configKey"] = "epgSource";
|
||||||
|
menu_epg["_label"] = "Selection of the EPG source";
|
||||||
|
menu_epg["_text"] = "EPG source";
|
||||||
|
menu_epg["_headline"] = "Selection of the EPG source";
|
||||||
|
menu_epg["_usage"] = "PMS: Use EPG data from Plex or Emby.<br>XEPG: Use of external EPG data (XMLTV).<br> Several XMLTV sources possible.<br> Allows editing and order channels.<br> M3U / XMLTV export (HTTP link for IPTV apps)."
|
||||||
|
menu_epg["name"] = "epgSource";
|
||||||
|
menu_epg["id"] = "epgSource";
|
||||||
|
menu_epg["value"] = menu_epg["name"];
|
||||||
|
menu_epg["placeholder"] = "EPG source";
|
||||||
|
menu_epg["onclick"] = "javascript: toggleMenu(this);";
|
||||||
|
menu_epg["class"] = "menu-notActive";
|
||||||
|
menu_epg["_optionValues"] = new Array("PMS", "XEPG");
|
||||||
|
|
||||||
|
var menu_xepg = new Object();
|
||||||
|
menu_xepg["_element"] = "LI";
|
||||||
|
menu_xepg["_menuType"] = "checkbox";
|
||||||
|
menu_xepg["_configKey"] = "xteveAutoUpdate";
|
||||||
|
menu_xepg["_label"] = "Automatic update of xTeVe";
|
||||||
|
menu_xepg["_headline"] = "Automatic update of xTeVe";
|
||||||
|
menu_xepg["_usage"] = "If a new version of xTeVe is available, it will be automatically installed."
|
||||||
|
menu_xepg["name"] = "xteveAutoUpdate";
|
||||||
|
menu_xepg["id"] = "xteveAutoUpdate";
|
||||||
|
menu_xepg["value"] = menu_xepg["name"];
|
||||||
|
menu_xepg["onclick"] = "javascript: toggleMenu(this);";
|
||||||
|
menu_xepg["class"] = "menu-notActive";
|
||||||
|
|
||||||
|
var menu_autoBackupPath = new Object();
|
||||||
|
menu_autoBackupPath["_element"] = "LI";
|
||||||
|
menu_autoBackupPath["_menuType"] = "singleInput";
|
||||||
|
menu_autoBackupPath["_configKey"] = "backup.path";
|
||||||
|
menu_autoBackupPath["_label"] = "Location for automatic backups";
|
||||||
|
menu_autoBackupPath["_headline"] = "Location for automatic backups";
|
||||||
|
menu_autoBackupPath["_usage"] = "Before any update of the provider data by the schedule, xTeVe creates a backup. The path for the automatic backups can be changed. xTeVe requires write permission for this folder."
|
||||||
|
menu_autoBackupPath["name"] = "backup.path";
|
||||||
|
menu_autoBackupPath["id"] = "backup.path";
|
||||||
|
menu_autoBackupPath["value"] = menu_autoBackupPath["name"];
|
||||||
|
menu_autoBackupPath["onclick"] = "javascript: toggleMenu(this);";
|
||||||
|
menu_autoBackupPath["class"] = "menu-notActive";
|
||||||
|
|
||||||
|
var menu_autoBackupKeep = new Object();
|
||||||
|
menu_autoBackupKeep["_element"] = "LI";
|
||||||
|
menu_autoBackupKeep["_menuType"] = "select";
|
||||||
|
menu_autoBackupKeep["_configKey"] = "backup.keep";
|
||||||
|
menu_autoBackupKeep["_text"] = "Keep";
|
||||||
|
menu_autoBackupKeep["_label"] = "Number of backups to keep";
|
||||||
|
menu_autoBackupKeep["_headline"] = "Number of backups to keep";
|
||||||
|
menu_autoBackupKeep["_usage"] = ""
|
||||||
|
menu_autoBackupKeep["name"] = "backup.keep";
|
||||||
|
menu_autoBackupKeep["id"] = "backup.keep";
|
||||||
|
menu_autoBackupKeep["value"] = menu_autoBackupKeep["name"];
|
||||||
|
menu_autoBackupKeep["onclick"] = "javascript: toggleMenu(this);";
|
||||||
|
menu_autoBackupKeep["class"] = "menu-notActive";
|
||||||
|
|
||||||
|
var optionValues = new Array(5, 10, 20, 30, 40, 50);
|
||||||
|
menu_autoBackupKeep["_optionValues"] = optionValues;
|
||||||
|
|
||||||
|
|
||||||
|
var menu_buffer = new Object();
|
||||||
|
menu_buffer["_element"] = "LI";
|
||||||
|
menu_buffer["_menuType"] = "checkbox";
|
||||||
|
menu_buffer["_configKey"] = "buffer";
|
||||||
|
menu_buffer["_label"] = "Stream buffering [Experimental]";
|
||||||
|
menu_buffer["_headline"] = "Stream buffering [Experimental]";
|
||||||
|
menu_buffer["_usage"] = "With activated buffer, streams can be played and recorded more fluently.<br>The stream is passed from xTeVe to Plex / Emby"
|
||||||
|
menu_buffer["name"] = "buffer";
|
||||||
|
menu_buffer["id"] = "buffer";
|
||||||
|
menu_buffer["value"] = menu_buffer["name"];
|
||||||
|
menu_buffer["onclick"] = "javascript: toggleMenu(this);";
|
||||||
|
menu_buffer["class"] = "menu-notActive";
|
||||||
|
|
||||||
|
var menu_api = new Object();
|
||||||
|
menu_api["_element"] = "LI";
|
||||||
|
menu_api["_menuType"] = "checkbox";
|
||||||
|
menu_api["_configKey"] = "api";
|
||||||
|
menu_api["_label"] = "API interface";
|
||||||
|
menu_api["_headline"] = "API interface";
|
||||||
|
menu_api["_usage"] = 'Via API interface it is possible to send commands to xTeVe. API documentation is available <a href="https://xteve.de?scroll=api">here</a> '
|
||||||
|
//menu_api["_usage"] = 'Via API interface it is possible to send commands to xTeVe. API documentation is available <a href="http://localhost:1313?scroll=api">here</a> '
|
||||||
|
menu_api["name"] = "api";
|
||||||
|
menu_api["id"] = "api";
|
||||||
|
menu_api["value"] = menu_api["name"];
|
||||||
|
menu_api["onclick"] = "javascript: toggleMenu(this);";
|
||||||
|
menu_api["class"] = "menu-notActive";
|
||||||
|
|
||||||
|
var menu_authenticationWeb = new Object();
|
||||||
|
menu_authenticationWeb["_element"] = "LI";
|
||||||
|
menu_authenticationWeb["_menuType"] = "checkbox";
|
||||||
|
menu_authenticationWeb["_configKey"] = "authentication.web";
|
||||||
|
menu_authenticationWeb["_label"] = "User authentication";
|
||||||
|
menu_authenticationWeb["_headline"] = "User authentication";
|
||||||
|
menu_authenticationWeb["_usage"] = "Access to xTeVe requires authentication."
|
||||||
|
menu_authenticationWeb["name"] = "authentication.web";
|
||||||
|
menu_authenticationWeb["id"] = "authentication.web";
|
||||||
|
menu_authenticationWeb["value"] = menu_authenticationWeb["name"];
|
||||||
|
menu_authenticationWeb["onclick"] = "javascript: toggleMenu(this);";
|
||||||
|
menu_authenticationWeb["class"] = "menu-notActive";
|
||||||
|
|
||||||
|
var menu_authenticationPms = new Object();
|
||||||
|
menu_authenticationPms["_element"] = "LI";
|
||||||
|
menu_authenticationPms["_menuType"] = "checkbox";
|
||||||
|
menu_authenticationPms["_configKey"] = "authentication.pms";
|
||||||
|
menu_authenticationPms["_label"] = "Plex authentication.";
|
||||||
|
menu_authenticationPms["_headline"] = "Plex authentication.";
|
||||||
|
menu_authenticationPms["_usage"] = "Plex requests are only possible with authentication.<br>Warning!!! After activating this function xTeVe must be delete in the PMS DVR settings and set up again."
|
||||||
|
menu_authenticationPms["name"] = "authentication.pms";
|
||||||
|
menu_authenticationPms["id"] = "authentication.pms";
|
||||||
|
menu_authenticationPms["value"] = menu_authenticationPms["name"];
|
||||||
|
menu_authenticationPms["onclick"] = "javascript: toggleMenu(this);";
|
||||||
|
menu_authenticationPms["class"] = "menu-notActive";
|
||||||
|
|
||||||
|
var menu_authenticationM3u = new Object();
|
||||||
|
menu_authenticationM3u["_element"] = "LI";
|
||||||
|
menu_authenticationM3u["_menuType"] = "checkbox";
|
||||||
|
menu_authenticationM3u["_configKey"] = "authentication.m3u";
|
||||||
|
menu_authenticationM3u["_label"] = "M3U authentication.";
|
||||||
|
menu_authenticationM3u["_headline"] = "M3U authentication.";
|
||||||
|
menu_authenticationM3u["_usage"] = "Downloading the M3U file via an HTTP request is only possible with authentication."
|
||||||
|
menu_authenticationM3u["name"] = "authentication.m3u";
|
||||||
|
menu_authenticationM3u["id"] = "authentication.m3u";
|
||||||
|
menu_authenticationM3u["value"] = menu_authenticationM3u["name"];
|
||||||
|
menu_authenticationM3u["onclick"] = "javascript: toggleMenu(this);";
|
||||||
|
menu_authenticationM3u["class"] = "menu-notActive";
|
||||||
|
|
||||||
|
|
||||||
|
var menu_authenticationXml = new Object();
|
||||||
|
menu_authenticationXml["_element"] = "LI";
|
||||||
|
menu_authenticationXml["_menuType"] = "checkbox";
|
||||||
|
menu_authenticationXml["_configKey"] = "authentication.xml";
|
||||||
|
menu_authenticationXml["_label"] = "XEPG authentication";
|
||||||
|
menu_authenticationXml["_headline"] = "XEPG authentication";
|
||||||
|
menu_authenticationXml["_usage"] = "Downloading the XEPG (XMLTV) file via an HTTP request is only possible with authentication."
|
||||||
|
menu_authenticationXml["name"] = "authentication.xml";
|
||||||
|
menu_authenticationXml["id"] = "authentication.xml";
|
||||||
|
menu_authenticationXml["value"] = menu_authenticationXml["name"];
|
||||||
|
menu_authenticationXml["onclick"] = "javascript: toggleMenu(this);";
|
||||||
|
menu_authenticationXml["class"] = "menu-notActive";
|
||||||
|
|
||||||
|
var menu_authenticationApi = new Object();
|
||||||
|
menu_authenticationApi["_element"] = "LI";
|
||||||
|
menu_authenticationApi["_menuType"] = "checkbox";
|
||||||
|
menu_authenticationApi["_configKey"] = "authentication.api";
|
||||||
|
menu_authenticationApi["_label"] = "API authentication";
|
||||||
|
menu_authenticationApi["_headline"] = "API authentication";
|
||||||
|
menu_authenticationApi["_usage"] = "Access to the API interface is only possible with authentication."
|
||||||
|
menu_authenticationApi["name"] = "authentication.api";
|
||||||
|
menu_authenticationApi["id"] = "authentication.api";
|
||||||
|
menu_authenticationApi["value"] = menu_authenticationApi["name"];
|
||||||
|
menu_authenticationApi["onclick"] = "javascript: toggleMenu(this);";
|
||||||
|
menu_authenticationApi["class"] = "menu-notActive";
|
||||||
|
|
||||||
|
|
||||||
|
// Main menu
|
||||||
|
menu[10] = menu_m3u;
|
||||||
|
|
||||||
|
switch(config["epgSource"]) {
|
||||||
|
case "PMS":
|
||||||
|
menu[20] = menu_id;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "XMLTV":
|
||||||
|
menu[40] = menu_xmltv;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "XEPG":
|
||||||
|
menu[40] = menu_xmltv;
|
||||||
|
menu[50] = menu_mapping;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu[30] = menu_filter;
|
||||||
|
|
||||||
|
if (config["authentication.web"] == true) {
|
||||||
|
menu[60] = menu_users;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu[70] = menu_settings;
|
||||||
|
menu[80] = menu_log;
|
||||||
|
if (config["authentication.web"] == true) {
|
||||||
|
menu[100] = menu_logout;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Sub-Menu
|
||||||
|
|
||||||
|
subMenu[701] = menu_schedule;
|
||||||
|
subMenu[702] = menu_filesUpdate;
|
||||||
|
subMenu[703] = menu_tuner;
|
||||||
|
subMenu[704] = menu_epg;
|
||||||
|
subMenu[705] = menu_xepg;
|
||||||
|
subMenu[706] = menu_autoBackupPath;
|
||||||
|
subMenu[707] = menu_autoBackupKeep;
|
||||||
|
subMenu[708] = menu_buffer;
|
||||||
|
|
||||||
|
subMenu[710] = menu_authenticationWeb;
|
||||||
|
|
||||||
|
if (config["authentication.web"] == true) {
|
||||||
|
subMenu[711] = menu_authenticationPms;
|
||||||
|
subMenu[712] = menu_authenticationM3u;
|
||||||
|
subMenu[713] = menu_authenticationXml;
|
||||||
|
subMenu[714] = menu_authenticationApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
subMenu[799] = menu_api;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMenu() {
|
||||||
|
|
||||||
|
showElement("popup", false);
|
||||||
|
|
||||||
|
//console.log(config);
|
||||||
|
setMenuItem();
|
||||||
|
var menuItems = getObjKeys(menu)
|
||||||
|
var nav = document.getElementsByTagName("NAV")[0];
|
||||||
|
nav.innerHTML = "";
|
||||||
|
var newItem = new Object();
|
||||||
|
|
||||||
|
for (var i = 0; i < menuItems.length; i++) {
|
||||||
|
|
||||||
|
|
||||||
|
var newItem = menu[menuItems[i]];
|
||||||
|
newItem["id"] = menuItems[i];
|
||||||
|
|
||||||
|
|
||||||
|
switch(newItem.hasOwnProperty("_icon")) {
|
||||||
|
case true:
|
||||||
|
var itemText = newItem["_text"];
|
||||||
|
delete newItem["_text"]
|
||||||
|
nav.appendChild(createElement(newItem));
|
||||||
|
newItem["_text"] = itemText;
|
||||||
|
var newIcon = new Object();
|
||||||
|
newIcon["_element"] = "IMG";
|
||||||
|
newIcon["src"] = newItem["_icon"];
|
||||||
|
|
||||||
|
var currentElement = document.getElementById(menuItems[i]);
|
||||||
|
currentElement.appendChild(createElement(newIcon));
|
||||||
|
|
||||||
|
|
||||||
|
var text = new Object();
|
||||||
|
text["_element"] = "P"
|
||||||
|
text["_text"] = itemText;
|
||||||
|
text["class"] = "nav-text"
|
||||||
|
currentElement.appendChild(createElement(text));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
nav.appendChild(createElement(newIcon));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (activeMenu != undefined) {
|
||||||
|
//console.log(activeMenu);
|
||||||
|
toggleMenu(activeMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMenu(elm) {
|
||||||
|
//showStreams(false);
|
||||||
|
clearInterval(logInterval)
|
||||||
|
activeMenu = elm;
|
||||||
|
var item = menu[elm.id]
|
||||||
|
var div = document.getElementById("settings");
|
||||||
|
div.innerHTML = "";
|
||||||
|
|
||||||
|
// Set Headline
|
||||||
|
var headline = new Object();
|
||||||
|
headline["_element"] = "H4";
|
||||||
|
headline["_text"] = item["_headline"];
|
||||||
|
div.appendChild(createElement(headline));
|
||||||
|
|
||||||
|
// Sub-Menu
|
||||||
|
if (item.hasOwnProperty("_subMenu") == true) {
|
||||||
|
openSubMenu(item);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mapping, Users, Log, Files
|
||||||
|
switch(item["_configKey"]) {
|
||||||
|
case "mapping": openMappingEditor(item); return; break;
|
||||||
|
case "users": openUsers(item); return; break;
|
||||||
|
case "log": showLog(item); return; break;
|
||||||
|
case "files.m3u": openFiles(item, "m3u"); return; break;
|
||||||
|
case "files.xmltv": openFiles(item, "xmltv"); return; break;
|
||||||
|
|
||||||
|
case "filter": showStreams(true); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var newHR = new Object();
|
||||||
|
newHR["_element"] = "HR"
|
||||||
|
div.appendChild(createElement(newHR));
|
||||||
|
|
||||||
|
var newEntry = new Object();
|
||||||
|
newEntry["_element"] = "INPUT";
|
||||||
|
newEntry["type"] = "button";
|
||||||
|
//newEntry["class"] = "save";
|
||||||
|
newEntry["value"] = "Save";
|
||||||
|
newEntry["onclick"] = "saveData2('settings')"
|
||||||
|
div.appendChild(createElement(newEntry));
|
||||||
|
|
||||||
|
|
||||||
|
var newWrapper = new Object();
|
||||||
|
newWrapper["_element"] = "DIV";
|
||||||
|
newWrapper["id"] = "box-wrapper";
|
||||||
|
div.appendChild(createElement(newWrapper));
|
||||||
|
|
||||||
|
div = div.lastChild;
|
||||||
|
|
||||||
|
div.appendChild(createMenuItem(item))
|
||||||
|
|
||||||
|
// usage Info
|
||||||
|
switch(menu[activeMenu.id].hasOwnProperty("_usage")) {
|
||||||
|
case true:
|
||||||
|
var usageItem = new Object();
|
||||||
|
usageItem["_element"] = "PRE"
|
||||||
|
usageItem["_text"] = menu[activeMenu.id]["_usage"];
|
||||||
|
div.appendChild(createElement(usageItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateWrapperHeight();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMenuItem(item) {
|
||||||
|
var element = document.createElement("DIV");
|
||||||
|
switch(item["_menuType"]) {
|
||||||
|
case "inputArray":
|
||||||
|
if (config.hasOwnProperty(item["_configKey"]) == true) {
|
||||||
|
var value = config[item["_configKey"]];
|
||||||
|
} else {
|
||||||
|
var value = new Array();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < value.length; i++) {
|
||||||
|
var newEntry = new Object();
|
||||||
|
newEntry = item
|
||||||
|
delete newEntry["onclick"];
|
||||||
|
newEntry["_element"] = "INPUT";
|
||||||
|
newEntry["value"] = value[i];
|
||||||
|
newEntry["type"] = "search";
|
||||||
|
newEntry["data-menutype"] = item["_menuType"];
|
||||||
|
newEntry["data-menukey"] = item["_configKey"];
|
||||||
|
element.appendChild(createElement(newEntry));
|
||||||
|
|
||||||
|
}
|
||||||
|
// New entry for array
|
||||||
|
var newEntry = new Object();
|
||||||
|
newEntry["_element"] = "INPUT";
|
||||||
|
newEntry["type"] = "search";
|
||||||
|
newEntry["name"] = item["name"];
|
||||||
|
newEntry["placeholder"] = item["placeholder"];
|
||||||
|
newEntry["value"] = "";
|
||||||
|
newEntry["data-menutype"] = item["_menuType"];
|
||||||
|
newEntry["data-menukey"] = item["_configKey"];
|
||||||
|
element.appendChild(createElement(newEntry));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "singleInput":
|
||||||
|
var value = config[item["_configKey"]];
|
||||||
|
if (value == undefined) {
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
var newEntry = new Object();
|
||||||
|
newEntry = item;
|
||||||
|
delete newEntry["onclick"];
|
||||||
|
newEntry["_element"] = "INPUT";
|
||||||
|
newEntry["value"] = value;
|
||||||
|
newEntry["type"] = "search";
|
||||||
|
newEntry["data-menutype"] = item["_menuType"];
|
||||||
|
newEntry["data-menukey"] = item["_configKey"];
|
||||||
|
element.appendChild(createElement(newEntry));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "checkbox":
|
||||||
|
var value = config[item["_configKey"]];
|
||||||
|
if (value == undefined) {
|
||||||
|
value = false;
|
||||||
|
}
|
||||||
|
var newEntry = new Object();
|
||||||
|
newEntry = item;
|
||||||
|
delete newEntry["onclick"];
|
||||||
|
newEntry["_element"] = "INPUT";
|
||||||
|
newEntry["value"] = value;
|
||||||
|
newEntry["type"] = "checkbox";
|
||||||
|
newEntry["data-menutype"] = item["_menuType"];
|
||||||
|
newEntry["data-menukey"] = item["_configKey"];
|
||||||
|
element.appendChild(createElement(newEntry));
|
||||||
|
element.getElementsByTagName("INPUT")[0].checked = value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "select":
|
||||||
|
var value = config[item["_configKey"]];
|
||||||
|
var newEntry = new Object();
|
||||||
|
newEntry = item;
|
||||||
|
delete newEntry["onclick"]
|
||||||
|
newEntry["_element"] = "SELECT";
|
||||||
|
element.appendChild(createElement(newEntry));
|
||||||
|
var selectElement = element.getElementsByTagName("SELECT")[0];
|
||||||
|
var values = item["_optionValues"];
|
||||||
|
for (var i = 0; i < values.length; i++) {
|
||||||
|
var newEntry = new Object;
|
||||||
|
newEntry["_element"] = "OPTION";
|
||||||
|
newEntry["_text"] = item["_text"] + ": " + values[i];
|
||||||
|
newEntry["value"] = values[i];
|
||||||
|
selectElement.appendChild(createElement(newEntry));
|
||||||
|
}
|
||||||
|
selectElement.value = value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openSubMenu(item) {
|
||||||
|
var entrys = item["_subMenu"].split(",");
|
||||||
|
var div = document.getElementById("settings");
|
||||||
|
|
||||||
|
var newHR = new Object();
|
||||||
|
newHR["_element"] = "HR"
|
||||||
|
div.appendChild(createElement(newHR));
|
||||||
|
|
||||||
|
var newEntry = new Object();
|
||||||
|
newEntry["_element"] = "INPUT";
|
||||||
|
newEntry["type"] = "button";
|
||||||
|
//newEntry["class"] = "save";
|
||||||
|
newEntry["value"] = "Save";
|
||||||
|
newEntry["onclick"] = "saveData2('settings')"
|
||||||
|
div.appendChild(createElement(newEntry));
|
||||||
|
|
||||||
|
if (item["_configKey"] == "settings") {
|
||||||
|
var newEntry = new Object();
|
||||||
|
newEntry["_element"] = "INPUT";
|
||||||
|
newEntry["type"] = "button";
|
||||||
|
//newEntry["class"] = "save";
|
||||||
|
newEntry["value"] = "Backup";
|
||||||
|
newEntry["onclick"] = "xteveBackup()"
|
||||||
|
div.appendChild(createElement(newEntry));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item["_configKey"] == "settings") {
|
||||||
|
var newEntry = new Object();
|
||||||
|
newEntry["_element"] = "INPUT";
|
||||||
|
newEntry["type"] = "button";
|
||||||
|
//newEntry["class"] = "save";
|
||||||
|
newEntry["value"] = "Restore";
|
||||||
|
newEntry["onclick"] = "xteveRestore(this)"
|
||||||
|
div.appendChild(createElement(newEntry));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var newWrapper = new Object();
|
||||||
|
newWrapper["_element"] = "DIV";
|
||||||
|
newWrapper["id"] = "box-wrapper";
|
||||||
|
div.appendChild(createElement(newWrapper));
|
||||||
|
|
||||||
|
div = div.lastChild;
|
||||||
|
|
||||||
|
|
||||||
|
for (var i = 0; i < entrys.length; i++) {
|
||||||
|
var item = subMenu[entrys[i]];
|
||||||
|
if (item == undefined) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var container = new Object();
|
||||||
|
container["_element"] = "DIV";
|
||||||
|
div.appendChild(createElement(container));
|
||||||
|
|
||||||
|
var divContainer = div.lastChild;
|
||||||
|
|
||||||
|
var headline = new Object();
|
||||||
|
headline["_element"] = "H5";
|
||||||
|
headline["_text"] = item["_headline"];
|
||||||
|
divContainer.appendChild(createElement(headline));
|
||||||
|
|
||||||
|
divContainer.appendChild(createMenuItem(item))
|
||||||
|
|
||||||
|
switch(item.hasOwnProperty("_usage")) {
|
||||||
|
case true:
|
||||||
|
var usageItem = new Object();
|
||||||
|
usageItem["_element"] = "PRE"
|
||||||
|
usageItem["_text"] = item["_usage"];
|
||||||
|
divContainer.appendChild(createElement(usageItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
var hr = new Object();
|
||||||
|
hr["_element"] = "HR";
|
||||||
|
divContainer.appendChild(createElement(hr));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateWrapperHeight();
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveData2(elm) {
|
||||||
|
var div = document.getElementById(elm);
|
||||||
|
var inputs = div.getElementsByTagName("INPUT");
|
||||||
|
var selects = div.getElementsByTagName("SELECT");
|
||||||
|
var value, configKey;
|
||||||
|
var data = new Object();
|
||||||
|
var valueArr = new Array();
|
||||||
|
var newData = false;
|
||||||
|
|
||||||
|
for (var i = 0; i < inputs.length; i++) {
|
||||||
|
if (inputs[i].type != "button") {
|
||||||
|
var menuType = inputs[i].getAttribute("data-menutype");
|
||||||
|
|
||||||
|
//console.log(menuType);
|
||||||
|
switch(menuType) {
|
||||||
|
case "singleInput":
|
||||||
|
value = inputs[i].value;
|
||||||
|
if (value == "" || value == undefined) {
|
||||||
|
data = new Object();
|
||||||
|
data["delete"] = inputs[i].name
|
||||||
|
newData = true;
|
||||||
|
} else {
|
||||||
|
newData = true;
|
||||||
|
data[inputs[i].name] = value;
|
||||||
|
console.log(data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "inputArray":
|
||||||
|
value = inputs[i].value;
|
||||||
|
if (value != "" && value != undefined) {
|
||||||
|
newData = true;
|
||||||
|
valueArr.push(value)
|
||||||
|
data[inputs[i].name] = valueArr;
|
||||||
|
configKey = inputs[i].name;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "checkbox":
|
||||||
|
value = inputs[i].checked
|
||||||
|
data[inputs[i].name] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Delete config key
|
||||||
|
if (valueArr.length == 0 && newData == false) {
|
||||||
|
newData = true;
|
||||||
|
data = new Object();
|
||||||
|
data["delete"] = configKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (var i = 0; i < selects.length; i++) {
|
||||||
|
var value = selects[i].options[selects[i].selectedIndex].value;
|
||||||
|
switch(isNaN(value)) {
|
||||||
|
case false: value = parseInt(value); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
data[selects[i].name] = value;
|
||||||
|
newData = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(data, newData);
|
||||||
|
|
||||||
|
if (newData == true) {
|
||||||
|
data["cmd"] = "saveConfig";
|
||||||
|
if (!data.hasOwnProperty('filter')) {
|
||||||
|
data["filter"] = config["filter"]
|
||||||
|
}
|
||||||
|
var settings = new Object();
|
||||||
|
settings["cmd"] = data["cmd"];
|
||||||
|
settings["settings"] = data;
|
||||||
|
console.log(settings);
|
||||||
|
xTeVe(settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
1747
html/js/menu_ts.js
Normal file
105
html/js/network_ts.js
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
var Server = /** @class */ (function () {
|
||||||
|
function Server(cmd) {
|
||||||
|
this.cmd = cmd;
|
||||||
|
}
|
||||||
|
Server.prototype.request = function (data) {
|
||||||
|
if (SERVER_CONNECTION == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SERVER_CONNECTION = true;
|
||||||
|
console.log(data);
|
||||||
|
if (this.cmd != "updateLog") {
|
||||||
|
showElement("loading", true);
|
||||||
|
UNDO = new Object();
|
||||||
|
}
|
||||||
|
switch (window.location.protocol) {
|
||||||
|
case "http:":
|
||||||
|
this.protocol = "ws://";
|
||||||
|
break;
|
||||||
|
case "https://":
|
||||||
|
this.protocol = "wss://";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
var url = this.protocol + window.location.hostname + ":" + window.location.port + "/data/" + "?Token=" + getCookie("Token");
|
||||||
|
data["cmd"] = this.cmd;
|
||||||
|
var ws = new WebSocket(url);
|
||||||
|
ws.onopen = function () {
|
||||||
|
WS_AVAILABLE = true;
|
||||||
|
console.log("REQUEST (JS):");
|
||||||
|
console.log(data);
|
||||||
|
console.log("REQUEST: (JSON)");
|
||||||
|
console.log(JSON.stringify(data));
|
||||||
|
this.send(JSON.stringify(data));
|
||||||
|
};
|
||||||
|
ws.onerror = function (e) {
|
||||||
|
console.log("No websocket connection to xTeVe could be established. Check your network configuration.");
|
||||||
|
SERVER_CONNECTION = false;
|
||||||
|
if (WS_AVAILABLE == false) {
|
||||||
|
alert("No websocket connection to xTeVe could be established. Check your network configuration.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ws.onmessage = function (e) {
|
||||||
|
SERVER_CONNECTION = false;
|
||||||
|
showElement("loading", false);
|
||||||
|
console.log("RESPONSE:");
|
||||||
|
var response = JSON.parse(e.data);
|
||||||
|
console.log(response);
|
||||||
|
if (response.hasOwnProperty("token")) {
|
||||||
|
document.cookie = "Token=" + response["token"];
|
||||||
|
}
|
||||||
|
if (response["status"] == false) {
|
||||||
|
alert(response["err"]);
|
||||||
|
if (response.hasOwnProperty("reload")) {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (response.hasOwnProperty("logoURL")) {
|
||||||
|
var div = document.getElementById("channel-icon");
|
||||||
|
div.value = response["logoURL"];
|
||||||
|
div.className = "changed";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (data["cmd"]) {
|
||||||
|
case "updateLog":
|
||||||
|
SERVER["log"] = response["log"];
|
||||||
|
if (document.getElementById("content_log")) {
|
||||||
|
showLogs(false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
SERVER = new Object();
|
||||||
|
SERVER = response;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (response.hasOwnProperty("openMenu")) {
|
||||||
|
var menu = document.getElementById(response["openMenu"]);
|
||||||
|
menu.click();
|
||||||
|
showElement("popup", false);
|
||||||
|
}
|
||||||
|
if (response.hasOwnProperty("openLink")) {
|
||||||
|
window.location = response["openLink"];
|
||||||
|
}
|
||||||
|
if (response.hasOwnProperty("alert")) {
|
||||||
|
alert(response["alert"]);
|
||||||
|
}
|
||||||
|
if (response.hasOwnProperty("reload")) {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
if (response.hasOwnProperty("wizard")) {
|
||||||
|
createLayout();
|
||||||
|
configurationWizard[response["wizard"]].createWizard();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
createLayout();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return Server;
|
||||||
|
}());
|
||||||
|
function getCookie(name) {
|
||||||
|
var value = "; " + document.cookie;
|
||||||
|
var parts = value.split("; " + name + "=");
|
||||||
|
if (parts.length == 2)
|
||||||
|
return parts.pop().split(";").shift();
|
||||||
|
}
|
||||||
442
html/js/settings_ts.js
Normal file
@@ -0,0 +1,442 @@
|
|||||||
|
var __extends = (this && this.__extends) || (function () {
|
||||||
|
var extendStatics = function (d, b) {
|
||||||
|
extendStatics = Object.setPrototypeOf ||
|
||||||
|
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||||
|
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||||
|
return extendStatics(d, b);
|
||||||
|
};
|
||||||
|
return function (d, b) {
|
||||||
|
extendStatics(d, b);
|
||||||
|
function __() { this.constructor = d; }
|
||||||
|
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
var SettingsCategory = /** @class */ (function () {
|
||||||
|
function SettingsCategory() {
|
||||||
|
this.DocumentID = "content_settings";
|
||||||
|
}
|
||||||
|
SettingsCategory.prototype.createCategoryHeadline = function (value) {
|
||||||
|
var element = document.createElement("H4");
|
||||||
|
element.innerHTML = value;
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
SettingsCategory.prototype.createHR = function () {
|
||||||
|
var element = document.createElement("HR");
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
SettingsCategory.prototype.createSettings = function (settingsKey) {
|
||||||
|
var setting = document.createElement("TR");
|
||||||
|
var content = new PopupContent();
|
||||||
|
var data = SERVER["settings"][settingsKey];
|
||||||
|
switch (settingsKey) {
|
||||||
|
// Texteingaben
|
||||||
|
case "update":
|
||||||
|
var tdLeft = document.createElement("TD");
|
||||||
|
tdLeft.innerHTML = "{{.settings.update.title}}" + ":";
|
||||||
|
var tdRight = document.createElement("TD");
|
||||||
|
var input = content.createInput("text", "update", data.toString());
|
||||||
|
input.setAttribute("placeholder", "{{.settings.update.placeholder}}");
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||||
|
tdRight.appendChild(input);
|
||||||
|
setting.appendChild(tdLeft);
|
||||||
|
setting.appendChild(tdRight);
|
||||||
|
break;
|
||||||
|
case "backup.path":
|
||||||
|
var tdLeft = document.createElement("TD");
|
||||||
|
tdLeft.innerHTML = "{{.settings.backupPath.title}}" + ":";
|
||||||
|
var tdRight = document.createElement("TD");
|
||||||
|
var input = content.createInput("text", "backup.path", data);
|
||||||
|
input.setAttribute("placeholder", "{{.settings.backupPath.placeholder}}");
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||||
|
tdRight.appendChild(input);
|
||||||
|
setting.appendChild(tdLeft);
|
||||||
|
setting.appendChild(tdRight);
|
||||||
|
break;
|
||||||
|
case "temp.path":
|
||||||
|
var tdLeft = document.createElement("TD");
|
||||||
|
tdLeft.innerHTML = "{{.settings.tempPath.title}}" + ":";
|
||||||
|
var tdRight = document.createElement("TD");
|
||||||
|
var input = content.createInput("text", "temp.path", data);
|
||||||
|
input.setAttribute("placeholder", "{{.settings.tmpPath.placeholder}}");
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||||
|
tdRight.appendChild(input);
|
||||||
|
setting.appendChild(tdLeft);
|
||||||
|
setting.appendChild(tdRight);
|
||||||
|
break;
|
||||||
|
case "user.agent":
|
||||||
|
var tdLeft = document.createElement("TD");
|
||||||
|
tdLeft.innerHTML = "{{.settings.userAgent.title}}" + ":";
|
||||||
|
var tdRight = document.createElement("TD");
|
||||||
|
var input = content.createInput("text", "user.agent", data);
|
||||||
|
input.setAttribute("placeholder", "{{.settings.userAgent.placeholder}}");
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||||
|
tdRight.appendChild(input);
|
||||||
|
setting.appendChild(tdLeft);
|
||||||
|
setting.appendChild(tdRight);
|
||||||
|
break;
|
||||||
|
case "buffer.timeout":
|
||||||
|
var tdLeft = document.createElement("TD");
|
||||||
|
tdLeft.innerHTML = "{{.settings.bufferTimeout.title}}" + ":";
|
||||||
|
var tdRight = document.createElement("TD");
|
||||||
|
var input = content.createInput("text", "buffer.timeout", data);
|
||||||
|
input.setAttribute("placeholder", "{{.settings.bufferTimeout.placeholder}}");
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||||
|
tdRight.appendChild(input);
|
||||||
|
setting.appendChild(tdLeft);
|
||||||
|
setting.appendChild(tdRight);
|
||||||
|
break;
|
||||||
|
// Checkboxen
|
||||||
|
case "authentication.web":
|
||||||
|
var tdLeft = document.createElement("TD");
|
||||||
|
tdLeft.innerHTML = "{{.settings.authenticationWEB.title}}" + ":";
|
||||||
|
var tdRight = document.createElement("TD");
|
||||||
|
var input = content.createCheckbox(settingsKey);
|
||||||
|
input.checked = data;
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||||
|
tdRight.appendChild(input);
|
||||||
|
setting.appendChild(tdLeft);
|
||||||
|
setting.appendChild(tdRight);
|
||||||
|
break;
|
||||||
|
case "authentication.pms":
|
||||||
|
var tdLeft = document.createElement("TD");
|
||||||
|
tdLeft.innerHTML = "{{.settings.authenticationPMS.title}}" + ":";
|
||||||
|
var tdRight = document.createElement("TD");
|
||||||
|
var input = content.createCheckbox(settingsKey);
|
||||||
|
input.checked = data;
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||||
|
tdRight.appendChild(input);
|
||||||
|
setting.appendChild(tdLeft);
|
||||||
|
setting.appendChild(tdRight);
|
||||||
|
break;
|
||||||
|
case "authentication.m3u":
|
||||||
|
var tdLeft = document.createElement("TD");
|
||||||
|
tdLeft.innerHTML = "{{.settings.authenticationM3U.title}}" + ":";
|
||||||
|
var tdRight = document.createElement("TD");
|
||||||
|
var input = content.createCheckbox(settingsKey);
|
||||||
|
input.checked = data;
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||||
|
tdRight.appendChild(input);
|
||||||
|
setting.appendChild(tdLeft);
|
||||||
|
setting.appendChild(tdRight);
|
||||||
|
break;
|
||||||
|
case "authentication.xml":
|
||||||
|
var tdLeft = document.createElement("TD");
|
||||||
|
tdLeft.innerHTML = "{{.settings.authenticationXML.title}}" + ":";
|
||||||
|
var tdRight = document.createElement("TD");
|
||||||
|
var input = content.createCheckbox(settingsKey);
|
||||||
|
input.checked = data;
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||||
|
tdRight.appendChild(input);
|
||||||
|
setting.appendChild(tdLeft);
|
||||||
|
setting.appendChild(tdRight);
|
||||||
|
break;
|
||||||
|
case "authentication.api":
|
||||||
|
var tdLeft = document.createElement("TD");
|
||||||
|
tdLeft.innerHTML = "{{.settings.authenticationAPI.title}}" + ":";
|
||||||
|
var tdRight = document.createElement("TD");
|
||||||
|
var input = content.createCheckbox(settingsKey);
|
||||||
|
input.checked = data;
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||||
|
tdRight.appendChild(input);
|
||||||
|
setting.appendChild(tdLeft);
|
||||||
|
setting.appendChild(tdRight);
|
||||||
|
break;
|
||||||
|
case "files.update":
|
||||||
|
var tdLeft = document.createElement("TD");
|
||||||
|
tdLeft.innerHTML = "{{.settings.filesUpdate.title}}" + ":";
|
||||||
|
var tdRight = document.createElement("TD");
|
||||||
|
var input = content.createCheckbox(settingsKey);
|
||||||
|
input.checked = data;
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||||
|
tdRight.appendChild(input);
|
||||||
|
setting.appendChild(tdLeft);
|
||||||
|
setting.appendChild(tdRight);
|
||||||
|
break;
|
||||||
|
case "cache.images":
|
||||||
|
var tdLeft = document.createElement("TD");
|
||||||
|
tdLeft.innerHTML = "{{.settings.cacheImages.title}}" + ":";
|
||||||
|
var tdRight = document.createElement("TD");
|
||||||
|
var input = content.createCheckbox(settingsKey);
|
||||||
|
input.checked = data;
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||||
|
tdRight.appendChild(input);
|
||||||
|
setting.appendChild(tdLeft);
|
||||||
|
setting.appendChild(tdRight);
|
||||||
|
break;
|
||||||
|
case "xepg.replace.missing.images":
|
||||||
|
var tdLeft = document.createElement("TD");
|
||||||
|
tdLeft.innerHTML = "{{.settings.replaceEmptyImages.title}}" + ":";
|
||||||
|
var tdRight = document.createElement("TD");
|
||||||
|
var input = content.createCheckbox(settingsKey);
|
||||||
|
input.checked = data;
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||||
|
tdRight.appendChild(input);
|
||||||
|
setting.appendChild(tdLeft);
|
||||||
|
setting.appendChild(tdRight);
|
||||||
|
break;
|
||||||
|
case "xteveAutoUpdate":
|
||||||
|
var tdLeft = document.createElement("TD");
|
||||||
|
tdLeft.innerHTML = "{{.settings.xteveAutoUpdate.title}}" + ":";
|
||||||
|
var tdRight = document.createElement("TD");
|
||||||
|
var input = content.createCheckbox(settingsKey);
|
||||||
|
input.checked = data;
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||||
|
tdRight.appendChild(input);
|
||||||
|
setting.appendChild(tdLeft);
|
||||||
|
setting.appendChild(tdRight);
|
||||||
|
break;
|
||||||
|
case "buffer":
|
||||||
|
var tdLeft = document.createElement("TD");
|
||||||
|
tdLeft.innerHTML = "{{.settings.streamBuffering.title}}" + ":";
|
||||||
|
var tdRight = document.createElement("TD");
|
||||||
|
var input = content.createCheckbox(settingsKey);
|
||||||
|
input.checked = data;
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||||
|
tdRight.appendChild(input);
|
||||||
|
setting.appendChild(tdLeft);
|
||||||
|
setting.appendChild(tdRight);
|
||||||
|
break;
|
||||||
|
case "api":
|
||||||
|
var tdLeft = document.createElement("TD");
|
||||||
|
tdLeft.innerHTML = "{{.settings.api.title}}" + ":";
|
||||||
|
var tdRight = document.createElement("TD");
|
||||||
|
var input = content.createCheckbox(settingsKey);
|
||||||
|
input.checked = data;
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||||
|
tdRight.appendChild(input);
|
||||||
|
setting.appendChild(tdLeft);
|
||||||
|
setting.appendChild(tdRight);
|
||||||
|
break;
|
||||||
|
// Select
|
||||||
|
case "tuner":
|
||||||
|
var tdLeft = document.createElement("TD");
|
||||||
|
tdLeft.innerHTML = "{{.settings.tuner.title}}" + ":";
|
||||||
|
var tdRight = document.createElement("TD");
|
||||||
|
var text = new Array();
|
||||||
|
var values = new Array();
|
||||||
|
for (var i = 1; i <= 100; i++) {
|
||||||
|
text.push(i);
|
||||||
|
values.push(i);
|
||||||
|
}
|
||||||
|
var select = content.createSelect(text, values, data, settingsKey);
|
||||||
|
select.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||||
|
tdRight.appendChild(select);
|
||||||
|
setting.appendChild(tdLeft);
|
||||||
|
setting.appendChild(tdRight);
|
||||||
|
break;
|
||||||
|
case "epgSource":
|
||||||
|
var tdLeft = document.createElement("TD");
|
||||||
|
tdLeft.innerHTML = "{{.settings.epgSource.title}}" + ":";
|
||||||
|
var tdRight = document.createElement("TD");
|
||||||
|
var text = ["PMS", "XEPG"];
|
||||||
|
var values = ["PMS", "XEPG"];
|
||||||
|
var select = content.createSelect(text, values, data, settingsKey);
|
||||||
|
select.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||||
|
tdRight.appendChild(select);
|
||||||
|
setting.appendChild(tdLeft);
|
||||||
|
setting.appendChild(tdRight);
|
||||||
|
break;
|
||||||
|
case "backup.keep":
|
||||||
|
var tdLeft = document.createElement("TD");
|
||||||
|
tdLeft.innerHTML = "{{.settings.backupKeep.title}}" + ":";
|
||||||
|
var tdRight = document.createElement("TD");
|
||||||
|
var text = ["5", "10", "20", "30", "40", "50"];
|
||||||
|
var values = ["5", "10", "20", "30", "40", "50"];
|
||||||
|
var select = content.createSelect(text, values, data, settingsKey);
|
||||||
|
select.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||||
|
tdRight.appendChild(select);
|
||||||
|
setting.appendChild(tdLeft);
|
||||||
|
setting.appendChild(tdRight);
|
||||||
|
break;
|
||||||
|
case "buffer.size.kb":
|
||||||
|
var tdLeft = document.createElement("TD");
|
||||||
|
tdLeft.innerHTML = "{{.settings.bufferSize.title}}" + ":";
|
||||||
|
var tdRight = document.createElement("TD");
|
||||||
|
var text = ["0.5 MB", "1 MB", "2 MB", "3 MB", "4 MB", "5 MB", "6 MB", "7 MB", "8 MB"];
|
||||||
|
var values = ["512", "1024", "2048", "3072", "4096", "5120", "6144", "7168", "8192"];
|
||||||
|
var select = content.createSelect(text, values, data, settingsKey);
|
||||||
|
select.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||||
|
tdRight.appendChild(select);
|
||||||
|
setting.appendChild(tdLeft);
|
||||||
|
setting.appendChild(tdRight);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return setting;
|
||||||
|
};
|
||||||
|
SettingsCategory.prototype.createDescription = function (settingsKey) {
|
||||||
|
var description = document.createElement("TR");
|
||||||
|
var text;
|
||||||
|
switch (settingsKey) {
|
||||||
|
case "authentication.web":
|
||||||
|
text = "{{.settings.authenticationWEB.description}}";
|
||||||
|
break;
|
||||||
|
case "authentication.m3u":
|
||||||
|
text = "{{.settings.authenticationM3U.description}}";
|
||||||
|
break;
|
||||||
|
case "authentication.pms":
|
||||||
|
text = "{{.settings.authenticationPMS.description}}";
|
||||||
|
break;
|
||||||
|
case "authentication.xml":
|
||||||
|
text = "{{.settings.authenticationXML.description}}";
|
||||||
|
break;
|
||||||
|
case "authentication.api":
|
||||||
|
if (SERVER["settings"]["authentication.web"] == true) {
|
||||||
|
text = "{{.settings.authenticationAPI.description}}";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "xteveAutoUpdate":
|
||||||
|
text = "{{.settings.xteveAutoUpdate.description}}";
|
||||||
|
break;
|
||||||
|
case "backup.keep":
|
||||||
|
text = "{{.settings.backupKeep.description}}";
|
||||||
|
break;
|
||||||
|
case "backup.path":
|
||||||
|
text = "{{.settings.backupPath.description}}";
|
||||||
|
break;
|
||||||
|
case "temp.path":
|
||||||
|
text = "{{.settings.tempPath.description}}";
|
||||||
|
break;
|
||||||
|
case "buffer":
|
||||||
|
text = "{{.settings.streamBuffering.description}}";
|
||||||
|
break;
|
||||||
|
case "buffer.size.kb":
|
||||||
|
text = "{{.settings.bufferSize.description}}";
|
||||||
|
break;
|
||||||
|
case "buffer.timeout":
|
||||||
|
text = "{{.settings.bufferTimeout.description}}";
|
||||||
|
break;
|
||||||
|
case "user.agent":
|
||||||
|
text = "{{.settings.userAgent.description}}";
|
||||||
|
break;
|
||||||
|
case "epgSource":
|
||||||
|
text = "{{.settings.epgSource.description}}";
|
||||||
|
break;
|
||||||
|
case "tuner":
|
||||||
|
text = "{{.settings.tuner.description}}";
|
||||||
|
break;
|
||||||
|
case "update":
|
||||||
|
text = "{{.settings.update.description}}";
|
||||||
|
break;
|
||||||
|
case "api":
|
||||||
|
text = "{{.settings.api.description}}";
|
||||||
|
break;
|
||||||
|
case "files.update":
|
||||||
|
text = "{{.settings.filesUpdate.description}}";
|
||||||
|
break;
|
||||||
|
case "cache.images":
|
||||||
|
text = "{{.settings.cacheImages.description}}";
|
||||||
|
break;
|
||||||
|
case "xepg.replace.missing.images":
|
||||||
|
text = "{{.settings.replaceEmptyImages.description}}";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
text = "";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
var tdLeft = document.createElement("TD");
|
||||||
|
tdLeft.innerHTML = "";
|
||||||
|
var tdRight = document.createElement("TD");
|
||||||
|
var pre = document.createElement("PRE");
|
||||||
|
pre.innerHTML = text;
|
||||||
|
tdRight.appendChild(pre);
|
||||||
|
description.appendChild(tdLeft);
|
||||||
|
description.appendChild(tdRight);
|
||||||
|
return description;
|
||||||
|
};
|
||||||
|
return SettingsCategory;
|
||||||
|
}());
|
||||||
|
var SettingsCategoryItem = /** @class */ (function (_super) {
|
||||||
|
__extends(SettingsCategoryItem, _super);
|
||||||
|
function SettingsCategoryItem(headline, settingsKeys) {
|
||||||
|
var _this = _super.call(this) || this;
|
||||||
|
_this.headline = headline;
|
||||||
|
_this.settingsKeys = settingsKeys;
|
||||||
|
return _this;
|
||||||
|
}
|
||||||
|
SettingsCategoryItem.prototype.createCategory = function () {
|
||||||
|
var _this = this;
|
||||||
|
var headline = this.createCategoryHeadline(this.headline);
|
||||||
|
var settingsKeys = this.settingsKeys;
|
||||||
|
var doc = document.getElementById(this.DocumentID);
|
||||||
|
doc.appendChild(headline);
|
||||||
|
// Tabelle für die Kategorie erstellen
|
||||||
|
var table = document.createElement("TABLE");
|
||||||
|
var keys = settingsKeys.split(",");
|
||||||
|
keys.forEach(function (settingsKey) {
|
||||||
|
switch (settingsKey) {
|
||||||
|
case "authentication.pms":
|
||||||
|
case "authentication.m3u":
|
||||||
|
case "authentication.xml":
|
||||||
|
case "authentication.api":
|
||||||
|
if (SERVER["settings"]["authentication.web"] == false) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
var item = _this.createSettings(settingsKey);
|
||||||
|
var description = _this.createDescription(settingsKey);
|
||||||
|
table.appendChild(item);
|
||||||
|
table.appendChild(description);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
doc.appendChild(table);
|
||||||
|
doc.appendChild(this.createHR());
|
||||||
|
};
|
||||||
|
return SettingsCategoryItem;
|
||||||
|
}(SettingsCategory));
|
||||||
|
function showSettings() {
|
||||||
|
console.log("SETTINGS");
|
||||||
|
for (var i = 0; i < settingsCategory.length; i++) {
|
||||||
|
settingsCategory[i].createCategory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function saveSettings() {
|
||||||
|
console.log("Save Settings");
|
||||||
|
var cmd = "saveSettings";
|
||||||
|
var div = document.getElementById("content_settings");
|
||||||
|
var settings = div.getElementsByClassName("changed");
|
||||||
|
var newSettings = new Object();
|
||||||
|
for (var i = 0; i < settings.length; i++) {
|
||||||
|
var name;
|
||||||
|
var value;
|
||||||
|
switch (settings[i].tagName) {
|
||||||
|
case "INPUT":
|
||||||
|
switch (settings[i].type) {
|
||||||
|
case "checkbox":
|
||||||
|
name = settings[i].name;
|
||||||
|
value = settings[i].checked;
|
||||||
|
newSettings[name] = value;
|
||||||
|
break;
|
||||||
|
case "text":
|
||||||
|
name = settings[i].name;
|
||||||
|
value = settings[i].value;
|
||||||
|
switch (name) {
|
||||||
|
case "update":
|
||||||
|
value = value.split(",");
|
||||||
|
value = value.filter(function (e) { return e; });
|
||||||
|
break;
|
||||||
|
case "buffer.timeout":
|
||||||
|
value = parseFloat(value);
|
||||||
|
}
|
||||||
|
newSettings[name] = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "SELECT":
|
||||||
|
name = settings[i].name;
|
||||||
|
value = settings[i].value;
|
||||||
|
// Wenn der Wert eine Zahl ist, wird dieser als Zahl gespeichert
|
||||||
|
if (isNaN(value)) {
|
||||||
|
newSettings[name] = value;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newSettings[name] = parseInt(value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var data = new Object();
|
||||||
|
data["settings"] = newSettings;
|
||||||
|
var server = new Server(cmd);
|
||||||
|
server.request(data);
|
||||||
|
}
|
||||||
341
html/js/users.js
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
function openUsers(elm) {
|
||||||
|
colomnSort = 0;
|
||||||
|
|
||||||
|
var newDiv = document.getElementById("settings");
|
||||||
|
|
||||||
|
var newEntry = new Object();
|
||||||
|
newEntry["_element"] = "HR";
|
||||||
|
newDiv.appendChild(createElement(newEntry));
|
||||||
|
|
||||||
|
var newEntry = new Object();
|
||||||
|
newEntry["_element"] = "INPUT";
|
||||||
|
newEntry["type"] = "button";
|
||||||
|
newEntry["class"] = "button";
|
||||||
|
newEntry["value"] = "New";
|
||||||
|
newEntry["onclick"] = "userDetail(0)";
|
||||||
|
newDiv.appendChild(createElement(newEntry));
|
||||||
|
|
||||||
|
var div = document.getElementById("settings");
|
||||||
|
|
||||||
|
// Build table
|
||||||
|
var newTable = new Object();
|
||||||
|
newTable["_element"] = "TABLE";
|
||||||
|
newTable["id"] = "id_mapping";
|
||||||
|
newTable["class"] = "table-mapping";
|
||||||
|
div.appendChild(createElement(newTable));
|
||||||
|
|
||||||
|
setTimeout(function(){
|
||||||
|
createUsersTable();
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createUsersTable() {
|
||||||
|
var table = document.getElementById("id_mapping");
|
||||||
|
table.innerHTML = "";
|
||||||
|
var newTR = new Object();
|
||||||
|
newTR["_element"] = "TR";
|
||||||
|
newTR["class"] = "table-mapping-header";
|
||||||
|
table.appendChild(createElement(newTR));
|
||||||
|
|
||||||
|
var tr = table.lastChild;
|
||||||
|
var trHeadlines = new Array("Username", "Password", "WEB", "PMS", "M3U", "XML", "API")
|
||||||
|
|
||||||
|
for (var i = 0; i < trHeadlines.length; i++) {
|
||||||
|
var newTD = new Object();
|
||||||
|
newTD["_element"] = "TD";
|
||||||
|
newTD["_text"] = trHeadlines[i];
|
||||||
|
tr.appendChild(createElement(newTD));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Sort users
|
||||||
|
var userIds = getObjKeys(users);
|
||||||
|
|
||||||
|
var userObj = new Object();
|
||||||
|
|
||||||
|
for (var i = 0; i < userIds.length; i++) {
|
||||||
|
var username = users[userIds[i]]["data"]["username"];
|
||||||
|
userObj[username] = userIds[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
var allUsers = getObjKeys(userObj);
|
||||||
|
allUsers.sort();
|
||||||
|
// --
|
||||||
|
|
||||||
|
for (var i = 0; i < allUsers.length; i++) {
|
||||||
|
var table = document.getElementById("id_mapping");
|
||||||
|
var userID = userObj[allUsers[i]];
|
||||||
|
var username = allUsers[i];
|
||||||
|
var item = users[userID]["data"];
|
||||||
|
|
||||||
|
// Create TR
|
||||||
|
var newTR = new Object();
|
||||||
|
newTR["_element"] = "TR";
|
||||||
|
newTR["class"] = "";
|
||||||
|
newTR["id"] = userID;
|
||||||
|
newTR["onclick"] = 'javascript: userDetail("' + userID + '");';
|
||||||
|
table.appendChild(createElement(newTR));
|
||||||
|
|
||||||
|
var tr = table.lastChild;
|
||||||
|
|
||||||
|
// Create username TD
|
||||||
|
var newTD = new Object();
|
||||||
|
newTD["_element"] = "P";
|
||||||
|
newTD["_text"] = username;
|
||||||
|
createNewTD(newTD, tr);
|
||||||
|
|
||||||
|
// Create password TD
|
||||||
|
var newTD = new Object();
|
||||||
|
newTD["_element"] = "P";
|
||||||
|
newTD["_text"] = ".....";
|
||||||
|
createNewTD(newTD, tr);
|
||||||
|
|
||||||
|
// Create web access
|
||||||
|
var newTD = new Object();
|
||||||
|
newTD["_element"] = "P";
|
||||||
|
switch(item["authentication.web"]){
|
||||||
|
case true: newTD["_text"] = "✓"; break;
|
||||||
|
default: newTD["_text"] = "-"; break;
|
||||||
|
}
|
||||||
|
createNewTD(newTD, tr);
|
||||||
|
|
||||||
|
// Create PMS access
|
||||||
|
var newTD = new Object();
|
||||||
|
newTD["_element"] = "P";
|
||||||
|
switch(item["authentication.pms"]){
|
||||||
|
case true: newTD["_text"] = "✓"; break;
|
||||||
|
default: newTD["_text"] = "-"; break;
|
||||||
|
}
|
||||||
|
createNewTD(newTD, tr);
|
||||||
|
|
||||||
|
// Create M3U access
|
||||||
|
var newTD = new Object();
|
||||||
|
newTD["_element"] = "P";
|
||||||
|
switch(item["authentication.m3u"]){
|
||||||
|
case true: newTD["_text"] = "✓"; break;
|
||||||
|
default: newTD["_text"] = "-"; break;
|
||||||
|
}
|
||||||
|
createNewTD(newTD, tr);
|
||||||
|
|
||||||
|
// Create XMLTV access
|
||||||
|
var newTD = new Object();
|
||||||
|
newTD["_element"] = "P";
|
||||||
|
switch(item["authentication.xml"]){
|
||||||
|
case true: newTD["_text"] = "✓"; break;
|
||||||
|
default: newTD["_text"] = "-"; break;
|
||||||
|
}
|
||||||
|
createNewTD(newTD, tr);
|
||||||
|
|
||||||
|
// Create API access
|
||||||
|
var newTD = new Object();
|
||||||
|
newTD["_element"] = "P";
|
||||||
|
|
||||||
|
switch(item["authentication.api"]){
|
||||||
|
case true: newTD["_text"] = "✓"; break;
|
||||||
|
default: newTD["_text"] = "-"; break;
|
||||||
|
}
|
||||||
|
createNewTD(newTD, tr);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// usage Info
|
||||||
|
var div = document.getElementById("settings");
|
||||||
|
switch(menu[activeMenu.id].hasOwnProperty("_usage")) {
|
||||||
|
case true:
|
||||||
|
var usageItem = new Object();
|
||||||
|
usageItem["_element"] = "PRE"
|
||||||
|
usageItem["_text"] = menu[activeMenu.id]["_usage"];
|
||||||
|
|
||||||
|
var newHR = new Object();
|
||||||
|
newHR["_element"] = "HR"
|
||||||
|
div.appendChild(createElement(newHR));
|
||||||
|
div.appendChild(createElement(usageItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
sortTable(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function userDetail(userID) {
|
||||||
|
showPopUpElement('user-detail');
|
||||||
|
setTimeout(function(){
|
||||||
|
showElement("popup", true);
|
||||||
|
}, 10);
|
||||||
|
var defaultUser;
|
||||||
|
|
||||||
|
document.getElementById("saveUserDetail").setAttribute("onclick", 'javascript: saveUserDetail("' + userID + '", false)');
|
||||||
|
document.getElementById("deleteUserDetail").setAttribute("onclick", 'javascript: saveUserDetail("' + userID + '", true)');
|
||||||
|
|
||||||
|
var data = new Object();
|
||||||
|
|
||||||
|
switch(userID) {
|
||||||
|
case 0: // New User
|
||||||
|
data["username"] = "";
|
||||||
|
data["authentication.web"] = false;
|
||||||
|
data["authentication.pms"] = true;
|
||||||
|
data["authentication.xml"] = true;
|
||||||
|
data["authentication.m3u"] = false;
|
||||||
|
data["authentication.api"] = false;
|
||||||
|
data["defaultUser"] = false;
|
||||||
|
setTimeout(function(){
|
||||||
|
showElement("deleteUserDetail", false)
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
data = users[userID]["data"];
|
||||||
|
showElement("deleteUserDetail", true)
|
||||||
|
document.getElementById("deleteUserDetail").className = "delete";
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var username = data["username"];
|
||||||
|
data["password"] = "";
|
||||||
|
data["confirm"] = "";
|
||||||
|
|
||||||
|
var keys = getObjKeys(data);
|
||||||
|
defaultUser = data["defaultUser"];
|
||||||
|
if (data.hasOwnProperty("defaultUser")) {
|
||||||
|
defaultUser = JSON.parse(data["defaultUser"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
|
||||||
|
if(document.getElementById(keys[i])){
|
||||||
|
var td = document.getElementById(keys[i])
|
||||||
|
} else {
|
||||||
|
var td = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newItem = new Object();
|
||||||
|
|
||||||
|
newItem["_element"] = "INPUT";
|
||||||
|
|
||||||
|
newItem["value"] = data[keys[i]];
|
||||||
|
newItem["name"] = keys[i];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
switch(keys[i].indexOf("authentication")) {
|
||||||
|
case -1:
|
||||||
|
if (keys[i] == "password" || keys[i] == "confirm") {
|
||||||
|
newItem["type"] = "password";
|
||||||
|
} else {
|
||||||
|
newItem["type"] = "text";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
newItem["type"] = "checkbox";
|
||||||
|
|
||||||
|
if (keys[i] == "authentication.web" && defaultUser == true) {
|
||||||
|
newItem["onclick"] = "return false";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[keys[i]] == true) {
|
||||||
|
newItem["checked"] = data[keys[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(keys[i]) {
|
||||||
|
case "defaultUser":
|
||||||
|
//if (data[keys[i]] == true) {
|
||||||
|
newItem["type"] = "hidden";
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (td != undefined) {
|
||||||
|
td.innerHTML = "";
|
||||||
|
var element = createNewElement(newItem)
|
||||||
|
//console.log(element);
|
||||||
|
td.appendChild(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (defaultUser == true) {
|
||||||
|
showElement("deleteUserDetail", false)
|
||||||
|
} else {
|
||||||
|
showElement("deleteUserDetail", true)
|
||||||
|
document.getElementById("deleteUserDetail").className = "delete";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveUserDetail(userID, deleteUser) {
|
||||||
|
|
||||||
|
var inputs = document.getElementById("user-detail-table").getElementsByTagName("INPUT");
|
||||||
|
|
||||||
|
var newUserData = new Object();
|
||||||
|
for (var i = 0; i < inputs.length; i++) {
|
||||||
|
switch(inputs[i].type) {
|
||||||
|
case "checkbox": newUserData[inputs[i].name] = inputs[i].checked; break;
|
||||||
|
default: newUserData[inputs[i].name] = inputs[i].value; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputs["username"].value.length == 0) {
|
||||||
|
inputs["username"].style.border = "solid 1px red";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(userID) {
|
||||||
|
case "0":
|
||||||
|
if (inputs["password"].value.length == 0) {
|
||||||
|
console.log(inputs["password"].value.length);
|
||||||
|
inputs["password"].style.border = "solid 1px red";
|
||||||
|
return
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputs["password"].value.length > 0) {
|
||||||
|
if (inputs["password"].value != inputs["confirm"].value) {
|
||||||
|
inputs["password"].style.border = "solid 1px red";
|
||||||
|
inputs["confirm"].style.border = "solid 1px red";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = new Object();
|
||||||
|
|
||||||
|
switch(userID) {
|
||||||
|
case "0":
|
||||||
|
//data = newUserData
|
||||||
|
data["userData"] = newUserData
|
||||||
|
data["cmd"] = "saveNewUser"; break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
var thisUser = new Object();
|
||||||
|
|
||||||
|
if (deleteUser == true) {
|
||||||
|
if (confirm('Delete the selected user?')) {
|
||||||
|
data["deleteUser"] = true;
|
||||||
|
} else {
|
||||||
|
showElement("popup", false);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thisUser[userID] = newUserData;
|
||||||
|
|
||||||
|
data["userData"] = thisUser;
|
||||||
|
data["cmd"] = "saveUserData"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
xTeVe(data);
|
||||||
|
//createUsersTable()
|
||||||
|
showElement("popup", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
419
html/lang/en.json
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
{
|
||||||
|
"mainMenu": {
|
||||||
|
"item":{
|
||||||
|
"playlist": "Playlist",
|
||||||
|
"pmsID": "PMS ID",
|
||||||
|
"filter": "Filter",
|
||||||
|
"xmltv": "XMLTV",
|
||||||
|
"mapping": "Mapping",
|
||||||
|
"users": "Users",
|
||||||
|
"settings": "Settings",
|
||||||
|
"log": "Log",
|
||||||
|
"logout": "Logout"
|
||||||
|
},
|
||||||
|
"headline": {
|
||||||
|
"playlist": "Local or remote playlists",
|
||||||
|
"filter": "Filter playlist",
|
||||||
|
"xmltv": "Local or remote XMLTV files",
|
||||||
|
"mapping": "Map playlist channels to EPG channels",
|
||||||
|
"users": "User management",
|
||||||
|
"settings": "Settings",
|
||||||
|
"log": "Log",
|
||||||
|
"logout": "Logout"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"confirm":{
|
||||||
|
"restore": "All data will be replaced with those from the backup.Should the files be restored?"
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"fileLoadingError": "File couldn't be loaded",
|
||||||
|
"invalidChannelNumber": "Invalid channel number"
|
||||||
|
},
|
||||||
|
"button":{
|
||||||
|
"back": "Back",
|
||||||
|
"backup": "Backup",
|
||||||
|
"bulkEdit": "Bulk Edit",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"delete": "Delete",
|
||||||
|
"done": "Done",
|
||||||
|
"login": "Login",
|
||||||
|
"new": "New",
|
||||||
|
"next": "Next",
|
||||||
|
"restore": "Restore",
|
||||||
|
"save": "Save",
|
||||||
|
"search": "Search",
|
||||||
|
"update": "Update",
|
||||||
|
"craeteAccount": "Create Account",
|
||||||
|
"resetlogs": "Reset Logs",
|
||||||
|
"uploadLogo": "Upload Logo"
|
||||||
|
},
|
||||||
|
"filter": {
|
||||||
|
"table": {
|
||||||
|
"name": "Filter Name",
|
||||||
|
"type": "Filter Type",
|
||||||
|
"filter": "Filter"
|
||||||
|
},
|
||||||
|
"custom": "Custom",
|
||||||
|
"group": "Group",
|
||||||
|
"name": {
|
||||||
|
"title": "Filter Name",
|
||||||
|
"placeholder": "Filter name",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"title": "Description",
|
||||||
|
"placeholder": "Description",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"title": "Type",
|
||||||
|
"groupTitle": "Group Title",
|
||||||
|
"customFilter": "Custom Filter"
|
||||||
|
},
|
||||||
|
"caseSensitive": {
|
||||||
|
"title": "Case Sensitive",
|
||||||
|
"placeholder": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"filterRule": {
|
||||||
|
"title": "Filter Rule",
|
||||||
|
"placeholder": "Sport {HD} !{ES,IT}",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"filterGroup": {
|
||||||
|
"title": "Group Title",
|
||||||
|
"placeholder": "",
|
||||||
|
"description": "Select a M3U group. (Counter)<br>Changing the group title in the M3U invalidates the filter."
|
||||||
|
},
|
||||||
|
"include": {
|
||||||
|
"title": "Include",
|
||||||
|
"placeholder": "FHD,UHD",
|
||||||
|
"description": "Channel name must include.<br>(Comma separated) Comma means or"
|
||||||
|
},
|
||||||
|
"exclude": {
|
||||||
|
"title": "Exclude",
|
||||||
|
"placeholder": "ES,IT",
|
||||||
|
"description": "Channel name must not contain.<br>(Comma separated) Comma means or"
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
"playlist": {
|
||||||
|
"table": {
|
||||||
|
"playlist": "Playlist",
|
||||||
|
"tuner": "Tuner",
|
||||||
|
"lastUpdate": "Last Update",
|
||||||
|
"availability": "Availability",
|
||||||
|
"type": "Type",
|
||||||
|
"streams": "Streams",
|
||||||
|
"groupTitle": "group-title",
|
||||||
|
"tvgID": "tvg-id",
|
||||||
|
"uniqueID": "Unique ID"
|
||||||
|
},
|
||||||
|
"playlistType": {
|
||||||
|
"title": "Playlist type",
|
||||||
|
"placeholder": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"title": "Type",
|
||||||
|
"placeholder": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"title": "Name",
|
||||||
|
"placeholder": "Playlist name",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"title": "Description",
|
||||||
|
"placeholder": "Description",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"fileM3U": {
|
||||||
|
"title": "M3U File",
|
||||||
|
"placeholder": "File path or URL of the M3U",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"fileHDHR": {
|
||||||
|
"title": "HDHomeRun IP",
|
||||||
|
"placeholder": "IP address and port (192.168.1.10:5004)",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"tuner": {
|
||||||
|
"title": "Tuner / Streams",
|
||||||
|
"placeholder": "",
|
||||||
|
"description": "Number of parallel connections that can be established to the provider. <br>Only available with activated buffer.<br>New settings will only be applied after quitting all streams."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xmltv": {
|
||||||
|
"table": {
|
||||||
|
"guide": "Guide",
|
||||||
|
"lastUpdate": "Last Update",
|
||||||
|
"availability": "Availability",
|
||||||
|
"channels": "Channels",
|
||||||
|
"programs": "Programs"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"title": "Name",
|
||||||
|
"placeholder": "Guide name",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"title": "Description",
|
||||||
|
"placeholder": "Description",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"fileXMLTV": {
|
||||||
|
"title": "XMLTV File",
|
||||||
|
"placeholder": "File path or URL of the XMLTV",
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mapping": {
|
||||||
|
"table": {
|
||||||
|
"chNo": "Ch. No.",
|
||||||
|
"logo": "Logo",
|
||||||
|
"channelName": "Channel Name",
|
||||||
|
"playlist": "Playlist",
|
||||||
|
"groupTitle": "Group Title",
|
||||||
|
"xmltvFile": "XMLTV File",
|
||||||
|
"xmltvID": "XMLTV ID"
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"title": "Active",
|
||||||
|
"placeholder": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"channelName": {
|
||||||
|
"title": "Channel Name",
|
||||||
|
"placeholder": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"updateChannelName": {
|
||||||
|
"title": "Update Channel Name",
|
||||||
|
"placeholder": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"channelLogo": {
|
||||||
|
"title": "Logo URL",
|
||||||
|
"placeholder": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"updateChannelLogo": {
|
||||||
|
"title": "Update Channel Logo",
|
||||||
|
"placeholder": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"epgCategory": {
|
||||||
|
"title": "EPG Category",
|
||||||
|
"placeholder": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"m3uGroupTitle": {
|
||||||
|
"title": "Group Title (xteve.m3u)",
|
||||||
|
"placeholder": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"xmltvFile": {
|
||||||
|
"title": "XMLTV File",
|
||||||
|
"placeholder": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"xmltvChannel": {
|
||||||
|
"title": "XMLTV Channel",
|
||||||
|
"placeholder": "",
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"table": {
|
||||||
|
"username": "Username",
|
||||||
|
"password": "Password",
|
||||||
|
"web": "WEB",
|
||||||
|
"pms": "PMS",
|
||||||
|
"m3u": "M3U",
|
||||||
|
"xml": "XML",
|
||||||
|
"api": "API"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"title": "Username",
|
||||||
|
"placeholder": "Username",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"title": "Password",
|
||||||
|
"placeholder": "Passoword",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"confirm": {
|
||||||
|
"title": "Confirm",
|
||||||
|
"placeholder": "Password confirm",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"web": {
|
||||||
|
"title": "Web Access",
|
||||||
|
"placeholder": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"pms": {
|
||||||
|
"title": "PMS Access",
|
||||||
|
"placeholder": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"m3u": {
|
||||||
|
"title": "M3U Access",
|
||||||
|
"placeholder": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"xml": {
|
||||||
|
"title": "XML Access",
|
||||||
|
"placeholder": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"api": {
|
||||||
|
"title": "API Access",
|
||||||
|
"placeholder": "",
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"category": {
|
||||||
|
"general": "General",
|
||||||
|
"files": "Files",
|
||||||
|
"streaming": "Streaming",
|
||||||
|
"backup": "Backup",
|
||||||
|
"authentication": "Authentication"
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"title": "Schedule for updating (Playlist, XMLTV, Backup)",
|
||||||
|
"placeholder": "0000,1000,2000",
|
||||||
|
"description": "Time in 24 hour format (0800 = 8:00 am). More times can be entered comma separated."
|
||||||
|
},
|
||||||
|
"api": {
|
||||||
|
"title": "API Interface",
|
||||||
|
"description": "Via API interface it is possible to send commands to xTeVe. API documentation is <a href='https://github.com/xteve-project/xTeVe-Documentation/blob/master/en/configuration.md#api'>here</a>"
|
||||||
|
},
|
||||||
|
"epgSource": {
|
||||||
|
"title": "EPG Source",
|
||||||
|
"description": "PMS:<br>- Use EPG data from Plex or Emby <br><br>XEPG:<br>- Use of one or more XMLTV files<br>- Channel management<br>- M3U / XMLTV export (HTTP link for IPTV apps)"
|
||||||
|
},
|
||||||
|
"tuner":{
|
||||||
|
"title": "Number of Tuners",
|
||||||
|
"description": "Number of parallel connections that can be established to the provider.<br>Available for: Plex, Emby (HDHR), M3U (with active buffer).<br>After a change, xTeVe must be delete in the Plex / Emby DVR settings and set up again."
|
||||||
|
},
|
||||||
|
"filesUpdate": {
|
||||||
|
"title": "Updates all files at startup",
|
||||||
|
"description": "Updates all playlists, tuner and XMLTV files at startup."
|
||||||
|
},
|
||||||
|
"cacheImages": {
|
||||||
|
"title": "Image caching",
|
||||||
|
"description": "All images from the XMLTV file are cached, allowing faster rendering of the grid in the client.<br>Downloading the images may take a while and will be done in the background."
|
||||||
|
},
|
||||||
|
"replaceEmptyImages": {
|
||||||
|
"title": "Replace missing program images",
|
||||||
|
"description": "If the poster in the XMLTV program is missing, the channel logo will be used."
|
||||||
|
},
|
||||||
|
"xteveAutoUpdate": {
|
||||||
|
"title": "Automatic update of xTeVe",
|
||||||
|
"description": "If a new version of xTeVe is available, it will be automatically installed. The updates are downloaded from GitHub."
|
||||||
|
},
|
||||||
|
"streamBuffering": {
|
||||||
|
"title": "Stream Buffer",
|
||||||
|
"description": "- The stream is passed from xTeVe to Plex / Emby / M3U Player<br>- Small jerking of the streams can be compensated<br>- HLS / M3U8 support"
|
||||||
|
},
|
||||||
|
"bufferSize": {
|
||||||
|
"title": "Buffer Size",
|
||||||
|
"description": "Buffer size in MB.<br>M3U8: If the TS segment smaller then the buffer size, the file size of the segment is used."
|
||||||
|
},
|
||||||
|
"bufferTimeout": {
|
||||||
|
"title": "Timeout for new client connections",
|
||||||
|
"description": "The xTeVe buffer waits until new client connections are established. Helpful for fast channel switching. Value in milliseconds.",
|
||||||
|
"placeholder": "100"
|
||||||
|
},
|
||||||
|
"userAgent": {
|
||||||
|
"title": "User agent",
|
||||||
|
"description": "User Agent for HTTP requests",
|
||||||
|
"placeholder": "xTeVe"
|
||||||
|
},
|
||||||
|
"backupPath": {
|
||||||
|
"title": "Location for automatic backups",
|
||||||
|
"placeholder": "/mnt/data/backup/xteve/",
|
||||||
|
"description": "Before any update of the provider data by the schedule, xTeVe creates a backup. The path for the automatic backups can be changed. xTeVe requires write permission for this folder."
|
||||||
|
},
|
||||||
|
"tempPath": {
|
||||||
|
"title": "Location for the temporary files",
|
||||||
|
"placeholder": "/tmp/xteve/",
|
||||||
|
"description": "Location for the buffer files."
|
||||||
|
},
|
||||||
|
"backupKeep": {
|
||||||
|
"title": "Number of backups to keep",
|
||||||
|
"description": "Number of backups to keep. Older backups are automatically deleted."
|
||||||
|
},
|
||||||
|
"authenticationWEB": {
|
||||||
|
"title": "WEB Authentication",
|
||||||
|
"description": "Access to the web interface only possible with credentials."
|
||||||
|
},
|
||||||
|
"authenticationPMS": {
|
||||||
|
"title": "PMS Authentication",
|
||||||
|
"description": "Plex requests are only possible with authentication. <br><b>Warning!!!</b> After activating this function xTeVe must be delete in the PMS DVR settings and set up again."
|
||||||
|
},
|
||||||
|
"authenticationM3U": {
|
||||||
|
"title": "M3U Authentication",
|
||||||
|
"description": "Downloading the xteve.m3u file via an HTTP request is only possible with authentication."
|
||||||
|
},
|
||||||
|
"authenticationXML": {
|
||||||
|
"title": "XML Authentication",
|
||||||
|
"description": "Downloading the xteve.xml file via an HTTP request is only possible with authentication"
|
||||||
|
},
|
||||||
|
"authenticationAPI": {
|
||||||
|
"title": "API Authentication",
|
||||||
|
"description": "Access to the API interface is only possible with authentication."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wizard": {
|
||||||
|
"epgSource": {
|
||||||
|
"title": "EPG Source",
|
||||||
|
"description": "PMS:<br>- Use EPG data from Plex or Emby <br><br>XEPG:<br>- Use of one or more XMLTV files<br>- Channel management<br>- M3U / XMLTV export (HTTP link for IPTV apps)"
|
||||||
|
},
|
||||||
|
"tuner":{
|
||||||
|
"title": "Number of tuners",
|
||||||
|
"description": "Number of parallel connections that can be established to the provider.<br>Available for: Plex, Emby (HDHR), M3U (with active buffer).<br>After a change, xTeVe must be delete in the Plex / Emby DVR settings and set up again."
|
||||||
|
},
|
||||||
|
"m3u": {
|
||||||
|
"title": "M3U Playlist",
|
||||||
|
"description": "Local or remote playlists"
|
||||||
|
},
|
||||||
|
"xmltv": {
|
||||||
|
"title": "XMLTV File",
|
||||||
|
"description": "Local or remote XMLTV file"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"failed": "User authentication failed",
|
||||||
|
"headline": "Login",
|
||||||
|
"username": {
|
||||||
|
"title": "Username",
|
||||||
|
"placeholder": "Username"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"title": "Password",
|
||||||
|
"placeholder": "Password"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"failed": "Password does not match",
|
||||||
|
"headline": "Create user account",
|
||||||
|
"username": {
|
||||||
|
"title": "Username",
|
||||||
|
"placeholder": "Username"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"title": "Password",
|
||||||
|
"placeholder": "Password"
|
||||||
|
},
|
||||||
|
"confirm": {
|
||||||
|
"title": "Confirm",
|
||||||
|
"placeholder": "Confirm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
46
html/login.html
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>xTeVe</title>
|
||||||
|
<link rel="stylesheet" href="css/screen.css" type="text/css">
|
||||||
|
<link rel="stylesheet" href="css/base.css" type="text/css">
|
||||||
|
<script language="javascript" type="text/javascript" src="js/network_ts.js"></script>
|
||||||
|
<script language="javascript" type="text/javascript" src="js/authentication_ts.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="header" class="imgCenter"></div>
|
||||||
|
|
||||||
|
<div id="box">
|
||||||
|
|
||||||
|
<div id="headline">
|
||||||
|
<h1 id="head-text" class="center">{{.login.headline}}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p id="err" class="errorMsg center">{{.authenticationErr}}</p>
|
||||||
|
|
||||||
|
<div id="content">
|
||||||
|
|
||||||
|
<form id="authentication" action="/web/" method="post">
|
||||||
|
|
||||||
|
<h5>{{.login.username.title}}:</h5>
|
||||||
|
<input id="username" type="text" name="username" placeholder="Username" value="">
|
||||||
|
<h5>{{.login.password.title}}:</h5>
|
||||||
|
<input id="password" type="password" name="password" placeholder="Password" value="">
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="box-footer">
|
||||||
|
<input id="submit" class="" type="button" value="{{.button.login}}" onclick="javascript: login();">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
30
html/maintenance.html
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>xTeVe</title>
|
||||||
|
<link rel="stylesheet" href="css/screen.css" type="text/css">
|
||||||
|
<link rel="stylesheet" href="css/base.css" type="text/css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="header" class="imgCenter"></div>
|
||||||
|
|
||||||
|
<div id="box">
|
||||||
|
|
||||||
|
<div id="headline">
|
||||||
|
<h1 id="head-text" class="center">Maintenance</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="content">
|
||||||
|
xTeVe is updating the database, please try again later.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="box-footer"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
html/video/stream-limit.ts
Normal file
169
src/authentication.go
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"../src/internal/authentication"
|
||||||
|
)
|
||||||
|
|
||||||
|
func activatedSystemAuthentication() (err error) {
|
||||||
|
|
||||||
|
err = authentication.Init(System.Folder.Config, 60)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaults = make(map[string]interface{})
|
||||||
|
defaults["authentication.web"] = false
|
||||||
|
defaults["authentication.pms"] = false
|
||||||
|
defaults["authentication.xml"] = false
|
||||||
|
defaults["authentication.api"] = false
|
||||||
|
err = authentication.SetDefaultUserData(defaults)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFirstUserForAuthentication(username, password string) (token string, err error) {
|
||||||
|
|
||||||
|
var authenticationErr = func(err error) {
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = authentication.CreateDefaultUser(username, password)
|
||||||
|
authenticationErr(err)
|
||||||
|
|
||||||
|
token, err = authentication.UserAuthentication(username, password)
|
||||||
|
authenticationErr(err)
|
||||||
|
|
||||||
|
token, err = authentication.CheckTheValidityOfTheToken(token)
|
||||||
|
authenticationErr(err)
|
||||||
|
|
||||||
|
var userData = make(map[string]interface{})
|
||||||
|
userData["username"] = username
|
||||||
|
userData["authentication.web"] = true
|
||||||
|
userData["authentication.pms"] = true
|
||||||
|
userData["authentication.m3u"] = true
|
||||||
|
userData["authentication.xml"] = true
|
||||||
|
userData["authentication.api"] = false
|
||||||
|
userData["defaultUser"] = true
|
||||||
|
|
||||||
|
userID, err := authentication.GetUserID(token)
|
||||||
|
authenticationErr(err)
|
||||||
|
|
||||||
|
err = authentication.WriteUserData(userID, userData)
|
||||||
|
authenticationErr(err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func tokenAuthentication(token string) (newToken string, err error) {
|
||||||
|
|
||||||
|
if System.ConfigurationWizard == true {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newToken, err = authentication.CheckTheValidityOfTheToken(token)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func basicAuth(r *http.Request, level string) (username string, err error) {
|
||||||
|
|
||||||
|
err = errors.New("User authentication failed")
|
||||||
|
|
||||||
|
auth := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
|
||||||
|
|
||||||
|
if len(auth) != 2 || auth[0] != "Basic" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, _ := base64.StdEncoding.DecodeString(auth[1])
|
||||||
|
pair := strings.SplitN(string(payload), ":", 2)
|
||||||
|
|
||||||
|
username = pair[0]
|
||||||
|
var password = pair[1]
|
||||||
|
|
||||||
|
token, err := authentication.UserAuthentication(username, password)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = checkAuthorizationLevel(token, level)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func urlAuth(r *http.Request, requestType string) (err error) {
|
||||||
|
var level, token string
|
||||||
|
|
||||||
|
var username = r.URL.Query().Get("username")
|
||||||
|
var password = r.URL.Query().Get("password")
|
||||||
|
|
||||||
|
switch requestType {
|
||||||
|
|
||||||
|
case "m3u":
|
||||||
|
level = "authentication.m3u"
|
||||||
|
if Settings.AuthenticationM3U == true {
|
||||||
|
token, err = authentication.UserAuthentication(username, password)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = checkAuthorizationLevel(token, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "xml":
|
||||||
|
level = "authentication.xml"
|
||||||
|
if Settings.AuthenticationXML == true {
|
||||||
|
token, err = authentication.UserAuthentication(username, password)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = checkAuthorizationLevel(token, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkAuthorizationLevel(token, level string) (err error) {
|
||||||
|
|
||||||
|
var authenticationErr = func(err error) {
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, err := authentication.GetUserID(token)
|
||||||
|
authenticationErr(err)
|
||||||
|
|
||||||
|
userData, err := authentication.ReadUserData(userID)
|
||||||
|
authenticationErr(err)
|
||||||
|
|
||||||
|
if len(userData) > 0 {
|
||||||
|
|
||||||
|
if v, ok := userData[level].(bool); ok {
|
||||||
|
|
||||||
|
if v == false {
|
||||||
|
err = errors.New("No authorization")
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
userData[level] = false
|
||||||
|
err = authentication.WriteUserData(userID, userData)
|
||||||
|
err = errors.New("No authorization")
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
err = authentication.WriteUserData(userID, userData)
|
||||||
|
err = errors.New("No authorization")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
191
src/backup.go
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
b64 "encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func xTeVeAutoBackup() (err error) {
|
||||||
|
|
||||||
|
var archiv = "xteve_auto_backup_" + time.Now().Format("20060102_1504") + ".zip"
|
||||||
|
var target string
|
||||||
|
var sourceFiles = make([]string, 0)
|
||||||
|
var oldBackupFiles = make([]string, 0)
|
||||||
|
var debug string
|
||||||
|
|
||||||
|
if len(Settings.BackupPath) > 0 {
|
||||||
|
System.Folder.Backup = Settings.BackupPath
|
||||||
|
}
|
||||||
|
|
||||||
|
showInfo("Backup Path:" + System.Folder.Backup)
|
||||||
|
|
||||||
|
err = checkFolder(System.Folder.Backup)
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 1070)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alte Backups löschen
|
||||||
|
files, err := ioutil.ReadDir(System.Folder.Backup)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
|
||||||
|
if filepath.Ext(file.Name()) == ".zip" && strings.Contains(file.Name(), "xteve_auto_backup") {
|
||||||
|
oldBackupFiles = append(oldBackupFiles, file.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alle Backups löschen
|
||||||
|
var end int
|
||||||
|
switch Settings.BackupKeep {
|
||||||
|
case 0:
|
||||||
|
end = 0
|
||||||
|
default:
|
||||||
|
end = Settings.BackupKeep - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(oldBackupFiles)-end; i++ {
|
||||||
|
|
||||||
|
os.RemoveAll(System.Folder.Backup + oldBackupFiles[i])
|
||||||
|
debug = fmt.Sprintf("Delete backup file:%s", oldBackupFiles[i])
|
||||||
|
showDebug(debug, 1)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if Settings.BackupKeep == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backup erstellen
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
target = System.Folder.Backup + archiv
|
||||||
|
|
||||||
|
for _, i := range SystemFiles {
|
||||||
|
sourceFiles = append(sourceFiles, System.Folder.Config+i)
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceFiles = append(sourceFiles, System.Folder.ImagesUpload)
|
||||||
|
|
||||||
|
err = zipFiles(sourceFiles, target)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
debug = fmt.Sprintf("Create backup file:%s", target)
|
||||||
|
showDebug(debug, 1)
|
||||||
|
|
||||||
|
showInfo("Backup file:" + target)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func xteveBackup() (archiv string, err error) {
|
||||||
|
|
||||||
|
err = checkFolder(System.Folder.Temp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
archiv = "xteve_backup_" + time.Now().Format("20060102_1504") + ".zip"
|
||||||
|
|
||||||
|
var target = System.Folder.Temp + archiv
|
||||||
|
var sourceFiles = make([]string, 0)
|
||||||
|
|
||||||
|
for _, i := range SystemFiles {
|
||||||
|
sourceFiles = append(sourceFiles, System.Folder.Config+i)
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceFiles = append(sourceFiles, System.Folder.Data)
|
||||||
|
|
||||||
|
err = zipFiles(sourceFiles, target)
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func xteveRestore(input string) (newWebURL string, err error) {
|
||||||
|
|
||||||
|
var newPort, oldPort string
|
||||||
|
|
||||||
|
// Base64 Json String in base64 umwandeln
|
||||||
|
b64data := input[strings.IndexByte(input, ',')+1:]
|
||||||
|
|
||||||
|
// Base64 in bytes umwandeln und speichern
|
||||||
|
sDec, err := b64.StdEncoding.DecodeString(b64data)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var archive = System.Folder.Temp + "restore.zip"
|
||||||
|
|
||||||
|
err = writeByteToFile(archive, sDec)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zip Archiv entpacken
|
||||||
|
err = extractZIP(archive, System.Folder.Config)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neue Config laden um den Port zu überprüfen
|
||||||
|
newConfig, err := loadJSONFileToMap(System.Folder.Config + "settings.json")
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newPort = newConfig["port"].(string)
|
||||||
|
oldPort = Settings.Port
|
||||||
|
|
||||||
|
if newPort == oldPort {
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSettings()
|
||||||
|
|
||||||
|
err := Init()
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 0)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = StartSystem(true)
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 0)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = System.URLBase + "/web/"
|
||||||
|
newWebURL = strings.Replace(url, ":"+oldPort, ":"+newPort, 1)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
1405
src/buffer.go
Normal file
148
src/compression.go
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func zipFiles(sourceFiles []string, target string) error {
|
||||||
|
|
||||||
|
zipfile, err := os.Create(target)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer zipfile.Close()
|
||||||
|
|
||||||
|
archive := zip.NewWriter(zipfile)
|
||||||
|
defer archive.Close()
|
||||||
|
|
||||||
|
for _, source := range sourceFiles {
|
||||||
|
|
||||||
|
info, err := os.Stat(source)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseDir string
|
||||||
|
if info.IsDir() {
|
||||||
|
baseDir = filepath.Base(System.Folder.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
header, err := zip.FileInfoHeader(info)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if baseDir != "" {
|
||||||
|
header.Name = filepath.Join(strings.TrimPrefix(path, System.Folder.Config))
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
header.Name += string(os.PathSeparator)
|
||||||
|
} else {
|
||||||
|
header.Method = zip.Deflate
|
||||||
|
}
|
||||||
|
|
||||||
|
writer, err := archive.CreateHeader(header)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(writer, file)
|
||||||
|
|
||||||
|
return err
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractZIP(archive, target string) (err error) {
|
||||||
|
|
||||||
|
reader, err := zip.OpenReader(archive)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(target, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range reader.File {
|
||||||
|
|
||||||
|
path := filepath.Join(target, file.Name)
|
||||||
|
if file.FileInfo().IsDir() {
|
||||||
|
os.MkdirAll(path, file.Mode())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fileReader, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer fileReader.Close()
|
||||||
|
|
||||||
|
targetFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer targetFile.Close()
|
||||||
|
|
||||||
|
if _, err := io.Copy(targetFile, fileReader); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractGZIP(gzipBody []byte, fileSource string) (body []byte, err error) {
|
||||||
|
|
||||||
|
var b = bytes.NewBuffer(gzipBody)
|
||||||
|
|
||||||
|
var r io.Reader
|
||||||
|
r, err = gzip.NewReader(b)
|
||||||
|
if err != nil {
|
||||||
|
// Keine gzip Datei
|
||||||
|
body = gzipBody
|
||||||
|
err = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
showInfo("Extract gzip:" + fileSource)
|
||||||
|
|
||||||
|
var resB bytes.Buffer
|
||||||
|
_, err = resB.ReadFrom(r)
|
||||||
|
if err != nil {
|
||||||
|
body = gzipBody
|
||||||
|
err = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body = resB.Bytes()
|
||||||
|
return
|
||||||
|
}
|
||||||
242
src/config.go
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// System : Beinhaltet alle Systeminformationen
|
||||||
|
var System SystemStruct
|
||||||
|
|
||||||
|
// WebScreenLog : Logs werden im RAM gespeichert und für das Webinterface bereitgestellt
|
||||||
|
var WebScreenLog WebScreenLogStruct
|
||||||
|
|
||||||
|
// Settings : Inhalt der settings.json
|
||||||
|
var Settings SettingsStrcut
|
||||||
|
|
||||||
|
// Data : Alle Daten werden hier abgelegt. (Lineup, XMLTV)
|
||||||
|
var Data DataStruct
|
||||||
|
|
||||||
|
// SystemFiles : Alle Systemdateien
|
||||||
|
var SystemFiles = []string{"authentication.json", "pms.json", "settings.json", "xepg.json", "urls.json"}
|
||||||
|
|
||||||
|
// BufferInformation : Informationen über den Buffer (aktive Streams, maximale Streams)
|
||||||
|
var BufferInformation sync.Map
|
||||||
|
|
||||||
|
// BufferClients : Anzahl der Clients die einen Stream über den Buffer abspielen
|
||||||
|
var BufferClients sync.Map
|
||||||
|
|
||||||
|
// Init : Systeminitialisierung
|
||||||
|
func Init() (err error) {
|
||||||
|
|
||||||
|
var debug string
|
||||||
|
|
||||||
|
// System Einstellungen
|
||||||
|
System.AppName = strings.ToLower(System.Name)
|
||||||
|
System.ARCH = runtime.GOARCH
|
||||||
|
System.OS = runtime.GOOS
|
||||||
|
System.ServerProtocol.API = "http"
|
||||||
|
System.ServerProtocol.DVR = "http"
|
||||||
|
System.ServerProtocol.M3U = "http"
|
||||||
|
System.ServerProtocol.WEB = "http"
|
||||||
|
System.ServerProtocol.XML = "http"
|
||||||
|
System.DVRLimit = 480
|
||||||
|
System.Compatibility = "1.4.4"
|
||||||
|
|
||||||
|
// Default Logeinträge, wird später von denen aus der settings.json überschrieben. Muss gemacht werden, damit die ersten Einträge auch im Log (webUI aangezeigt werden)
|
||||||
|
Settings.LogEntriesRAM = 500
|
||||||
|
|
||||||
|
// Variablen für den Update Prozess
|
||||||
|
//System.Update.Git = "https://github.com/xteve-project/xTeVe-Downloads/blob"
|
||||||
|
System.Update.Git = fmt.Sprintf("https://github.com/%s/%s/blob", System.GitHub.User, System.GitHub.Repo)
|
||||||
|
System.Update.Name = "xteve_2"
|
||||||
|
|
||||||
|
// Ordnerpfade festlegen
|
||||||
|
var tempFolder = os.TempDir() + string(os.PathSeparator) + System.AppName + string(os.PathSeparator)
|
||||||
|
tempFolder = getPlatformPath(strings.Replace(tempFolder, "//", "/", -1))
|
||||||
|
|
||||||
|
if len(System.Folder.Config) == 0 {
|
||||||
|
System.Folder.Config = GetUserHomeDirectory() + string(os.PathSeparator) + "." + System.AppName + string(os.PathSeparator)
|
||||||
|
} else {
|
||||||
|
System.Folder.Config = strings.TrimRight(System.Folder.Config, string(os.PathSeparator)) + string(os.PathSeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Folder.Config = getPlatformPath(System.Folder.Config)
|
||||||
|
|
||||||
|
System.Folder.Backup = System.Folder.Config + "backup" + string(os.PathSeparator)
|
||||||
|
System.Folder.Data = System.Folder.Config + "data" + string(os.PathSeparator)
|
||||||
|
System.Folder.Cache = System.Folder.Config + "cache" + string(os.PathSeparator)
|
||||||
|
System.Folder.ImagesCache = System.Folder.Cache + "images" + string(os.PathSeparator)
|
||||||
|
System.Folder.ImagesUpload = System.Folder.Data + "images" + string(os.PathSeparator)
|
||||||
|
System.Folder.Temp = tempFolder
|
||||||
|
|
||||||
|
// Dev Info
|
||||||
|
showDevInfo()
|
||||||
|
|
||||||
|
// System Ordner erstellen
|
||||||
|
err = createSystemFolders()
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 1070)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
System.File.XML = getPlatformFile(fmt.Sprintf("%s%s.xml", System.Folder.Data, System.AppName))
|
||||||
|
System.File.M3U = getPlatformFile(fmt.Sprintf("%s%s.m3u", System.Folder.Data, System.AppName))
|
||||||
|
|
||||||
|
err = activatedSystemAuthentication()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = resolveHostIP()
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 1002)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Menü für das Webinterface
|
||||||
|
System.WEB.Menu = []string{"playlist", "filter", "xmltv", "mapping", "users", "settings", "log", "logout"}
|
||||||
|
|
||||||
|
fmt.Println("For help run: " + getPlatformFile(os.Args[0]) + " -h")
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
// Überprüfen ob xTeVe als root läuft
|
||||||
|
if os.Geteuid() == 0 {
|
||||||
|
showWarning(2010)
|
||||||
|
}
|
||||||
|
|
||||||
|
if System.Flag.Debug > 0 {
|
||||||
|
debug = fmt.Sprintf("Debug Level:%d", System.Flag.Debug)
|
||||||
|
showDebug(debug, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
showInfo(fmt.Sprintf("Version:%s Build: %s", System.Version, System.Build))
|
||||||
|
showInfo(fmt.Sprintf("System IP Addresses:IPv4: %d | IPv6: %d", len(System.IPAddressesV4), len(System.IPAddressesV6)))
|
||||||
|
showInfo("Hostname:" + System.Hostname)
|
||||||
|
showInfo(fmt.Sprintf("System Folder:%s", getPlatformPath(System.Folder.Config)))
|
||||||
|
|
||||||
|
// Systemdateien erstellen (Falls nicht vorhanden)
|
||||||
|
err = createSystemFiles()
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 1071)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bedingte Update Änderungen durchführen
|
||||||
|
err = conditionalUpdateChanges()
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Einstellungen laden (settings.json)
|
||||||
|
showInfo(fmt.Sprintf("Load Settings:%s", System.File.Settings))
|
||||||
|
|
||||||
|
_, err = loadSettings()
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Berechtigung aller Ordner überprüfen
|
||||||
|
err = checkFilePermission(System.Folder.Config)
|
||||||
|
if err == nil {
|
||||||
|
err = checkFilePermission(System.Folder.Temp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separaten tmp Ordner für jede Instanz
|
||||||
|
//System.Folder.Temp = System.Folder.Temp + Settings.UUID + string(os.PathSeparator)
|
||||||
|
showInfo(fmt.Sprintf("Temporary Folder:%s", getPlatformPath(System.Folder.Temp)))
|
||||||
|
|
||||||
|
err = checkFolder(System.Folder.Temp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = removeChildItems(getPlatformPath(System.Folder.Temp))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DLNA Server starten
|
||||||
|
go SSDP()
|
||||||
|
|
||||||
|
// Branch festlegen
|
||||||
|
System.Branch = Settings.Branch
|
||||||
|
|
||||||
|
if System.Dev == true {
|
||||||
|
System.Branch = "Development"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(System.Branch) == 0 {
|
||||||
|
System.Branch = "master"
|
||||||
|
}
|
||||||
|
|
||||||
|
showInfo(fmt.Sprintf("GitHub:https://github.com/%s", System.GitHub.User))
|
||||||
|
showInfo(fmt.Sprintf("Git Branch:%s [%s]", System.Branch, System.GitHub.User))
|
||||||
|
|
||||||
|
// Domainnamen setzten
|
||||||
|
setGlobalDomain(fmt.Sprintf("%s:%s", System.IPAddress, Settings.Port))
|
||||||
|
|
||||||
|
System.URLBase = fmt.Sprintf("%s://%s:%s", System.ServerProtocol.WEB, System.IPAddress, Settings.Port)
|
||||||
|
|
||||||
|
// HTML Dateien erstellen, mit dev == true werden die lokalen HTML Dateien verwendet
|
||||||
|
if System.Dev == true {
|
||||||
|
|
||||||
|
HTMLInit("webUI", "src", "html"+string(os.PathSeparator), "src"+string(os.PathSeparator)+"webUI.go")
|
||||||
|
err = BuildGoFile()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
loadHTMLMap()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartSystem : System wird gestartet
|
||||||
|
func StartSystem(updateProviderFiles bool) (err error) {
|
||||||
|
|
||||||
|
setDeviceID()
|
||||||
|
|
||||||
|
if System.ScanInProgress == 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Systeminformationen in der Konsole ausgeben
|
||||||
|
showInfo(fmt.Sprintf("UUID:%s", Settings.UUID))
|
||||||
|
showInfo(fmt.Sprintf("Tuner (Plex / Emby):%d", Settings.Tuner))
|
||||||
|
showInfo(fmt.Sprintf("EPG Source:%s", Settings.EpgSource))
|
||||||
|
showInfo(fmt.Sprintf("Plex Channel Limit:%d", System.DVRLimit))
|
||||||
|
|
||||||
|
// Providerdaten aktualisieren
|
||||||
|
if len(Settings.Files.M3U) > 0 && Settings.FilesUpdate == true || updateProviderFiles == true {
|
||||||
|
|
||||||
|
err = xTeVeAutoBackup()
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 1090)
|
||||||
|
}
|
||||||
|
|
||||||
|
getProviderData("m3u", "")
|
||||||
|
getProviderData("hdhr", "")
|
||||||
|
|
||||||
|
if Settings.EpgSource == "XEPG" {
|
||||||
|
getProviderData("xmltv", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
err = buildDatabaseDVR()
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buildXEPG(false)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
953
src/data.go
Normal file
@@ -0,0 +1,953 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"../src/internal/authentication"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Einstellungen ändern (WebUI)
|
||||||
|
func updateServerSettings(request RequestStruct) (settings SettingsStrcut, err error) {
|
||||||
|
|
||||||
|
var oldSettings = jsonToMap(mapToJSON(Settings))
|
||||||
|
var newSettings = jsonToMap(mapToJSON(request.Settings))
|
||||||
|
var reloadData = false
|
||||||
|
var cacheImages = false
|
||||||
|
var createXEPGFiles = false
|
||||||
|
var debug string
|
||||||
|
|
||||||
|
for key, value := range newSettings {
|
||||||
|
|
||||||
|
if _, ok := oldSettings[key]; ok {
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
|
||||||
|
case "tuner":
|
||||||
|
showWarning(2105)
|
||||||
|
|
||||||
|
case "epgSource":
|
||||||
|
reloadData = true
|
||||||
|
|
||||||
|
case "update":
|
||||||
|
// Die Formatierung der Uhrzeit überprüfen (0000 - 2359)
|
||||||
|
for _, i := range newSettings[key].([]interface{}) {
|
||||||
|
|
||||||
|
_, err := time.Parse("1504", i.(string))
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 1012)
|
||||||
|
return Settings, err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
case "cache.images":
|
||||||
|
cacheImages = true
|
||||||
|
|
||||||
|
case "xepg.replace.missing.images":
|
||||||
|
createXEPGFiles = true
|
||||||
|
|
||||||
|
case "backup.path":
|
||||||
|
value = strings.TrimRight(value.(string), string(os.PathSeparator)) + string(os.PathSeparator)
|
||||||
|
err = checkFolder(value.(string))
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
err = checkFilePermission(value.(string))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
case "temp.path":
|
||||||
|
value = strings.TrimRight(value.(string), string(os.PathSeparator)) + string(os.PathSeparator)
|
||||||
|
err = checkFolder(value.(string))
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
err = checkFilePermission(value.(string))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
oldSettings[key] = value
|
||||||
|
|
||||||
|
switch fmt.Sprintf("%T", value) {
|
||||||
|
|
||||||
|
case "bool":
|
||||||
|
debug = fmt.Sprintf("Save Setting:Key: %s | Value: %t (%T)", key, value, value)
|
||||||
|
|
||||||
|
case "string":
|
||||||
|
debug = fmt.Sprintf("Save Setting:Key: %s | Value: %s (%T)", key, value, value)
|
||||||
|
|
||||||
|
case "[]interface {}":
|
||||||
|
debug = fmt.Sprintf("Save Setting:Key: %s | Value: %v (%T)", key, value, value)
|
||||||
|
|
||||||
|
case "float64":
|
||||||
|
debug = fmt.Sprintf("Save Setting:Key: %s | Value: %d (%T)", key, int(value.(float64)), value)
|
||||||
|
|
||||||
|
default:
|
||||||
|
debug = fmt.Sprintf("%T", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
showDebug(debug, 1)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Einstellungen aktualisieren
|
||||||
|
err = json.Unmarshal([]byte(mapToJSON(oldSettings)), &Settings)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if Settings.AuthenticationWEB == false {
|
||||||
|
|
||||||
|
Settings.AuthenticationAPI = false
|
||||||
|
Settings.AuthenticationM3U = false
|
||||||
|
Settings.AuthenticationPMS = false
|
||||||
|
Settings.AuthenticationWEB = false
|
||||||
|
Settings.AuthenticationXML = false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
err = saveSettings(Settings)
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
settings = Settings
|
||||||
|
|
||||||
|
if reloadData == true {
|
||||||
|
|
||||||
|
err = buildDatabaseDVR()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buildXEPG(false)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if cacheImages == true {
|
||||||
|
|
||||||
|
if Settings.EpgSource == "XEPG" {
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
|
||||||
|
if Settings.CacheImages == true {
|
||||||
|
|
||||||
|
createXMLTVFile()
|
||||||
|
cachingImages()
|
||||||
|
createXMLTVFile()
|
||||||
|
createM3UFile()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
createXMLTVFile()
|
||||||
|
createM3UFile()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if createXEPGFiles == true {
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
createXMLTVFile()
|
||||||
|
createM3UFile()
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Providerdaten speichern (WebUI)
|
||||||
|
func saveFiles(request RequestStruct, fileType string) (err error) {
|
||||||
|
|
||||||
|
var filesMap = make(map[string]interface{})
|
||||||
|
var newData = make(map[string]interface{})
|
||||||
|
var indicator string
|
||||||
|
var reloadData = false
|
||||||
|
|
||||||
|
switch fileType {
|
||||||
|
case "m3u":
|
||||||
|
filesMap = Settings.Files.M3U
|
||||||
|
newData = request.Files.M3U
|
||||||
|
indicator = "M"
|
||||||
|
|
||||||
|
case "hdhr":
|
||||||
|
filesMap = Settings.Files.HDHR
|
||||||
|
newData = request.Files.HDHR
|
||||||
|
indicator = "H"
|
||||||
|
|
||||||
|
case "xmltv":
|
||||||
|
filesMap = Settings.Files.XMLTV
|
||||||
|
newData = request.Files.XMLTV
|
||||||
|
indicator = "X"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(filesMap) == 0 {
|
||||||
|
filesMap = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
for dataID, data := range newData {
|
||||||
|
|
||||||
|
if dataID == "-" {
|
||||||
|
|
||||||
|
// Neue Providerdatei
|
||||||
|
dataID = indicator + randomString(19)
|
||||||
|
data.(map[string]interface{})["new"] = true
|
||||||
|
filesMap[dataID] = data
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Bereits vorhandene Providerdatei
|
||||||
|
for key, value := range data.(map[string]interface{}) {
|
||||||
|
|
||||||
|
var oldData = filesMap[dataID].(map[string]interface{})
|
||||||
|
oldData[key] = value
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fileType {
|
||||||
|
|
||||||
|
case "m3u":
|
||||||
|
Settings.Files.M3U = filesMap
|
||||||
|
|
||||||
|
case "hdhr":
|
||||||
|
Settings.Files.HDHR = filesMap
|
||||||
|
|
||||||
|
case "xmltv":
|
||||||
|
Settings.Files.XMLTV = filesMap
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neue Providerdatei
|
||||||
|
if _, ok := data.(map[string]interface{})["new"]; ok {
|
||||||
|
|
||||||
|
reloadData = true
|
||||||
|
err = getProviderData(fileType, dataID)
|
||||||
|
delete(data.(map[string]interface{}), "new")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
delete(filesMap, dataID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := data.(map[string]interface{})["delete"]; ok {
|
||||||
|
|
||||||
|
deleteLocalProviderFiles(dataID, fileType)
|
||||||
|
reloadData = true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
err = saveSettings(Settings)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if reloadData == true {
|
||||||
|
|
||||||
|
err = buildDatabaseDVR()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
buildXEPG(false)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings, _ = loadSettings()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Providerdaten manuell aktualisieren (WebUI)
|
||||||
|
func updateFile(request RequestStruct, fileType string) (err error) {
|
||||||
|
|
||||||
|
var updateData = make(map[string]interface{})
|
||||||
|
|
||||||
|
switch fileType {
|
||||||
|
|
||||||
|
case "m3u":
|
||||||
|
updateData = request.Files.M3U
|
||||||
|
|
||||||
|
case "hdhr":
|
||||||
|
updateData = request.Files.HDHR
|
||||||
|
|
||||||
|
case "xmltv":
|
||||||
|
updateData = request.Files.XMLTV
|
||||||
|
}
|
||||||
|
|
||||||
|
for dataID := range updateData {
|
||||||
|
|
||||||
|
err = getProviderData(fileType, dataID)
|
||||||
|
if err == nil {
|
||||||
|
err = buildDatabaseDVR()
|
||||||
|
buildXEPG(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Providerdaten löschen (WebUI)
|
||||||
|
func deleteLocalProviderFiles(dataID, fileType string) {
|
||||||
|
|
||||||
|
var removeData = make(map[string]interface{})
|
||||||
|
var fileExtension string
|
||||||
|
|
||||||
|
switch fileType {
|
||||||
|
|
||||||
|
case "m3u":
|
||||||
|
removeData = Settings.Files.M3U
|
||||||
|
fileExtension = ".m3u"
|
||||||
|
|
||||||
|
case "hdhr":
|
||||||
|
removeData = Settings.Files.HDHR
|
||||||
|
fileExtension = ".json"
|
||||||
|
|
||||||
|
case "xmltv":
|
||||||
|
removeData = Settings.Files.XMLTV
|
||||||
|
fileExtension = ".xml"
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := removeData[dataID]; ok {
|
||||||
|
delete(removeData, dataID)
|
||||||
|
os.RemoveAll(System.Folder.Data + dataID + fileExtension)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filtereinstellungen speichern (WebUI)
|
||||||
|
func saveFilter(request RequestStruct) (settings SettingsStrcut, err error) {
|
||||||
|
|
||||||
|
var filterMap = make(map[int64]interface{})
|
||||||
|
var newData = make(map[int64]interface{})
|
||||||
|
var defaultFilter FilterStruct
|
||||||
|
|
||||||
|
defaultFilter.Active = true
|
||||||
|
defaultFilter.CaseSensitive = false
|
||||||
|
|
||||||
|
filterMap = Settings.Filter
|
||||||
|
newData = request.Filter
|
||||||
|
|
||||||
|
var createNewID = func() (id int64) {
|
||||||
|
|
||||||
|
newID:
|
||||||
|
if _, ok := filterMap[id]; ok {
|
||||||
|
id++
|
||||||
|
goto newID
|
||||||
|
}
|
||||||
|
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
for dataID, data := range newData {
|
||||||
|
|
||||||
|
if dataID == -1 {
|
||||||
|
|
||||||
|
// Neuer Filter
|
||||||
|
dataID = createNewID()
|
||||||
|
filterMap[dataID] = jsonToMap(mapToJSON(defaultFilter))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter aktualisieren / löschen
|
||||||
|
for key, value := range data.(map[string]interface{}) {
|
||||||
|
|
||||||
|
var oldData = filterMap[dataID].(map[string]interface{})
|
||||||
|
oldData[key] = value
|
||||||
|
|
||||||
|
// Filter löschen
|
||||||
|
if _, ok := data.(map[string]interface{})["delete"]; ok {
|
||||||
|
|
||||||
|
delete(filterMap, dataID)
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
err = saveSettings(Settings)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
settings = Settings
|
||||||
|
|
||||||
|
err = buildDatabaseDVR()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buildXEPG(false)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// XEPG Mapping speichern
|
||||||
|
func saveXEpgMapping(request RequestStruct) (err error) {
|
||||||
|
|
||||||
|
var tmp = Data.XEPG
|
||||||
|
|
||||||
|
err = json.Unmarshal([]byte(mapToJSON(request.EpgMapping)), &tmp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = saveMapToJSONFile(System.File.XEPG, request.EpgMapping)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
Data.XEPG.Channels = request.EpgMapping
|
||||||
|
|
||||||
|
cleanupXEPG()
|
||||||
|
buildXEPG(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benutzerdaten speichern (WebUI)
|
||||||
|
func saveUserData(request RequestStruct) (err error) {
|
||||||
|
|
||||||
|
var userData = request.UserData
|
||||||
|
|
||||||
|
var newCredentials = func(userID string, newUserData map[string]interface{}) (err error) {
|
||||||
|
|
||||||
|
var newUsername, newPassword string
|
||||||
|
if username, ok := newUserData["username"].(string); ok {
|
||||||
|
newUsername = username
|
||||||
|
}
|
||||||
|
|
||||||
|
if password, ok := newUserData["password"].(string); ok {
|
||||||
|
newPassword = password
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(newUsername) > 0 {
|
||||||
|
err = authentication.ChangeCredentials(userID, newUsername, newPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for userID, newUserData := range userData {
|
||||||
|
|
||||||
|
err = newCredentials(userID, newUserData.(map[string]interface{}))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.DeleteUser == true {
|
||||||
|
err = authentication.RemoveUser(userID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(newUserData.(map[string]interface{}), "password")
|
||||||
|
delete(newUserData.(map[string]interface{}), "confirm")
|
||||||
|
|
||||||
|
if _, ok := newUserData.(map[string]interface{})["delete"]; ok {
|
||||||
|
|
||||||
|
authentication.RemoveUser(userID)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
err = authentication.WriteUserData(userID, newUserData.(map[string]interface{}))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neuen Benutzer anlegen (WebUI)
|
||||||
|
func saveNewUser(request RequestStruct) (err error) {
|
||||||
|
|
||||||
|
var data = request.UserData
|
||||||
|
var username = data["username"].(string)
|
||||||
|
var password = data["password"].(string)
|
||||||
|
|
||||||
|
delete(data, "password")
|
||||||
|
delete(data, "confirm")
|
||||||
|
|
||||||
|
userID, err := authentication.CreateNewUser(username, password)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = authentication.WriteUserData(userID, data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wizard (WebUI)
|
||||||
|
func saveWizard(request RequestStruct) (nextStep int, err error) {
|
||||||
|
|
||||||
|
var wizard = jsonToMap(mapToJSON(request.Wizard))
|
||||||
|
|
||||||
|
for key, value := range wizard {
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
|
||||||
|
case "tuner":
|
||||||
|
Settings.Tuner = int(value.(float64))
|
||||||
|
nextStep = 1
|
||||||
|
|
||||||
|
case "epgSource":
|
||||||
|
Settings.EpgSource = value.(string)
|
||||||
|
nextStep = 2
|
||||||
|
|
||||||
|
case "m3u", "xmltv":
|
||||||
|
|
||||||
|
var filesMap = make(map[string]interface{})
|
||||||
|
var data = make(map[string]interface{})
|
||||||
|
var indicator, dataID string
|
||||||
|
|
||||||
|
filesMap = make(map[string]interface{})
|
||||||
|
|
||||||
|
data["type"] = key
|
||||||
|
data["new"] = true
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
|
||||||
|
case "m3u":
|
||||||
|
filesMap = Settings.Files.M3U
|
||||||
|
data["name"] = "M3U"
|
||||||
|
indicator = "M"
|
||||||
|
|
||||||
|
case "xmltv":
|
||||||
|
filesMap = Settings.Files.XMLTV
|
||||||
|
data["name"] = "XMLTV"
|
||||||
|
indicator = "X"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
dataID = indicator + randomString(19)
|
||||||
|
data["file.source"] = value.(string)
|
||||||
|
|
||||||
|
filesMap[dataID] = data
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case "m3u":
|
||||||
|
Settings.Files.M3U = filesMap
|
||||||
|
nextStep = 3
|
||||||
|
|
||||||
|
err = getProviderData(key, dataID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 000)
|
||||||
|
delete(filesMap, dataID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = buildDatabaseDVR()
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 000)
|
||||||
|
delete(filesMap, dataID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if Settings.EpgSource == "PMS" {
|
||||||
|
nextStep = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
case "xmltv":
|
||||||
|
Settings.Files.XMLTV = filesMap
|
||||||
|
nextStep = 10
|
||||||
|
|
||||||
|
err = getProviderData(key, dataID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
ShowError(err, 000)
|
||||||
|
delete(filesMap, dataID)
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
buildXEPG(false)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
err = saveSettings(Settings)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filterregeln erstellen
|
||||||
|
func createFilterRules() (err error) {
|
||||||
|
|
||||||
|
Data.Filter = nil
|
||||||
|
var dataFilter Filter
|
||||||
|
|
||||||
|
for _, f := range Settings.Filter {
|
||||||
|
|
||||||
|
var filter FilterStruct
|
||||||
|
|
||||||
|
var exclude, include string
|
||||||
|
|
||||||
|
err = json.Unmarshal([]byte(mapToJSON(f)), &filter)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch filter.Type {
|
||||||
|
|
||||||
|
case "custom-filter":
|
||||||
|
dataFilter.CaseSensitive = filter.CaseSensitive
|
||||||
|
dataFilter.Rule = filter.Filter
|
||||||
|
dataFilter.Type = filter.Type
|
||||||
|
|
||||||
|
Data.Filter = append(Data.Filter, dataFilter)
|
||||||
|
|
||||||
|
case "group-title":
|
||||||
|
if len(filter.Include) > 0 {
|
||||||
|
include = fmt.Sprintf(" {%s}", filter.Include)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(filter.Exclude) > 0 {
|
||||||
|
exclude = fmt.Sprintf(" !{%s}", filter.Exclude)
|
||||||
|
}
|
||||||
|
|
||||||
|
dataFilter.CaseSensitive = filter.CaseSensitive
|
||||||
|
dataFilter.Rule = fmt.Sprintf("%s%s%s", filter.Filter, include, exclude)
|
||||||
|
dataFilter.Type = filter.Type
|
||||||
|
|
||||||
|
Data.Filter = append(Data.Filter, dataFilter)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Datenbank für das DVR System erstellen
|
||||||
|
func buildDatabaseDVR() (err error) {
|
||||||
|
|
||||||
|
System.ScanInProgress = 1
|
||||||
|
|
||||||
|
Data.Streams.All = make([]interface{}, 0)
|
||||||
|
Data.Streams.Active = make([]interface{}, 0)
|
||||||
|
Data.Streams.Inactive = make([]interface{}, 0)
|
||||||
|
Data.Playlist.M3U.Groups.Text = []string{}
|
||||||
|
Data.Playlist.M3U.Groups.Value = []string{}
|
||||||
|
Data.StreamPreviewUI.Active = []string{}
|
||||||
|
Data.StreamPreviewUI.Inactive = []string{}
|
||||||
|
|
||||||
|
var availableFileTypes = []string{"m3u", "hdhr"}
|
||||||
|
|
||||||
|
var tmpGroupsM3U = make(map[string]int64)
|
||||||
|
|
||||||
|
err = createFilterRules()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fileType := range availableFileTypes {
|
||||||
|
|
||||||
|
var playlistFile = getLocalProviderFiles(fileType)
|
||||||
|
|
||||||
|
for n, i := range playlistFile {
|
||||||
|
|
||||||
|
var channels []interface{}
|
||||||
|
var groupTitle, tvgID, uuid int = 0, 0, 0
|
||||||
|
var keys = []string{"group-title", "tvg-id", "uuid"}
|
||||||
|
var compatibility = make(map[string]int)
|
||||||
|
|
||||||
|
var id = strings.TrimSuffix(getFilenameFromPath(i), path.Ext(getFilenameFromPath(i)))
|
||||||
|
var playlistName = getProviderParameter(id, fileType, "name")
|
||||||
|
|
||||||
|
switch fileType {
|
||||||
|
|
||||||
|
case "m3u":
|
||||||
|
channels, err = parsePlaylist(i, fileType)
|
||||||
|
case "hdhr":
|
||||||
|
channels, err = parsePlaylist(i, fileType)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 1005)
|
||||||
|
err = errors.New(playlistName + ": Local copy of the file no longer exists")
|
||||||
|
ShowError(err, 0)
|
||||||
|
playlistFile = append(playlistFile[:n], playlistFile[n+1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Streams analysieren
|
||||||
|
for _, stream := range channels {
|
||||||
|
|
||||||
|
var s = stream.(map[string]string)
|
||||||
|
s["_file.m3u.path"] = i
|
||||||
|
s["_file.m3u.name"] = playlistName
|
||||||
|
s["_file.m3u.id"] = id
|
||||||
|
|
||||||
|
// Kompatibilität berechnen
|
||||||
|
for _, key := range keys {
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case "uuid":
|
||||||
|
if value, ok := s["_uuid.key"]; ok {
|
||||||
|
if len(value) > 0 {
|
||||||
|
uuid++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case "group-title":
|
||||||
|
if value, ok := s[key]; ok {
|
||||||
|
if len(value) > 0 {
|
||||||
|
|
||||||
|
if _, ok := tmpGroupsM3U[value]; ok {
|
||||||
|
tmpGroupsM3U[value]++
|
||||||
|
} else {
|
||||||
|
tmpGroupsM3U[value] = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
groupTitle++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case "tvg-id":
|
||||||
|
if value, ok := s[key]; ok {
|
||||||
|
if len(value) > 0 {
|
||||||
|
tvgID++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Data.Streams.All = append(Data.Streams.All, stream)
|
||||||
|
|
||||||
|
// Neuer Filter ab Version 1.3.0
|
||||||
|
var preview string
|
||||||
|
var status = filterThisStream(stream)
|
||||||
|
|
||||||
|
if name, ok := s["name"]; ok {
|
||||||
|
var group string
|
||||||
|
|
||||||
|
if v, ok := s["group-title"]; ok {
|
||||||
|
group = v
|
||||||
|
}
|
||||||
|
|
||||||
|
preview = fmt.Sprintf("%s [%s]", name, group)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
switch status {
|
||||||
|
|
||||||
|
case true:
|
||||||
|
Data.StreamPreviewUI.Active = append(Data.StreamPreviewUI.Active, preview)
|
||||||
|
Data.Streams.Active = append(Data.Streams.Active, stream)
|
||||||
|
|
||||||
|
case false:
|
||||||
|
Data.StreamPreviewUI.Inactive = append(Data.StreamPreviewUI.Inactive, preview)
|
||||||
|
Data.Streams.Inactive = append(Data.Streams.Inactive, stream)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if tvgID == 0 {
|
||||||
|
compatibility["tvg.id"] = 0
|
||||||
|
} else {
|
||||||
|
compatibility["tvg.id"] = int(tvgID * 100 / len(channels))
|
||||||
|
}
|
||||||
|
|
||||||
|
if groupTitle == 0 {
|
||||||
|
compatibility["group.title"] = 0
|
||||||
|
} else {
|
||||||
|
compatibility["group.title"] = int(groupTitle * 100 / len(channels))
|
||||||
|
}
|
||||||
|
|
||||||
|
if uuid == 0 {
|
||||||
|
compatibility["stream.id"] = 0
|
||||||
|
} else {
|
||||||
|
compatibility["stream.id"] = int(uuid * 100 / len(channels))
|
||||||
|
}
|
||||||
|
|
||||||
|
compatibility["streams"] = len(channels)
|
||||||
|
|
||||||
|
setProviderCompatibility(id, fileType, compatibility)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for group, count := range tmpGroupsM3U {
|
||||||
|
var text = fmt.Sprintf("%s (%d)", group, count)
|
||||||
|
var value = fmt.Sprintf("%s", group)
|
||||||
|
Data.Playlist.M3U.Groups.Text = append(Data.Playlist.M3U.Groups.Text, text)
|
||||||
|
Data.Playlist.M3U.Groups.Value = append(Data.Playlist.M3U.Groups.Value, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(Data.Playlist.M3U.Groups.Text)
|
||||||
|
sort.Strings(Data.Playlist.M3U.Groups.Value)
|
||||||
|
|
||||||
|
if len(Data.Streams.Active) == 0 && len(Data.Streams.All) <= System.DVRLimit && len(Settings.Filter) == 0 {
|
||||||
|
Data.Streams.Active = Data.Streams.All
|
||||||
|
Data.Streams.Inactive = make([]interface{}, 0)
|
||||||
|
|
||||||
|
Data.StreamPreviewUI.Active = Data.StreamPreviewUI.Inactive
|
||||||
|
Data.StreamPreviewUI.Inactive = []string{}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(Data.Streams.Active) > System.DVRLimit {
|
||||||
|
showWarning(2000)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(Settings.Filter) == 0 && len(Data.Streams.All) > System.DVRLimit {
|
||||||
|
showWarning(2001)
|
||||||
|
}
|
||||||
|
|
||||||
|
System.ScanInProgress = 0
|
||||||
|
showInfo(fmt.Sprintf("All streams:%d", len(Data.Streams.All)))
|
||||||
|
showInfo(fmt.Sprintf("Active streams:%d", len(Data.Streams.Active)))
|
||||||
|
showInfo(fmt.Sprintf("Filter:%d", len(Data.Filter)))
|
||||||
|
|
||||||
|
sort.Strings(Data.StreamPreviewUI.Active)
|
||||||
|
sort.Strings(Data.StreamPreviewUI.Inactive)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Speicherort aller lokalen Providerdateien laden, immer für eine Dateityp (M3U, XMLTV usw.)
|
||||||
|
func getLocalProviderFiles(fileType string) (localFiles []string) {
|
||||||
|
|
||||||
|
var fileExtension string
|
||||||
|
var dataMap = make(map[string]interface{})
|
||||||
|
|
||||||
|
switch fileType {
|
||||||
|
|
||||||
|
case "m3u":
|
||||||
|
fileExtension = ".m3u"
|
||||||
|
dataMap = Settings.Files.M3U
|
||||||
|
|
||||||
|
case "hdhr":
|
||||||
|
fileExtension = ".json"
|
||||||
|
dataMap = Settings.Files.HDHR
|
||||||
|
|
||||||
|
case "xmltv":
|
||||||
|
fileExtension = ".xml"
|
||||||
|
dataMap = Settings.Files.XMLTV
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for dataID := range dataMap {
|
||||||
|
localFiles = append(localFiles, System.Folder.Data+dataID+fileExtension)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Providerparameter anhand von dem Key ausgeben
|
||||||
|
func getProviderParameter(id, fileType, key string) (s string) {
|
||||||
|
|
||||||
|
var dataMap = make(map[string]interface{})
|
||||||
|
|
||||||
|
switch fileType {
|
||||||
|
case "m3u":
|
||||||
|
dataMap = Settings.Files.M3U
|
||||||
|
|
||||||
|
case "hdhr":
|
||||||
|
dataMap = Settings.Files.HDHR
|
||||||
|
|
||||||
|
case "xmltv":
|
||||||
|
dataMap = Settings.Files.XMLTV
|
||||||
|
}
|
||||||
|
|
||||||
|
if data, ok := dataMap[id].(map[string]interface{}); ok {
|
||||||
|
|
||||||
|
if v, ok := data[key].(string); ok {
|
||||||
|
s = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := data[key].(float64); ok {
|
||||||
|
s = strconv.Itoa(int(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider Statistiken Kompatibilität aktualisieren
|
||||||
|
func setProviderCompatibility(id, fileType string, compatibility map[string]int) {
|
||||||
|
|
||||||
|
var dataMap = make(map[string]interface{})
|
||||||
|
|
||||||
|
switch fileType {
|
||||||
|
case "m3u":
|
||||||
|
dataMap = Settings.Files.M3U
|
||||||
|
|
||||||
|
case "hdhr":
|
||||||
|
dataMap = Settings.Files.HDHR
|
||||||
|
|
||||||
|
case "xmltv":
|
||||||
|
dataMap = Settings.Files.XMLTV
|
||||||
|
}
|
||||||
|
|
||||||
|
if data, ok := dataMap[id].(map[string]interface{}); ok {
|
||||||
|
|
||||||
|
data["compatibility"] = compatibility
|
||||||
|
|
||||||
|
switch fileType {
|
||||||
|
case "m3u":
|
||||||
|
Settings.Files.M3U = dataMap
|
||||||
|
case "hdhr":
|
||||||
|
Settings.Files.HDHR = dataMap
|
||||||
|
case "xmltv":
|
||||||
|
Settings.Files.XMLTV = dataMap
|
||||||
|
}
|
||||||
|
|
||||||
|
saveSettings(Settings)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
236
src/hdhr.go
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeInteraceFromHDHR(content []byte, playlistName, id string) (channels []interface{}, err error) {
|
||||||
|
|
||||||
|
var hdhrData []interface{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(content, &hdhrData)
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
for _, d := range hdhrData {
|
||||||
|
|
||||||
|
var channel = make(map[string]string)
|
||||||
|
var data = d.(map[string]interface{})
|
||||||
|
|
||||||
|
channel["group-title"] = playlistName
|
||||||
|
channel["name"] = data["GuideName"].(string)
|
||||||
|
channel["tvg-id"] = data["GuideName"].(string)
|
||||||
|
channel["url"] = data["URL"].(string)
|
||||||
|
channel["ID-"+id] = data["GuideNumber"].(string)
|
||||||
|
channel["_uuid.key"] = "ID-" + id
|
||||||
|
channel["_values"] = playlistName + " " + channel["name"]
|
||||||
|
|
||||||
|
channels = append(channels, channel)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCapability() (xmlContent []byte, err error) {
|
||||||
|
|
||||||
|
var capability Capability
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
|
capability.Xmlns = "urn:schemas-upnp-org:device-1-0"
|
||||||
|
capability.URLBase = System.ServerProtocol.WEB + "://" + System.Domain
|
||||||
|
|
||||||
|
capability.SpecVersion.Major = 1
|
||||||
|
capability.SpecVersion.Minor = 0
|
||||||
|
|
||||||
|
capability.Device.DeviceType = "urn:schemas-upnp-org:device:MediaServer:1"
|
||||||
|
capability.Device.FriendlyName = System.Name
|
||||||
|
capability.Device.Manufacturer = "Silicondust"
|
||||||
|
capability.Device.ModelName = "HDTC-2US"
|
||||||
|
capability.Device.ModelNumber = "HDTC-2US"
|
||||||
|
capability.Device.SerialNumber = ""
|
||||||
|
capability.Device.UDN = "uuid:" + System.DeviceID
|
||||||
|
|
||||||
|
output, err := xml.MarshalIndent(capability, " ", " ")
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 1003)
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.Write([]byte(xml.Header))
|
||||||
|
buffer.Write([]byte(output))
|
||||||
|
xmlContent = buffer.Bytes()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDiscover() (jsonContent []byte, err error) {
|
||||||
|
|
||||||
|
var discover Discover
|
||||||
|
|
||||||
|
discover.BaseURL = System.ServerProtocol.WEB + "://" + System.Domain
|
||||||
|
discover.DeviceAuth = System.AppName
|
||||||
|
discover.DeviceID = System.DeviceID
|
||||||
|
discover.FirmwareName = "bin_" + System.Version
|
||||||
|
discover.FirmwareVersion = System.Version
|
||||||
|
discover.FriendlyName = System.Name
|
||||||
|
|
||||||
|
discover.LineupURL = fmt.Sprintf("%s://%s/lineup.json", System.ServerProtocol.DVR, System.Domain)
|
||||||
|
discover.Manufacturer = "Golang"
|
||||||
|
discover.ModelNumber = System.Version
|
||||||
|
discover.TunerCount = Settings.Tuner
|
||||||
|
|
||||||
|
jsonContent, err = json.MarshalIndent(discover, "", " ")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLineupStatus() (jsonContent []byte, err error) {
|
||||||
|
|
||||||
|
var lineupStatus LineupStatus
|
||||||
|
|
||||||
|
lineupStatus.ScanInProgress = System.ScanInProgress
|
||||||
|
lineupStatus.ScanPossible = 0
|
||||||
|
lineupStatus.Source = "Cable"
|
||||||
|
lineupStatus.SourceList = []string{"IPTV", "Cable"}
|
||||||
|
|
||||||
|
jsonContent, err = json.MarshalIndent(lineupStatus, "", " ")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLineup() (jsonContent []byte, err error) {
|
||||||
|
|
||||||
|
var lineup Lineup
|
||||||
|
|
||||||
|
switch Settings.EpgSource {
|
||||||
|
|
||||||
|
case "PMS":
|
||||||
|
for i, dsa := range Data.Streams.Active {
|
||||||
|
|
||||||
|
var m3uChannel M3UChannelStructXEPG
|
||||||
|
|
||||||
|
err = json.Unmarshal([]byte(mapToJSON(dsa)), &m3uChannel)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var stream LineupStream
|
||||||
|
stream.GuideName = m3uChannel.Name
|
||||||
|
switch len(m3uChannel.UUIDValue) {
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
stream.GuideNumber = fmt.Sprintf("%d", i+1000)
|
||||||
|
guideNumber, err := getGuideNumberPMS(stream.GuideName)
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.GuideNumber = guideNumber
|
||||||
|
|
||||||
|
default:
|
||||||
|
stream.GuideNumber = m3uChannel.UUIDValue
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.URL, err = createStreamingURL("DVR", m3uChannel.FileM3UID, stream.GuideNumber, m3uChannel.Name, m3uChannel.URL)
|
||||||
|
if err == nil {
|
||||||
|
lineup = append(lineup, stream)
|
||||||
|
} else {
|
||||||
|
ShowError(err, 1202)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
case "XEPG":
|
||||||
|
for _, dxc := range Data.XEPG.Channels {
|
||||||
|
|
||||||
|
var xepgChannel XEPGChannelStruct
|
||||||
|
err = json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if xepgChannel.XActive == true {
|
||||||
|
var stream LineupStream
|
||||||
|
stream.GuideName = xepgChannel.XName
|
||||||
|
stream.GuideNumber = xepgChannel.XChannelID
|
||||||
|
//stream.URL = fmt.Sprintf("%s://%s/stream/%s-%s", System.ServerProtocol.DVR, System.Domain, xepgChannel.FileM3UID, base64.StdEncoding.EncodeToString([]byte(xepgChannel.URL)))
|
||||||
|
stream.URL, err = createStreamingURL("DVR", xepgChannel.FileM3UID, xepgChannel.XChannelID, xepgChannel.XName, xepgChannel.URL)
|
||||||
|
if err == nil {
|
||||||
|
lineup = append(lineup, stream)
|
||||||
|
} else {
|
||||||
|
ShowError(err, 1202)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonContent, err = json.MarshalIndent(lineup, "", " ")
|
||||||
|
|
||||||
|
Data.Cache.PMS = nil
|
||||||
|
|
||||||
|
saveMapToJSONFile(System.File.URLS, Data.Cache.StreamingURLS)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGuideNumberPMS(channelName string) (pmsID string, err error) {
|
||||||
|
|
||||||
|
if len(Data.Cache.PMS) == 0 {
|
||||||
|
|
||||||
|
Data.Cache.PMS = make(map[string]string)
|
||||||
|
|
||||||
|
pms, err := loadJSONFileToMap(System.File.PMS)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range pms {
|
||||||
|
Data.Cache.PMS[key] = value.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var getNewID = func(channelName string) (id string) {
|
||||||
|
|
||||||
|
var i int
|
||||||
|
|
||||||
|
newID:
|
||||||
|
|
||||||
|
var ids []string
|
||||||
|
id = fmt.Sprintf("id-%d", i)
|
||||||
|
|
||||||
|
for _, v := range Data.Cache.PMS {
|
||||||
|
ids = append(ids, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if indexOfString(id, ids) != -1 {
|
||||||
|
i++
|
||||||
|
goto newID
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if value, ok := Data.Cache.PMS[channelName]; ok {
|
||||||
|
|
||||||
|
pmsID = value
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
pmsID = getNewID(channelName)
|
||||||
|
Data.Cache.PMS[channelName] = pmsID
|
||||||
|
saveMapToJSONFile(System.File.PMS, Data.Cache.PMS)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
147
src/html-build.go
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var htmlFolder string
|
||||||
|
var goFile string
|
||||||
|
var mapName string
|
||||||
|
var packageName string
|
||||||
|
|
||||||
|
var blankMap = make(map[string]interface{})
|
||||||
|
|
||||||
|
// HTMLInit : Dateipfade festlegen
|
||||||
|
// mapName = Name der zu erstellenden map
|
||||||
|
// htmlFolder: Ordner der HTML Dateien
|
||||||
|
// packageName: Name des package
|
||||||
|
func HTMLInit(name, pkg, folder, file string) {
|
||||||
|
|
||||||
|
htmlFolder = folder
|
||||||
|
goFile = file
|
||||||
|
mapName = name
|
||||||
|
packageName = pkg
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildGoFile : Erstellt das GO Dokument
|
||||||
|
func BuildGoFile() error {
|
||||||
|
|
||||||
|
var err = checkHTMLFile(htmlFolder)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var content string
|
||||||
|
content += `package ` + packageName + "\n\n"
|
||||||
|
content += `var ` + mapName + ` = make(map[string]interface{})` + "\n\n"
|
||||||
|
content += "func loadHTMLMap() {" + "\n\n"
|
||||||
|
|
||||||
|
content += createMapFromFiles(htmlFolder) + "\n"
|
||||||
|
|
||||||
|
content += "}" + "\n\n"
|
||||||
|
writeStringToFile(goFile, content)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHTMLString : base64 -> string
|
||||||
|
func GetHTMLString(base string) string {
|
||||||
|
content, _ := base64.StdEncoding.DecodeString(base)
|
||||||
|
return string(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMapFromFiles(folder string) string {
|
||||||
|
|
||||||
|
var path = getLocalPath(folder)
|
||||||
|
|
||||||
|
err := filepath.Walk(path, readFilesToMap)
|
||||||
|
if err != nil {
|
||||||
|
checkErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var content string
|
||||||
|
|
||||||
|
for key := range blankMap {
|
||||||
|
var newKey = key
|
||||||
|
content += ` ` + mapName + `["` + newKey + `"` + `] = "` + blankMap[key].(string) + `"` + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFilesToMap(path string, info os.FileInfo, err error) error {
|
||||||
|
|
||||||
|
if info.IsDir() == false {
|
||||||
|
var base64Str = fileToBase64(getLocalPath(path))
|
||||||
|
blankMap[path] = base64Str
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileToBase64(file string) string {
|
||||||
|
|
||||||
|
imgFile, _ := os.Open(file)
|
||||||
|
defer imgFile.Close()
|
||||||
|
|
||||||
|
// create a new buffer base on file size
|
||||||
|
fInfo, _ := imgFile.Stat()
|
||||||
|
var size = fInfo.Size()
|
||||||
|
buf := make([]byte, int64(size))
|
||||||
|
|
||||||
|
// read file content into buffer
|
||||||
|
fReader := bufio.NewReader(imgFile)
|
||||||
|
fReader.Read(buf)
|
||||||
|
|
||||||
|
imgBase64Str := base64.StdEncoding.EncodeToString(buf)
|
||||||
|
|
||||||
|
return imgBase64Str
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLocalPath(filename string) string {
|
||||||
|
|
||||||
|
path, file := filepath.Split(filename)
|
||||||
|
var newPath = filepath.Dir(path)
|
||||||
|
|
||||||
|
var newFileName = newPath + "/" + file
|
||||||
|
|
||||||
|
return newFileName
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeStringToFile(filename, content string) error {
|
||||||
|
|
||||||
|
err := ioutil.WriteFile(getPlatformFile(filename), []byte(content), 0644)
|
||||||
|
if err != nil {
|
||||||
|
checkErr(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkHTMLFile(filename string) error {
|
||||||
|
|
||||||
|
if _, err := os.Stat(getLocalPath(filename)); os.IsNotExist(err) {
|
||||||
|
fmt.Println(filename)
|
||||||
|
checkErr(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkErr(err error) {
|
||||||
|
if err != nil {
|
||||||
|
_, file, line, _ := runtime.Caller(1)
|
||||||
|
log.Println("ERROR: [", err, "] in ", file, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
153
src/images.go
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
b64 "encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getCacheImageURL(url string) (cacheImageURL string) {
|
||||||
|
|
||||||
|
url = strings.Trim(url, "\r\n")
|
||||||
|
|
||||||
|
var urlMD5 = getMD5(url)
|
||||||
|
var fileExtension = filepath.Ext(url)
|
||||||
|
|
||||||
|
if indexOfString(urlMD5+fileExtension, Data.Cache.ImagesFiles) == -1 {
|
||||||
|
Data.Cache.ImagesFiles = append(Data.Cache.ImagesFiles, urlMD5+fileExtension)
|
||||||
|
}
|
||||||
|
|
||||||
|
if Settings.CacheImages == false || System.ImageCachingInProgress == 1 {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
if indexOfString(urlMD5+fileExtension, Data.Cache.ImagesCache) != -1 {
|
||||||
|
|
||||||
|
cacheImageURL = fmt.Sprintf("%s://%s/images/%s%s", System.ServerProtocol.WEB, System.Domain, urlMD5, fileExtension)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if strings.Contains(url, System.Domain+"/images/") == false {
|
||||||
|
|
||||||
|
if indexOfString(url, Data.Cache.ImagesURLS) == -1 {
|
||||||
|
Data.Cache.ImagesURLS = append(Data.Cache.ImagesURLS, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheImageURL = url
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func cachingImages() {
|
||||||
|
|
||||||
|
if Settings.CacheImages == false || System.ImageCachingInProgress == 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
System.ImageCachingInProgress = 1
|
||||||
|
|
||||||
|
showInfo("Image Caching:Images are cached")
|
||||||
|
|
||||||
|
for _, url := range Data.Cache.ImagesURLS {
|
||||||
|
|
||||||
|
if len(url) > 0 {
|
||||||
|
cacheImage(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
showInfo("Image Caching:Done")
|
||||||
|
|
||||||
|
// Bilder die nicht mehr verwendet werden, werden gelöscht
|
||||||
|
files, err := ioutil.ReadDir(System.Folder.ImagesCache)
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
|
||||||
|
if indexOfString(file.Name(), Data.Cache.ImagesFiles) == -1 {
|
||||||
|
|
||||||
|
var debug = fmt.Sprintf("Image Caching:Remove file: %s %s %d", System.Folder.ImagesCache+file.Name(), file.Name(), len(file.Name()))
|
||||||
|
showDebug(debug, 1)
|
||||||
|
err := os.RemoveAll(System.Folder.ImagesCache + file.Name())
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
System.ImageCachingInProgress = 0
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func cacheImage(url string) {
|
||||||
|
|
||||||
|
var debug string
|
||||||
|
var urlMD5 = getMD5(url)
|
||||||
|
var fileExtension = filepath.Ext(url)
|
||||||
|
|
||||||
|
debug = fmt.Sprintf("Image Caching:File: %s Download: %s", urlMD5+fileExtension, url)
|
||||||
|
showDebug(debug, 1)
|
||||||
|
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var filePath = System.Folder.ImagesCache + urlMD5 + fileExtension
|
||||||
|
|
||||||
|
// Datei speichern
|
||||||
|
file, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(file, resp.Body)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func uploadLogo(input, filename string) (logoURL string, err error) {
|
||||||
|
|
||||||
|
b64data := input[strings.IndexByte(input, ',')+1:]
|
||||||
|
|
||||||
|
// BAse64 in bytes umwandeln un speichern
|
||||||
|
sDec, err := b64.StdEncoding.DecodeString(b64data)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var file = fmt.Sprintf("%s%s", System.Folder.ImagesUpload, filename)
|
||||||
|
|
||||||
|
err = writeByteToFile(file, sDec)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logoURL = fmt.Sprintf("%s://%s/data_images/%s", System.ServerProtocol.WEB, System.Domain, filename)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
592
src/internal/authentication/authentication.go
Executable file
@@ -0,0 +1,592 @@
|
|||||||
|
package authentication
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
|
||||||
|
"time"
|
||||||
|
//"fmt"
|
||||||
|
//"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const tokenLength = 40
|
||||||
|
const saltLength = 20
|
||||||
|
const idLength = 10
|
||||||
|
|
||||||
|
var tokenValidity int
|
||||||
|
var database string
|
||||||
|
|
||||||
|
var databaseFile = "authentication.json"
|
||||||
|
|
||||||
|
var data = make(map[string]interface{})
|
||||||
|
var tokens = make(map[string]interface{})
|
||||||
|
|
||||||
|
var initAuthentication = false
|
||||||
|
|
||||||
|
// Cookie : cookie
|
||||||
|
type Cookie struct {
|
||||||
|
Name string
|
||||||
|
Value string
|
||||||
|
Path string
|
||||||
|
Domain string
|
||||||
|
Expires time.Time
|
||||||
|
RawExpires string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Framework examples
|
||||||
|
|
||||||
|
/*
|
||||||
|
func main() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
var checkErr = func(err error) {
|
||||||
|
log.Println(err)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = Init("", 10) // Path to save the data, Validity of tokens in minutes | (error)
|
||||||
|
if err != nil {
|
||||||
|
checkErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
err = CreateDefaultUser("admin", "123")
|
||||||
|
if err != nil {
|
||||||
|
checkErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
err = CreateNewUser("xteve", "xteve") // Username, Password | (error)
|
||||||
|
if err != nil {
|
||||||
|
checkErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
err, token := UserAuthentication("xteve", "xteve") // Username, Password | (error, token)
|
||||||
|
if err != nil {
|
||||||
|
checkErr(err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("UserAuthentication()")
|
||||||
|
fmt.Println("Token:", token)
|
||||||
|
fmt.Println("---")
|
||||||
|
}
|
||||||
|
|
||||||
|
err, newToken := CheckTheValidityOfTheToken(token) // Current token | (error, new token)
|
||||||
|
if err != nil {
|
||||||
|
checkErr(err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("CheckTheValidityOfTheToken()")
|
||||||
|
fmt.Println("New Token:", newToken)
|
||||||
|
fmt.Println("---")
|
||||||
|
}
|
||||||
|
|
||||||
|
err, userID := GetUserID(newToken) // Current token | (error, user id)
|
||||||
|
if err != nil {
|
||||||
|
checkErr(err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("GetUserID()")
|
||||||
|
fmt.Println("User ID:", userID)
|
||||||
|
fmt.Println("---")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var userData = make(map[string]interface{})
|
||||||
|
userData["type"] = "Administrator"
|
||||||
|
err = WriteUserData(userID, userData) // User id, user data | (error)
|
||||||
|
if err != nil {
|
||||||
|
checkErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err, userData = ReadUserData(userID) // User id | (error, userData)
|
||||||
|
if err != nil {
|
||||||
|
checkErr(err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("ReadUserData()")
|
||||||
|
fmt.Println("User data:", userData)
|
||||||
|
fmt.Println("---")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = RemoveUser(userID)
|
||||||
|
if err != nil {
|
||||||
|
checkErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Init : databasePath = Path to authentication.json
|
||||||
|
func Init(databasePath string, validity int) (err error) {
|
||||||
|
database = filepath.Dir(databasePath) + string(os.PathSeparator) + databaseFile
|
||||||
|
|
||||||
|
// Check if the database already exists
|
||||||
|
if _, err = os.Stat(database); os.IsNotExist(err) {
|
||||||
|
// Create an empty database
|
||||||
|
var defaults = make(map[string]interface{})
|
||||||
|
defaults["dbVersion"] = "1.0"
|
||||||
|
defaults["hash"] = "sha256"
|
||||||
|
defaults["users"] = make(map[string]interface{})
|
||||||
|
|
||||||
|
if saveDatabase(defaults) != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loading the database
|
||||||
|
err = loadDatabase()
|
||||||
|
|
||||||
|
// Set Token Validity
|
||||||
|
tokenValidity = validity
|
||||||
|
initAuthentication = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDefaultUser = created efault user
|
||||||
|
func CreateDefaultUser(username, password string) (err error) {
|
||||||
|
|
||||||
|
err = checkInit()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var users = data["users"].(map[string]interface{})
|
||||||
|
// Check if the default user exists
|
||||||
|
if len(users) > 0 {
|
||||||
|
err = createError(001)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaults = defaultsForNewUser(username, password)
|
||||||
|
users[defaults["_id"].(string)] = defaults
|
||||||
|
saveDatabase(data)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNewUser : create new user
|
||||||
|
func CreateNewUser(username, password string) (userID string, err error) {
|
||||||
|
|
||||||
|
err = checkInit()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var checkIfTheUserAlreadyExists = func(username string, userData map[string]interface{}) (err error) {
|
||||||
|
var salt = userData["_salt"].(string)
|
||||||
|
var loginUsername = userData["_username"].(string)
|
||||||
|
|
||||||
|
if SHA256(username, salt) == loginUsername {
|
||||||
|
err = createError(020)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var users = data["users"].(map[string]interface{})
|
||||||
|
for _, userData := range users {
|
||||||
|
err = checkIfTheUserAlreadyExists(username, userData.(map[string]interface{}))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaults = defaultsForNewUser(username, password)
|
||||||
|
userID = defaults["_id"].(string)
|
||||||
|
users[userID] = defaults
|
||||||
|
|
||||||
|
saveDatabase(data)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserAuthentication : user authentication
|
||||||
|
func UserAuthentication(username, password string) (token string, err error) {
|
||||||
|
|
||||||
|
err = checkInit()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var login = func(username, password string, loginData map[string]interface{}) (err error) {
|
||||||
|
err = createError(010)
|
||||||
|
|
||||||
|
var salt = loginData["_salt"].(string)
|
||||||
|
var loginUsername = loginData["_username"].(string)
|
||||||
|
var loginPassword = loginData["_password"].(string)
|
||||||
|
|
||||||
|
if SHA256(username, salt) == loginUsername {
|
||||||
|
if SHA256(password, salt) == loginPassword {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var users = data["users"].(map[string]interface{})
|
||||||
|
for id, loginData := range users {
|
||||||
|
err = login(username, password, loginData.(map[string]interface{}))
|
||||||
|
if err == nil {
|
||||||
|
token = setToken(id, "-")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckTheValidityOfTheToken : check token
|
||||||
|
func CheckTheValidityOfTheToken(token string) (newToken string, err error) {
|
||||||
|
|
||||||
|
err = checkInit()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = createError(011)
|
||||||
|
|
||||||
|
if v, ok := tokens[token]; ok {
|
||||||
|
var expires = v.(map[string]interface{})["expires"].(time.Time)
|
||||||
|
var userID = v.(map[string]interface{})["id"].(string)
|
||||||
|
|
||||||
|
if expires.Sub(time.Now().Local()) < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newToken = setToken(userID, token)
|
||||||
|
|
||||||
|
err = nil
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserID : get user ID
|
||||||
|
func GetUserID(token string) (userID string, err error) {
|
||||||
|
|
||||||
|
err = checkInit()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = createError(002)
|
||||||
|
|
||||||
|
if v, ok := tokens[token]; ok {
|
||||||
|
var expires = v.(map[string]interface{})["expires"].(time.Time)
|
||||||
|
userID = v.(map[string]interface{})["id"].(string)
|
||||||
|
|
||||||
|
if expires.Sub(time.Now().Local()) < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteUserData : save user date
|
||||||
|
func WriteUserData(userID string, userData map[string]interface{}) (err error) {
|
||||||
|
|
||||||
|
err = checkInit()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = createError(030)
|
||||||
|
|
||||||
|
if v, ok := data["users"].(map[string]interface{})[userID].(map[string]interface{}); ok {
|
||||||
|
|
||||||
|
v["data"] = userData
|
||||||
|
err = saveDatabase(data)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUserData : load user date
|
||||||
|
func ReadUserData(userID string) (userData map[string]interface{}, err error) {
|
||||||
|
|
||||||
|
err = checkInit()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = createError(031)
|
||||||
|
|
||||||
|
if v, ok := data["users"].(map[string]interface{})[userID].(map[string]interface{}); ok {
|
||||||
|
userData = v["data"].(map[string]interface{})
|
||||||
|
err = nil
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveUser : remove user
|
||||||
|
func RemoveUser(userID string) (err error) {
|
||||||
|
|
||||||
|
err = checkInit()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = createError(032)
|
||||||
|
|
||||||
|
if _, ok := data["users"].(map[string]interface{})[userID]; ok {
|
||||||
|
|
||||||
|
delete(data["users"].(map[string]interface{}), userID)
|
||||||
|
err = saveDatabase(data)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaultUserData : set default user data
|
||||||
|
func SetDefaultUserData(defaults map[string]interface{}) (err error) {
|
||||||
|
|
||||||
|
allUserData, err := GetAllUserData()
|
||||||
|
|
||||||
|
for _, d := range allUserData {
|
||||||
|
var data = d.(map[string]interface{})["data"].(map[string]interface{})
|
||||||
|
var userID = d.(map[string]interface{})["_id"].(string)
|
||||||
|
|
||||||
|
for k, v := range defaults {
|
||||||
|
if _, ok := data[k]; ok {
|
||||||
|
// Key exist
|
||||||
|
} else {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = WriteUserData(userID, data)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeCredentials : change credentials
|
||||||
|
func ChangeCredentials(userID, username, password string) (err error) {
|
||||||
|
err = checkInit()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = createError(032)
|
||||||
|
|
||||||
|
if userData, ok := data["users"].(map[string]interface{})[userID]; ok {
|
||||||
|
//var userData = tmp.(map[string]interface{})
|
||||||
|
var salt = userData.(map[string]interface{})["_salt"].(string)
|
||||||
|
|
||||||
|
if len(username) > 0 {
|
||||||
|
userData.(map[string]interface{})["_username"] = SHA256(username, salt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(password) > 0 {
|
||||||
|
userData.(map[string]interface{})["_password"] = SHA256(password, salt)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = saveDatabase(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllUserData : get all user data
|
||||||
|
func GetAllUserData() (allUserData map[string]interface{}, err error) {
|
||||||
|
|
||||||
|
err = checkInit()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) == 0 {
|
||||||
|
var defaults = make(map[string]interface{})
|
||||||
|
defaults["dbVersion"] = "1.0"
|
||||||
|
defaults["hash"] = "sha256"
|
||||||
|
defaults["users"] = make(map[string]interface{})
|
||||||
|
saveDatabase(defaults)
|
||||||
|
data = defaults
|
||||||
|
}
|
||||||
|
|
||||||
|
allUserData = data["users"].(map[string]interface{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckTheValidityOfTheTokenFromHTTPHeader : get token from HTTP header
|
||||||
|
func CheckTheValidityOfTheTokenFromHTTPHeader(w http.ResponseWriter, r *http.Request) (writer http.ResponseWriter, newToken string, err error) {
|
||||||
|
err = createError(011)
|
||||||
|
for _, cookie := range r.Cookies() {
|
||||||
|
if cookie.Name == "Token" {
|
||||||
|
var token string
|
||||||
|
token, err = CheckTheValidityOfTheToken(cookie.Value)
|
||||||
|
//fmt.Println("T", token, err)
|
||||||
|
writer = SetCookieToken(w, token)
|
||||||
|
newToken = token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Framework tools
|
||||||
|
|
||||||
|
func checkInit() (err error) {
|
||||||
|
if initAuthentication == false {
|
||||||
|
err = createError(000)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveDatabase(tmpMap interface{}) (err error) {
|
||||||
|
|
||||||
|
jsonString, err := json.MarshalIndent(tmpMap, "", " ")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(database, []byte(jsonString), 0600)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadDatabase() (err error) {
|
||||||
|
jsonString, err := ioutil.ReadFile(database)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal([]byte(jsonString), &data)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SHA256 : password + salt = sha256 string
|
||||||
|
func SHA256(secret, salt string) string {
|
||||||
|
key := []byte(secret)
|
||||||
|
h := hmac.New(sha256.New, key)
|
||||||
|
h.Write([]byte("_remote_db"))
|
||||||
|
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomString(n int) string {
|
||||||
|
const alphanum = "-AbCdEfGhIjKlMnOpQrStUvWxYz0123456789aBcDeFgHiJkLmNoPqRsTuVwXyZ_"
|
||||||
|
|
||||||
|
var bytes = make([]byte, n)
|
||||||
|
rand.Read(bytes)
|
||||||
|
for i, b := range bytes {
|
||||||
|
bytes[i] = alphanum[b%byte(len(alphanum))]
|
||||||
|
}
|
||||||
|
return string(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomID(n int) string {
|
||||||
|
const alphanum = "ABCDEFGHJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
|
||||||
|
var bytes = make([]byte, n)
|
||||||
|
rand.Read(bytes)
|
||||||
|
for i, b := range bytes {
|
||||||
|
bytes[i] = alphanum[b%byte(len(alphanum))]
|
||||||
|
}
|
||||||
|
return string(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createError(errCode int) (err error) {
|
||||||
|
var errMsg string
|
||||||
|
switch errCode {
|
||||||
|
case 000:
|
||||||
|
errMsg = "Authentication has not yet been initialized"
|
||||||
|
case 001:
|
||||||
|
errMsg = "Default user already exists"
|
||||||
|
case 002:
|
||||||
|
errMsg = "No user id found for this token"
|
||||||
|
case 010:
|
||||||
|
errMsg = "User authentication failed"
|
||||||
|
case 011:
|
||||||
|
errMsg = "Session has expired"
|
||||||
|
case 020:
|
||||||
|
errMsg = "User already exists"
|
||||||
|
case 030:
|
||||||
|
errMsg = "User data could not be saved"
|
||||||
|
case 031:
|
||||||
|
errMsg = "User data could not be read"
|
||||||
|
case 032:
|
||||||
|
errMsg = "User ID was not found"
|
||||||
|
}
|
||||||
|
|
||||||
|
err = errors.New(errMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultsForNewUser(username, password string) map[string]interface{} {
|
||||||
|
var defaults = make(map[string]interface{})
|
||||||
|
var salt = randomString(saltLength)
|
||||||
|
defaults["_username"] = SHA256(username, salt)
|
||||||
|
defaults["_password"] = SHA256(password, salt)
|
||||||
|
defaults["_salt"] = salt
|
||||||
|
defaults["_id"] = "id-" + randomID(idLength)
|
||||||
|
//defaults["_one.time.token"] = randomString(tokenLength)
|
||||||
|
defaults["data"] = make(map[string]interface{})
|
||||||
|
|
||||||
|
return defaults
|
||||||
|
}
|
||||||
|
|
||||||
|
func setToken(id, oldToken string) (newToken string) {
|
||||||
|
delete(tokens, oldToken)
|
||||||
|
|
||||||
|
loopToken:
|
||||||
|
newToken = randomString(tokenLength)
|
||||||
|
if _, ok := tokens[newToken]; ok {
|
||||||
|
goto loopToken
|
||||||
|
}
|
||||||
|
|
||||||
|
var tmp = make(map[string]interface{})
|
||||||
|
tmp["id"] = id
|
||||||
|
tmp["expires"] = time.Now().Local().Add(time.Minute * time.Duration(tokenValidity))
|
||||||
|
|
||||||
|
tokens[newToken] = tmp
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapToJSON(tmpMap interface{}) string {
|
||||||
|
jsonString, err := json.MarshalIndent(tmpMap, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "{}"
|
||||||
|
}
|
||||||
|
return string(jsonString)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCookieToken : set cookie
|
||||||
|
func SetCookieToken(w http.ResponseWriter, token string) http.ResponseWriter {
|
||||||
|
expiration := time.Now().Add(time.Minute * time.Duration(tokenValidity))
|
||||||
|
cookie := http.Cookie{Name: "Token", Value: token, Expires: expiration}
|
||||||
|
http.SetCookie(w, &cookie)
|
||||||
|
return w
|
||||||
|
}
|
||||||
84
src/internal/m3u-parser/m3u-parser_test.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package m3u
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type M3UStream struct {
|
||||||
|
GroupTitle string `json:"group-title,required"`
|
||||||
|
Name string `json:"name,required"`
|
||||||
|
TvgID string `json:"tvg-id,required"`
|
||||||
|
TvgLogo string `json:"tvg-logo,required"`
|
||||||
|
TvgName string `json:"tvg-name,required"`
|
||||||
|
URL string `json:"url,required"`
|
||||||
|
UUIDKey string `json:"_uuid.key,omitempty"`
|
||||||
|
UUIDValue string `json:"_uuid.value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStream1(t *testing.T) {
|
||||||
|
|
||||||
|
var file = "test_list_1.m3u"
|
||||||
|
var content, err = ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
streams, err := MakeInterfaceFromM3U(content)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = checkStream(streams)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Streams:", len(streams))
|
||||||
|
t.Log(streams)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkStream(streamInterface []interface{}) (err error) {
|
||||||
|
|
||||||
|
for i, s := range streamInterface {
|
||||||
|
|
||||||
|
var stream = s.(map[string]string)
|
||||||
|
var m3uStream M3UStream
|
||||||
|
|
||||||
|
jsonString, err := json.MarshalIndent(stream, "", " ")
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
err = json.Unmarshal(jsonString, &m3uStream)
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
log.Print(fmt.Sprintf("Stream: %d", i))
|
||||||
|
log.Print(fmt.Sprintf("Name*: %s", m3uStream.Name))
|
||||||
|
log.Print(fmt.Sprintf("URL*: %s", m3uStream.URL))
|
||||||
|
log.Print(fmt.Sprintf("tvg-name: %s", m3uStream.TvgName))
|
||||||
|
log.Print(fmt.Sprintf("tvg-id**: %s", m3uStream.TvgID))
|
||||||
|
log.Print(fmt.Sprintf("tvg-logo: %s", m3uStream.TvgLogo))
|
||||||
|
log.Print(fmt.Sprintf("group-title**: %s", m3uStream.GroupTitle))
|
||||||
|
|
||||||
|
if len(m3uStream.UUIDKey) > 0 {
|
||||||
|
log.Print(fmt.Sprintf("UUID key***: %s", m3uStream.UUIDKey))
|
||||||
|
log.Print(fmt.Sprintf("UUID value: %s", m3uStream.UUIDValue))
|
||||||
|
} else {
|
||||||
|
log.Print(fmt.Sprintf("UUID key: false"))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println(fmt.Sprintf("- - - - - (*: Required) | (**: Nice to have) | (***: Love it) - - - - -"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
7
src/internal/m3u-parser/test_list_1.m3u
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#EXTM3U url-tvg="http://example.com/file.xml" x-tvg-url="http://example.com/xteve.xml"
|
||||||
|
#EXTINF:0 channelID="1" tvg-chno="1" tvg-name="Channel.1" tvg-id="tvg.id.1" tvg-logo="https://example/logo.png" group-title="Group 1",Channel 1
|
||||||
|
http://example.com/stream/1
|
||||||
|
|
||||||
|
#EXTINF:0 channelID="2" tvg-chno="2" tvg-name="Channel.2" tvg-id="tvg.id.2" tvg-logo="https://example/logo.png" group-title="Group 2",Channel 2
|
||||||
|
#123
|
||||||
|
http://example.com/stream/2
|
||||||
267
src/internal/m3u-parser/xteve_m3uParser.go
Executable file
@@ -0,0 +1,267 @@
|
|||||||
|
package m3u
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MakeInterfaceFromM3U :
|
||||||
|
func MakeInterfaceFromM3U(byteStream []byte) (allChannels []interface{}, err error) {
|
||||||
|
|
||||||
|
var content = string(byteStream)
|
||||||
|
var channelName string
|
||||||
|
|
||||||
|
var parseMetaData = func(channel string) (stream map[string]string) {
|
||||||
|
|
||||||
|
stream = make(map[string]string)
|
||||||
|
var exceptForParameter = `[a-z-A-Z=]*(".*?")`
|
||||||
|
var exceptForChannelName = `,([^\n]*|,[^\r]*)`
|
||||||
|
|
||||||
|
var lines = strings.Split(strings.Replace(channel, "\r\n", "\n", -1), "\n")
|
||||||
|
|
||||||
|
// Zeilen mit # und leerer Zeilen entfernen
|
||||||
|
for i := len(lines) - 1; i >= 0; i-- {
|
||||||
|
|
||||||
|
if len(lines[i]) == 0 || lines[i][0:1] == "#" {
|
||||||
|
lines = append(lines[:i], lines[i+1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(lines) >= 2 {
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
|
||||||
|
_, err := url.ParseRequestURI(line)
|
||||||
|
|
||||||
|
switch err {
|
||||||
|
|
||||||
|
case nil:
|
||||||
|
stream["url"] = strings.Trim(line, "\r\n")
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
var value string
|
||||||
|
// Alle Parameter parsen
|
||||||
|
var p = regexp.MustCompile(exceptForParameter)
|
||||||
|
var streamParameter = p.FindAllString(line, -1)
|
||||||
|
|
||||||
|
for _, p := range streamParameter {
|
||||||
|
|
||||||
|
line = strings.Replace(line, p, "", 1)
|
||||||
|
|
||||||
|
p = strings.Replace(p, `"`, "", -1)
|
||||||
|
var parameter = strings.Split(p, "=")
|
||||||
|
|
||||||
|
if len(parameter) == 2 {
|
||||||
|
|
||||||
|
// TVG Key als Kleinbuchstaben speichern
|
||||||
|
switch strings.Contains(parameter[0], "tvg") {
|
||||||
|
|
||||||
|
case true:
|
||||||
|
stream[strings.ToLower(parameter[0])] = parameter[1]
|
||||||
|
case false:
|
||||||
|
stream[parameter[0]] = parameter[1]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL's nicht an die Filterfunktion übergeben
|
||||||
|
if !strings.Contains(parameter[1], "://") && len(parameter[1]) > 0 {
|
||||||
|
value = value + parameter[1] + " "
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kanalnamen parsen
|
||||||
|
n := regexp.MustCompile(exceptForChannelName)
|
||||||
|
var name = n.FindAllString(line, 1)
|
||||||
|
|
||||||
|
if len(name) > 0 {
|
||||||
|
channelName = name[0]
|
||||||
|
channelName = strings.Replace(channelName, `,`, "", 1)
|
||||||
|
channelName = strings.TrimRight(channelName, "\r\n")
|
||||||
|
channelName = strings.TrimRight(channelName, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(channelName) == 0 {
|
||||||
|
|
||||||
|
if v, ok := stream["tvg-name"]; ok {
|
||||||
|
channelName = v
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
channelName = strings.TrimRight(channelName, " ")
|
||||||
|
|
||||||
|
// Kanäle ohne Namen werden augelassen
|
||||||
|
if len(channelName) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stream["name"] = channelName
|
||||||
|
value = value + channelName
|
||||||
|
|
||||||
|
stream["_values"] = value
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nach eindeutiger ID im Stream suchen
|
||||||
|
for key, value := range stream {
|
||||||
|
|
||||||
|
if !strings.Contains(strings.ToLower(key), "tvg-id") {
|
||||||
|
|
||||||
|
if strings.Contains(strings.ToLower(key), "id") {
|
||||||
|
|
||||||
|
stream["_uuid.key"] = key
|
||||||
|
stream["_uuid.value"] = value
|
||||||
|
//os.Exit(0)
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//fmt.Println(content)
|
||||||
|
|
||||||
|
if strings.Contains(content, "#EXTM3U") {
|
||||||
|
|
||||||
|
var channels = strings.Split(content, "#EXTINF")
|
||||||
|
|
||||||
|
channels = append(channels[:0], channels[1:]...)
|
||||||
|
|
||||||
|
for _, channel := range channels {
|
||||||
|
|
||||||
|
var stream = parseMetaData(channel)
|
||||||
|
|
||||||
|
if len(stream) > 0 && stream != nil {
|
||||||
|
allChannels = append(allChannels, stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
err = errors.New("No valid m3u file")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeInterfaceFromM3U2 :
|
||||||
|
func MakeInterfaceFromM3U2(byteStream []byte) (allChannels []interface{}, err error) {
|
||||||
|
var content = string(byteStream)
|
||||||
|
//var allChannels = make([]interface{}, 0)
|
||||||
|
|
||||||
|
var channels = strings.Split(content, "#EXTINF")
|
||||||
|
|
||||||
|
var parseMetaData = func(metaData string) map[string]string {
|
||||||
|
var values string // Save all values in a key
|
||||||
|
var channel = make(map[string]string)
|
||||||
|
|
||||||
|
var exceptForParameter = `[a-z-A-Z=]*(".*?")`
|
||||||
|
//var exceptForChannelName = `(,[^.$\n]*|,[^.$\r]*)`
|
||||||
|
var exceptForChannelName = `(,[^\n]*|,[^\r]*)`
|
||||||
|
|
||||||
|
var exceptForStreamingURL = `(\n.*?\n|\r.*?\r|\n.*?\z|\r.*?\z)`
|
||||||
|
//var exceptForStreamingURL = `^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?`
|
||||||
|
|
||||||
|
// Parse all parameters
|
||||||
|
p := regexp.MustCompile(exceptForParameter)
|
||||||
|
var parameter = p.FindAllString(metaData, -1)
|
||||||
|
//fmt.Println(parameter)
|
||||||
|
for _, i := range parameter {
|
||||||
|
var remove = i
|
||||||
|
i = strings.Replace(i, `"`, "", -1)
|
||||||
|
if strings.Contains(i, "=") {
|
||||||
|
var item = strings.Split(i, "=")
|
||||||
|
switch strings.Contains(item[0], "tvg") {
|
||||||
|
case true:
|
||||||
|
channel[strings.ToLower(item[0])] = item[1]
|
||||||
|
case false:
|
||||||
|
channel[item[0]] = item[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
switch strings.Contains(item[1], "://") {
|
||||||
|
case false:
|
||||||
|
values = values + item[1] + " "
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
metaData = strings.Replace(metaData, remove, "", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse channel name (after the comma)
|
||||||
|
n := regexp.MustCompile(exceptForChannelName)
|
||||||
|
var name = n.FindAllString(metaData, 1)
|
||||||
|
//name[len(name) - 1] = strings.Replace(name[len(name) - 1], `\r`, "", -1)
|
||||||
|
|
||||||
|
var channelName string
|
||||||
|
if len(name) == 0 {
|
||||||
|
if v, ok := channel["tvg-name"]; ok {
|
||||||
|
channelName = v
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channelName = name[len(name)-1][1:len(name[len(name)-1])]
|
||||||
|
}
|
||||||
|
|
||||||
|
channelName = strings.Replace(channelName, `"`, "", -1)
|
||||||
|
|
||||||
|
var replacer = strings.NewReplacer("\n", "", "\r", "")
|
||||||
|
channel["name"] = replacer.Replace(channelName)
|
||||||
|
|
||||||
|
values = values + channelName + " "
|
||||||
|
|
||||||
|
// Parse streaming URL
|
||||||
|
u := regexp.MustCompile(exceptForStreamingURL)
|
||||||
|
var streamingURL = u.FindAllString(metaData, -1)
|
||||||
|
var url = strings.Replace(streamingURL[0], "\n", "", -1)
|
||||||
|
url = strings.Replace(url, "\r", "", -1)
|
||||||
|
url = strings.Trim(url, "\r\n")
|
||||||
|
channel["url"] = url
|
||||||
|
|
||||||
|
channel["_values"] = values
|
||||||
|
|
||||||
|
// Search for a unique ID
|
||||||
|
|
||||||
|
for key, value := range channel {
|
||||||
|
if !strings.Contains(strings.ToLower(key), "tvg-id") {
|
||||||
|
if strings.Contains(strings.ToLower(key), "id") {
|
||||||
|
channel["_uuid.key"] = key
|
||||||
|
channel["_uuid.value"] = value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return channel
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(channels[0], "#EXTM3U") {
|
||||||
|
|
||||||
|
for _, thisStream := range channels {
|
||||||
|
if !strings.Contains(thisStream, "#EXTM3U") {
|
||||||
|
var channel = parseMetaData(thisStream)
|
||||||
|
allChannels = append(allChannels, channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
err = errors.New("No valid m3u file")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
129
src/internal/up2date/client/client.go
Executable file
@@ -0,0 +1,129 @@
|
|||||||
|
package up2date
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClientInfo : Information about the key (NAME OS, ARCH, UUID, KEY)
|
||||||
|
type ClientInfo struct {
|
||||||
|
Arch string `json:"arch,required"`
|
||||||
|
Branch string `json:"branch,required"`
|
||||||
|
CMD string `json:"cmd,omitempty"`
|
||||||
|
Name string `json:"name,required"`
|
||||||
|
OS string `json:"os,required"`
|
||||||
|
URL string `json:"url,required"`
|
||||||
|
|
||||||
|
Response ServerResponse `json:"response,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//ServerResponse : Response from server after client request
|
||||||
|
type ServerResponse struct {
|
||||||
|
Status bool `json:"status,omitempty"`
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
|
UpdateBIN string `json:"update.url.bin,omitempty"`
|
||||||
|
UpdateZIP string `json:"update.url.zip,omitempty"`
|
||||||
|
Filename string `json:"filename.bin,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updater : Client infos
|
||||||
|
var Updater ClientInfo
|
||||||
|
|
||||||
|
// UpdateURL : URL for the new binary
|
||||||
|
var UpdateURL string
|
||||||
|
|
||||||
|
// Init : Init
|
||||||
|
func Init() {
|
||||||
|
Updater.OS = runtime.GOOS
|
||||||
|
Updater.Arch = runtime.GOARCH
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersion : Information about the latest version
|
||||||
|
func GetVersion() (err error) {
|
||||||
|
|
||||||
|
Updater.CMD = "getVersion"
|
||||||
|
err = serverRequest()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func serverRequest() (err error) {
|
||||||
|
|
||||||
|
var serverResponse ServerResponse
|
||||||
|
jsonByte, err := json.MarshalIndent(Updater, "", " ")
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
// Serververbindung prüfen
|
||||||
|
u, err := url.Parse(Updater.URL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var server = u.Host
|
||||||
|
|
||||||
|
timeout := time.Duration(1 * time.Second)
|
||||||
|
_, err = net.DialTimeout("tcp", server, timeout)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check redirect 301 <---> 308
|
||||||
|
redirect, err := http.NewRequest("POST", Updater.URL, nil)
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
client.CheckRedirect = func(redirect *http.Request, via []*http.Request) error {
|
||||||
|
return errors.New("Redirect")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(redirect)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// Redirect
|
||||||
|
if resp.StatusCode >= 301 && resp.StatusCode <= 308 { //status code 301 <---> 308
|
||||||
|
Updater.URL = resp.Header.Get("Location")
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ---
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", Updater.URL, bytes.NewBuffer(jsonByte))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
client = &http.Client{}
|
||||||
|
resp, err = client.Do(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
//fmt.Println(resp.StatusCode, Updater.URL, Updater.CMD)
|
||||||
|
err = fmt.Errorf(fmt.Sprintf("%d: %s (%s)", resp.StatusCode, http.StatusText(resp.StatusCode), Updater.URL))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
Updater.CMD = ""
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
err = json.Unmarshal(body, &serverResponse)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
Updater.Response = serverResponse
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
271
src/internal/up2date/client/update.go
Executable file
@@ -0,0 +1,271 @@
|
|||||||
|
package up2date
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/kardianos/osext"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DoUpdate : Update binary
|
||||||
|
func DoUpdate(fileType, filenameBIN string) (err error) {
|
||||||
|
|
||||||
|
var url string
|
||||||
|
switch fileType {
|
||||||
|
case "bin":
|
||||||
|
url = Updater.Response.UpdateBIN
|
||||||
|
case "zip":
|
||||||
|
url = Updater.Response.UpdateZIP
|
||||||
|
}
|
||||||
|
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
filenameBIN = filenameBIN + ".exe"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(url) > 0 {
|
||||||
|
log.Println("["+strings.ToUpper(fileType)+"]", "New version ("+Updater.Name+"):", Updater.Response.Version)
|
||||||
|
|
||||||
|
// Download new binary
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
log.Println("["+strings.ToUpper(fileType)+"]", "Download new version...")
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
log.Println("["+strings.ToUpper(fileType)+"]", "Download new version...OK")
|
||||||
|
return fmt.Errorf("bad status: %s", resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change binary filename to .filename
|
||||||
|
binary, err := osext.Executable()
|
||||||
|
var filename = getFilenameFromPath(binary)
|
||||||
|
var path = getPlatformPath(binary)
|
||||||
|
var oldBinary = path + "_old_" + filename
|
||||||
|
var newBinary = binary
|
||||||
|
|
||||||
|
// ZIP
|
||||||
|
var tmpFolder = path + "tmp"
|
||||||
|
var tmpFile = tmpFolder + string(os.PathSeparator) + filenameBIN
|
||||||
|
|
||||||
|
//fmt.Println(binary, path+"."+filename)
|
||||||
|
os.Rename(newBinary, oldBinary)
|
||||||
|
|
||||||
|
// Save the new binary with the old file name
|
||||||
|
out, err := os.Create(binary)
|
||||||
|
if err != nil {
|
||||||
|
restorOldBinary(oldBinary, newBinary)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
// Write the body to file
|
||||||
|
|
||||||
|
_, err = io.Copy(out, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
restorOldBinary(oldBinary, newBinary)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update as a ZIP file
|
||||||
|
if fileType == "zip" {
|
||||||
|
|
||||||
|
log.Println("["+strings.ToUpper(fileType)+"]", "Update file:", filenameBIN)
|
||||||
|
log.Println("["+strings.ToUpper(fileType)+"]", "Unzip ZIP file...")
|
||||||
|
err = extractZIP(binary, tmpFolder)
|
||||||
|
|
||||||
|
binary = newBinary
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
log.Println("["+strings.ToUpper(fileType)+"]", "Unzip ZIP file...ERROR")
|
||||||
|
|
||||||
|
restorOldBinary(oldBinary, newBinary)
|
||||||
|
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
|
||||||
|
log.Println("["+strings.ToUpper(fileType)+"]", "Unzip ZIP file...OK")
|
||||||
|
log.Println("["+strings.ToUpper(fileType)+"]", "Copy binary file...")
|
||||||
|
|
||||||
|
err = copyFile(tmpFile, binary)
|
||||||
|
if err == nil {
|
||||||
|
log.Println("["+strings.ToUpper(fileType)+"]", "Copy binary file...OK")
|
||||||
|
} else {
|
||||||
|
|
||||||
|
log.Println("["+strings.ToUpper(fileType)+"]", "Copy binary file...ERROR")
|
||||||
|
restorOldBinary(oldBinary, newBinary)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
os.RemoveAll(tmpFolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the permission
|
||||||
|
err = os.Chmod(binary, 0755)
|
||||||
|
|
||||||
|
// Close the new file !Windows
|
||||||
|
out.Close()
|
||||||
|
|
||||||
|
log.Println("["+strings.ToUpper(fileType)+"]", "Update Successful")
|
||||||
|
|
||||||
|
// Restart binary (Windows)
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
|
||||||
|
bin, err := os.Executable()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
restorOldBinary(oldBinary, newBinary)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var pid = os.Getpid()
|
||||||
|
var process, _ = os.FindProcess(pid)
|
||||||
|
|
||||||
|
if proc, err := start(bin); err == nil {
|
||||||
|
|
||||||
|
os.RemoveAll(oldBinary)
|
||||||
|
process.Kill()
|
||||||
|
proc.Wait()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
restorOldBinary(oldBinary, newBinary)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Restart binary (Linux and UNIX)
|
||||||
|
file, _ := osext.Executable()
|
||||||
|
os.RemoveAll(oldBinary)
|
||||||
|
err = syscall.Exec(file, os.Args, os.Environ())
|
||||||
|
if err != nil {
|
||||||
|
restorOldBinary(oldBinary, newBinary)
|
||||||
|
log.Fatal(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func start(args ...string) (p *os.Process, err error) {
|
||||||
|
|
||||||
|
if args[0], err = exec.LookPath(args[0]); err == nil {
|
||||||
|
//fmt.Println(args[0])
|
||||||
|
var procAttr os.ProcAttr
|
||||||
|
procAttr.Files = []*os.File{os.Stdin, os.Stdout, os.Stderr}
|
||||||
|
p, err := os.StartProcess(args[0], args, &procAttr)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func restorOldBinary(oldBinary, newBinary string) {
|
||||||
|
os.RemoveAll(newBinary)
|
||||||
|
os.Rename(oldBinary, newBinary)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPlatformFile(filename string) string {
|
||||||
|
|
||||||
|
path, file := filepath.Split(filename)
|
||||||
|
var newPath = filepath.Dir(path)
|
||||||
|
var newFileName = newPath + string(os.PathSeparator) + file
|
||||||
|
|
||||||
|
return newFileName
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFilenameFromPath(path string) string {
|
||||||
|
|
||||||
|
file := filepath.Base(path)
|
||||||
|
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPlatformPath(path string) string {
|
||||||
|
|
||||||
|
var newPath = filepath.Dir(path) + string(os.PathSeparator)
|
||||||
|
|
||||||
|
return newPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFile(src, dst string) (err error) {
|
||||||
|
in, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
out, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(out, in)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return out.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractZIP(archive, target string) (err error) {
|
||||||
|
|
||||||
|
reader, err := zip.OpenReader(archive)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(target, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range reader.File {
|
||||||
|
|
||||||
|
path := filepath.Join(target, file.Name)
|
||||||
|
if file.FileInfo().IsDir() {
|
||||||
|
os.MkdirAll(path, file.Mode())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fileReader, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer fileReader.Close()
|
||||||
|
|
||||||
|
targetFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer targetFile.Close()
|
||||||
|
|
||||||
|
if _, err := io.Copy(targetFile, fileReader); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
238
src/m3u.go
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
m3u "../src/internal/m3u-parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Playlisten parsen
|
||||||
|
func parsePlaylist(filename, fileType string) (channels []interface{}, err error) {
|
||||||
|
|
||||||
|
content, err := readByteFromFile(filename)
|
||||||
|
var id = strings.TrimSuffix(getFilenameFromPath(filename), path.Ext(getFilenameFromPath(filename)))
|
||||||
|
var playlistName = getProviderParameter(id, fileType, "name")
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
switch fileType {
|
||||||
|
case "m3u":
|
||||||
|
channels, err = m3u.MakeInterfaceFromM3U(content)
|
||||||
|
case "hdhr":
|
||||||
|
channels, err = makeInteraceFromHDHR(content, playlistName, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Streams filtern
|
||||||
|
func filterThisStream(s interface{}) (status bool) {
|
||||||
|
|
||||||
|
status = false
|
||||||
|
var stream = s.(map[string]string)
|
||||||
|
var regexpYES = `[{]+[^.]+[}]`
|
||||||
|
var regexpNO = `!+[{]+[^.]+[}]`
|
||||||
|
|
||||||
|
for _, filter := range Data.Filter {
|
||||||
|
|
||||||
|
var group, name, search string
|
||||||
|
var exclude, include string
|
||||||
|
var match = false
|
||||||
|
|
||||||
|
var streamValues = strings.Replace(stream["_values"], "\r", "", -1)
|
||||||
|
|
||||||
|
if v, ok := stream["group-title"]; ok {
|
||||||
|
group = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := stream["name"]; ok {
|
||||||
|
name = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unerwünschte Streams !{DEU}
|
||||||
|
r := regexp.MustCompile(regexpNO)
|
||||||
|
val := r.FindStringSubmatch(filter.Rule)
|
||||||
|
|
||||||
|
if len(val) == 1 {
|
||||||
|
|
||||||
|
exclude = val[0][2 : len(val[0])-1]
|
||||||
|
filter.Rule = strings.Replace(filter.Rule, " "+val[0], "", -1)
|
||||||
|
filter.Rule = strings.Replace(filter.Rule, val[0], "", -1)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Muss zusätzlich erfüllt sein {DEU}
|
||||||
|
r = regexp.MustCompile(regexpYES)
|
||||||
|
val = r.FindStringSubmatch(filter.Rule)
|
||||||
|
|
||||||
|
if len(val) == 1 {
|
||||||
|
|
||||||
|
include = val[0][1 : len(val[0])-1]
|
||||||
|
filter.Rule = strings.Replace(filter.Rule, " "+val[0], "", -1)
|
||||||
|
filter.Rule = strings.Replace(filter.Rule, val[0], "", -1)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
switch filter.CaseSensitive {
|
||||||
|
|
||||||
|
case false:
|
||||||
|
|
||||||
|
streamValues = strings.ToLower(streamValues)
|
||||||
|
filter.Rule = strings.ToLower(filter.Rule)
|
||||||
|
exclude = strings.ToLower(exclude)
|
||||||
|
include = strings.ToLower(include)
|
||||||
|
group = strings.ToLower(group)
|
||||||
|
name = strings.ToLower(name)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
switch filter.Type {
|
||||||
|
|
||||||
|
case "group-title":
|
||||||
|
search = name
|
||||||
|
|
||||||
|
if group == filter.Rule {
|
||||||
|
match = true
|
||||||
|
}
|
||||||
|
|
||||||
|
case "custom-filter":
|
||||||
|
search = streamValues
|
||||||
|
if strings.Contains(search, filter.Rule) {
|
||||||
|
match = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if match == true {
|
||||||
|
|
||||||
|
if len(exclude) > 0 {
|
||||||
|
var status = checkConditions(search, exclude, "exclude")
|
||||||
|
if status == false {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(include) > 0 {
|
||||||
|
var status = checkConditions(search, include, "include")
|
||||||
|
if status == false {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bedingungen für den Filter
|
||||||
|
func checkConditions(streamValues, conditions, coType string) (status bool) {
|
||||||
|
|
||||||
|
switch coType {
|
||||||
|
|
||||||
|
case "exclude":
|
||||||
|
status = true
|
||||||
|
|
||||||
|
case "include":
|
||||||
|
status = false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
conditions = strings.Replace(conditions, ", ", ",", -1)
|
||||||
|
conditions = strings.Replace(conditions, " ,", ",", -1)
|
||||||
|
|
||||||
|
var keys = strings.Split(conditions, ",")
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
|
||||||
|
if strings.Contains(streamValues, key) {
|
||||||
|
|
||||||
|
switch coType {
|
||||||
|
|
||||||
|
case "exclude":
|
||||||
|
return false
|
||||||
|
|
||||||
|
case "include":
|
||||||
|
return true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// xTeVe M3U Datei erstellen
|
||||||
|
func buildM3U(groups []string) (m3u string, err error) {
|
||||||
|
|
||||||
|
var m3uChannels = make(map[float64]XEPGChannelStruct)
|
||||||
|
var channelNumbers []float64
|
||||||
|
|
||||||
|
for _, dxc := range Data.XEPG.Channels {
|
||||||
|
|
||||||
|
var xepgChannel XEPGChannelStruct
|
||||||
|
err := json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
if xepgChannel.XActive == true {
|
||||||
|
|
||||||
|
if len(groups) > 0 {
|
||||||
|
|
||||||
|
if indexOfString(xepgChannel.XGroupTitle, groups) == -1 {
|
||||||
|
goto Done
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var channelNumber, err = strconv.ParseFloat(strings.TrimSpace(xepgChannel.XChannelID), 64)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
m3uChannels[channelNumber] = xepgChannel
|
||||||
|
channelNumbers = append(channelNumbers, channelNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Done:
|
||||||
|
}
|
||||||
|
|
||||||
|
// M3U Inhalt erstellen
|
||||||
|
sort.Float64s(channelNumbers)
|
||||||
|
|
||||||
|
var xmltvURL = fmt.Sprintf("%s://%s/xmltv/xteve.xml", System.ServerProtocol.XML, System.Domain)
|
||||||
|
m3u = fmt.Sprintf(`#EXTM3U url-tvg="%s" x-tvg-url="%s"`+"\n", xmltvURL, xmltvURL)
|
||||||
|
|
||||||
|
for _, channelNumber := range channelNumbers {
|
||||||
|
|
||||||
|
var channel = m3uChannels[channelNumber]
|
||||||
|
var parameter = fmt.Sprintf(`#EXTINF:0 channelID="%s" tvg-chno="%s" tvg-name="%s" tvg-id="%s" tvg-logo="%s" group-title="%s",%s`+"\n", channel.XEPG, channel.XChannelID, channel.XName, channel.XChannelID, getCacheImageURL(channel.TvgLogo), channel.XGroupTitle, channel.XName)
|
||||||
|
var stream, err = createStreamingURL("M3U", channel.FileM3UID, channel.XChannelID, channel.XName, channel.URL)
|
||||||
|
if err == nil {
|
||||||
|
m3u = m3u + parameter + stream + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(groups) == 0 {
|
||||||
|
|
||||||
|
var filename = System.Folder.Data + "xteve.m3u"
|
||||||
|
err = writeByteToFile(filename, []byte(m3u))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
84
src/maintenance.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitMaintenance : Wartungsprozess initialisieren
|
||||||
|
func InitMaintenance() (err error) {
|
||||||
|
|
||||||
|
rand.Seed(time.Now().Unix())
|
||||||
|
System.TimeForAutoUpdate = fmt.Sprintf("0%d%d", randomTime(0, 2), randomTime(10, 59))
|
||||||
|
|
||||||
|
go maintenance()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func maintenance() {
|
||||||
|
|
||||||
|
for {
|
||||||
|
|
||||||
|
var t = time.Now()
|
||||||
|
|
||||||
|
// Aktualisierung der Playlist und XMLTV Dateien
|
||||||
|
if System.ScanInProgress == 0 {
|
||||||
|
|
||||||
|
for _, schedule := range Settings.Update {
|
||||||
|
|
||||||
|
if schedule == t.Format("1504") {
|
||||||
|
|
||||||
|
showInfo("Update:" + schedule)
|
||||||
|
|
||||||
|
// Backup erstellen
|
||||||
|
err := xTeVeAutoBackup()
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Playlist und XMLTV Dateien aktualisieren
|
||||||
|
getProviderData("m3u", "")
|
||||||
|
getProviderData("hdhr", "")
|
||||||
|
|
||||||
|
if Settings.EpgSource == "XEPG" {
|
||||||
|
getProviderData("xmltv", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Datenbank für DVR erstellen
|
||||||
|
err = buildDatabaseDVR()
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 000)
|
||||||
|
}
|
||||||
|
|
||||||
|
if Settings.CacheImages == false && System.ImageCachingInProgress == 0 {
|
||||||
|
removeChildItems(System.Folder.ImagesCache)
|
||||||
|
}
|
||||||
|
|
||||||
|
// XEPG Dateien erstellen
|
||||||
|
Data.Cache.XMLTV = make(map[string]XMLTV)
|
||||||
|
buildXEPG(false)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update xTeVe (Binary)
|
||||||
|
if System.TimeForAutoUpdate == t.Format("1504") {
|
||||||
|
BinaryUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(60 * time.Second)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomTime(min, max int) int {
|
||||||
|
rand.Seed(time.Now().Unix())
|
||||||
|
return rand.Intn(max-min) + min
|
||||||
|
}
|
||||||
323
src/provider.go
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
m3u "../src/internal/m3u-parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fileType: Welcher Dateityp soll aktualisiert werden (m3u, hdhr, xml) | fileID: Update einer bestimmten Datei (Provider ID)
|
||||||
|
func getProviderData(fileType, fileID string) (err error) {
|
||||||
|
|
||||||
|
var fileExtension, serverFileName string
|
||||||
|
var body = make([]byte, 0)
|
||||||
|
var newProvider = false
|
||||||
|
var dataMap = make(map[string]interface{})
|
||||||
|
|
||||||
|
var saveDateFromProvider = func(fileSource, serverFileName, id string, body []byte) (err error) {
|
||||||
|
|
||||||
|
var data = make(map[string]interface{})
|
||||||
|
|
||||||
|
if value, ok := dataMap[id].(map[string]interface{}); ok {
|
||||||
|
data = value
|
||||||
|
} else {
|
||||||
|
data["id.provider"] = id
|
||||||
|
dataMap[id] = data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default keys für die Providerdaten
|
||||||
|
var keys = []string{"name", "description", "type", "file." + System.AppName, "file.source", "tuner", "last.update", "compatibility", "counter.error", "counter.download", "provider.availability"}
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
|
||||||
|
if _, ok := data[key]; !ok {
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
|
||||||
|
case "name":
|
||||||
|
data[key] = serverFileName
|
||||||
|
|
||||||
|
case "description":
|
||||||
|
data[key] = ""
|
||||||
|
|
||||||
|
case "type":
|
||||||
|
data[key] = fileType
|
||||||
|
|
||||||
|
case "file." + System.AppName:
|
||||||
|
data[key] = id + fileExtension
|
||||||
|
|
||||||
|
case "file.source":
|
||||||
|
data[key] = fileSource
|
||||||
|
|
||||||
|
case "last.update":
|
||||||
|
data[key] = time.Now().Format("2006-01-02 15:04:05")
|
||||||
|
|
||||||
|
case "tuner":
|
||||||
|
if fileType == "m3u" || fileType == "hdhr" {
|
||||||
|
if _, ok := data[key].(float64); !ok {
|
||||||
|
data[key] = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case "compatibility":
|
||||||
|
data[key] = make(map[string]interface{})
|
||||||
|
|
||||||
|
case "counter.download":
|
||||||
|
data[key] = 0.0
|
||||||
|
|
||||||
|
case "counter.error":
|
||||||
|
data[key] = 0.0
|
||||||
|
|
||||||
|
case "provider.availability":
|
||||||
|
data[key] = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := data["id.provider"]; !ok {
|
||||||
|
data["id.provider"] = id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Datei extrahieren
|
||||||
|
body, err = extractGZIP(body, fileSource)
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 000)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Daten überprüfen
|
||||||
|
showInfo("Check File:" + fileSource)
|
||||||
|
switch fileType {
|
||||||
|
|
||||||
|
case "m3u":
|
||||||
|
_, err = m3u.MakeInterfaceFromM3U(body)
|
||||||
|
|
||||||
|
case "hdhr":
|
||||||
|
_, err = jsonToInterface(string(body))
|
||||||
|
|
||||||
|
case "xmltv":
|
||||||
|
err = checkXMLCompatibility(id, body)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var filePath = System.Folder.Data + data["file."+System.AppName].(string)
|
||||||
|
|
||||||
|
err = writeByteToFile(filePath, body)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
data["last.update"] = time.Now().Format("2006-01-02 15:04:05")
|
||||||
|
data["counter.download"] = data["counter.download"].(float64) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fileType {
|
||||||
|
|
||||||
|
case "m3u":
|
||||||
|
dataMap = Settings.Files.M3U
|
||||||
|
fileExtension = ".m3u"
|
||||||
|
|
||||||
|
case "hdhr":
|
||||||
|
dataMap = Settings.Files.HDHR
|
||||||
|
fileExtension = ".json"
|
||||||
|
|
||||||
|
case "xmltv":
|
||||||
|
dataMap = Settings.Files.XMLTV
|
||||||
|
fileExtension = ".xml"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for dataID, d := range dataMap {
|
||||||
|
|
||||||
|
var data = d.(map[string]interface{})
|
||||||
|
var fileSource = data["file.source"].(string)
|
||||||
|
newProvider = false
|
||||||
|
|
||||||
|
if _, ok := data["new"]; ok {
|
||||||
|
newProvider = true
|
||||||
|
delete(data, "new")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wenn eine ID vorhanden ist und nicht mit der aus der Datanbank übereinstimmt, wird die Aktualisierung übersprungen (goto)
|
||||||
|
if len(fileID) > 0 && newProvider == false {
|
||||||
|
if dataID != fileID {
|
||||||
|
goto Done
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fileType {
|
||||||
|
|
||||||
|
case "hdhr":
|
||||||
|
|
||||||
|
// Laden vom HDHomeRun Tuner
|
||||||
|
showInfo("Tuner:" + fileSource)
|
||||||
|
var tunerURL = "http://" + fileSource + "/lineup.json"
|
||||||
|
serverFileName, body, err = downloadFileFromServer(tunerURL)
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
if strings.Contains(fileSource, "http://") || strings.Contains(fileSource, "https://") {
|
||||||
|
|
||||||
|
// Laden vom Remote Server
|
||||||
|
showInfo("Download:" + fileSource)
|
||||||
|
serverFileName, body, err = downloadFileFromServer(fileSource)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Laden einer lokalen Datei
|
||||||
|
showInfo("Open:" + fileSource)
|
||||||
|
|
||||||
|
err = checkFile(fileSource)
|
||||||
|
if err == nil {
|
||||||
|
body, err = readByteFromFile(fileSource)
|
||||||
|
serverFileName = getFilenameFromPath(fileSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
err = saveDateFromProvider(fileSource, serverFileName, dataID, body)
|
||||||
|
if err == nil {
|
||||||
|
showInfo("Save File:" + fileSource + " [ID: " + dataID + "]")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
ShowError(err, 000)
|
||||||
|
var downloadErr = err
|
||||||
|
|
||||||
|
if newProvider == false {
|
||||||
|
|
||||||
|
// Prüfen ob ältere Datei vorhanden ist
|
||||||
|
var file = System.Folder.Data + dataID + fileExtension
|
||||||
|
|
||||||
|
err = checkFile(file)
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
if len(fileID) == 0 {
|
||||||
|
showWarning(1011)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = downloadErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fehler Counter um 1 erhöhen
|
||||||
|
var data = make(map[string]interface{})
|
||||||
|
if value, ok := dataMap[dataID].(map[string]interface{}); ok {
|
||||||
|
|
||||||
|
data = value
|
||||||
|
data["counter.error"] = data["counter.error"].(float64) + 1
|
||||||
|
data["counter.download"] = data["counter.download"].(float64) + 1
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return downloadErr
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Berechnen der Fehlerquote
|
||||||
|
if newProvider == false {
|
||||||
|
|
||||||
|
if value, ok := dataMap[dataID].(map[string]interface{}); ok {
|
||||||
|
|
||||||
|
var data = make(map[string]interface{})
|
||||||
|
data = value
|
||||||
|
|
||||||
|
if data["counter.error"].(float64) == 0 {
|
||||||
|
data["provider.availability"] = 100
|
||||||
|
} else {
|
||||||
|
data["provider.availability"] = int(data["counter.error"].(float64)*100/data["counter.download"].(float64)*-1 + 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fileType {
|
||||||
|
|
||||||
|
case "m3u":
|
||||||
|
Settings.Files.M3U = dataMap
|
||||||
|
|
||||||
|
case "hdhr":
|
||||||
|
Settings.Files.HDHR = dataMap
|
||||||
|
|
||||||
|
case "xmltv":
|
||||||
|
Settings.Files.XMLTV = dataMap
|
||||||
|
delete(Data.Cache.XMLTV, System.Folder.Data+dataID+fileExtension)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
saveSettings(Settings)
|
||||||
|
|
||||||
|
Done:
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadFileFromServer(providerURL string) (filename string, body []byte, err error) {
|
||||||
|
|
||||||
|
_, err = url.ParseRequestURI(providerURL)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Get(providerURL)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Header.Set("User-Agent", Settings.UserAgent)
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
err = fmt.Errorf(fmt.Sprintf("%d: %s "+http.StatusText(resp.StatusCode), resp.StatusCode, providerURL))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dateiname aus dem Header holen
|
||||||
|
var index = strings.Index(resp.Header.Get("Content-Disposition"), "filename")
|
||||||
|
|
||||||
|
if index > -1 {
|
||||||
|
|
||||||
|
var headerFilename = resp.Header.Get("Content-Disposition")[index:len(resp.Header.Get("Content-Disposition"))]
|
||||||
|
var value = strings.Split(headerFilename, `=`)
|
||||||
|
var f = strings.Replace(value[1], `"`, "", -1)
|
||||||
|
|
||||||
|
f = strings.Replace(f, `;`, "", -1)
|
||||||
|
filename = f
|
||||||
|
showInfo("Header filename:" + filename)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
var cleanFilename = strings.SplitN(getFilenameFromPath(providerURL), "?", 2)
|
||||||
|
filename = cleanFilename[0]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
404
src/screen.go
Normal file
@@ -0,0 +1,404 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func showInfo(str string) {
|
||||||
|
|
||||||
|
var max = 22
|
||||||
|
var msg = strings.SplitN(str, ":", 2)
|
||||||
|
var length = len(msg[0])
|
||||||
|
var space string
|
||||||
|
|
||||||
|
if len(msg) == 2 {
|
||||||
|
|
||||||
|
for i := length; i < max; i++ {
|
||||||
|
space = space + " "
|
||||||
|
}
|
||||||
|
|
||||||
|
msg[0] = msg[0] + ":" + space
|
||||||
|
|
||||||
|
var logMsg = fmt.Sprintf("[%s] %s%s", System.Name, msg[0], msg[1])
|
||||||
|
|
||||||
|
printLogOnScreen(logMsg, "info")
|
||||||
|
|
||||||
|
logMsg = strings.Replace(logMsg, " ", " ", -1)
|
||||||
|
WebScreenLog.Log = append(WebScreenLog.Log, time.Now().Format("2006-01-02 15:04:05")+" "+logMsg)
|
||||||
|
logCleanUp()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func showDebug(str string, level int) {
|
||||||
|
|
||||||
|
if System.Flag.Debug < level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var max = 22
|
||||||
|
var msg = strings.SplitN(str, ":", 2)
|
||||||
|
var length = len(msg[0])
|
||||||
|
var space string
|
||||||
|
var mutex = sync.RWMutex{}
|
||||||
|
|
||||||
|
if len(msg) == 2 {
|
||||||
|
|
||||||
|
for i := length; i < max; i++ {
|
||||||
|
space = space + " "
|
||||||
|
}
|
||||||
|
msg[0] = msg[0] + ":" + space
|
||||||
|
|
||||||
|
var logMsg = fmt.Sprintf("[DEBUG] %s%s", msg[0], msg[1])
|
||||||
|
|
||||||
|
printLogOnScreen(logMsg, "debug")
|
||||||
|
|
||||||
|
mutex.Lock()
|
||||||
|
logMsg = strings.Replace(logMsg, " ", " ", -1)
|
||||||
|
WebScreenLog.Log = append(WebScreenLog.Log, time.Now().Format("2006-01-02 15:04:05")+" "+logMsg)
|
||||||
|
logCleanUp()
|
||||||
|
mutex.Unlock()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func showHighlight(str string) {
|
||||||
|
|
||||||
|
var max = 22
|
||||||
|
var msg = strings.SplitN(str, ":", 2)
|
||||||
|
var length = len(msg[0])
|
||||||
|
var space string
|
||||||
|
|
||||||
|
var notification Notification
|
||||||
|
notification.Type = "info"
|
||||||
|
|
||||||
|
if len(msg) == 2 {
|
||||||
|
|
||||||
|
for i := length; i < max; i++ {
|
||||||
|
space = space + " "
|
||||||
|
}
|
||||||
|
|
||||||
|
msg[0] = msg[0] + ":" + space
|
||||||
|
|
||||||
|
var logMsg = fmt.Sprintf("[%s] %s%s", System.Name, msg[0], msg[1])
|
||||||
|
|
||||||
|
printLogOnScreen(logMsg, "highlight")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.Type = "info"
|
||||||
|
notification.Message = msg[1]
|
||||||
|
|
||||||
|
addNotification(notification)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func showWarning(errCode int) {
|
||||||
|
|
||||||
|
var errMsg = getErrMsg(errCode)
|
||||||
|
var logMsg = fmt.Sprintf("[%s] [WARNING] %s", System.Name, errMsg)
|
||||||
|
var mutex = sync.RWMutex{}
|
||||||
|
|
||||||
|
printLogOnScreen(logMsg, "warning")
|
||||||
|
|
||||||
|
mutex.Lock()
|
||||||
|
WebScreenLog.Log = append(WebScreenLog.Log, time.Now().Format("2006-01-02 15:04:05")+" "+logMsg)
|
||||||
|
WebScreenLog.Warnings++
|
||||||
|
mutex.Unlock()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShowError : Zeigt die Fehlermeldungen in der Konsole
|
||||||
|
func ShowError(err error, errCode int) {
|
||||||
|
|
||||||
|
var mutex = sync.RWMutex{}
|
||||||
|
|
||||||
|
var errMsg = getErrMsg(errCode)
|
||||||
|
var logMsg = fmt.Sprintf("[%s] [ERROR] %s (%s) - EC: %d", System.Name, err, errMsg, errCode)
|
||||||
|
|
||||||
|
printLogOnScreen(logMsg, "error")
|
||||||
|
|
||||||
|
mutex.Lock()
|
||||||
|
WebScreenLog.Log = append(WebScreenLog.Log, time.Now().Format("2006-01-02 15:04:05")+" "+logMsg)
|
||||||
|
WebScreenLog.Errors++
|
||||||
|
mutex.Unlock()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func printLogOnScreen(logMsg string, logType string) {
|
||||||
|
|
||||||
|
var color string
|
||||||
|
|
||||||
|
switch logType {
|
||||||
|
|
||||||
|
case "info":
|
||||||
|
color = "\033[0m"
|
||||||
|
|
||||||
|
case "debug":
|
||||||
|
color = "\033[35m"
|
||||||
|
|
||||||
|
case "highlight":
|
||||||
|
color = "\033[32m"
|
||||||
|
|
||||||
|
case "warning":
|
||||||
|
color = "\033[33m"
|
||||||
|
|
||||||
|
case "error":
|
||||||
|
color = "\033[31m"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
switch runtime.GOOS {
|
||||||
|
|
||||||
|
case "windows":
|
||||||
|
log.Println(logMsg)
|
||||||
|
|
||||||
|
default:
|
||||||
|
fmt.Print(color)
|
||||||
|
log.Println(logMsg)
|
||||||
|
fmt.Print("\033[0m")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func logCleanUp() {
|
||||||
|
|
||||||
|
var logEntriesRAM = Settings.LogEntriesRAM
|
||||||
|
var logs = WebScreenLog.Log
|
||||||
|
|
||||||
|
WebScreenLog.Warnings = 0
|
||||||
|
WebScreenLog.Errors = 0
|
||||||
|
|
||||||
|
if len(logs) > logEntriesRAM {
|
||||||
|
|
||||||
|
var tmp = make([]string, 0)
|
||||||
|
for i := len(logs) - logEntriesRAM; i < logEntriesRAM; i++ {
|
||||||
|
tmp = append(tmp, logs[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
logs = tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, log := range logs {
|
||||||
|
|
||||||
|
if strings.Contains(log, "WARNING") {
|
||||||
|
WebScreenLog.Warnings++
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(log, "ERROR") {
|
||||||
|
WebScreenLog.Errors++
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
WebScreenLog.Log = logs
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fehlercodes
|
||||||
|
func getErrMsg(errCode int) (errMsg string) {
|
||||||
|
|
||||||
|
switch errCode {
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
// Errors
|
||||||
|
case 1001:
|
||||||
|
errMsg = fmt.Sprintf("Web server could not be started.")
|
||||||
|
case 1002:
|
||||||
|
errMsg = fmt.Sprintf("No local IP address found.")
|
||||||
|
case 1003:
|
||||||
|
errMsg = fmt.Sprintf("Invalid xml")
|
||||||
|
case 1004:
|
||||||
|
errMsg = fmt.Sprintf("File not found")
|
||||||
|
case 1005:
|
||||||
|
errMsg = fmt.Sprintf("Invalide m3u")
|
||||||
|
case 1006:
|
||||||
|
errMsg = fmt.Sprintf("No playlist!")
|
||||||
|
case 1007:
|
||||||
|
errMsg = fmt.Sprintf("XEPG requires an XMLTV file.")
|
||||||
|
case 1010:
|
||||||
|
errMsg = fmt.Sprintf("Invalid file compression")
|
||||||
|
case 1011:
|
||||||
|
errMsg = fmt.Sprintf("Data is corrupt or unavailable, %s now uses an older version of this file", System.Name)
|
||||||
|
case 1012:
|
||||||
|
errMsg = fmt.Sprintf("Invalid formatting of the time")
|
||||||
|
case 1013:
|
||||||
|
errMsg = fmt.Sprintf("Invalid settings file (%s), file must be at least version %s", System.File.Settings, System.Compatibility)
|
||||||
|
|
||||||
|
case 1020:
|
||||||
|
errMsg = fmt.Sprintf("Data could not be saved, invalid keyword")
|
||||||
|
|
||||||
|
// Datenbank Update
|
||||||
|
case 1030:
|
||||||
|
errMsg = fmt.Sprintf("Invalid settings file (%s)", System.File.Settings)
|
||||||
|
|
||||||
|
// M3U Parser
|
||||||
|
case 1050:
|
||||||
|
errMsg = fmt.Sprintf("Invalid duration specification in the M3U8 playlist.")
|
||||||
|
|
||||||
|
// M3U Parser
|
||||||
|
case 1060:
|
||||||
|
errMsg = fmt.Sprintf("Invalid characters found in the tvg parameters, streams with invalid parameters were skipped.")
|
||||||
|
|
||||||
|
// Dateisystem
|
||||||
|
case 1070:
|
||||||
|
errMsg = fmt.Sprintf("Folder could not be created.")
|
||||||
|
case 1071:
|
||||||
|
errMsg = fmt.Sprintf("File could not be created")
|
||||||
|
|
||||||
|
// Backup
|
||||||
|
case 1090:
|
||||||
|
errMsg = fmt.Sprintf("Automatic backup failed")
|
||||||
|
|
||||||
|
// Websockets
|
||||||
|
case 1100:
|
||||||
|
errMsg = fmt.Sprintf("WebUI build error")
|
||||||
|
case 1101:
|
||||||
|
errMsg = fmt.Sprintf("WebUI request error")
|
||||||
|
case 1102:
|
||||||
|
errMsg = fmt.Sprintf("WebUI response error")
|
||||||
|
|
||||||
|
// PMS Guide Numbers
|
||||||
|
case 1200:
|
||||||
|
errMsg = fmt.Sprintf("Could not create file")
|
||||||
|
|
||||||
|
// Stream URL Fehler
|
||||||
|
case 1201:
|
||||||
|
errMsg = fmt.Sprintf("Plex stream error")
|
||||||
|
case 1202:
|
||||||
|
errMsg = fmt.Sprintf("Steaming URL could not be found in any playlist")
|
||||||
|
case 1203:
|
||||||
|
errMsg = fmt.Sprintf("Steaming URL could not be found in any playlist")
|
||||||
|
|
||||||
|
// Warnings
|
||||||
|
case 2000:
|
||||||
|
errMsg = fmt.Sprintf("Plex can not handle more than %d streams. If you do not use Plex, you can ignore this warning.", System.DVRLimit)
|
||||||
|
case 2001:
|
||||||
|
errMsg = fmt.Sprintf("%s has loaded more than %d streams. Use the filter to reduce the number of streams.", System.Name, System.DVRLimit)
|
||||||
|
case 2002:
|
||||||
|
errMsg = fmt.Sprintf("PMS can not play m3u8 streams")
|
||||||
|
case 2003:
|
||||||
|
errMsg = fmt.Sprintf("PMS can not play streams over RTSP.")
|
||||||
|
case 2004:
|
||||||
|
errMsg = fmt.Sprintf("Buffer is disabled for this stream.")
|
||||||
|
case 2005:
|
||||||
|
errMsg = fmt.Sprintf("There are no channels mapped, use the mapping menu to assign EPG data to the channels.")
|
||||||
|
case 2010:
|
||||||
|
errMsg = fmt.Sprintf("No valid streaming URL")
|
||||||
|
|
||||||
|
case 2099:
|
||||||
|
errMsg = fmt.Sprintf("Updates have been disabled by the developer")
|
||||||
|
|
||||||
|
// Tuner
|
||||||
|
case 2105:
|
||||||
|
errMsg = fmt.Sprintf("The number of tuners has changed, you have to delete " + System.Name + " in Plex / Emby HDHR and set it up again.")
|
||||||
|
case 2106:
|
||||||
|
errMsg = fmt.Sprintf("This function is only available with XEPG as EPG source")
|
||||||
|
|
||||||
|
case 2110:
|
||||||
|
errMsg = fmt.Sprintf("Don't run this as Root!")
|
||||||
|
|
||||||
|
case 2300:
|
||||||
|
errMsg = fmt.Sprintf("No channel logo found in the XMLTV or M3U file.")
|
||||||
|
case 2301:
|
||||||
|
errMsg = fmt.Sprintf("XMLTV file no longer available, channel has been deactivated.")
|
||||||
|
case 2302:
|
||||||
|
errMsg = fmt.Sprintf("Channel ID in the XMLTV file has changed. Channel has been deactivated.")
|
||||||
|
|
||||||
|
// Benutzerauthentifizierung
|
||||||
|
case 3000:
|
||||||
|
errMsg = fmt.Sprintf("Database for user authentication could not be initialized.")
|
||||||
|
case 3001:
|
||||||
|
errMsg = fmt.Sprintf("The user has no authorization to load the channels.")
|
||||||
|
|
||||||
|
// Buffer
|
||||||
|
case 4000:
|
||||||
|
errMsg = fmt.Sprintf("Connection to streaming source was interrupted.")
|
||||||
|
case 4001:
|
||||||
|
errMsg = fmt.Sprintf("Too many errors connecting to the provider. Streaming is canceled.")
|
||||||
|
case 4002:
|
||||||
|
errMsg = fmt.Sprintf("New URL for the redirect to the streaming server is missing")
|
||||||
|
case 4003:
|
||||||
|
errMsg = fmt.Sprintf("Server sends an incompatible content-type")
|
||||||
|
case 4004:
|
||||||
|
errMsg = fmt.Sprintf("This error message comes from the provider")
|
||||||
|
case 4005:
|
||||||
|
errMsg = fmt.Sprintf("Temporary buffer files could not be deleted")
|
||||||
|
|
||||||
|
// Buffer (M3U8)
|
||||||
|
case 4050:
|
||||||
|
errMsg = fmt.Sprintf("Invalid M3U8 file")
|
||||||
|
case 4051:
|
||||||
|
errMsg = fmt.Sprintf("#EXTM3U header is missing")
|
||||||
|
|
||||||
|
// Caching
|
||||||
|
case 4100:
|
||||||
|
errMsg = fmt.Sprintf("Unknown content type for downloaded image")
|
||||||
|
|
||||||
|
// API
|
||||||
|
case 5000:
|
||||||
|
errMsg = fmt.Sprintf("Invalid API command")
|
||||||
|
|
||||||
|
// Update Server
|
||||||
|
case 6001:
|
||||||
|
errMsg = fmt.Sprintf("Ivalid key")
|
||||||
|
case 6002:
|
||||||
|
errMsg = fmt.Sprintf("Update failed")
|
||||||
|
case 6003:
|
||||||
|
errMsg = fmt.Sprintf("Server not available")
|
||||||
|
case 6004:
|
||||||
|
errMsg = fmt.Sprintf("xTeVe update available")
|
||||||
|
|
||||||
|
default:
|
||||||
|
errMsg = fmt.Sprintf("Unknown error / warning (%d)", errCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
func addNotification(notification Notification) (err error) {
|
||||||
|
|
||||||
|
var i int
|
||||||
|
var t = time.Now().UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond))
|
||||||
|
notification.Time = strconv.FormatInt(t, 10)
|
||||||
|
notification.New = true
|
||||||
|
|
||||||
|
if len(notification.Headline) == 0 {
|
||||||
|
notification.Headline = strings.ToUpper(notification.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(System.Notification) == 0 {
|
||||||
|
System.Notification = make(map[string]Notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Notification[notification.Time] = notification
|
||||||
|
|
||||||
|
for key := range System.Notification {
|
||||||
|
|
||||||
|
if i < len(System.Notification)-10 {
|
||||||
|
delete(System.Notification, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
69
src/ssdp.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/koron/go-ssdp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SSDP : SSPD / DLNA Server
|
||||||
|
func SSDP() {
|
||||||
|
|
||||||
|
showInfo(fmt.Sprintf("SSDP / DLNA:%t", Settings.SSDP))
|
||||||
|
|
||||||
|
if Settings.SSDP == false {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
ad, err := ssdp.Advertise(
|
||||||
|
"upnp:"+System.AppName, // send as "ST"
|
||||||
|
System.DeviceID+"::upnp:"+System.AppName, // send as "USN"
|
||||||
|
System.URLBase+"/device.xml", // send as "LOCATION"
|
||||||
|
System.AppName, // send as "SERVER"
|
||||||
|
1800) // send as "maxAge" in "CACHE-CONTROL"
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug SSDP
|
||||||
|
if System.Flag.Debug == 3 {
|
||||||
|
ssdp.Logger = log.New(os.Stderr, "[SSDP] ", log.LstdFlags)
|
||||||
|
}
|
||||||
|
|
||||||
|
var aliveTick <-chan time.Time
|
||||||
|
var ai = 10
|
||||||
|
|
||||||
|
if ai > 0 {
|
||||||
|
aliveTick = time.Tick(time.Duration(ai) * time.Second)
|
||||||
|
} else {
|
||||||
|
aliveTick = make(chan time.Time)
|
||||||
|
}
|
||||||
|
|
||||||
|
quit := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(quit, os.Interrupt)
|
||||||
|
|
||||||
|
loop:
|
||||||
|
|
||||||
|
for {
|
||||||
|
|
||||||
|
select {
|
||||||
|
|
||||||
|
case <-aliveTick:
|
||||||
|
ad.Alive()
|
||||||
|
case <-quit:
|
||||||
|
os.Exit(0)
|
||||||
|
break loop
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ad.Bye()
|
||||||
|
ad.Close()
|
||||||
|
}
|
||||||
109
src/struct-buffer.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Playlist : Enthält allen Playlistinformationen, die der Buffer benötigr
|
||||||
|
type Playlist struct {
|
||||||
|
Folder string
|
||||||
|
PlaylistID string
|
||||||
|
PlaylistName string
|
||||||
|
Tuner int
|
||||||
|
|
||||||
|
Clients map[int]ThisClient
|
||||||
|
Streams map[int]ThisStream
|
||||||
|
}
|
||||||
|
|
||||||
|
// ThisClient : Clientinfos
|
||||||
|
type ThisClient struct {
|
||||||
|
Connection int
|
||||||
|
}
|
||||||
|
|
||||||
|
// ThisStream : Enthält Informationen zu dem abzuspielenden Stream einer Playlist
|
||||||
|
type ThisStream struct {
|
||||||
|
ChannelName string
|
||||||
|
Error string
|
||||||
|
Folder string
|
||||||
|
MD5 string
|
||||||
|
NetworkBandwidth int
|
||||||
|
PlaylistID string
|
||||||
|
PlaylistName string
|
||||||
|
Status bool
|
||||||
|
URL string
|
||||||
|
|
||||||
|
Segment []Segment
|
||||||
|
|
||||||
|
// Serverinformationen
|
||||||
|
Location string
|
||||||
|
URLFile string
|
||||||
|
URLHost string
|
||||||
|
URLPath string
|
||||||
|
URLRedirect string
|
||||||
|
URLScheme string
|
||||||
|
URLStreamingServer string
|
||||||
|
|
||||||
|
// Wird nur für HLS / M3U8 verwendet
|
||||||
|
Body string
|
||||||
|
Difference float64
|
||||||
|
Duration float64
|
||||||
|
DynamicBandwidth bool
|
||||||
|
FirstSequence int64
|
||||||
|
HLS bool
|
||||||
|
LastSequence int64
|
||||||
|
M3U8URL string
|
||||||
|
NewSegCount int
|
||||||
|
OldSegCount int
|
||||||
|
Sequence int64
|
||||||
|
TimeDiff float64
|
||||||
|
TimeEnd time.Time
|
||||||
|
TimeSegDuration float64
|
||||||
|
TimeStart time.Time
|
||||||
|
Version int
|
||||||
|
Wait float64
|
||||||
|
|
||||||
|
DynamicStream map[int]DynamicStream
|
||||||
|
|
||||||
|
// Lokale Temp Datein
|
||||||
|
OldSegments []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Segment : URL Segmente (HLS / M3U8)
|
||||||
|
type Segment struct {
|
||||||
|
Duration float64
|
||||||
|
Info bool
|
||||||
|
Sequence int64
|
||||||
|
URL string
|
||||||
|
Version int
|
||||||
|
Wait float64
|
||||||
|
|
||||||
|
StreamInf struct {
|
||||||
|
AverageBandwidth int
|
||||||
|
Bandwidth int
|
||||||
|
Framerate float64
|
||||||
|
Resolution string
|
||||||
|
SegmentURL string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DynamicStream : Streaminformationen bei dynamischer Bandbreite
|
||||||
|
type DynamicStream struct {
|
||||||
|
AverageBandwidth int
|
||||||
|
Bandwidth int
|
||||||
|
Framerate float64
|
||||||
|
Resolution string
|
||||||
|
URL string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientConnection : Client Verbindungen
|
||||||
|
type ClientConnection struct {
|
||||||
|
Connection int
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
|
// BandwidthCalculation : Bandbreitenberechnung für den Stream
|
||||||
|
type BandwidthCalculation struct {
|
||||||
|
NetworkBandwidth int
|
||||||
|
Size int
|
||||||
|
Start time.Time
|
||||||
|
Stop time.Time
|
||||||
|
TimeDiff float64
|
||||||
|
}
|
||||||
61
src/struct-hdhr.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import "encoding/xml"
|
||||||
|
|
||||||
|
// Capability : HDHR Capability XML
|
||||||
|
type Capability struct {
|
||||||
|
URLBase string `xml:"URLBase"`
|
||||||
|
XMLName xml.Name `xml:"root"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
|
||||||
|
SpecVersion struct {
|
||||||
|
Major int `xml:"major"`
|
||||||
|
Minor int `xml:"minor"`
|
||||||
|
} `xml:"specVersion"`
|
||||||
|
|
||||||
|
Device struct {
|
||||||
|
DeviceType string `xml:"deviceType"`
|
||||||
|
FriendlyName string `xml:"friendlyName"`
|
||||||
|
Manufacturer string `xml:"manufacturer"`
|
||||||
|
ModelName string `xml:"modelName"`
|
||||||
|
ModelNumber string `xml:"modelNumber"`
|
||||||
|
SerialNumber string `xml:"serialNumber"`
|
||||||
|
UDN string `xml:"UDN"`
|
||||||
|
} `xml:"device"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover : HDHR Discover /discover.json
|
||||||
|
type Discover struct {
|
||||||
|
BaseURL string `json:"BaseURL"`
|
||||||
|
DeviceAuth string `json:"DeviceAuth"`
|
||||||
|
DeviceID string `json:"DeviceID"`
|
||||||
|
FirmwareName string `json:"FirmwareName"`
|
||||||
|
FirmwareVersion string `json:"FirmwareVersion"`
|
||||||
|
FriendlyName string `json:"FriendlyName"`
|
||||||
|
LineupURL string `json:"LineupURL"`
|
||||||
|
Manufacturer string `json:"Manufacturer"`
|
||||||
|
ModelNumber string `json:"ModelNumber"`
|
||||||
|
TunerCount int `json:"TunerCount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LineupStatus : HDHR Lineup status /lineup_status.json
|
||||||
|
type LineupStatus struct {
|
||||||
|
ScanInProgress int `json:"ScanInProgress"`
|
||||||
|
ScanPossible int `json:"ScanPossible"`
|
||||||
|
Source string `json:"Source"`
|
||||||
|
SourceList []string `json:"SourceList"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lineup : HDHR Lineup /lineup.json
|
||||||
|
type Lineup []interface {
|
||||||
|
//GuideName string `json:"GuideName"`
|
||||||
|
//GuideNumber string `json:"GuideNumber"`
|
||||||
|
//URL string `json:"URL"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LineupStream : HDHR einzelner Stream im Lineup
|
||||||
|
type LineupStream struct {
|
||||||
|
GuideName string `json:"GuideName"`
|
||||||
|
GuideNumber string `json:"GuideNumber"`
|
||||||
|
URL string `json:"URL"`
|
||||||
|
}
|
||||||
280
src/struct-system.go
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
// SystemStruct : Beinhaltet alle Systeminformationen
|
||||||
|
type SystemStruct struct {
|
||||||
|
Addresses struct {
|
||||||
|
DVR string
|
||||||
|
M3U string
|
||||||
|
XML string
|
||||||
|
}
|
||||||
|
|
||||||
|
APIVersion string
|
||||||
|
AppName string
|
||||||
|
ARCH string
|
||||||
|
Branch string
|
||||||
|
Build string
|
||||||
|
Compatibility string
|
||||||
|
ConfigurationWizard bool
|
||||||
|
Dev bool
|
||||||
|
DeviceID string
|
||||||
|
Domain string
|
||||||
|
DVRLimit int
|
||||||
|
|
||||||
|
File struct {
|
||||||
|
Authentication string
|
||||||
|
M3U string
|
||||||
|
PMS string
|
||||||
|
Settings string
|
||||||
|
URLS string
|
||||||
|
XEPG string
|
||||||
|
XML string
|
||||||
|
}
|
||||||
|
|
||||||
|
Flag struct {
|
||||||
|
Branch string
|
||||||
|
Debug int
|
||||||
|
Port string
|
||||||
|
SSDP bool
|
||||||
|
}
|
||||||
|
|
||||||
|
Folder struct {
|
||||||
|
Backup string
|
||||||
|
Cache string
|
||||||
|
Config string
|
||||||
|
Data string
|
||||||
|
ImagesCache string
|
||||||
|
ImagesUpload string
|
||||||
|
Temp string
|
||||||
|
}
|
||||||
|
|
||||||
|
Hostname string
|
||||||
|
ImageCachingInProgress int
|
||||||
|
IPAddress string
|
||||||
|
IPAddressesList []string
|
||||||
|
IPAddressesV4 []string
|
||||||
|
IPAddressesV6 []string
|
||||||
|
Name string
|
||||||
|
OS string
|
||||||
|
ScanInProgress int
|
||||||
|
TimeForAutoUpdate string
|
||||||
|
|
||||||
|
Notification map[string]Notification
|
||||||
|
|
||||||
|
ServerProtocol struct {
|
||||||
|
API string
|
||||||
|
DVR string
|
||||||
|
M3U string
|
||||||
|
WEB string
|
||||||
|
XML string
|
||||||
|
}
|
||||||
|
|
||||||
|
GitHub struct {
|
||||||
|
Branch string
|
||||||
|
Repo string
|
||||||
|
Update bool
|
||||||
|
User string
|
||||||
|
}
|
||||||
|
|
||||||
|
Update struct {
|
||||||
|
Git string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
URLBase string
|
||||||
|
Version string
|
||||||
|
WEB struct {
|
||||||
|
Menu []string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GitStruct : Updateinformationen von GitHub
|
||||||
|
type GitStruct struct {
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataStruct : Alle Daten werden hier abgelegt. (Lineup, XMLTV)
|
||||||
|
type DataStruct struct {
|
||||||
|
Cache struct {
|
||||||
|
ImagesCache []string
|
||||||
|
ImagesFiles []string
|
||||||
|
ImagesURLS []string
|
||||||
|
PMS map[string]string
|
||||||
|
|
||||||
|
StreamingURLS map[string]StreamInfo
|
||||||
|
XMLTV map[string]XMLTV
|
||||||
|
|
||||||
|
Streams struct {
|
||||||
|
Active []string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Filter []Filter
|
||||||
|
|
||||||
|
Playlist struct {
|
||||||
|
M3U struct {
|
||||||
|
Groups struct {
|
||||||
|
Text []string
|
||||||
|
Value []string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamPreviewUI struct {
|
||||||
|
Active []string
|
||||||
|
Inactive []string
|
||||||
|
}
|
||||||
|
|
||||||
|
Streams struct {
|
||||||
|
Active []interface{}
|
||||||
|
All []interface{}
|
||||||
|
Inactive []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
XMLTV struct {
|
||||||
|
Files []string
|
||||||
|
Mapping map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
XEPG struct {
|
||||||
|
Channels map[string]interface{}
|
||||||
|
XEPGCount int64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter : Wird für die Filterregeln verwendet
|
||||||
|
type Filter struct {
|
||||||
|
CaseSensitive bool
|
||||||
|
Rule string
|
||||||
|
Type string
|
||||||
|
}
|
||||||
|
|
||||||
|
// XEPGChannelStruct : XEPG Struktur
|
||||||
|
type XEPGChannelStruct struct {
|
||||||
|
FileM3UID string `json:"_file.m3u.id,required"`
|
||||||
|
FileM3UName string `json:"_file.m3u.name,required"`
|
||||||
|
FileM3UPath string `json:"_file.m3u.path,required"`
|
||||||
|
GroupTitle string `json:"group-title,required"`
|
||||||
|
Name string `json:"name,required"`
|
||||||
|
TvgID string `json:"tvg-id,required"`
|
||||||
|
TvgLogo string `json:"tvg-logo,required"`
|
||||||
|
TvgName string `json:"tvg-name,required"`
|
||||||
|
URL string `json:"url,required"`
|
||||||
|
UUIDKey string `json:"_uuid.key,required"`
|
||||||
|
UUIDValue string `json:"_uuid.value,omitempty"`
|
||||||
|
Values string `json:"_values,required"`
|
||||||
|
XActive bool `json:"x-active,required"`
|
||||||
|
XCategory string `json:"x-category,required"`
|
||||||
|
XChannelID string `json:"x-channelID,required"`
|
||||||
|
XEPG string `json:"x-epg,required"`
|
||||||
|
XGroupTitle string `json:"x-group-title,required"`
|
||||||
|
XMapping string `json:"x-mapping,required"`
|
||||||
|
XmltvFile string `json:"x-xmltv-file,required"`
|
||||||
|
XName string `json:"x-name,required"`
|
||||||
|
XUpdateChannelIcon bool `json:"x-update-channel-icon,required"`
|
||||||
|
XUpdateChannelName bool `json:"x-update-channel-name,required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// M3UChannelStructXEPG : M3U Struktur für XEPG
|
||||||
|
type M3UChannelStructXEPG struct {
|
||||||
|
FileM3UID string `json:"_file.m3u.id,required"`
|
||||||
|
FileM3UName string `json:"_file.m3u.name,required"`
|
||||||
|
FileM3UPath string `json:"_file.m3u.path,required"`
|
||||||
|
GroupTitle string `json:"group-title,required"`
|
||||||
|
Name string `json:"name,required"`
|
||||||
|
TvgID string `json:"tvg-id,required"`
|
||||||
|
TvgLogo string `json:"tvg-logo,required"`
|
||||||
|
TvgName string `json:"tvg-name,required"`
|
||||||
|
URL string `json:"url,required"`
|
||||||
|
UUIDKey string `json:"_uuid.key,required"`
|
||||||
|
UUIDValue string `json:"_uuid.value,required"`
|
||||||
|
Values string `json:"_values,required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterStruct : Filter Struktur
|
||||||
|
type FilterStruct struct {
|
||||||
|
Active bool `json:"active,required"`
|
||||||
|
CaseSensitive bool `json:"caseSensitive,required"`
|
||||||
|
Description string `json:"description,required"`
|
||||||
|
Exclude string `json:"exclude,required"`
|
||||||
|
Filter string `json:"filter,required"`
|
||||||
|
Include string `json:"include,required"`
|
||||||
|
Name string `json:"name,required"`
|
||||||
|
Rule string `json:"rule,omitempty"`
|
||||||
|
Type string `json:"type,required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamingURLS : Informationen zu allen streaming URL's
|
||||||
|
type StreamingURLS struct {
|
||||||
|
Streams map[string]StreamInfo `json:"channels,required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamInfo : Informationen zum Kanal für die streaming URL
|
||||||
|
type StreamInfo struct {
|
||||||
|
ChannelNumber string `json:"channelNumber,required"`
|
||||||
|
Name string `json:"name,required"`
|
||||||
|
PlaylistID string `json:"playlistID,required"`
|
||||||
|
URL string `json:"url,required"`
|
||||||
|
URLid string `json:"urlID,required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notification : Notifikationen im Webinterface
|
||||||
|
type Notification struct {
|
||||||
|
Headline string `json:"headline,required"`
|
||||||
|
Message string `json:"message,required"`
|
||||||
|
New bool `json:"new,required"`
|
||||||
|
Time string `json:"time,required"`
|
||||||
|
Type string `json:"type,required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SettingsStrcut : Inhalt der settings.json
|
||||||
|
type SettingsStrcut struct {
|
||||||
|
API bool `json:"api"`
|
||||||
|
AuthenticationAPI bool `json:"authentication.api"`
|
||||||
|
AuthenticationM3U bool `json:"authentication.m3u"`
|
||||||
|
AuthenticationPMS bool `json:"authentication.pms"`
|
||||||
|
AuthenticationWEB bool `json:"authentication.web"`
|
||||||
|
AuthenticationXML bool `json:"authentication.xml"`
|
||||||
|
BackupKeep int `json:"backup.keep"`
|
||||||
|
BackupPath string `json:"backup.path"`
|
||||||
|
Branch string `json:"git.branch,omitempty"`
|
||||||
|
Buffer bool `json:"buffer"`
|
||||||
|
BufferSize int `json:"buffer.size.kb"`
|
||||||
|
BufferTimeout float64 `json:"buffer.timeout"`
|
||||||
|
CacheImages bool `json:"cache.images"`
|
||||||
|
EpgSource string `json:"epgSource"`
|
||||||
|
FileM3U []string `json:"file,omitempty"` // Beim Wizard wird die M3U in ein Slice gespeichert
|
||||||
|
FileXMLTV []string `json:"xmltv,omitempty"` // Altes Speichersystem der Provider XML Datei Slice (Wird für die Umwandlung auf das neue benötigt)
|
||||||
|
|
||||||
|
Files struct {
|
||||||
|
HDHR map[string]interface{} `json:"hdhr"`
|
||||||
|
M3U map[string]interface{} `json:"m3u"`
|
||||||
|
XMLTV map[string]interface{} `json:"xmltv"`
|
||||||
|
} `json:"files"`
|
||||||
|
|
||||||
|
FilesUpdate bool `json:"files.update"`
|
||||||
|
Filter map[int64]interface{} `json:"filter"`
|
||||||
|
Key string `json:"key,omitempty"`
|
||||||
|
Language string `json:"language"`
|
||||||
|
LogEntriesRAM int `json:"log.entries.ram"`
|
||||||
|
M3U8AdaptiveBandwidthMBPS int `json:"m3u8.adaptive.bandwidth.mbps"`
|
||||||
|
MappingFirstChannel float64 `json:"mapping.first.channel"`
|
||||||
|
Port string `json:"port"`
|
||||||
|
SSDP bool `json:"ssdp"`
|
||||||
|
TempPath string `json:"temp.path"`
|
||||||
|
Tuner int `json:"tuner"`
|
||||||
|
Update []string `json:"update"`
|
||||||
|
UpdateURL string `json:"update.url,omitempty"`
|
||||||
|
UserAgent string `json:"user.agent"`
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
XepgReplaceMissingImages bool `json:"xepg.replace.missing.images"`
|
||||||
|
XteveAutoUpdate bool `json:"xteveAutoUpdate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LanguageUI : Sprache für das WebUI
|
||||||
|
type LanguageUI struct {
|
||||||
|
Login struct {
|
||||||
|
Failed string
|
||||||
|
}
|
||||||
|
}
|
||||||
145
src/struct-webserver.go
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
// RequestStruct : Anfragen über die Websocket Schnittstelle
|
||||||
|
type RequestStruct struct {
|
||||||
|
// Befehle an xTeVe
|
||||||
|
Cmd string `json:"cmd,required"`
|
||||||
|
|
||||||
|
// Benutzer
|
||||||
|
DeleteUser bool `json:"deleteUser,omitempty"`
|
||||||
|
UserData map[string]interface{} `json:"userData,omitempty"`
|
||||||
|
|
||||||
|
// Mapping
|
||||||
|
EpgMapping map[string]interface{} `json:"epgMapping,omitempty"`
|
||||||
|
|
||||||
|
// Restore
|
||||||
|
Base64 string `json:"base64,omitempty"`
|
||||||
|
|
||||||
|
// Neue Werte für die Einstellungen (settings.json)
|
||||||
|
Settings struct {
|
||||||
|
API *bool `json:"api,omitempty"`
|
||||||
|
AuthenticationAPI *bool `json:"authentication.api,omitempty"`
|
||||||
|
AuthenticationM3U *bool `json:"authentication.m3u,omitempty"`
|
||||||
|
AuthenticationPMS *bool `json:"authentication.pms,omitempty"`
|
||||||
|
AuthenticationWEP *bool `json:"authentication.web,omitempty"`
|
||||||
|
AuthenticationXML *bool `json:"authentication.xml,omitempty"`
|
||||||
|
BackupKeep *int `json:"backup.keep,omitempty"`
|
||||||
|
BackupPath *string `json:"backup.path,omitempty"`
|
||||||
|
Buffer *bool `json:"buffer,omitempty"`
|
||||||
|
BufferSize *int `json:"buffer.size.kb, omitempty"`
|
||||||
|
BufferTimeout *float64 `json:"buffer.timeout,omitempty"`
|
||||||
|
CacheImages *bool `json:"cache.images,omitempty"`
|
||||||
|
EpgSource *string `json:"epgSource,omitempty"`
|
||||||
|
FilesUpdate *bool `json:"files.update,omitempty"`
|
||||||
|
TempPath *string `json:"temp.path,omitempty"`
|
||||||
|
Tuner *int `json:"tuner,omitempty"`
|
||||||
|
Update *[]string `json:"update,omitempty"`
|
||||||
|
UserAgent *string `json:"user.agent,omitempty"`
|
||||||
|
XepgReplaceMissingImages *bool `json:"xepg.replace.missing.images,omitempty"`
|
||||||
|
XteveAutoUpdate *bool `json:"xteveAutoUpdate,omitempty"`
|
||||||
|
} `json:"settings,omitempty"`
|
||||||
|
|
||||||
|
// Upload Logo
|
||||||
|
Filename string `json:"filename,omitempty"`
|
||||||
|
|
||||||
|
// Filter
|
||||||
|
Filter map[int64]interface{} `json:"filter,omitempty"`
|
||||||
|
|
||||||
|
// Dateien (M3U, HDHR, XMLTV)
|
||||||
|
Files struct {
|
||||||
|
HDHR map[string]interface{} `json:"hdhr,omitempty"`
|
||||||
|
M3U map[string]interface{} `json:"m3u,omitempty"`
|
||||||
|
XMLTV map[string]interface{} `json:"xmltv,omitempty"`
|
||||||
|
} `json:"files,omitempty"`
|
||||||
|
|
||||||
|
// Wizard
|
||||||
|
Wizard struct {
|
||||||
|
EpgSource *string `json:"epgSource,omitempty"`
|
||||||
|
M3U *string `json:"m3u,omitempty"`
|
||||||
|
Tuner *int `json:"tuner,omitempty"`
|
||||||
|
XMLTV *string `json:"xmltv,omitempty"`
|
||||||
|
} `json:"wizard,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseStruct : Antworten an den Client (WEB)
|
||||||
|
type ResponseStruct struct {
|
||||||
|
ClientInfo struct {
|
||||||
|
ARCH string `json:"arch"`
|
||||||
|
Branch string `json:"branch,omitempty"`
|
||||||
|
DVR string `json:"DVR"`
|
||||||
|
EpgSource string `json:"epgSource"`
|
||||||
|
Errors int `json:"errors"`
|
||||||
|
M3U string `json:"m3u-url,required"`
|
||||||
|
OS string `json:"os"`
|
||||||
|
Streams string `json:"streams"`
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Warnings int `json:"warnings"`
|
||||||
|
XEPGCount int64 `json:"xepg"`
|
||||||
|
XML string `json:"xepg-url,required"`
|
||||||
|
} `json:"clientInfo,omitempty"`
|
||||||
|
|
||||||
|
Data struct {
|
||||||
|
Playlist struct {
|
||||||
|
M3U struct {
|
||||||
|
Groups struct {
|
||||||
|
Text []string `json:"text,required"`
|
||||||
|
Value []string `json:"value,required"`
|
||||||
|
} `json:"groups,required"`
|
||||||
|
} `json:"m3u,required"`
|
||||||
|
} `json:"playlist,required"`
|
||||||
|
|
||||||
|
StreamPreviewUI struct {
|
||||||
|
Active []string `json:"activeStreams,required"`
|
||||||
|
Inactive []string `json:"inactiveStreams,required"`
|
||||||
|
}
|
||||||
|
} `json:"data,required"`
|
||||||
|
|
||||||
|
Alert string `json:"alert,omitempty"`
|
||||||
|
ConfigurationWizard bool `json:"configurationWizard,required"`
|
||||||
|
Error string `json:"err,omitempty"`
|
||||||
|
Log WebScreenLogStruct `json:"log,required"`
|
||||||
|
LogoURL string `json:"logoURL,omitempty"`
|
||||||
|
OpenLink string `json:"openLink,omitempty"`
|
||||||
|
OpenMenu string `json:"openMenu,omitempty"`
|
||||||
|
Reload bool `json:"reload,omitempty"`
|
||||||
|
Settings SettingsStrcut `json:"settings,required"`
|
||||||
|
Status bool `json:"status,required"`
|
||||||
|
Token string `json:"token,omitempty"`
|
||||||
|
Users map[string]interface{} `json:"users,omitempty"`
|
||||||
|
Wizard int `json:"wizard,omitempty"`
|
||||||
|
XEPG map[string]interface{} `json:"xepg,required"`
|
||||||
|
|
||||||
|
Notification map[string]Notification `json:"notification,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIRequestStruct : Anfrage über die API Schnittstelle
|
||||||
|
type APIRequestStruct struct {
|
||||||
|
Cmd string `json:"cmd"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIResponseStruct : Antwort an den Client (API)
|
||||||
|
type APIResponseStruct struct {
|
||||||
|
EpgSource string `json:"epg.source,omitempty"`
|
||||||
|
Error string `json:"err,omitempty"`
|
||||||
|
Status bool `json:"status,required"`
|
||||||
|
StreamsActive int64 `json:"streams.active,omitempty"`
|
||||||
|
StreamsAll int64 `json:"streams.all,omitempty"`
|
||||||
|
StreamsXepg int64 `json:"streams.xepg,omitempty"`
|
||||||
|
Token string `json:"token,omitempty"`
|
||||||
|
URLDvr string `json:"url.dvr,omitempty"`
|
||||||
|
URLM3U string `json:"url.m3u,omitempty"`
|
||||||
|
URLXepg string `json:"url.xepg,omitempty"`
|
||||||
|
VersionAPI string `json:"version.api,omitempty"`
|
||||||
|
VersionXteve string `json:"version.xteve,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebScreenLogStruct : Logs werden im RAM gespeichert und für das Webinterface bereitgestellt
|
||||||
|
type WebScreenLogStruct struct {
|
||||||
|
Errors int `json:"errors,required"`
|
||||||
|
Log []string `json:"log,required"`
|
||||||
|
Warnings int `json:"warnings,required"`
|
||||||
|
}
|
||||||
123
src/struct-xml.go
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import "encoding/xml"
|
||||||
|
|
||||||
|
// XMLTV : XMLTV Datei
|
||||||
|
type XMLTV struct {
|
||||||
|
Generator string `xml:"generator-info-name,attr"`
|
||||||
|
Source string `xml:"source-info-name,attr"`
|
||||||
|
XMLName xml.Name `xml:"tv"`
|
||||||
|
|
||||||
|
Channel []*Channel `xml:"channel"`
|
||||||
|
Program []*Program `xml:"programme"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Channel : Kanäle
|
||||||
|
type Channel struct {
|
||||||
|
ID string `xml:"id,attr"`
|
||||||
|
DisplayName []DisplayName `xml:"display-name"`
|
||||||
|
Icon Icon `xml:"icon"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisplayName : Kanalname
|
||||||
|
type DisplayName struct {
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icon : Senderlogo
|
||||||
|
type Icon struct {
|
||||||
|
Src string `xml:"src,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Program : Programme
|
||||||
|
type Program struct {
|
||||||
|
Channel string `xml:"channel,attr"`
|
||||||
|
Start string `xml:"start,attr"`
|
||||||
|
Stop string `xml:"stop,attr"`
|
||||||
|
|
||||||
|
Title []*Title `xml:"title"`
|
||||||
|
SubTitle []*SubTitle `xml:"sub-title"`
|
||||||
|
Desc []*Desc `xml:"desc"`
|
||||||
|
Category []*Category `xml:"category"`
|
||||||
|
Country []*Country `xml:"country"`
|
||||||
|
EpisodeNum []*EpisodeNum `xml:"episode-num"`
|
||||||
|
Poster []Poster `xml:"icon"`
|
||||||
|
Language []*Language `xml:"language"`
|
||||||
|
Video Video `xml:"video"`
|
||||||
|
Date string `xml:"date"`
|
||||||
|
PreviouslyShown *PreviouslyShown `xml:"previously-shown"`
|
||||||
|
New *New `xml:"new"`
|
||||||
|
Live *Live `xml:"live"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title : Programmtitel
|
||||||
|
type Title struct {
|
||||||
|
Lang string `xml:"lang,attr"`
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubTitle : Kurzbeschreibung
|
||||||
|
type SubTitle struct {
|
||||||
|
Lang string `xml:"lang,attr"`
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//Desc : Programmbeschreibung
|
||||||
|
type Desc struct {
|
||||||
|
Lang string `xml:"lang,attr"`
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Category : Kategorien
|
||||||
|
type Category struct {
|
||||||
|
Lang string `xml:"lang,attr"`
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Language : Sprachen
|
||||||
|
type Language struct {
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Country : Länder
|
||||||
|
type Country struct {
|
||||||
|
Lang string `xml:"lang,attr"`
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EpisodeNum : Episodennummerierung
|
||||||
|
type EpisodeNum struct {
|
||||||
|
System string `xml:"system,attr"`
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poster : Programmposter / Cover
|
||||||
|
type Poster struct {
|
||||||
|
Height string `xml:"height,attr"`
|
||||||
|
Src string `xml:"src,attr"`
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
Width string `xml:"width,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Video : Video Metadaten
|
||||||
|
type Video struct {
|
||||||
|
Aspect string `xml:"aspect,omitempty"`
|
||||||
|
Colour string `xml:"colour,omitempty"`
|
||||||
|
Present string `xml:"present,omitempty"`
|
||||||
|
Quality string `xml:"quality,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreviouslyShown : Widerholung bzw. Erstausstrahlung
|
||||||
|
type PreviouslyShown struct {
|
||||||
|
Start string `xml:"start,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// New : Sendung als neu deklarieren
|
||||||
|
type New struct {
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Live : Sendung als Liveübertragung deklarieren
|
||||||
|
type Live struct {
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
}
|
||||||
323
src/system.go
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Entwicklerinfos anzeigen
|
||||||
|
func showDevInfo() {
|
||||||
|
|
||||||
|
if System.Dev == true {
|
||||||
|
|
||||||
|
fmt.Print("\033[31m")
|
||||||
|
fmt.Println("* * * * * D E V M O D E * * * * *")
|
||||||
|
fmt.Println("Version: ", System.Version)
|
||||||
|
fmt.Println("Build: ", System.Build)
|
||||||
|
fmt.Println("* * * * * * * * * * * * * * * * * *")
|
||||||
|
fmt.Print("\033[0m")
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alle Systemordner erstellen
|
||||||
|
func createSystemFolders() (err error) {
|
||||||
|
|
||||||
|
e := reflect.ValueOf(&System.Folder).Elem()
|
||||||
|
|
||||||
|
for i := 0; i < e.NumField(); i++ {
|
||||||
|
|
||||||
|
var folder = e.Field(i).Interface().(string)
|
||||||
|
|
||||||
|
err = checkFolder(folder)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alle Systemdateien erstellen
|
||||||
|
func createSystemFiles() (err error) {
|
||||||
|
|
||||||
|
var debug string
|
||||||
|
for _, file := range SystemFiles {
|
||||||
|
|
||||||
|
var filename = getPlatformFile(System.Folder.Config + file)
|
||||||
|
|
||||||
|
err = checkFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
// Datei existiert nicht, wird jetzt erstellt
|
||||||
|
err = saveMapToJSONFile(filename, make(map[string]interface{}))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
debug = fmt.Sprintf("Create File:%s", filename)
|
||||||
|
showDebug(debug, 1)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
switch file {
|
||||||
|
|
||||||
|
case "authentication.json":
|
||||||
|
System.File.Authentication = filename
|
||||||
|
case "pms.json":
|
||||||
|
System.File.PMS = filename
|
||||||
|
case "settings.json":
|
||||||
|
System.File.Settings = filename
|
||||||
|
case "xepg.json":
|
||||||
|
System.File.XEPG = filename
|
||||||
|
case "urls.json":
|
||||||
|
System.File.URLS = filename
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Einstellungen laden und default Werte setzen (xTeVe)
|
||||||
|
func loadSettings() (settings SettingsStrcut, err error) {
|
||||||
|
|
||||||
|
settingsMap, err := loadJSONFileToMap(System.File.Settings)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deafult Werte setzten
|
||||||
|
var defaults = make(map[string]interface{})
|
||||||
|
var dataMap = make(map[string]interface{})
|
||||||
|
|
||||||
|
dataMap["xmltv"] = make(map[string]interface{})
|
||||||
|
dataMap["m3u"] = make(map[string]interface{})
|
||||||
|
dataMap["hdhr"] = make(map[string]interface{})
|
||||||
|
|
||||||
|
defaults["api"] = false
|
||||||
|
defaults["authentication.api"] = false
|
||||||
|
defaults["authentication.m3u"] = false
|
||||||
|
defaults["authentication.pms"] = false
|
||||||
|
defaults["authentication.web"] = false
|
||||||
|
defaults["authentication.xml"] = false
|
||||||
|
defaults["backup.keep"] = 10
|
||||||
|
defaults["backup.path"] = System.Folder.Backup
|
||||||
|
defaults["buffer"] = false
|
||||||
|
defaults["buffer.size.kb"] = 1024
|
||||||
|
defaults["buffer.timeout"] = 500
|
||||||
|
defaults["cache.images"] = false
|
||||||
|
defaults["epgSource"] = "XEPG"
|
||||||
|
defaults["files"] = dataMap
|
||||||
|
defaults["files.update"] = true
|
||||||
|
defaults["filter"] = make(map[string]interface{})
|
||||||
|
defaults["git.branch"] = System.Branch
|
||||||
|
defaults["language"] = "en"
|
||||||
|
defaults["log.entries.ram"] = 500
|
||||||
|
defaults["mapping.first.channel"] = 1000
|
||||||
|
defaults["xepg.replace.missing.images"] = true
|
||||||
|
defaults["m3u8.adaptive.bandwidth.mbps"] = 10
|
||||||
|
defaults["port"] = "34400"
|
||||||
|
defaults["ssdp"] = true
|
||||||
|
defaults["tuner"] = 1
|
||||||
|
defaults["update"] = []string{"0000"}
|
||||||
|
defaults["user.agent"] = System.Name
|
||||||
|
defaults["uuid"] = createUUID()
|
||||||
|
defaults["version"] = System.Version
|
||||||
|
defaults["xteveAutoUpdate"] = true
|
||||||
|
defaults["temp.path"] = System.Folder.Temp
|
||||||
|
|
||||||
|
// Default Werte setzen
|
||||||
|
for key, value := range defaults {
|
||||||
|
if _, ok := settingsMap[key]; !ok {
|
||||||
|
settingsMap[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal([]byte(mapToJSON(settingsMap)), &settings)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Einstellungen von den Flags übernehmen
|
||||||
|
if len(System.Flag.Port) > 0 {
|
||||||
|
settings.Port = System.Flag.Port
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(System.Flag.Branch) > 0 {
|
||||||
|
settings.Branch = System.Flag.Branch
|
||||||
|
showInfo(fmt.Sprintf("Git Branch:Switching Git Branch to -> %s", settings.Branch))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = saveSettings(settings)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Einstellungen speichern (xTeVe)
|
||||||
|
func saveSettings(settings SettingsStrcut) (err error) {
|
||||||
|
|
||||||
|
if settings.BackupKeep == 0 {
|
||||||
|
settings.BackupKeep = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(settings.BackupPath) == 0 {
|
||||||
|
settings.BackupPath = System.Folder.Backup
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.BufferTimeout < 0 {
|
||||||
|
settings.BufferTimeout = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Folder.Temp = settings.TempPath + settings.UUID + string(os.PathSeparator)
|
||||||
|
|
||||||
|
err = writeByteToFile(System.File.Settings, []byte(mapToJSON(settings)))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings = settings
|
||||||
|
|
||||||
|
if System.Dev == true {
|
||||||
|
Settings.UUID = "2019-01-DEV-xTeVe!"
|
||||||
|
}
|
||||||
|
|
||||||
|
setDeviceID()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zugriff über die Domain ermöglichen
|
||||||
|
func setGlobalDomain(domain string) {
|
||||||
|
|
||||||
|
System.Domain = domain
|
||||||
|
|
||||||
|
switch Settings.AuthenticationPMS {
|
||||||
|
case true:
|
||||||
|
System.Addresses.DVR = "username:password@" + System.Domain
|
||||||
|
case false:
|
||||||
|
System.Addresses.DVR = System.Domain
|
||||||
|
}
|
||||||
|
|
||||||
|
switch Settings.AuthenticationM3U {
|
||||||
|
case true:
|
||||||
|
System.Addresses.M3U = System.ServerProtocol.M3U + "://" + System.Domain + "/m3u/xteve.m3u?username=xxx&password=yyy<br>(Specific groups: [http://...&group-title=foo,bar])"
|
||||||
|
case false:
|
||||||
|
System.Addresses.M3U = System.ServerProtocol.M3U + "://" + System.Domain + "/m3u/xteve.m3u (Specific groups: [http://...?group-title=foo,bar])"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch Settings.AuthenticationXML {
|
||||||
|
case true:
|
||||||
|
System.Addresses.XML = System.ServerProtocol.XML + "://" + System.Domain + "/xmltv/xteve.xml?username=xxx&password=yyy"
|
||||||
|
case false:
|
||||||
|
System.Addresses.XML = System.ServerProtocol.XML + "://" + System.Domain + "/xmltv/xteve.xml"
|
||||||
|
}
|
||||||
|
|
||||||
|
if Settings.EpgSource != "XEPG" {
|
||||||
|
System.Addresses.M3U = getErrMsg(2106)
|
||||||
|
System.Addresses.XML = getErrMsg(2106)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UUID generieren
|
||||||
|
func createUUID() (uuid string) {
|
||||||
|
uuid = time.Now().Format("2006-01") + "-" + randomString(4) + "-" + randomString(6)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eindeutige Geräte ID für Plex generieren
|
||||||
|
func setDeviceID() {
|
||||||
|
|
||||||
|
var id = Settings.UUID
|
||||||
|
|
||||||
|
switch Settings.Tuner {
|
||||||
|
case 1:
|
||||||
|
System.DeviceID = id
|
||||||
|
|
||||||
|
default:
|
||||||
|
System.DeviceID = fmt.Sprintf("%s:%d", id, Settings.Tuner)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider Streaming-URL zu xTeVe Streaming-URL konvertieren
|
||||||
|
func createStreamingURL(streamingType, playlistID, channelNumber, channelName, url string) (streamingURL string, err error) {
|
||||||
|
|
||||||
|
var streamInfo StreamInfo
|
||||||
|
var serverProtocol string
|
||||||
|
|
||||||
|
if len(Data.Cache.StreamingURLS) == 0 {
|
||||||
|
Data.Cache.StreamingURLS = make(map[string]StreamInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
var urlID = getMD5(fmt.Sprintf("%s-%s", playlistID, url))
|
||||||
|
|
||||||
|
if s, ok := Data.Cache.StreamingURLS[urlID]; ok {
|
||||||
|
|
||||||
|
streamInfo = s
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
streamInfo.URL = url
|
||||||
|
streamInfo.Name = channelName
|
||||||
|
streamInfo.PlaylistID = playlistID
|
||||||
|
streamInfo.ChannelNumber = channelNumber
|
||||||
|
streamInfo.URLid = urlID
|
||||||
|
|
||||||
|
Data.Cache.StreamingURLS[urlID] = streamInfo
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
switch streamingType {
|
||||||
|
|
||||||
|
case "DVR":
|
||||||
|
serverProtocol = System.ServerProtocol.DVR
|
||||||
|
|
||||||
|
case "M3U":
|
||||||
|
serverProtocol = System.ServerProtocol.M3U
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
streamingURL = fmt.Sprintf("%s://%s/stream/%s", serverProtocol, System.Domain, streamInfo.URLid)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStreamInfo(urlID string) (streamInfo StreamInfo, err error) {
|
||||||
|
|
||||||
|
if len(Data.Cache.StreamingURLS) == 0 {
|
||||||
|
|
||||||
|
tmp, err := loadJSONFileToMap(System.File.URLS)
|
||||||
|
if err != nil {
|
||||||
|
return streamInfo, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal([]byte(mapToJSON(tmp)), &Data.Cache.StreamingURLS)
|
||||||
|
if err != nil {
|
||||||
|
return streamInfo, err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if s, ok := Data.Cache.StreamingURLS[urlID]; ok {
|
||||||
|
streamInfo = s
|
||||||
|
streamInfo.URL = strings.Trim(streamInfo.URL, "\r\n")
|
||||||
|
} else {
|
||||||
|
err = errors.New("Streaming error")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
356
src/toolchain.go
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- System Tools ---
|
||||||
|
|
||||||
|
// Prüft ob der Ordner existiert, falls nicht, wir der Ordner erstellt
|
||||||
|
func checkFolder(path string) (err error) {
|
||||||
|
|
||||||
|
var debug string
|
||||||
|
_, err = os.Stat(filepath.Dir(path))
|
||||||
|
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
// Ordner existiert nicht, wird jetzt erstellt
|
||||||
|
|
||||||
|
err = os.MkdirAll(getPlatformPath(path), 0755)
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
debug = fmt.Sprintf("Create Folder:%s", path)
|
||||||
|
showDebug(debug, 1)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüft ob die datei im Dateisystem existiert
|
||||||
|
func checkFile(filename string) (err error) {
|
||||||
|
|
||||||
|
var file = getPlatformFile(filename)
|
||||||
|
|
||||||
|
if _, err = os.Stat(file); os.IsNotExist(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserHomeDirectory : Benutzer Homer Verzeichnis
|
||||||
|
func GetUserHomeDirectory() (userHomeDirectory string) {
|
||||||
|
|
||||||
|
usr, err := user.Current()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
for _, name := range []string{"HOME", "USERPROFILE"} {
|
||||||
|
|
||||||
|
if dir := os.Getenv(name); dir != "" {
|
||||||
|
userHomeDirectory = dir
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
userHomeDirectory = usr.HomeDir
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkFilePermission(dir string) (err error) {
|
||||||
|
|
||||||
|
var filename = dir + "permission.test"
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(filename, []byte(""), 0644)
|
||||||
|
if err == nil {
|
||||||
|
err = os.RemoveAll(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ordnerpfad für das laufende OS generieren
|
||||||
|
func getPlatformPath(path string) string {
|
||||||
|
return filepath.Dir(path) + string(os.PathSeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dateipfad für das laufende OS generieren
|
||||||
|
func getPlatformFile(filename string) (osFilePath string) {
|
||||||
|
|
||||||
|
path, file := filepath.Split(filename)
|
||||||
|
var newPath = filepath.Dir(path)
|
||||||
|
osFilePath = newPath + string(os.PathSeparator) + file
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dateinamen aus dem Dateipfad ausgeben
|
||||||
|
func getFilenameFromPath(path string) (file string) {
|
||||||
|
return filepath.Base(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nicht mehr verwendete Systemdaten löschen
|
||||||
|
func removeOldSystemData() {
|
||||||
|
// Temporären Ordner löschen
|
||||||
|
os.RemoveAll(System.Folder.Temp)
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
func removeChildItems(dir string) error {
|
||||||
|
|
||||||
|
files, err := filepath.Glob(filepath.Join(dir, "*"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
|
||||||
|
err = os.RemoveAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON
|
||||||
|
func mapToJSON(tmpMap interface{}) string {
|
||||||
|
|
||||||
|
jsonString, err := json.MarshalIndent(tmpMap, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "{}"
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(jsonString)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsonToMap(content string) map[string]interface{} {
|
||||||
|
|
||||||
|
var tmpMap = make(map[string]interface{})
|
||||||
|
json.Unmarshal([]byte(content), &tmpMap)
|
||||||
|
|
||||||
|
return (tmpMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsonToMapInt64(content string) map[int64]interface{} {
|
||||||
|
|
||||||
|
var tmpMap = make(map[int64]interface{})
|
||||||
|
json.Unmarshal([]byte(content), &tmpMap)
|
||||||
|
|
||||||
|
return (tmpMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsonToInterface(content string) (tmpMap interface{}, err error) {
|
||||||
|
|
||||||
|
err = json.Unmarshal([]byte(content), &tmpMap)
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveMapToJSONFile(file string, tmpMap interface{}) error {
|
||||||
|
|
||||||
|
var filename = getPlatformFile(file)
|
||||||
|
jsonString, err := json.MarshalIndent(tmpMap, "", " ")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(filename, []byte(jsonString), 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadJSONFileToMap(file string) (tmpMap map[string]interface{}, err error) {
|
||||||
|
|
||||||
|
f, err := os.Open(getPlatformFile(file))
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
content, err := ioutil.ReadAll(f)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
err = json.Unmarshal([]byte(content), &tmpMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binary
|
||||||
|
func readByteFromFile(file string) (content []byte, err error) {
|
||||||
|
|
||||||
|
f, err := os.Open(getPlatformFile(file))
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
content, err = ioutil.ReadAll(f)
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeByteToFile(file string, data []byte) (err error) {
|
||||||
|
|
||||||
|
var filename = getPlatformFile(file)
|
||||||
|
err = ioutil.WriteFile(filename, data, 0644)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func readStringFromFile(file string) (str string, err error) {
|
||||||
|
|
||||||
|
var content []byte
|
||||||
|
var filename = getPlatformFile(file)
|
||||||
|
|
||||||
|
err = checkFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err = ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
str = string(content)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Netzwerk
|
||||||
|
func resolveHostIP() (err error) {
|
||||||
|
|
||||||
|
netInterfaceAddresses, err := net.InterfaceAddrs()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, netInterfaceAddress := range netInterfaceAddresses {
|
||||||
|
|
||||||
|
networkIP, ok := netInterfaceAddress.(*net.IPNet)
|
||||||
|
System.IPAddressesList = append(System.IPAddressesList, networkIP.IP.String())
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
|
||||||
|
var ip = networkIP.IP.String()
|
||||||
|
|
||||||
|
if networkIP.IP.To4() != nil {
|
||||||
|
|
||||||
|
System.IPAddressesV4 = append(System.IPAddressesV4, ip)
|
||||||
|
|
||||||
|
if !networkIP.IP.IsLoopback() && ip[0:7] != "169.254" {
|
||||||
|
System.IPAddress = ip
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
System.IPAddressesV6 = append(System.IPAddressesV6, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Hostname, err = os.Hostname()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sonstiges
|
||||||
|
func randomString(n int) string {
|
||||||
|
|
||||||
|
const alphanum = "AB1CD2EF3GH4IJ5KL6MN7OP8QR9ST0UVWXYZ"
|
||||||
|
|
||||||
|
var bytes = make([]byte, n)
|
||||||
|
|
||||||
|
rand.Read(bytes)
|
||||||
|
|
||||||
|
for i, b := range bytes {
|
||||||
|
bytes[i] = alphanum[b%byte(len(alphanum))]
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTemplate(content string, tmpMap map[string]interface{}) (result string) {
|
||||||
|
|
||||||
|
t := template.Must(template.New("template").Parse(content))
|
||||||
|
|
||||||
|
var tpl bytes.Buffer
|
||||||
|
|
||||||
|
if err := t.Execute(&tpl, tmpMap); err != nil {
|
||||||
|
ShowError(err, 0)
|
||||||
|
}
|
||||||
|
result = tpl.String()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func indexOfString(element string, data []string) int {
|
||||||
|
|
||||||
|
for k, v := range data {
|
||||||
|
if element == v {
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func indexOfFloat64(element float64, data []float64) int {
|
||||||
|
|
||||||
|
for k, v := range data {
|
||||||
|
if element == v {
|
||||||
|
return (k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func indexOfInt(element int, data []int) int {
|
||||||
|
|
||||||
|
for k, v := range data {
|
||||||
|
if element == v {
|
||||||
|
return (k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMD5(str string) string {
|
||||||
|
|
||||||
|
md5Hasher := md5.New()
|
||||||
|
md5Hasher.Write([]byte(str))
|
||||||
|
|
||||||
|
return hex.EncodeToString(md5Hasher.Sum(nil))
|
||||||
|
}
|
||||||
273
src/update.go
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
up2date "../src/internal/up2date/client"
|
||||||
|
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BinaryUpdate : Binary Update Prozess. Git Branch master und beta wird von GitHub geladen.
|
||||||
|
func BinaryUpdate() (err error) {
|
||||||
|
|
||||||
|
if System.GitHub.Update == false {
|
||||||
|
showWarning(2099)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var debug string
|
||||||
|
|
||||||
|
var updater = &up2date.Updater
|
||||||
|
updater.Name = System.Update.Name
|
||||||
|
updater.Branch = System.Branch
|
||||||
|
|
||||||
|
up2date.Init()
|
||||||
|
|
||||||
|
switch System.Branch {
|
||||||
|
|
||||||
|
// Update von GitHub
|
||||||
|
case "master", "beta":
|
||||||
|
|
||||||
|
var gitInfo = fmt.Sprintf("%s/%s/info.json?raw=true", System.Update.Git, System.Branch)
|
||||||
|
var zipFile = fmt.Sprintf("%s/%s/%s_%s_%s.zip?raw=true", System.Update.Git, System.Branch, System.AppName, System.OS, System.ARCH)
|
||||||
|
var body []byte
|
||||||
|
|
||||||
|
var git GitStruct
|
||||||
|
|
||||||
|
resp, err := http.Get(gitInfo)
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 0)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
|
||||||
|
if resp.StatusCode == 404 {
|
||||||
|
err = fmt.Errorf(fmt.Sprintf("Update Server: %s (%s)", http.StatusText(resp.StatusCode), gitInfo))
|
||||||
|
ShowError(err, 6003)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = fmt.Errorf(fmt.Sprintf("%d: %s (%s)", resp.StatusCode, http.StatusText(resp.StatusCode), gitInfo))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
err = json.Unmarshal(body, &git)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
updater.Response.Status = true
|
||||||
|
updater.Response.UpdateZIP = zipFile
|
||||||
|
updater.Response.Version = git.Version
|
||||||
|
updater.Response.Filename = git.Filename
|
||||||
|
|
||||||
|
// Update vom eigenen Server
|
||||||
|
default:
|
||||||
|
|
||||||
|
updater.URL = Settings.UpdateURL
|
||||||
|
|
||||||
|
if len(updater.URL) == 0 {
|
||||||
|
showInfo(fmt.Sprintf("Update URL:No server URL specified, update will not be performed. Branch: %s", System.Branch))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
showInfo("Update URL:" + updater.URL)
|
||||||
|
fmt.Println("-----------------")
|
||||||
|
|
||||||
|
// Versionsinformationen vom Server laden
|
||||||
|
err = up2date.GetVersion()
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
debug = fmt.Sprintf(err.Error())
|
||||||
|
showDebug(debug, 1)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(updater.Response.Reason) > 0 {
|
||||||
|
|
||||||
|
err = fmt.Errorf(fmt.Sprintf("Update Server: %s", updater.Response.Reason))
|
||||||
|
ShowError(err, 6002)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentVersion = System.Version + "." + System.Build
|
||||||
|
|
||||||
|
// Versionsnummer überprüfen
|
||||||
|
if updater.Response.Version > currentVersion && updater.Response.Status == true {
|
||||||
|
|
||||||
|
if Settings.XteveAutoUpdate == true {
|
||||||
|
// Update durchführen
|
||||||
|
var fileType, url string
|
||||||
|
|
||||||
|
showInfo(fmt.Sprintf("Update Available:Version: %s", updater.Response.Version))
|
||||||
|
|
||||||
|
switch System.Branch {
|
||||||
|
|
||||||
|
// Update von GitHub
|
||||||
|
case "master", "beta":
|
||||||
|
showInfo(fmt.Sprintf("Update Server:GitHub"))
|
||||||
|
|
||||||
|
// Update vom eigenen Server
|
||||||
|
default:
|
||||||
|
showInfo(fmt.Sprintf("Update Server:%s", Settings.UpdateURL))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
showInfo(fmt.Sprintf("Start Update:Branch: %s", updater.Branch))
|
||||||
|
|
||||||
|
// Neue Version als BIN Datei herunterladen
|
||||||
|
if len(updater.Response.UpdateBIN) > 0 {
|
||||||
|
url = updater.Response.UpdateBIN
|
||||||
|
fileType = "bin"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neue Version als ZIP Datei herunterladen
|
||||||
|
if len(updater.Response.UpdateZIP) > 0 {
|
||||||
|
url = updater.Response.UpdateZIP
|
||||||
|
fileType = "zip"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(url) > 0 {
|
||||||
|
|
||||||
|
err = up2date.DoUpdate(fileType, updater.Response.Filename)
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 6002)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Hinweis ausgeben
|
||||||
|
showWarning(6004)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func conditionalUpdateChanges() (err error) {
|
||||||
|
|
||||||
|
checkVersion:
|
||||||
|
settingsMap, err := loadJSONFileToMap(System.File.Settings)
|
||||||
|
if err != nil || len(settingsMap) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if settingsVersion, ok := settingsMap["version"].(string); ok {
|
||||||
|
|
||||||
|
// Letzte Kompatible Version (1.4.4)
|
||||||
|
if settingsVersion < System.Compatibility {
|
||||||
|
err = errors.New(getErrMsg(1013))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch settingsVersion {
|
||||||
|
|
||||||
|
case "1.4.4":
|
||||||
|
// UUID Wert in xepg.json setzen
|
||||||
|
err = setValueForUUID()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neuer Filter (WebUI). Alte Filtereinstellungen werden konvertiert
|
||||||
|
if oldFilter, ok := settingsMap["filter"].([]interface{}); ok {
|
||||||
|
var newFilterMap = convertToNewFilter(oldFilter)
|
||||||
|
settingsMap["filter"] = newFilterMap
|
||||||
|
|
||||||
|
settingsMap["version"] = "1.9.0"
|
||||||
|
|
||||||
|
err = saveMapToJSONFile(System.File.Settings, settingsMap)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
goto checkVersion
|
||||||
|
|
||||||
|
} else {
|
||||||
|
err = errors.New(getErrMsg(1030))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
case "1.9.0":
|
||||||
|
// Falls es in einem späteren Update Änderungen an der Datenbank gibt, geht es hier weiter
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// settings.json ist zu alt (älter als Version 1.4.4)
|
||||||
|
err = errors.New(getErrMsg(1013))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertToNewFilter(oldFilter []interface{}) (newFilterMap map[int]interface{}) {
|
||||||
|
|
||||||
|
newFilterMap = make(map[int]interface{})
|
||||||
|
|
||||||
|
switch reflect.TypeOf(oldFilter).Kind() {
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
s := reflect.ValueOf(oldFilter)
|
||||||
|
|
||||||
|
for i := 0; i < s.Len(); i++ {
|
||||||
|
|
||||||
|
var newFilter FilterStruct
|
||||||
|
newFilter.Active = true
|
||||||
|
newFilter.Name = fmt.Sprintf("Custom filter %d", i+1)
|
||||||
|
newFilter.Filter = s.Index(i).Interface().(string)
|
||||||
|
newFilter.Type = "custom-filter"
|
||||||
|
newFilter.CaseSensitive = false
|
||||||
|
|
||||||
|
newFilterMap[i] = newFilter
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func setValueForUUID() (err error) {
|
||||||
|
|
||||||
|
xepg, err := loadJSONFileToMap(System.File.XEPG)
|
||||||
|
|
||||||
|
for _, c := range xepg {
|
||||||
|
|
||||||
|
var xepgChannel = c.(map[string]interface{})
|
||||||
|
|
||||||
|
if uuidKey, ok := xepgChannel["_uuid.key"].(string); ok {
|
||||||
|
|
||||||
|
if value, ok := xepgChannel[uuidKey].(string); ok {
|
||||||
|
|
||||||
|
if len(value) > 0 {
|
||||||
|
xepgChannel["_uuid.value"] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
err = saveMapToJSONFile(System.File.XEPG, xepg)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
54
src/webUI.go
Normal file
1050
src/webserver.go
Normal file
967
src/xepg.go
Normal file
@@ -0,0 +1,967 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"path"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Provider XMLTV Datei überprüfen
|
||||||
|
func checkXMLCompatibility(id string, body []byte) (err error) {
|
||||||
|
|
||||||
|
var xmltv XMLTV
|
||||||
|
var compatibility = make(map[string]int)
|
||||||
|
|
||||||
|
err = xml.Unmarshal(body, &xmltv)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
compatibility["xmltv.channels"] = len(xmltv.Channel)
|
||||||
|
compatibility["xmltv.programs"] = len(xmltv.Program)
|
||||||
|
|
||||||
|
setProviderCompatibility(id, "xmltv", compatibility)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// XEPG Daten erstellen
|
||||||
|
func buildXEPG(background bool) {
|
||||||
|
|
||||||
|
if System.ScanInProgress == 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
System.ScanInProgress = 1
|
||||||
|
|
||||||
|
if Settings.EpgSource == "XEPG" {
|
||||||
|
|
||||||
|
switch background {
|
||||||
|
|
||||||
|
case true:
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
|
||||||
|
createXEPGMapping()
|
||||||
|
createXEPGDatabase()
|
||||||
|
mapping()
|
||||||
|
cleanupXEPG()
|
||||||
|
createXMLTVFile()
|
||||||
|
createM3UFile()
|
||||||
|
go cachingImages()
|
||||||
|
|
||||||
|
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
||||||
|
|
||||||
|
System.ScanInProgress = 0
|
||||||
|
|
||||||
|
// Cache löschen
|
||||||
|
/*
|
||||||
|
Data.Cache.XMLTV = make(map[string]XMLTV)
|
||||||
|
Data.Cache.XMLTV = nil
|
||||||
|
*/
|
||||||
|
runtime.GC()
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
|
case false:
|
||||||
|
|
||||||
|
createXEPGMapping()
|
||||||
|
createXEPGDatabase()
|
||||||
|
mapping()
|
||||||
|
cleanupXEPG()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
|
||||||
|
createXMLTVFile()
|
||||||
|
createM3UFile()
|
||||||
|
go cachingImages()
|
||||||
|
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
||||||
|
|
||||||
|
System.ScanInProgress = 0
|
||||||
|
|
||||||
|
// Cache löschen
|
||||||
|
//Data.Cache.XMLTV = make(map[string]XMLTV)
|
||||||
|
//Data.Cache.XMLTV = nil
|
||||||
|
runtime.GC()
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
getLineup()
|
||||||
|
System.ScanInProgress = 0
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// XEPG Daten aktualisieren
|
||||||
|
func updateXEPG(background bool) {
|
||||||
|
|
||||||
|
if System.ScanInProgress == 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
System.ScanInProgress = 1
|
||||||
|
|
||||||
|
if Settings.EpgSource == "XEPG" {
|
||||||
|
|
||||||
|
switch background {
|
||||||
|
|
||||||
|
case false:
|
||||||
|
|
||||||
|
createXEPGDatabase()
|
||||||
|
mapping()
|
||||||
|
cleanupXEPG()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
|
||||||
|
createXMLTVFile()
|
||||||
|
createM3UFile()
|
||||||
|
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
||||||
|
|
||||||
|
System.ScanInProgress = 0
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
|
case true:
|
||||||
|
System.ScanInProgress = 0
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
System.ScanInProgress = 0
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache löschen
|
||||||
|
//Data.Cache.XMLTV = nil //make(map[string]XMLTV)
|
||||||
|
//Data.Cache.XMLTV = make(map[string]XMLTV)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mapping Menü für die XMLTV Dateien erstellen
|
||||||
|
func createXEPGMapping() {
|
||||||
|
|
||||||
|
Data.XMLTV.Files = getLocalProviderFiles("xmltv")
|
||||||
|
Data.XMLTV.Mapping = make(map[string]interface{})
|
||||||
|
|
||||||
|
var tmpMap = make(map[string]interface{})
|
||||||
|
|
||||||
|
var friendlyDisplayName = func(channel Channel) (displayName string) {
|
||||||
|
var dn = channel.DisplayName
|
||||||
|
displayName = dn[0].Value
|
||||||
|
|
||||||
|
switch len(dn) {
|
||||||
|
case 1:
|
||||||
|
displayName = dn[0].Value
|
||||||
|
default:
|
||||||
|
displayName = fmt.Sprintf("%s (%s)", dn[1].Value, dn[0].Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(Data.XMLTV.Files) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(Data.XMLTV.Files) - 1; i >= 0; i-- {
|
||||||
|
|
||||||
|
var file = Data.XMLTV.Files[i]
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var fileID = strings.TrimSuffix(getFilenameFromPath(file), path.Ext(getFilenameFromPath(file)))
|
||||||
|
showInfo("XEPG:" + "Parse XMLTV file: " + getProviderParameter(fileID, "xmltv", "name"))
|
||||||
|
|
||||||
|
//xmltv, err = getLocalXMLTV(file)
|
||||||
|
var xmltv XMLTV
|
||||||
|
|
||||||
|
err = getLocalXMLTV(file, &xmltv)
|
||||||
|
if err != nil {
|
||||||
|
Data.XMLTV.Files = append(Data.XMLTV.Files, Data.XMLTV.Files[i+1:]...)
|
||||||
|
var errMsg = err.Error()
|
||||||
|
err = errors.New(getProviderParameter(fileID, "xmltv", "name") + ": " + errMsg)
|
||||||
|
ShowError(err, 000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// XML Parsen (Provider Datei)
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
// Daten aus der XML Datei in eine temporäre Map schreiben
|
||||||
|
var xmltvMap = make(map[string]interface{})
|
||||||
|
|
||||||
|
for _, c := range xmltv.Channel {
|
||||||
|
var channel = make(map[string]interface{})
|
||||||
|
|
||||||
|
channel["id"] = c.ID
|
||||||
|
channel["display-name"] = friendlyDisplayName(*c)
|
||||||
|
channel["icon"] = c.Icon.Src
|
||||||
|
|
||||||
|
xmltvMap[c.ID] = channel
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpMap[getFilenameFromPath(file)] = xmltvMap
|
||||||
|
Data.XMLTV.Mapping[getFilenameFromPath(file)] = xmltvMap
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auswahl für den Dummy erstellen
|
||||||
|
var dummy = make(map[string]interface{})
|
||||||
|
var times = []string{"30", "60", "90", "120"}
|
||||||
|
|
||||||
|
for _, i := range times {
|
||||||
|
|
||||||
|
var dummyChannel = make(map[string]string)
|
||||||
|
dummyChannel["display-name"] = i + " Minutes"
|
||||||
|
dummyChannel["id"] = i + "_Minutes"
|
||||||
|
dummyChannel["icon"] = ""
|
||||||
|
|
||||||
|
dummy[dummyChannel["id"]] = dummyChannel
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Data.XMLTV.Mapping = tmpMap
|
||||||
|
Data.XMLTV.Mapping["xTeVe Dummy"] = dummy
|
||||||
|
|
||||||
|
tmpMap = make(map[string]interface{})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// XEPG Datenbank erstellen / aktualisieren
|
||||||
|
func createXEPGDatabase() (err error) {
|
||||||
|
|
||||||
|
var allChannelNumbers []float64
|
||||||
|
Data.Cache.Streams.Active = []string{}
|
||||||
|
|
||||||
|
Data.XEPG.Channels, err = loadJSONFileToMap(System.File.XEPG)
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 1004)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var createNewID = func() (xepg string) {
|
||||||
|
|
||||||
|
var firstID = 0 //len(Data.XEPG.Channels)
|
||||||
|
|
||||||
|
newXEPGID:
|
||||||
|
|
||||||
|
if _, ok := Data.XEPG.Channels["x-ID."+strconv.FormatInt(int64(firstID), 10)]; ok {
|
||||||
|
firstID++
|
||||||
|
goto newXEPGID
|
||||||
|
}
|
||||||
|
|
||||||
|
xepg = "x-ID." + strconv.FormatInt(int64(firstID), 10)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var getFreeChannelNumber = func() (xChannelID string) {
|
||||||
|
|
||||||
|
var firstFreeNumber float64 = Settings.MappingFirstChannel
|
||||||
|
|
||||||
|
newNumber:
|
||||||
|
|
||||||
|
if indexOfFloat64(firstFreeNumber, allChannelNumbers) == -1 {
|
||||||
|
xChannelID = fmt.Sprintf("%g", firstFreeNumber)
|
||||||
|
allChannelNumbers = append(allChannelNumbers, firstFreeNumber)
|
||||||
|
} else {
|
||||||
|
firstFreeNumber++
|
||||||
|
goto newNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
showInfo("XEPG:" + "Update database")
|
||||||
|
|
||||||
|
// Kanal mit fehlenden Kanalnummern löschen
|
||||||
|
for id, dxc := range Data.XEPG.Channels {
|
||||||
|
|
||||||
|
var xepgChannel XEPGChannelStruct
|
||||||
|
err = json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(xepgChannel.XChannelID) == 0 {
|
||||||
|
fmt.Println(mapToJSON(xepgChannel))
|
||||||
|
delete(Data.XEPG.Channels, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if xChannelID, err := strconv.ParseFloat(xepgChannel.XChannelID, 64); err == nil {
|
||||||
|
allChannelNumbers = append(allChannelNumbers, xChannelID)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dsa := range Data.Streams.Active {
|
||||||
|
|
||||||
|
var channelExists = false // Entscheidet ob ein Kanal neu zu Datenbank hinzugefügt werden soll.
|
||||||
|
var channelHasUUID = false // Überprüft, ob der Kanal (Stream) eindeutige ID's besitzt
|
||||||
|
var currentXEPGID string // Aktuelle Datenbank ID (XEPG). Wird verwendet, um den Kanal in der Datenbank mit dem Stream der M3u zu aktualisieren
|
||||||
|
var m3uChannel M3UChannelStructXEPG
|
||||||
|
|
||||||
|
err = json.Unmarshal([]byte(mapToJSON(dsa)), &m3uChannel)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Data.Cache.Streams.Active = append(Data.Cache.Streams.Active, m3uChannel.Name)
|
||||||
|
|
||||||
|
// XEPG Datenbank durchlaufen um nach dem Kanal zu suchen.
|
||||||
|
for xepg, dxc := range Data.XEPG.Channels {
|
||||||
|
|
||||||
|
var xepgChannel XEPGChannelStruct
|
||||||
|
err = json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vergleichen des Streams anhand einer UUID in der M3U mit dem Kanal in der Databank
|
||||||
|
if len(xepgChannel.UUIDValue) > 0 && len(m3uChannel.UUIDValue) > 0 {
|
||||||
|
|
||||||
|
if xepgChannel.UUIDValue == m3uChannel.UUIDValue && xepgChannel.UUIDKey == m3uChannel.UUIDKey {
|
||||||
|
|
||||||
|
channelExists = true
|
||||||
|
channelHasUUID = true
|
||||||
|
currentXEPGID = xepg
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Vergleichen des Streams mit dem Kanal in der Databank anhand des Kanalnamens
|
||||||
|
//fmt.Println(xepgChannel.Name, xepgChannel.UUIDKey, xepgChannel.UUIDValue)
|
||||||
|
if xepgChannel.Name == m3uChannel.Name {
|
||||||
|
channelExists = true
|
||||||
|
currentXEPGID = xepg
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//os.Exit(0)
|
||||||
|
|
||||||
|
switch channelExists {
|
||||||
|
case true:
|
||||||
|
// Bereits vorhandener Kanal
|
||||||
|
var xepgChannel XEPGChannelStruct
|
||||||
|
err = json.Unmarshal([]byte(mapToJSON(Data.XEPG.Channels[currentXEPGID])), &xepgChannel)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Streaming URL aktualisieren
|
||||||
|
xepgChannel.URL = m3uChannel.URL
|
||||||
|
|
||||||
|
// Name aktualisieren, anhand des Names wird überprüft ob der Kanal noch in einer Playlist verhanden. Funktion: cleanupXEPG
|
||||||
|
xepgChannel.Name = m3uChannel.Name
|
||||||
|
|
||||||
|
// Kanalname aktualisieren, nur mit Kanal ID's möglich
|
||||||
|
if channelHasUUID == true {
|
||||||
|
if xepgChannel.XUpdateChannelName == true {
|
||||||
|
xepgChannel.XName = m3uChannel.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kanallogo aktualisieren. Wird bei vorhandenem Logo in der XMLTV Datei wieder überschrieben
|
||||||
|
if xepgChannel.XUpdateChannelIcon == true {
|
||||||
|
xepgChannel.TvgLogo = m3uChannel.TvgLogo
|
||||||
|
}
|
||||||
|
|
||||||
|
Data.XEPG.Channels[currentXEPGID] = xepgChannel
|
||||||
|
|
||||||
|
case false:
|
||||||
|
// Neuer Kanal
|
||||||
|
var xepg = createNewID()
|
||||||
|
var xChannelID = getFreeChannelNumber()
|
||||||
|
|
||||||
|
var newChannel XEPGChannelStruct
|
||||||
|
newChannel.FileM3UID = m3uChannel.FileM3UID
|
||||||
|
newChannel.FileM3UName = m3uChannel.FileM3UName
|
||||||
|
newChannel.FileM3UPath = m3uChannel.FileM3UPath
|
||||||
|
newChannel.Values = m3uChannel.Values
|
||||||
|
newChannel.GroupTitle = m3uChannel.GroupTitle
|
||||||
|
newChannel.Name = m3uChannel.Name
|
||||||
|
newChannel.TvgID = m3uChannel.TvgID
|
||||||
|
newChannel.TvgLogo = m3uChannel.TvgLogo
|
||||||
|
newChannel.TvgName = m3uChannel.TvgName
|
||||||
|
newChannel.URL = m3uChannel.URL
|
||||||
|
newChannel.XmltvFile = ""
|
||||||
|
newChannel.XMapping = ""
|
||||||
|
|
||||||
|
if len(m3uChannel.UUIDKey) > 0 {
|
||||||
|
newChannel.UUIDKey = m3uChannel.UUIDKey
|
||||||
|
newChannel.UUIDValue = m3uChannel.UUIDValue
|
||||||
|
}
|
||||||
|
|
||||||
|
newChannel.XName = m3uChannel.Name
|
||||||
|
newChannel.XGroupTitle = m3uChannel.GroupTitle
|
||||||
|
newChannel.XEPG = xepg
|
||||||
|
newChannel.XChannelID = xChannelID
|
||||||
|
|
||||||
|
Data.XEPG.Channels[xepg] = newChannel
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
err = saveMapToJSONFile(System.File.XEPG, Data.XEPG.Channels)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kanäle automatisch zuordnen und das Mapping überprüfen
|
||||||
|
func mapping() (err error) {
|
||||||
|
showInfo("XEPG:" + "Map channels")
|
||||||
|
|
||||||
|
for xepg, dxc := range Data.XEPG.Channels {
|
||||||
|
|
||||||
|
var xepgChannel XEPGChannelStruct
|
||||||
|
err = json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Automatische Mapping für neue Kanäle. Wird nur ausgeführt, wenn der Kanal deaktiviert ist und keine XMLTV Datei und kein XMLTV Kanal zugeordnet ist.
|
||||||
|
if xepgChannel.XActive == false {
|
||||||
|
|
||||||
|
// Werte kann "-" sein, deswegen len < 1
|
||||||
|
if len(xepgChannel.XmltvFile) < 1 && len(xepgChannel.XmltvFile) < 1 {
|
||||||
|
|
||||||
|
var tvgID = xepgChannel.TvgID
|
||||||
|
|
||||||
|
// Default für neuen Kanal setzen
|
||||||
|
xepgChannel.XmltvFile = "-"
|
||||||
|
xepgChannel.XMapping = "-"
|
||||||
|
|
||||||
|
Data.XEPG.Channels[xepg] = xepgChannel
|
||||||
|
|
||||||
|
for file, xmltvChannels := range Data.XMLTV.Mapping {
|
||||||
|
|
||||||
|
if channel, ok := xmltvChannels.(map[string]interface{})[tvgID]; ok {
|
||||||
|
|
||||||
|
if channelID, ok := channel.(map[string]interface{})["id"].(string); ok {
|
||||||
|
|
||||||
|
xepgChannel.XmltvFile = file
|
||||||
|
xepgChannel.XMapping = channelID
|
||||||
|
xepgChannel.XActive = true
|
||||||
|
|
||||||
|
// Falls in der XMLTV Datei ein Logo existiert, wird dieses verwendet. Falls nicht, dann das Logo aus der M3U Datei
|
||||||
|
if icon, ok := channel.(map[string]interface{})["icon"].(string); ok {
|
||||||
|
if len(icon) > 0 {
|
||||||
|
xepgChannel.TvgLogo = icon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Data.XEPG.Channels[xepg] = xepgChannel
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Überprüfen, ob die zugeordneten XMLTV Dateien und Kanäle noch existieren.
|
||||||
|
if xepgChannel.XActive == true {
|
||||||
|
|
||||||
|
var mapping = xepgChannel.XMapping
|
||||||
|
var file = xepgChannel.XmltvFile
|
||||||
|
|
||||||
|
if file != "xTeVe Dummy" {
|
||||||
|
|
||||||
|
if value, ok := Data.XMLTV.Mapping[file].(map[string]interface{}); ok {
|
||||||
|
|
||||||
|
if channel, ok := value[mapping].(map[string]interface{}); ok {
|
||||||
|
|
||||||
|
// Kanallogo aktualisieren
|
||||||
|
if logo, ok := channel["icon"].(string); ok {
|
||||||
|
|
||||||
|
if xepgChannel.XUpdateChannelIcon == true && len(logo) > 0 {
|
||||||
|
xepgChannel.TvgLogo = logo
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
ShowError(fmt.Errorf(fmt.Sprintf("Missing EPG data: %s", xepgChannel.Name)), 0)
|
||||||
|
showWarning(2302)
|
||||||
|
xepgChannel.XActive = false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
var fileID = strings.TrimSuffix(getFilenameFromPath(file), path.Ext(getFilenameFromPath(file)))
|
||||||
|
|
||||||
|
ShowError(fmt.Errorf("Missing XMLTV file: %s", getProviderParameter(fileID, "xmltv", "name")), 0)
|
||||||
|
showWarning(2301)
|
||||||
|
xepgChannel.XActive = false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(xepgChannel.XmltvFile) == 0 {
|
||||||
|
xepgChannel.XmltvFile = "-"
|
||||||
|
xepgChannel.XActive = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(xepgChannel.XMapping) == 0 {
|
||||||
|
xepgChannel.XMapping = "-"
|
||||||
|
xepgChannel.XActive = false
|
||||||
|
}
|
||||||
|
|
||||||
|
Data.XEPG.Channels[xepg] = xepgChannel
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
err = saveMapToJSONFile(System.File.XEPG, Data.XEPG.Channels)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// XMLTV Datei erstellen
|
||||||
|
func createXMLTVFile() (err error) {
|
||||||
|
|
||||||
|
Data.Cache.ImagesFiles = []string{}
|
||||||
|
Data.Cache.ImagesURLS = []string{}
|
||||||
|
Data.Cache.ImagesCache = []string{}
|
||||||
|
|
||||||
|
files, err := ioutil.ReadDir(System.Folder.ImagesCache)
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
|
||||||
|
if indexOfString(file.Name(), Data.Cache.ImagesCache) == -1 {
|
||||||
|
Data.Cache.ImagesCache = append(Data.Cache.ImagesCache, file.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(Data.XMLTV.Files) == 0 && len(Data.Streams.Active) == 0 {
|
||||||
|
Data.XEPG.Channels = make(map[string]interface{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
showInfo("XEPG:" + fmt.Sprintf("Create XMLTV file (%s)", System.File.XML))
|
||||||
|
|
||||||
|
var xepgXML XMLTV
|
||||||
|
|
||||||
|
xepgXML.Generator = System.Name
|
||||||
|
xepgXML.Source = fmt.Sprintf("%s - %s", System.Name, System.Version)
|
||||||
|
|
||||||
|
var tmpProgram = &XMLTV{}
|
||||||
|
|
||||||
|
for _, dxc := range Data.XEPG.Channels {
|
||||||
|
|
||||||
|
var xepgChannel XEPGChannelStruct
|
||||||
|
err := json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
if xepgChannel.XActive == true {
|
||||||
|
|
||||||
|
// Kanäle
|
||||||
|
var channel Channel
|
||||||
|
channel.ID = xepgChannel.XChannelID
|
||||||
|
channel.Icon = Icon{Src: getCacheImageURL(xepgChannel.TvgLogo)}
|
||||||
|
channel.DisplayName = append(channel.DisplayName, DisplayName{Value: xepgChannel.XName})
|
||||||
|
|
||||||
|
xepgXML.Channel = append(xepgXML.Channel, &channel)
|
||||||
|
|
||||||
|
// Programme
|
||||||
|
|
||||||
|
*tmpProgram, err = getProgramData(xepgChannel)
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
for _, program := range tmpProgram.Program {
|
||||||
|
xepgXML.Program = append(xepgXML.Program, program)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var content, _ = xml.MarshalIndent(xepgXML, " ", " ")
|
||||||
|
var xmlOutput = []byte(xml.Header + string(content))
|
||||||
|
writeByteToFile(System.File.XML, xmlOutput)
|
||||||
|
|
||||||
|
xepgXML = XMLTV{}
|
||||||
|
|
||||||
|
//saveMapToJSONFile(System.File.Images, Data.Cache.ImageCache)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Programmdaten erstellen (createXMLTVFile)
|
||||||
|
func getProgramData(xepgChannel XEPGChannelStruct) (xepgXML XMLTV, err error) {
|
||||||
|
|
||||||
|
var xmltvFile = System.Folder.Data + xepgChannel.XmltvFile
|
||||||
|
var channelID = xepgChannel.XMapping
|
||||||
|
|
||||||
|
var xmltv XMLTV
|
||||||
|
|
||||||
|
if xmltvFile == System.Folder.Data+"xTeVe Dummy" {
|
||||||
|
xmltv = createDummyProgram(xepgChannel)
|
||||||
|
} else {
|
||||||
|
|
||||||
|
err = getLocalXMLTV(xmltvFile, &xmltv)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, xmltvProgram := range xmltv.Program {
|
||||||
|
|
||||||
|
if xmltvProgram.Channel == channelID {
|
||||||
|
//fmt.Println(&channelID)
|
||||||
|
var program = &Program{}
|
||||||
|
|
||||||
|
// Channel ID
|
||||||
|
program.Channel = xepgChannel.XChannelID
|
||||||
|
program.Start = xmltvProgram.Start
|
||||||
|
program.Stop = xmltvProgram.Stop
|
||||||
|
|
||||||
|
// Title
|
||||||
|
program.Title = xmltvProgram.Title
|
||||||
|
|
||||||
|
// Sub title (Untertitel)
|
||||||
|
program.SubTitle = xmltvProgram.SubTitle
|
||||||
|
|
||||||
|
// Description (Beschreibung)
|
||||||
|
program.Desc = xmltvProgram.Desc
|
||||||
|
|
||||||
|
// Category (Kategorie)
|
||||||
|
getCategory(program, xmltvProgram, xepgChannel)
|
||||||
|
|
||||||
|
// Country (Länder)
|
||||||
|
program.Country = xmltvProgram.Country
|
||||||
|
|
||||||
|
// Program icon (Poster / Cover)
|
||||||
|
getPoster(program, xmltvProgram, xepgChannel)
|
||||||
|
|
||||||
|
// Language (Sprache)
|
||||||
|
program.Language = xmltvProgram.Language
|
||||||
|
|
||||||
|
// Episodes numbers (Episodennummern)
|
||||||
|
getEpisodeNum(program, xmltvProgram, xepgChannel)
|
||||||
|
|
||||||
|
// Video (Videoparameter)
|
||||||
|
getVideo(program, xmltvProgram, xepgChannel)
|
||||||
|
|
||||||
|
// Date (Datum)
|
||||||
|
program.Date = xmltvProgram.Date
|
||||||
|
|
||||||
|
// Previously shown (Wiederholung)
|
||||||
|
program.PreviouslyShown = xmltvProgram.PreviouslyShown
|
||||||
|
|
||||||
|
// New (Neu)
|
||||||
|
program.New = xmltvProgram.New
|
||||||
|
|
||||||
|
// Live
|
||||||
|
program.Live = xmltvProgram.Live
|
||||||
|
|
||||||
|
xepgXML.Program = append(xepgXML.Program, program)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dummy Daten erstellen (createXMLTVFile)
|
||||||
|
func createDummyProgram(xepgChannel XEPGChannelStruct) (dummyXMLTV XMLTV) {
|
||||||
|
|
||||||
|
var currentTime = time.Now()
|
||||||
|
var dateArray = strings.Fields(currentTime.String())
|
||||||
|
var offset = " " + dateArray[2]
|
||||||
|
var currentDay = currentTime.Format("20060102")
|
||||||
|
var startTime, _ = time.Parse("20060102150405", currentDay+"000000")
|
||||||
|
|
||||||
|
showInfo("Create Dummy Guide:" + "Time offset" + offset + " - " + xepgChannel.XName)
|
||||||
|
|
||||||
|
var dl = strings.Split(xepgChannel.XMapping, "_")
|
||||||
|
dummyLength, err := strconv.Atoi(dl[0])
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 000)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for d := 0; d < 4; d++ {
|
||||||
|
|
||||||
|
var epgStartTime = startTime.Add(time.Hour * time.Duration(d*24))
|
||||||
|
|
||||||
|
for t := dummyLength; t <= 1440; t = t + dummyLength {
|
||||||
|
|
||||||
|
var epgStopTime = epgStartTime.Add(time.Minute * time.Duration(dummyLength))
|
||||||
|
|
||||||
|
var epg Program
|
||||||
|
poster := Poster{}
|
||||||
|
|
||||||
|
epg.Channel = xepgChannel.XMapping
|
||||||
|
epg.Start = epgStartTime.Format("20060102150405") + offset
|
||||||
|
epg.Stop = epgStopTime.Format("20060102150405") + offset
|
||||||
|
epg.Title = append(epg.Title, &Title{Value: xepgChannel.XName + " (" + epgStartTime.Weekday().String()[0:2] + ". " + epgStartTime.Format("15:04") + " - " + epgStopTime.Format("15:04") + ")", Lang: "en"})
|
||||||
|
epg.Desc = append(epg.Desc, &Desc{Value: "xTeVe: (" + strconv.Itoa(dummyLength) + " Minutes) " + epgStartTime.Weekday().String() + " " + epgStartTime.Format("15:04") + " - " + epgStopTime.Format("15:04"), Lang: "en"})
|
||||||
|
|
||||||
|
if Settings.XepgReplaceMissingImages == true {
|
||||||
|
poster.Src = getCacheImageURL(xepgChannel.TvgLogo)
|
||||||
|
epg.Poster = append(epg.Poster, poster)
|
||||||
|
}
|
||||||
|
|
||||||
|
epg.EpisodeNum = append(epg.EpisodeNum, &EpisodeNum{Value: epgStartTime.Format("2006-01-02 15:04:05"), System: "original-air-date"})
|
||||||
|
|
||||||
|
epg.New = &New{Value: ""}
|
||||||
|
|
||||||
|
dummyXMLTV.Program = append(dummyXMLTV.Program, &epg)
|
||||||
|
epgStartTime = epgStopTime
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kategorien erweitern (createXMLTVFile)
|
||||||
|
func getCategory(program *Program, xmltvProgram *Program, xepgChannel XEPGChannelStruct) {
|
||||||
|
|
||||||
|
for _, i := range xmltvProgram.Category {
|
||||||
|
|
||||||
|
category := &Category{}
|
||||||
|
category.Value = i.Value
|
||||||
|
category.Lang = i.Lang
|
||||||
|
program.Category = append(program.Category, category)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(xepgChannel.XCategory) > 0 {
|
||||||
|
|
||||||
|
category := &Category{}
|
||||||
|
category.Value = xepgChannel.XCategory
|
||||||
|
category.Lang = "en"
|
||||||
|
program.Category = append(program.Category, category)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Programm Poster Cover aus der XMLTV Datei laden
|
||||||
|
func getPoster(program *Program, xmltvProgram *Program, xepgChannel XEPGChannelStruct) {
|
||||||
|
|
||||||
|
for _, poster := range xmltvProgram.Poster {
|
||||||
|
poster.Src = getCacheImageURL(poster.Src)
|
||||||
|
program.Poster = append(program.Poster, poster)
|
||||||
|
}
|
||||||
|
|
||||||
|
if Settings.XepgReplaceMissingImages == true {
|
||||||
|
|
||||||
|
if len(xmltvProgram.Poster) == 0 {
|
||||||
|
var poster Poster
|
||||||
|
poster.Src = getCacheImageURL(xepgChannel.TvgLogo)
|
||||||
|
program.Poster = append(program.Poster, poster)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Episodensystem übernehmen, falls keins vorhanden ist und eine Kategorie im Mapping eingestellt wurden, wird eine Episode erstellt
|
||||||
|
func getEpisodeNum(program *Program, xmltvProgram *Program, xepgChannel XEPGChannelStruct) {
|
||||||
|
|
||||||
|
program.EpisodeNum = xmltvProgram.EpisodeNum
|
||||||
|
|
||||||
|
if len(xepgChannel.XCategory) > 0 {
|
||||||
|
|
||||||
|
if len(xmltvProgram.EpisodeNum) == 0 {
|
||||||
|
program.EpisodeNum = append(program.EpisodeNum, &EpisodeNum{Value: time.Now().Format("2006-01-02"), System: "original-air-date"})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Videoparameter erstellen (createXMLTVFile)
|
||||||
|
func getVideo(program *Program, xmltvProgram *Program, xepgChannel XEPGChannelStruct) {
|
||||||
|
|
||||||
|
var video Video
|
||||||
|
video.Present = xmltvProgram.Video.Present
|
||||||
|
video.Colour = xmltvProgram.Video.Colour
|
||||||
|
video.Aspect = xmltvProgram.Video.Aspect
|
||||||
|
video.Quality = xmltvProgram.Video.Quality
|
||||||
|
|
||||||
|
if len(xmltvProgram.Video.Quality) == 0 {
|
||||||
|
|
||||||
|
if strings.Contains(strings.ToUpper(xepgChannel.XName), " HD") || strings.Contains(strings.ToUpper(xepgChannel.XName), " FHD") {
|
||||||
|
video.Quality = "HDTV"
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(strings.ToUpper(xepgChannel.XName), " UHD") || strings.Contains(strings.ToUpper(xepgChannel.XName), " 4K") {
|
||||||
|
video.Quality = "UHDTV"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
program.Video = video
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lokale Provider XMLTV Datei laden
|
||||||
|
func getLocalXMLTV(file string, xmltv *XMLTV) (err error) {
|
||||||
|
|
||||||
|
if _, ok := Data.Cache.XMLTV[file]; !ok {
|
||||||
|
|
||||||
|
// Cache initialisieren
|
||||||
|
if len(Data.Cache.XMLTV) == 0 {
|
||||||
|
Data.Cache.XMLTV = make(map[string]XMLTV)
|
||||||
|
}
|
||||||
|
|
||||||
|
// XML Daten lesen
|
||||||
|
content, err := readByteFromFile(file)
|
||||||
|
|
||||||
|
// Lokale XML Datei existiert nicht im Ordner: data
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 1004)
|
||||||
|
err = errors.New("Local copy of the file no longer exists")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// XML Datei parsen
|
||||||
|
err = xml.Unmarshal(content, &xmltv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
Data.Cache.XMLTV[file] = *xmltv
|
||||||
|
|
||||||
|
} else {
|
||||||
|
*xmltv = Data.Cache.XMLTV[file]
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// M3U Datei erstellen
|
||||||
|
func createM3UFile() {
|
||||||
|
|
||||||
|
showInfo("XEPG:" + fmt.Sprintf("Create M3U file (%s)", System.File.M3U))
|
||||||
|
_, err := buildM3U([]string{})
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 000)
|
||||||
|
}
|
||||||
|
|
||||||
|
saveMapToJSONFile(System.File.URLS, Data.Cache.StreamingURLS)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// XEPG Datenbank bereinigen
|
||||||
|
func cleanupXEPG() {
|
||||||
|
|
||||||
|
showInfo("XEPG:" + fmt.Sprintf("Cleanup database"))
|
||||||
|
Data.XEPG.XEPGCount = 0
|
||||||
|
|
||||||
|
for id, dxc := range Data.XEPG.Channels {
|
||||||
|
|
||||||
|
var xepgChannel XEPGChannelStruct
|
||||||
|
err := json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
if indexOfString(xepgChannel.Name, Data.Cache.Streams.Active) == -1 {
|
||||||
|
delete(Data.XEPG.Channels, id)
|
||||||
|
} else {
|
||||||
|
if xepgChannel.XActive == true {
|
||||||
|
Data.XEPG.XEPGCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
err := saveMapToJSONFile(System.File.XEPG, Data.XEPG.Channels)
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 000)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
showInfo("XEPG Channels:" + fmt.Sprintf("%d", Data.XEPG.XEPGCount))
|
||||||
|
|
||||||
|
if len(Data.Streams.Active) > 0 && Data.XEPG.XEPGCount == 0 {
|
||||||
|
showWarning(2005)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Streaming URL für die Channels App generieren
|
||||||
|
func getStreamByChannelID(channelID string) (playlistID, streamURL string, err error) {
|
||||||
|
|
||||||
|
err = errors.New("Channel not found")
|
||||||
|
|
||||||
|
for _, dxc := range Data.XEPG.Channels {
|
||||||
|
|
||||||
|
var xepgChannel XEPGChannelStruct
|
||||||
|
err := json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
||||||
|
|
||||||
|
fmt.Println(xepgChannel.XChannelID)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
if channelID == xepgChannel.XChannelID {
|
||||||
|
|
||||||
|
playlistID = xepgChannel.FileM3UID
|
||||||
|
streamURL = xepgChannel.URL
|
||||||
|
|
||||||
|
return playlistID, streamURL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
47
ts/authentication_ts.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
function login() {
|
||||||
|
var err:Boolean = false
|
||||||
|
var data = new Object()
|
||||||
|
var div:any = document.getElementById("content")
|
||||||
|
var form:any = document.getElementById("authentication")
|
||||||
|
|
||||||
|
var inputs:any = div.getElementsByTagName("INPUT")
|
||||||
|
|
||||||
|
console.log(inputs)
|
||||||
|
|
||||||
|
for (var i = inputs.length - 1; i >= 0; i--) {
|
||||||
|
|
||||||
|
var key:string = (inputs[i] as HTMLInputElement).name
|
||||||
|
var value:string = (inputs[i] as HTMLInputElement).value
|
||||||
|
|
||||||
|
if (value.length == 0) {
|
||||||
|
inputs[i].style.borderColor = "red"
|
||||||
|
err = true
|
||||||
|
}
|
||||||
|
|
||||||
|
data[key] = value
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err == true) {
|
||||||
|
data = new Object()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.hasOwnProperty("confirm")) {
|
||||||
|
|
||||||
|
if (data["confirm"] != data["password"]) {
|
||||||
|
alert("sdafsd")
|
||||||
|
document.getElementById('password').style.borderColor = "red"
|
||||||
|
document.getElementById('confirm').style.borderColor = "red"
|
||||||
|
|
||||||
|
document.getElementById("err").innerHTML = "{{.account.failed}}"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(data)
|
||||||
|
|
||||||
|
form.submit();
|
||||||
|
|
||||||
|
}
|
||||||
663
ts/base_ts.ts
Normal file
@@ -0,0 +1,663 @@
|
|||||||
|
var SERVER = new Object()
|
||||||
|
var BULK_EDIT:Boolean = false
|
||||||
|
var COLUMN_TO_SORT:number
|
||||||
|
var SEARCH_MAPPING = new Object()
|
||||||
|
var UNDO = new Object()
|
||||||
|
var SERVER_CONNECTION = false
|
||||||
|
var WS_AVAILABLE = false
|
||||||
|
|
||||||
|
|
||||||
|
// Menü
|
||||||
|
var menuItems = new Array()
|
||||||
|
menuItems.push(new MainMenuItem("playlist", "{{.mainMenu.item.playlist}}", "m3u.png", "{{.mainMenu.headline.playlist}}"))
|
||||||
|
//menuItems.push(new MainMenuItem("pmsID", "{{.mainMenu.item.pmsID}}", "number.png", "{{.mainMenu.headline.pmsID}}"))
|
||||||
|
menuItems.push(new MainMenuItem("filter", "{{.mainMenu.item.filter}}", "filter.png", "{{.mainMenu.headline.filter}}"))
|
||||||
|
menuItems.push(new MainMenuItem("xmltv", "{{.mainMenu.item.xmltv}}", "xmltv.png", "{{.mainMenu.headline.xmltv}}"))
|
||||||
|
menuItems.push(new MainMenuItem("mapping", "{{.mainMenu.item.mapping}}", "mapping.png", "{{.mainMenu.headline.mapping}}"))
|
||||||
|
menuItems.push(new MainMenuItem("users", "{{.mainMenu.item.users}}", "users.png", "{{.mainMenu.headline.users}}"))
|
||||||
|
menuItems.push(new MainMenuItem("settings", "{{.mainMenu.item.settings}}", "settings.png", "{{.mainMenu.headline.settings}}"))
|
||||||
|
menuItems.push(new MainMenuItem("log", "{{.mainMenu.item.log}}", "log.png", "{{.mainMenu.headline.log}}"))
|
||||||
|
menuItems.push(new MainMenuItem("logout", "{{.mainMenu.item.logout}}", "logout.png", "{{.mainMenu.headline.logout}}"))
|
||||||
|
|
||||||
|
// Kategorien für die Einstellungen
|
||||||
|
var settingsCategory = new Array()
|
||||||
|
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.general}}", "xteveAutoUpdate,tuner,epgSource,api"))
|
||||||
|
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.files}}", "update,files.update,temp.path,cache.images,xepg.replace.missing.images"))
|
||||||
|
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.streaming}}", "buffer,buffer.size.kb,buffer.timeout,user.agent"))
|
||||||
|
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.backup}}", "backup.path,backup.keep"))
|
||||||
|
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.authentication}}", "authentication.web,authentication.pms,authentication.m3u,authentication.xml,authentication.api"))
|
||||||
|
|
||||||
|
function showPopUpElement(elm) {
|
||||||
|
|
||||||
|
var allElements = new Array("popup-custom");
|
||||||
|
|
||||||
|
for (var i = 0; i < allElements.length; i++) {
|
||||||
|
showElement(allElements[i], false)
|
||||||
|
}
|
||||||
|
|
||||||
|
showElement(elm, true)
|
||||||
|
|
||||||
|
setTimeout(function(){
|
||||||
|
showElement("popup", true);
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function showElement(elmID, type) {
|
||||||
|
|
||||||
|
var cssClass:string
|
||||||
|
switch(type) {
|
||||||
|
case true: cssClass = "block"; break;
|
||||||
|
case false: cssClass = "none"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById(elmID).className = cssClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeButtonAction(element, buttonID, attribute) {
|
||||||
|
var value = element.options[element.selectedIndex].value;
|
||||||
|
document.getElementById(buttonID).setAttribute(attribute, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocalData(dataType, id):object {
|
||||||
|
var data = new Object()
|
||||||
|
switch(dataType) {
|
||||||
|
case "m3u":
|
||||||
|
data = SERVER["settings"]["files"][dataType][id]
|
||||||
|
break
|
||||||
|
|
||||||
|
case "hdhr":
|
||||||
|
data = SERVER["settings"]["files"][dataType][id]
|
||||||
|
break
|
||||||
|
|
||||||
|
case "filter":
|
||||||
|
case "custom-filter":
|
||||||
|
case "group-title":
|
||||||
|
if (id == -1) {
|
||||||
|
data["active"] = true
|
||||||
|
data["caseSensitive"] = false
|
||||||
|
data["description"] = ""
|
||||||
|
data["exclude"] = ""
|
||||||
|
data["filter"] = ""
|
||||||
|
data["include"] = ""
|
||||||
|
data["name"] = ""
|
||||||
|
data["type"] = "group-title"
|
||||||
|
SERVER["settings"]["filter"][id] = data
|
||||||
|
}
|
||||||
|
data = SERVER["settings"]["filter"][id]
|
||||||
|
break
|
||||||
|
|
||||||
|
case "xmltv":
|
||||||
|
data = SERVER["settings"]["files"][dataType][id]
|
||||||
|
break
|
||||||
|
|
||||||
|
case "users":
|
||||||
|
data = SERVER["users"][id]["data"]
|
||||||
|
break
|
||||||
|
|
||||||
|
case "mapping":
|
||||||
|
data = SERVER["xepg"]["epgMapping"][id]
|
||||||
|
break
|
||||||
|
|
||||||
|
case "m3uGroups":
|
||||||
|
data = SERVER["data"]["playlist"]["m3u"]["groups"]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
function getObjKeys(obj) {
|
||||||
|
var keys = new Array();
|
||||||
|
|
||||||
|
for (var i in obj) {
|
||||||
|
if (obj.hasOwnProperty(i)) {
|
||||||
|
keys.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllSelectedChannels():string[] {
|
||||||
|
|
||||||
|
var channels:string[] = new Array()
|
||||||
|
|
||||||
|
if (BULK_EDIT == false) {
|
||||||
|
return channels
|
||||||
|
}
|
||||||
|
|
||||||
|
var trs = document.getElementById("content_table").getElementsByTagName("TR")
|
||||||
|
|
||||||
|
for (var i = 1; i < trs.length; i++) {
|
||||||
|
|
||||||
|
if ((trs[i] as HTMLElement).style.display != "none") {
|
||||||
|
|
||||||
|
if ((trs[i].firstChild.firstChild as HTMLInputElement).checked == true) {
|
||||||
|
channels.push(trs[i].id)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return channels
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAllChannels() {
|
||||||
|
|
||||||
|
var bulk:Boolean = false
|
||||||
|
var trs = document.getElementById("content_table").getElementsByTagName("TR")
|
||||||
|
|
||||||
|
if ((trs[0].firstChild.firstChild as HTMLInputElement).checked == true) {
|
||||||
|
bulk = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 1; i < trs.length; i++) {
|
||||||
|
|
||||||
|
if ((trs[i] as HTMLElement).style.display != "none") {
|
||||||
|
|
||||||
|
switch (bulk) {
|
||||||
|
|
||||||
|
case true:
|
||||||
|
(trs[i].firstChild.firstChild as HTMLInputElement).checked = true
|
||||||
|
break
|
||||||
|
|
||||||
|
case false:
|
||||||
|
(trs[i].firstChild.firstChild as HTMLInputElement).checked = false
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function bulkEdit() {
|
||||||
|
|
||||||
|
BULK_EDIT = !BULK_EDIT
|
||||||
|
var className:string
|
||||||
|
var rows = document.getElementsByClassName("bulk");
|
||||||
|
|
||||||
|
switch (BULK_EDIT) {
|
||||||
|
case true:
|
||||||
|
className = "bulk showBulk"
|
||||||
|
break;
|
||||||
|
|
||||||
|
case false:
|
||||||
|
className = "bulk hideBulk"
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < rows.length; i++) {
|
||||||
|
rows[i].className = className;
|
||||||
|
(rows[i] as HTMLInputElement).checked = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortTable(column) {
|
||||||
|
//console.log(columm);
|
||||||
|
|
||||||
|
if (column == COLUMN_TO_SORT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var table = document.getElementById("content_table");
|
||||||
|
var tableHead = table.getElementsByTagName("TR")[0];
|
||||||
|
var tableItems = tableHead.getElementsByTagName("TD");
|
||||||
|
|
||||||
|
var sortObj = new Object();
|
||||||
|
var x, xValue;
|
||||||
|
var tableHeader
|
||||||
|
var sortByString = false
|
||||||
|
|
||||||
|
if (column > 0 && COLUMN_TO_SORT > 0) {
|
||||||
|
tableItems[COLUMN_TO_SORT].className = "pointer";
|
||||||
|
tableItems[column].className = "sortThis";
|
||||||
|
}
|
||||||
|
|
||||||
|
COLUMN_TO_SORT = column;
|
||||||
|
|
||||||
|
|
||||||
|
var rows = (table as HTMLTableElement).rows;
|
||||||
|
|
||||||
|
if (rows[1] != undefined) {
|
||||||
|
tableHeader = rows[0]
|
||||||
|
|
||||||
|
x = rows[1].getElementsByTagName("TD")[column];
|
||||||
|
|
||||||
|
for (i = 1; i < rows.length; i++) {
|
||||||
|
|
||||||
|
x = rows[i].getElementsByTagName("TD")[column];
|
||||||
|
|
||||||
|
switch(x.childNodes[0].tagName.toLowerCase()) {
|
||||||
|
case "input":
|
||||||
|
xValue = x.getElementsByTagName("INPUT")[0].value.toLowerCase();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "p":
|
||||||
|
xValue = x.getElementsByTagName("P")[0].innerText.toLowerCase();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: console.log(x.childNodes[0].tagName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xValue == "" || xValue == NaN) {
|
||||||
|
|
||||||
|
xValue = i
|
||||||
|
sortObj[i] = rows[i];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
switch(isNaN(xValue)) {
|
||||||
|
case false:
|
||||||
|
|
||||||
|
xValue = parseFloat(xValue);
|
||||||
|
sortObj[xValue] = rows[i]
|
||||||
|
break;
|
||||||
|
|
||||||
|
case true:
|
||||||
|
|
||||||
|
sortByString = true
|
||||||
|
sortObj[xValue.toLowerCase() + i] = rows[i]
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
while (table.firstChild) {
|
||||||
|
table.removeChild(table.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sortValues = getObjKeys(sortObj)
|
||||||
|
|
||||||
|
if (sortByString == true) {
|
||||||
|
sortValues.sort()
|
||||||
|
console.log(sortValues);
|
||||||
|
} else {
|
||||||
|
function sortFloat(a, b) {
|
||||||
|
return a - b;
|
||||||
|
}
|
||||||
|
sortValues.sort(sortFloat);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.appendChild(tableHeader)
|
||||||
|
|
||||||
|
for (var i = 0; i < sortValues.length; i++) {
|
||||||
|
|
||||||
|
table.appendChild(sortObj[sortValues[i]])
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSearchObj() {
|
||||||
|
|
||||||
|
SEARCH_MAPPING = new Object()
|
||||||
|
var data = SERVER["xepg"]["epgMapping"]
|
||||||
|
var channels = getObjKeys(data)
|
||||||
|
|
||||||
|
var channelKeys:string[] = ["x-active", "x-channelID", "x-name", "_file.m3u.name", "x-group-title"]
|
||||||
|
|
||||||
|
channels.forEach(id => {
|
||||||
|
|
||||||
|
channelKeys.forEach(key => {
|
||||||
|
|
||||||
|
if (key == "x-active") {
|
||||||
|
|
||||||
|
switch (data[id][key]) {
|
||||||
|
case true:
|
||||||
|
SEARCH_MAPPING[id] = "online "
|
||||||
|
break;
|
||||||
|
|
||||||
|
case false:
|
||||||
|
SEARCH_MAPPING[id] = "offline "
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
SEARCH_MAPPING[id] = SEARCH_MAPPING[id] + data[id][key] + " "
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchInMapping() {
|
||||||
|
|
||||||
|
var searchValue = (document.getElementById("searchMapping") as HTMLInputElement).value;
|
||||||
|
var trs = document.getElementById("content_table").getElementsByTagName("TR")
|
||||||
|
|
||||||
|
for (var i = 1; i < trs.length; ++i) {
|
||||||
|
|
||||||
|
var id = trs[i].getAttribute("id")
|
||||||
|
var element = SEARCH_MAPPING[id]
|
||||||
|
|
||||||
|
switch (element.toLowerCase().includes(searchValue.toLowerCase())) {
|
||||||
|
case true:
|
||||||
|
document.getElementById(id).style.display = ""
|
||||||
|
break;
|
||||||
|
|
||||||
|
case false:
|
||||||
|
document.getElementById(id).style.display = "none"
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateWrapperHeight() {
|
||||||
|
|
||||||
|
if (document.getElementById("box-wrapper")){
|
||||||
|
|
||||||
|
var elm = document.getElementById("box-wrapper");
|
||||||
|
|
||||||
|
var divs = new Array("myStreamsBox", "clientInfo", "content");
|
||||||
|
var elementsHeight = 0 - elm.offsetHeight;
|
||||||
|
for (var i = 0; i < divs.length; i++) {
|
||||||
|
elementsHeight = elementsHeight + document.getElementById(divs[i]).offsetHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
elm.style.height = window.innerHeight - elementsHeight + "px";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeChannelNumber(element) {
|
||||||
|
|
||||||
|
var dbID = element.parentNode.parentNode.id
|
||||||
|
|
||||||
|
var newNumber:number = parseFloat(element.value)
|
||||||
|
var channelNumbers:number[] = []
|
||||||
|
var data = SERVER["xepg"]["epgMapping"]
|
||||||
|
var channels = getObjKeys(data)
|
||||||
|
|
||||||
|
if (isNaN(newNumber)) {
|
||||||
|
alert("{{.alert.invalidChannelNumber}}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
channels.forEach(id => {
|
||||||
|
|
||||||
|
var channelNumber = parseFloat(data[id]["x-channelID"])
|
||||||
|
channelNumbers.push(channelNumber)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
for (var i = 0; i < channelNumbers.length; i++) {
|
||||||
|
|
||||||
|
if (channelNumbers.indexOf(newNumber) == -1) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.floor(newNumber) == newNumber) {
|
||||||
|
newNumber = newNumber + 1
|
||||||
|
} else {
|
||||||
|
newNumber = newNumber + 0.1;
|
||||||
|
newNumber.toFixed(1)
|
||||||
|
newNumber = Math.round(newNumber * 10) / 10
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data[dbID]["x-channelID"] = newNumber.toString()
|
||||||
|
element.value = newNumber
|
||||||
|
|
||||||
|
console.log(data[dbID]["x-channelID"])
|
||||||
|
|
||||||
|
if (COLUMN_TO_SORT == 1) {
|
||||||
|
COLUMN_TO_SORT = -1
|
||||||
|
sortTable(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function backup() {
|
||||||
|
|
||||||
|
var data = new Object()
|
||||||
|
console.log("Backup data")
|
||||||
|
|
||||||
|
var cmd = "xteveBackup"
|
||||||
|
|
||||||
|
console.log("SEND TO SERVER");
|
||||||
|
console.log(data)
|
||||||
|
|
||||||
|
var server:Server = new Server(cmd)
|
||||||
|
server.request(data)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleChannelStatus(id:string) {
|
||||||
|
|
||||||
|
var element:any
|
||||||
|
var status:boolean
|
||||||
|
|
||||||
|
if(document.getElementById("active")) {
|
||||||
|
var checkbox = (document.getElementById("active") as HTMLInputElement)
|
||||||
|
status = (checkbox).checked
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var ids:string[] = getAllSelectedChannels()
|
||||||
|
if (ids.length == 0) {
|
||||||
|
ids.push(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
ids.forEach(id => {
|
||||||
|
|
||||||
|
var channel = SERVER["xepg"]["epgMapping"][id]
|
||||||
|
|
||||||
|
channel["x-active"] = status
|
||||||
|
|
||||||
|
switch (channel["x-active"]) {
|
||||||
|
case true:
|
||||||
|
if (channel["x-xmltv-file"] == "-" || channel["x-mapping"] == "-") {
|
||||||
|
|
||||||
|
if (BULK_EDIT == false) {
|
||||||
|
alert(channel["x-name"] + ": Missing XMLTV file / channel")
|
||||||
|
checkbox.checked = false
|
||||||
|
}
|
||||||
|
|
||||||
|
channel["x-active"] = false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
case false:
|
||||||
|
// code...
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channel["x-active"] == false) {
|
||||||
|
document.getElementById(id).className = "notActiveEPG"
|
||||||
|
} else {
|
||||||
|
document.getElementById(id).className = "activeEPG"
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function restore() {
|
||||||
|
|
||||||
|
if (document.getElementById('upload')) {
|
||||||
|
document.getElementById('upload').remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
var restore = document.createElement("INPUT");
|
||||||
|
restore.setAttribute("type", "file");
|
||||||
|
restore.setAttribute("class", "notVisible");
|
||||||
|
restore.setAttribute("name", "");
|
||||||
|
restore.id = "upload";
|
||||||
|
|
||||||
|
document.body.appendChild(restore);
|
||||||
|
restore.click();
|
||||||
|
|
||||||
|
restore.onchange = function() {
|
||||||
|
|
||||||
|
var filename = (restore as HTMLInputElement).files[0].name
|
||||||
|
var check = confirm("File: " + filename + "\n{{.confirm.restore}}");
|
||||||
|
|
||||||
|
if (check == true) {
|
||||||
|
|
||||||
|
var reader = new FileReader();
|
||||||
|
var file = (document.querySelector('input[type=file]') as HTMLInputElement).files[0];
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
reader.onload = function() {
|
||||||
|
console.log(reader.result);
|
||||||
|
var data = new Object();
|
||||||
|
var cmd = "xteveRestore"
|
||||||
|
data["base64"] = reader.result
|
||||||
|
|
||||||
|
var server:Server = new Server(cmd)
|
||||||
|
server.request(data)
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} else {
|
||||||
|
alert("File could not be loaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
restore.remove()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadLogo() {
|
||||||
|
|
||||||
|
if (document.getElementById('upload')) {
|
||||||
|
document.getElementById('upload').remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
var upload = document.createElement("INPUT");
|
||||||
|
upload.setAttribute("type", "file");
|
||||||
|
upload.setAttribute("class", "notVisible");
|
||||||
|
upload.setAttribute("name", "");
|
||||||
|
upload.id = "upload";
|
||||||
|
|
||||||
|
document.body.appendChild(upload);
|
||||||
|
upload.click();
|
||||||
|
|
||||||
|
upload.onblur = function() {
|
||||||
|
alert()
|
||||||
|
}
|
||||||
|
|
||||||
|
upload.onchange = function() {
|
||||||
|
|
||||||
|
var filename = (upload as HTMLInputElement).files[0].name
|
||||||
|
|
||||||
|
var reader = new FileReader();
|
||||||
|
var file = (document.querySelector('input[type=file]') as HTMLInputElement).files[0];
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
reader.onload = function() {
|
||||||
|
console.log(reader.result);
|
||||||
|
var data = new Object();
|
||||||
|
var cmd = "uploadLogo"
|
||||||
|
data["base64"] = reader.result
|
||||||
|
data["filename"] = file.name
|
||||||
|
|
||||||
|
var server:Server = new Server(cmd)
|
||||||
|
server.request(data)
|
||||||
|
|
||||||
|
var updateLogo = (document.getElementById('update-icon') as HTMLInputElement)
|
||||||
|
updateLogo.checked = false
|
||||||
|
updateLogo.className = "changed"
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} else {
|
||||||
|
alert("File could not be loaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
upload.remove()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkUndo(key:string) {
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case "epgMapping":
|
||||||
|
if (UNDO.hasOwnProperty(key)) {
|
||||||
|
SERVER["xepg"][key] = JSON.parse(JSON.stringify(UNDO[key]))
|
||||||
|
} else {
|
||||||
|
UNDO[key] = JSON.parse(JSON.stringify(SERVER["xepg"][key]));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortSelect(elem) {
|
||||||
|
|
||||||
|
var tmpAry = [];
|
||||||
|
var selectedValue = elem[elem.selectedIndex].value;
|
||||||
|
|
||||||
|
for (var i=0;i<elem.options.length;i++) tmpAry.push(elem.options[i]);
|
||||||
|
|
||||||
|
tmpAry.sort(function(a,b){ return (a.text < b.text)?-1:1; });
|
||||||
|
while (elem.options.length > 0) elem.options[0] = null;
|
||||||
|
|
||||||
|
var newSelectedIndex = 0;
|
||||||
|
|
||||||
|
for (var i=0;i<tmpAry.length;i++) {
|
||||||
|
|
||||||
|
elem.options[i] = tmpAry[i];
|
||||||
|
if(elem.options[i].value == selectedValue) newSelectedIndex = i;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
elem.selectedIndex = newSelectedIndex; // Set new selected index after sorting
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLog() {
|
||||||
|
|
||||||
|
console.log("TOKEN")
|
||||||
|
var server:Server = new Server("updateLog")
|
||||||
|
server.request(new Object())
|
||||||
|
|
||||||
|
}
|
||||||
3
ts/compileJS.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
tsc *.ts --outDir ../html/js/
|
||||||
169
ts/configuration_ts.ts
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
class WizardCategory {
|
||||||
|
DocumentID = "content"
|
||||||
|
|
||||||
|
createCategoryHeadline(value:string):any {
|
||||||
|
var element = document.createElement("H4")
|
||||||
|
element.innerHTML = value
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WizardItem extends WizardCategory {
|
||||||
|
key:string
|
||||||
|
headline:string
|
||||||
|
|
||||||
|
constructor(key:string, headline:string) {
|
||||||
|
super()
|
||||||
|
this.headline = headline
|
||||||
|
this.key = key
|
||||||
|
}
|
||||||
|
|
||||||
|
createWizard():void {
|
||||||
|
var headline = this.createCategoryHeadline(this.headline)
|
||||||
|
var key = this.key
|
||||||
|
var content:PopupContent = new PopupContent()
|
||||||
|
var description:string
|
||||||
|
|
||||||
|
var doc = document.getElementById(this.DocumentID)
|
||||||
|
doc.innerHTML = ""
|
||||||
|
doc.appendChild(headline)
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case "tuner":
|
||||||
|
var text = new Array()
|
||||||
|
var values = new Array()
|
||||||
|
|
||||||
|
for (var i = 1; i <= 100; i++) {
|
||||||
|
text.push(i)
|
||||||
|
values.push(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
var select = content.createSelect(text, values, "1", key)
|
||||||
|
select.setAttribute("class", "wizard")
|
||||||
|
select.id = key
|
||||||
|
doc.appendChild(select)
|
||||||
|
|
||||||
|
description = "{{.wizard.tuner.description}}"
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "epgSource":
|
||||||
|
var text:any[] = ["PMS", "XEPG"]
|
||||||
|
var values:any[] = ["PMS", "XEPG"]
|
||||||
|
|
||||||
|
var select = content.createSelect(text, values, "XEPG", key)
|
||||||
|
select.setAttribute("class", "wizard")
|
||||||
|
select.id = key
|
||||||
|
doc.appendChild(select)
|
||||||
|
|
||||||
|
description = "{{.wizard.epgSource.description}}"
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
case "m3u":
|
||||||
|
var input = content.createInput("text", key, "")
|
||||||
|
input.setAttribute("class", "wizard")
|
||||||
|
input.id = key
|
||||||
|
doc.appendChild(input)
|
||||||
|
|
||||||
|
description = "{{.wizard.m3u.description}}"
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
case "xmltv":
|
||||||
|
var input = content.createInput("text", key, "")
|
||||||
|
input.setAttribute("class", "wizard")
|
||||||
|
input.id = key
|
||||||
|
doc.appendChild(input)
|
||||||
|
|
||||||
|
description = "{{.wizard.xmltv.description}}"
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.log(key)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pre = document.createElement("PRE")
|
||||||
|
pre.innerHTML = description
|
||||||
|
doc.appendChild(pre)
|
||||||
|
|
||||||
|
console.log(headline, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function readyForConfiguration(wizard:number) {
|
||||||
|
|
||||||
|
var server:Server = new Server("getServerConfig")
|
||||||
|
server.request(new Object())
|
||||||
|
|
||||||
|
showElement("loading", false)
|
||||||
|
|
||||||
|
configurationWizard[wizard].createWizard()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveWizard() {
|
||||||
|
|
||||||
|
var cmd = "saveWizard"
|
||||||
|
var div = document.getElementById("content")
|
||||||
|
var config = div.getElementsByClassName("wizard")
|
||||||
|
|
||||||
|
var wizard = new Object()
|
||||||
|
|
||||||
|
for (var i = 0; i < config.length; i++) {
|
||||||
|
|
||||||
|
var name:string
|
||||||
|
var value:any
|
||||||
|
|
||||||
|
switch (config[i].tagName) {
|
||||||
|
case "SELECT":
|
||||||
|
name = (config[i] as HTMLSelectElement).name
|
||||||
|
value = (config[i] as HTMLSelectElement).value
|
||||||
|
|
||||||
|
// Wenn der Wert eine Zahl ist, wird dieser als Zahl gespeichert
|
||||||
|
if(isNaN(value)){
|
||||||
|
wizard[name] = value
|
||||||
|
} else {
|
||||||
|
wizard[name] = parseInt(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
case "INPUT":
|
||||||
|
switch ((config[i] as HTMLInputElement).type) {
|
||||||
|
case "text":
|
||||||
|
name = (config[i] as HTMLInputElement).name
|
||||||
|
value = (config[i] as HTMLInputElement).value
|
||||||
|
|
||||||
|
wizard[name] = value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
// code...
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = new Object()
|
||||||
|
data["wizard"] = wizard
|
||||||
|
|
||||||
|
var server:Server = new Server(cmd)
|
||||||
|
server.request(data)
|
||||||
|
|
||||||
|
console.log(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wizard
|
||||||
|
var configurationWizard = new Array()
|
||||||
|
configurationWizard.push(new WizardItem("tuner", "{{.wizard.tuner.title}}"))
|
||||||
|
configurationWizard.push(new WizardItem("epgSource", "{{.wizard.epgSource.title}}"))
|
||||||
|
configurationWizard.push(new WizardItem("m3u", "{{.wizard.m3u.title}}"))
|
||||||
|
configurationWizard.push(new WizardItem("xmltv", "{{.wizard.xmltv.title}}"))
|
||||||
65
ts/logs_ts.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
class Log {
|
||||||
|
|
||||||
|
createLog(entry:string):any {
|
||||||
|
|
||||||
|
var element = document.createElement("PRE");
|
||||||
|
|
||||||
|
if (entry.indexOf("WARNING") != -1) {
|
||||||
|
element.className = "warningMsg"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.indexOf("ERROR") != -1) {
|
||||||
|
element.className = "errorMsg"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.indexOf("DEBUG") != -1) {
|
||||||
|
element.className = "debugMsg"
|
||||||
|
}
|
||||||
|
|
||||||
|
element.innerHTML = entry
|
||||||
|
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function showLogs(bottom:boolean) {
|
||||||
|
|
||||||
|
var log = new Log()
|
||||||
|
|
||||||
|
var logs = SERVER["log"]["log"]
|
||||||
|
var div = document.getElementById("content_log")
|
||||||
|
|
||||||
|
div.innerHTML = ""
|
||||||
|
|
||||||
|
var keys = getObjKeys(logs)
|
||||||
|
|
||||||
|
keys.forEach(logID => {
|
||||||
|
|
||||||
|
var entry = log.createLog(logs[logID])
|
||||||
|
|
||||||
|
div.append(entry)
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(function(){
|
||||||
|
|
||||||
|
if (bottom == true) {
|
||||||
|
|
||||||
|
var wrapper = document.getElementById("box-wrapper");
|
||||||
|
wrapper.scrollTop = wrapper.scrollHeight;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetLogs() {
|
||||||
|
|
||||||
|
var cmd = "resetLogs"
|
||||||
|
var data = new Object()
|
||||||
|
var server:Server = new Server(cmd)
|
||||||
|
server.request(data)
|
||||||
|
|
||||||
|
}
|
||||||
2198
ts/menu_ts.ts
Normal file
147
ts/network_ts.ts
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
class Server {
|
||||||
|
protocol:string
|
||||||
|
cmd:string
|
||||||
|
|
||||||
|
constructor(cmd:string) {
|
||||||
|
this.cmd = cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
request(data:Object):any {
|
||||||
|
|
||||||
|
if (SERVER_CONNECTION == true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
SERVER_CONNECTION = true
|
||||||
|
|
||||||
|
console.log(data)
|
||||||
|
if (this.cmd != "updateLog") {
|
||||||
|
showElement("loading", true)
|
||||||
|
UNDO = new Object()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(window.location.protocol) {
|
||||||
|
case "http:":
|
||||||
|
this.protocol = "ws://"
|
||||||
|
break
|
||||||
|
case "https://":
|
||||||
|
this.protocol = "wss://"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = this.protocol + window.location.hostname + ":" + window.location.port + "/data/" + "?Token=" + getCookie("Token")
|
||||||
|
|
||||||
|
data["cmd"] = this.cmd
|
||||||
|
var ws = new WebSocket(url)
|
||||||
|
ws.onopen = function() {
|
||||||
|
|
||||||
|
WS_AVAILABLE = true
|
||||||
|
|
||||||
|
console.log("REQUEST (JS):");
|
||||||
|
console.log(data)
|
||||||
|
|
||||||
|
console.log("REQUEST: (JSON)");
|
||||||
|
console.log(JSON.stringify(data))
|
||||||
|
|
||||||
|
this.send(JSON.stringify(data));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onerror = function(e) {
|
||||||
|
|
||||||
|
console.log("No websocket connection to xTeVe could be established. Check your network configuration.")
|
||||||
|
SERVER_CONNECTION = false
|
||||||
|
|
||||||
|
if (WS_AVAILABLE == false) {
|
||||||
|
alert("No websocket connection to xTeVe could be established. Check your network configuration.")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ws.onmessage = function (e) {
|
||||||
|
|
||||||
|
SERVER_CONNECTION = false
|
||||||
|
showElement("loading", false)
|
||||||
|
|
||||||
|
console.log("RESPONSE:");
|
||||||
|
var response = JSON.parse(e.data);
|
||||||
|
|
||||||
|
console.log(response);
|
||||||
|
|
||||||
|
if (response.hasOwnProperty("token")) {
|
||||||
|
document.cookie = "Token=" + response["token"]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response["status"] == false) {
|
||||||
|
|
||||||
|
alert(response["err"])
|
||||||
|
|
||||||
|
if (response.hasOwnProperty("reload")) {
|
||||||
|
location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (response.hasOwnProperty("logoURL")) {
|
||||||
|
var div = (document.getElementById("channel-icon") as HTMLInputElement)
|
||||||
|
div.value = response["logoURL"]
|
||||||
|
div.className = "changed"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (data["cmd"]) {
|
||||||
|
case "updateLog":
|
||||||
|
SERVER["log"] = response["log"]
|
||||||
|
if (document.getElementById("content_log")) {
|
||||||
|
showLogs(false)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
SERVER = new Object()
|
||||||
|
SERVER = response
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.hasOwnProperty("openMenu")) {
|
||||||
|
var menu = document.getElementById(response["openMenu"])
|
||||||
|
menu.click()
|
||||||
|
showElement("popup", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.hasOwnProperty("openLink")) {
|
||||||
|
window.location = response["openLink"]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.hasOwnProperty("alert")) {
|
||||||
|
alert(response["alert"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.hasOwnProperty("reload")) {
|
||||||
|
location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (response.hasOwnProperty("wizard")) {
|
||||||
|
createLayout()
|
||||||
|
configurationWizard[response["wizard"]].createWizard()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
createLayout()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCookie(name) {
|
||||||
|
var value = "; " + document.cookie;
|
||||||
|
var parts = value.split("; " + name + "=");
|
||||||
|
if (parts.length == 2) return parts.pop().split(";").shift();
|
||||||
|
}
|
||||||
564
ts/settings_ts.ts
Normal file
@@ -0,0 +1,564 @@
|
|||||||
|
class SettingsCategory {
|
||||||
|
DocumentID:string = "content_settings"
|
||||||
|
createCategoryHeadline(value:string):any {
|
||||||
|
var element = document.createElement("H4")
|
||||||
|
element.innerHTML = value
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
|
||||||
|
createHR():any {
|
||||||
|
var element = document.createElement("HR")
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
|
||||||
|
createSettings(settingsKey:string):any {
|
||||||
|
var setting = document.createElement("TR")
|
||||||
|
var content:PopupContent = new PopupContent()
|
||||||
|
var data = SERVER["settings"][settingsKey]
|
||||||
|
|
||||||
|
switch (settingsKey) {
|
||||||
|
|
||||||
|
// Texteingaben
|
||||||
|
case "update":
|
||||||
|
var tdLeft = document.createElement("TD")
|
||||||
|
tdLeft.innerHTML = "{{.settings.update.title}}" + ":"
|
||||||
|
|
||||||
|
var tdRight = document.createElement("TD")
|
||||||
|
var input = content.createInput("text", "update", data.toString())
|
||||||
|
input.setAttribute("placeholder", "{{.settings.update.placeholder}}")
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||||
|
tdRight.appendChild(input)
|
||||||
|
|
||||||
|
setting.appendChild(tdLeft)
|
||||||
|
setting.appendChild(tdRight)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "backup.path":
|
||||||
|
var tdLeft = document.createElement("TD")
|
||||||
|
tdLeft.innerHTML = "{{.settings.backupPath.title}}" + ":"
|
||||||
|
|
||||||
|
var tdRight = document.createElement("TD")
|
||||||
|
var input = content.createInput("text", "backup.path", data)
|
||||||
|
input.setAttribute("placeholder", "{{.settings.backupPath.placeholder}}")
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||||
|
tdRight.appendChild(input)
|
||||||
|
|
||||||
|
setting.appendChild(tdLeft)
|
||||||
|
setting.appendChild(tdRight)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "temp.path":
|
||||||
|
var tdLeft = document.createElement("TD")
|
||||||
|
tdLeft.innerHTML = "{{.settings.tempPath.title}}" + ":"
|
||||||
|
|
||||||
|
var tdRight = document.createElement("TD")
|
||||||
|
var input = content.createInput("text", "temp.path", data)
|
||||||
|
input.setAttribute("placeholder", "{{.settings.tmpPath.placeholder}}")
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||||
|
tdRight.appendChild(input)
|
||||||
|
|
||||||
|
setting.appendChild(tdLeft)
|
||||||
|
setting.appendChild(tdRight)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "user.agent":
|
||||||
|
var tdLeft = document.createElement("TD")
|
||||||
|
tdLeft.innerHTML = "{{.settings.userAgent.title}}" + ":"
|
||||||
|
|
||||||
|
var tdRight = document.createElement("TD")
|
||||||
|
var input = content.createInput("text", "user.agent", data)
|
||||||
|
input.setAttribute("placeholder", "{{.settings.userAgent.placeholder}}")
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||||
|
tdRight.appendChild(input)
|
||||||
|
|
||||||
|
setting.appendChild(tdLeft)
|
||||||
|
setting.appendChild(tdRight)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "buffer.timeout":
|
||||||
|
var tdLeft = document.createElement("TD")
|
||||||
|
tdLeft.innerHTML = "{{.settings.bufferTimeout.title}}" + ":"
|
||||||
|
|
||||||
|
var tdRight = document.createElement("TD")
|
||||||
|
var input = content.createInput("text", "buffer.timeout", data)
|
||||||
|
input.setAttribute("placeholder", "{{.settings.bufferTimeout.placeholder}}")
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||||
|
tdRight.appendChild(input)
|
||||||
|
|
||||||
|
setting.appendChild(tdLeft)
|
||||||
|
setting.appendChild(tdRight)
|
||||||
|
break
|
||||||
|
|
||||||
|
// Checkboxen
|
||||||
|
case "authentication.web":
|
||||||
|
var tdLeft = document.createElement("TD")
|
||||||
|
tdLeft.innerHTML = "{{.settings.authenticationWEB.title}}" + ":"
|
||||||
|
|
||||||
|
var tdRight = document.createElement("TD")
|
||||||
|
var input = content.createCheckbox(settingsKey)
|
||||||
|
input.checked = data
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||||
|
tdRight.appendChild(input)
|
||||||
|
|
||||||
|
setting.appendChild(tdLeft)
|
||||||
|
setting.appendChild(tdRight)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "authentication.pms":
|
||||||
|
var tdLeft = document.createElement("TD")
|
||||||
|
tdLeft.innerHTML = "{{.settings.authenticationPMS.title}}" + ":"
|
||||||
|
|
||||||
|
var tdRight = document.createElement("TD")
|
||||||
|
var input = content.createCheckbox(settingsKey)
|
||||||
|
input.checked = data
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||||
|
tdRight.appendChild(input)
|
||||||
|
|
||||||
|
setting.appendChild(tdLeft)
|
||||||
|
setting.appendChild(tdRight)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "authentication.m3u":
|
||||||
|
var tdLeft = document.createElement("TD")
|
||||||
|
tdLeft.innerHTML = "{{.settings.authenticationM3U.title}}" + ":"
|
||||||
|
|
||||||
|
var tdRight = document.createElement("TD")
|
||||||
|
var input = content.createCheckbox(settingsKey)
|
||||||
|
input.checked = data
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||||
|
tdRight.appendChild(input)
|
||||||
|
|
||||||
|
setting.appendChild(tdLeft)
|
||||||
|
setting.appendChild(tdRight)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "authentication.xml":
|
||||||
|
var tdLeft = document.createElement("TD")
|
||||||
|
tdLeft.innerHTML = "{{.settings.authenticationXML.title}}" + ":"
|
||||||
|
|
||||||
|
var tdRight = document.createElement("TD")
|
||||||
|
var input = content.createCheckbox(settingsKey)
|
||||||
|
input.checked = data
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||||
|
tdRight.appendChild(input)
|
||||||
|
|
||||||
|
setting.appendChild(tdLeft)
|
||||||
|
setting.appendChild(tdRight)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "authentication.api":
|
||||||
|
var tdLeft = document.createElement("TD")
|
||||||
|
tdLeft.innerHTML = "{{.settings.authenticationAPI.title}}" + ":"
|
||||||
|
|
||||||
|
var tdRight = document.createElement("TD")
|
||||||
|
var input = content.createCheckbox(settingsKey)
|
||||||
|
input.checked = data
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||||
|
tdRight.appendChild(input)
|
||||||
|
|
||||||
|
setting.appendChild(tdLeft)
|
||||||
|
setting.appendChild(tdRight)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "files.update":
|
||||||
|
var tdLeft = document.createElement("TD")
|
||||||
|
tdLeft.innerHTML = "{{.settings.filesUpdate.title}}" + ":"
|
||||||
|
|
||||||
|
var tdRight = document.createElement("TD")
|
||||||
|
var input = content.createCheckbox(settingsKey)
|
||||||
|
input.checked = data
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||||
|
tdRight.appendChild(input)
|
||||||
|
|
||||||
|
setting.appendChild(tdLeft)
|
||||||
|
setting.appendChild(tdRight)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "cache.images":
|
||||||
|
var tdLeft = document.createElement("TD")
|
||||||
|
tdLeft.innerHTML = "{{.settings.cacheImages.title}}" + ":"
|
||||||
|
|
||||||
|
var tdRight = document.createElement("TD")
|
||||||
|
var input = content.createCheckbox(settingsKey)
|
||||||
|
input.checked = data
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||||
|
tdRight.appendChild(input)
|
||||||
|
|
||||||
|
setting.appendChild(tdLeft)
|
||||||
|
setting.appendChild(tdRight)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "xepg.replace.missing.images":
|
||||||
|
var tdLeft = document.createElement("TD")
|
||||||
|
tdLeft.innerHTML = "{{.settings.replaceEmptyImages.title}}" + ":"
|
||||||
|
|
||||||
|
var tdRight = document.createElement("TD")
|
||||||
|
var input = content.createCheckbox(settingsKey)
|
||||||
|
input.checked = data
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||||
|
tdRight.appendChild(input)
|
||||||
|
|
||||||
|
setting.appendChild(tdLeft)
|
||||||
|
setting.appendChild(tdRight)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "xteveAutoUpdate":
|
||||||
|
var tdLeft = document.createElement("TD")
|
||||||
|
tdLeft.innerHTML = "{{.settings.xteveAutoUpdate.title}}" + ":"
|
||||||
|
|
||||||
|
var tdRight = document.createElement("TD")
|
||||||
|
var input = content.createCheckbox(settingsKey)
|
||||||
|
input.checked = data
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||||
|
tdRight.appendChild(input)
|
||||||
|
|
||||||
|
setting.appendChild(tdLeft)
|
||||||
|
setting.appendChild(tdRight)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "buffer":
|
||||||
|
var tdLeft = document.createElement("TD")
|
||||||
|
tdLeft.innerHTML = "{{.settings.streamBuffering.title}}" + ":"
|
||||||
|
|
||||||
|
var tdRight = document.createElement("TD")
|
||||||
|
var input = content.createCheckbox(settingsKey)
|
||||||
|
input.checked = data
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||||
|
tdRight.appendChild(input)
|
||||||
|
|
||||||
|
setting.appendChild(tdLeft)
|
||||||
|
setting.appendChild(tdRight)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "api":
|
||||||
|
var tdLeft = document.createElement("TD")
|
||||||
|
tdLeft.innerHTML = "{{.settings.api.title}}" + ":"
|
||||||
|
|
||||||
|
var tdRight = document.createElement("TD")
|
||||||
|
var input = content.createCheckbox(settingsKey)
|
||||||
|
input.checked = data
|
||||||
|
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||||
|
tdRight.appendChild(input)
|
||||||
|
|
||||||
|
setting.appendChild(tdLeft)
|
||||||
|
setting.appendChild(tdRight)
|
||||||
|
break
|
||||||
|
|
||||||
|
// Select
|
||||||
|
case "tuner":
|
||||||
|
var tdLeft = document.createElement("TD")
|
||||||
|
tdLeft.innerHTML = "{{.settings.tuner.title}}" + ":"
|
||||||
|
|
||||||
|
var tdRight = document.createElement("TD")
|
||||||
|
var text = new Array()
|
||||||
|
var values = new Array()
|
||||||
|
|
||||||
|
for (var i = 1; i <= 100; i++) {
|
||||||
|
text.push(i)
|
||||||
|
values.push(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
var select = content.createSelect(text, values, data, settingsKey)
|
||||||
|
select.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||||
|
tdRight.appendChild(select)
|
||||||
|
|
||||||
|
setting.appendChild(tdLeft)
|
||||||
|
setting.appendChild(tdRight)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "epgSource":
|
||||||
|
var tdLeft = document.createElement("TD")
|
||||||
|
tdLeft.innerHTML = "{{.settings.epgSource.title}}" + ":"
|
||||||
|
|
||||||
|
var tdRight = document.createElement("TD")
|
||||||
|
var text:any[] = ["PMS", "XEPG"]
|
||||||
|
var values:any[] = ["PMS", "XEPG"]
|
||||||
|
|
||||||
|
var select = content.createSelect(text, values, data, settingsKey)
|
||||||
|
select.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||||
|
tdRight.appendChild(select)
|
||||||
|
|
||||||
|
setting.appendChild(tdLeft)
|
||||||
|
setting.appendChild(tdRight)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "backup.keep":
|
||||||
|
var tdLeft = document.createElement("TD")
|
||||||
|
tdLeft.innerHTML = "{{.settings.backupKeep.title}}" + ":"
|
||||||
|
|
||||||
|
var tdRight = document.createElement("TD")
|
||||||
|
var text:any[] = ["5", "10", "20", "30", "40", "50"]
|
||||||
|
var values:any[] = ["5", "10", "20", "30", "40", "50"]
|
||||||
|
|
||||||
|
var select = content.createSelect(text, values, data, settingsKey)
|
||||||
|
select.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||||
|
tdRight.appendChild(select)
|
||||||
|
|
||||||
|
setting.appendChild(tdLeft)
|
||||||
|
setting.appendChild(tdRight)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "buffer.size.kb":
|
||||||
|
var tdLeft = document.createElement("TD")
|
||||||
|
tdLeft.innerHTML = "{{.settings.bufferSize.title}}" + ":"
|
||||||
|
|
||||||
|
var tdRight = document.createElement("TD")
|
||||||
|
var text:any[] = ["0.5 MB", "1 MB", "2 MB", "3 MB", "4 MB", "5 MB", "6 MB", "7 MB", "8 MB"]
|
||||||
|
var values:any[] = ["512", "1024", "2048", "3072", "4096", "5120", "6144", "7168", "8192"]
|
||||||
|
|
||||||
|
var select = content.createSelect(text, values, data, settingsKey)
|
||||||
|
select.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||||
|
tdRight.appendChild(select)
|
||||||
|
|
||||||
|
setting.appendChild(tdLeft)
|
||||||
|
setting.appendChild(tdRight)
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return setting
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
createDescription(settingsKey:string):any {
|
||||||
|
|
||||||
|
var description = document.createElement("TR")
|
||||||
|
var text:string
|
||||||
|
switch (settingsKey) {
|
||||||
|
|
||||||
|
case "authentication.web":
|
||||||
|
text = "{{.settings.authenticationWEB.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
|
case "authentication.m3u":
|
||||||
|
text = "{{.settings.authenticationM3U.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
|
case "authentication.pms":
|
||||||
|
text = "{{.settings.authenticationPMS.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
|
case "authentication.xml":
|
||||||
|
text = "{{.settings.authenticationXML.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
|
case "authentication.api":
|
||||||
|
if (SERVER["settings"]["authentication.web"] == true) {
|
||||||
|
text = "{{.settings.authenticationAPI.description}}"
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case "xteveAutoUpdate":
|
||||||
|
text = "{{.settings.xteveAutoUpdate.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
|
case "backup.keep":
|
||||||
|
text = "{{.settings.backupKeep.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
|
case "backup.path":
|
||||||
|
text = "{{.settings.backupPath.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
|
case "temp.path":
|
||||||
|
text = "{{.settings.tempPath.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
|
case "buffer":
|
||||||
|
text = "{{.settings.streamBuffering.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
|
case "buffer.size.kb":
|
||||||
|
text = "{{.settings.bufferSize.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
|
case "buffer.timeout":
|
||||||
|
text = "{{.settings.bufferTimeout.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
|
case "user.agent":
|
||||||
|
text = "{{.settings.userAgent.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
|
case "epgSource":
|
||||||
|
text = "{{.settings.epgSource.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
|
case "tuner":
|
||||||
|
text = "{{.settings.tuner.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
|
case "update":
|
||||||
|
text = "{{.settings.update.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
|
case "api":
|
||||||
|
text = "{{.settings.api.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
|
case "files.update":
|
||||||
|
text = "{{.settings.filesUpdate.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
|
case "cache.images":
|
||||||
|
text = "{{.settings.cacheImages.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
|
case "xepg.replace.missing.images":
|
||||||
|
text = "{{.settings.replaceEmptyImages.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
text = ""
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var tdLeft = document.createElement("TD")
|
||||||
|
tdLeft.innerHTML = ""
|
||||||
|
|
||||||
|
var tdRight = document.createElement("TD")
|
||||||
|
var pre = document.createElement("PRE")
|
||||||
|
pre.innerHTML = text
|
||||||
|
tdRight.appendChild(pre)
|
||||||
|
|
||||||
|
description.appendChild(tdLeft)
|
||||||
|
description.appendChild(tdRight)
|
||||||
|
|
||||||
|
return description
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsCategoryItem extends SettingsCategory {
|
||||||
|
headline:string
|
||||||
|
settingsKeys:string
|
||||||
|
|
||||||
|
constructor(headline:string, settingsKeys:string) {
|
||||||
|
super()
|
||||||
|
this.headline = headline
|
||||||
|
this.settingsKeys = settingsKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
createCategory():void {
|
||||||
|
var headline = this.createCategoryHeadline(this.headline)
|
||||||
|
var settingsKeys = this.settingsKeys
|
||||||
|
|
||||||
|
var doc = document.getElementById(this.DocumentID)
|
||||||
|
doc.appendChild(headline)
|
||||||
|
|
||||||
|
// Tabelle für die Kategorie erstellen
|
||||||
|
|
||||||
|
var table = document.createElement("TABLE")
|
||||||
|
|
||||||
|
var keys = settingsKeys.split(",")
|
||||||
|
|
||||||
|
keys.forEach(settingsKey => {
|
||||||
|
|
||||||
|
switch (settingsKey) {
|
||||||
|
|
||||||
|
case "authentication.pms":
|
||||||
|
case "authentication.m3u":
|
||||||
|
case "authentication.xml":
|
||||||
|
case "authentication.api":
|
||||||
|
if (SERVER["settings"]["authentication.web"] == false) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
var item = this.createSettings(settingsKey)
|
||||||
|
var description = this.createDescription(settingsKey)
|
||||||
|
|
||||||
|
table.appendChild(item)
|
||||||
|
table.appendChild(description)
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
doc.appendChild(table)
|
||||||
|
doc.appendChild(this.createHR())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSettings() {
|
||||||
|
console.log("SETTINGS");
|
||||||
|
|
||||||
|
for (let i = 0; i < settingsCategory.length; i++) {
|
||||||
|
settingsCategory[i].createCategory()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveSettings() {
|
||||||
|
console.log("Save Settings");
|
||||||
|
|
||||||
|
var cmd = "saveSettings"
|
||||||
|
var div = document.getElementById("content_settings")
|
||||||
|
var settings = div.getElementsByClassName("changed")
|
||||||
|
|
||||||
|
var newSettings = new Object();
|
||||||
|
|
||||||
|
for (let i = 0; i < settings.length; i++) {
|
||||||
|
|
||||||
|
var name:string
|
||||||
|
var value:any
|
||||||
|
|
||||||
|
switch (settings[i].tagName) {
|
||||||
|
case "INPUT":
|
||||||
|
|
||||||
|
switch ((settings[i] as HTMLInputElement).type) {
|
||||||
|
case "checkbox":
|
||||||
|
name = (settings[i] as HTMLInputElement).name
|
||||||
|
value = (settings[i] as HTMLInputElement).checked
|
||||||
|
newSettings[name] = value
|
||||||
|
break
|
||||||
|
|
||||||
|
case "text":
|
||||||
|
name = (settings[i] as HTMLInputElement).name
|
||||||
|
value = (settings[i] as HTMLInputElement).value
|
||||||
|
|
||||||
|
switch (name) {
|
||||||
|
case "update":
|
||||||
|
value = value.split(",")
|
||||||
|
value = value.filter(function(e:any) { return e})
|
||||||
|
break
|
||||||
|
|
||||||
|
case "buffer.timeout":
|
||||||
|
value = parseFloat(value)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
newSettings[name] = value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
case "SELECT":
|
||||||
|
name = (settings[i] as HTMLSelectElement).name
|
||||||
|
value = (settings[i] as HTMLSelectElement).value
|
||||||
|
|
||||||
|
// Wenn der Wert eine Zahl ist, wird dieser als Zahl gespeichert
|
||||||
|
if(isNaN(value)){
|
||||||
|
newSettings[name] = value
|
||||||
|
} else {
|
||||||
|
newSettings[name] = parseInt(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = new Object()
|
||||||
|
data["settings"] = newSettings
|
||||||
|
|
||||||
|
var server:Server = new Server(cmd)
|
||||||
|
server.request(data)
|
||||||
|
}
|
||||||
171
xteve.go
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
// Copyright 2019 marmei. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT license that can be found in the
|
||||||
|
// LICENSE file.
|
||||||
|
// GitHub: https://github.com/xteve-project/xTeVe
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"./src"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GitHubStruct : GitHub Account. Über diesen Account werden die Updates veröffentlicht
|
||||||
|
type GitHubStruct struct {
|
||||||
|
Branch string
|
||||||
|
Repo string
|
||||||
|
Update bool
|
||||||
|
User string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GitHub : GitHub Account
|
||||||
|
// If you want to fork this project, enter your Github account here. This prevents a newer version of xTeVe from updating your version.
|
||||||
|
var GitHub = GitHubStruct{Branch: "master", User: "xteve-project", Repo: "xTeVe-Downloads", Update: true}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Branch: GitHub Branch
|
||||||
|
User: GitHub Username
|
||||||
|
Repo: GitHub Repository
|
||||||
|
Update: Automatic updates from the GitHub repository [true|false]
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Name : Programname
|
||||||
|
const Name = "xTeVe"
|
||||||
|
|
||||||
|
// Version : Version, die Build Nummer wird in der main func geparst.
|
||||||
|
const Version = "2.0.0.0000"
|
||||||
|
|
||||||
|
// APIVersion : API Version
|
||||||
|
const APIVersion = "1.1.0"
|
||||||
|
|
||||||
|
// Dev : Aktiviert den Entwicklungsmodus. Für den Webserver werden dann die lokalen Dateien verwendet.
|
||||||
|
const Dev = false
|
||||||
|
|
||||||
|
var homeDirectory = fmt.Sprintf("%s%s.%s%s", src.GetUserHomeDirectory(), string(os.PathSeparator), strings.ToLower(Name), string(os.PathSeparator))
|
||||||
|
var samplePath = fmt.Sprintf("%spath%sto%sxteve%s", string(os.PathSeparator), string(os.PathSeparator), string(os.PathSeparator), string(os.PathSeparator))
|
||||||
|
|
||||||
|
var configFolder = flag.String("config", "", ": Config Folder ["+samplePath+"] (default: "+homeDirectory+")")
|
||||||
|
var port = flag.String("port", "", ": Server port [34400] (default: 34400)")
|
||||||
|
|
||||||
|
var gitBranch = flag.String("branch", "", ": Git Branch [master|beta] (default: master)")
|
||||||
|
var debug = flag.Int("debug", 0, ": Debug level [0 - 3] (default: 0)")
|
||||||
|
var h = flag.Bool("h", false, ": Show help")
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// Build-Nummer von der Versionsnummer trennen
|
||||||
|
var build = strings.Split(Version, ".")
|
||||||
|
|
||||||
|
var system = &src.System
|
||||||
|
system.APIVersion = APIVersion
|
||||||
|
system.Branch = GitHub.Branch
|
||||||
|
system.Build = build[len(build)-1:][0]
|
||||||
|
system.Dev = Dev
|
||||||
|
system.GitHub = GitHub
|
||||||
|
system.Name = Name
|
||||||
|
system.Version = strings.Join(build[0:len(build)-1], ".")
|
||||||
|
|
||||||
|
// Panic !!!
|
||||||
|
defer func() {
|
||||||
|
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("* * * * * FATAL ERROR * * * * *")
|
||||||
|
fmt.Println("OS: ", runtime.GOOS)
|
||||||
|
fmt.Println("Arch:", runtime.GOARCH)
|
||||||
|
fmt.Println("Err: ", r)
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
pc := make([]uintptr, 20)
|
||||||
|
runtime.Callers(2, pc)
|
||||||
|
|
||||||
|
for i := range pc {
|
||||||
|
|
||||||
|
if runtime.FuncForPC(pc[i]) != nil {
|
||||||
|
|
||||||
|
f := runtime.FuncForPC(pc[i])
|
||||||
|
file, line := f.FileLine(pc[i])
|
||||||
|
|
||||||
|
if string(file)[0:1] != "?" {
|
||||||
|
fmt.Printf("%s:%d %s\n", filepath.Base(file), line, f.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("* * * * * * * * * * * * * * * *")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *h {
|
||||||
|
flag.Usage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Webserver Port
|
||||||
|
if len(*port) > 0 {
|
||||||
|
system.Flag.Port = *port
|
||||||
|
}
|
||||||
|
|
||||||
|
// Branch
|
||||||
|
system.Flag.Branch = *gitBranch
|
||||||
|
if len(system.Flag.Branch) > 0 {
|
||||||
|
fmt.Println("Git Branch is now:", system.Flag.Branch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug Level
|
||||||
|
system.Flag.Debug = *debug
|
||||||
|
if system.Flag.Debug > 3 {
|
||||||
|
flag.Usage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Speicherort für die Konfigurationsdateien
|
||||||
|
if len(*configFolder) > 0 {
|
||||||
|
system.Folder.Config = *configFolder
|
||||||
|
}
|
||||||
|
|
||||||
|
err := src.Init()
|
||||||
|
if err != nil {
|
||||||
|
src.ShowError(err, 0)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = src.BinaryUpdate()
|
||||||
|
if err != nil {
|
||||||
|
src.ShowError(err, 0)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = src.StartSystem(false)
|
||||||
|
if err != nil {
|
||||||
|
src.ShowError(err, 0)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = src.InitMaintenance()
|
||||||
|
if err != nil {
|
||||||
|
src.ShowError(err, 0)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = src.StartWebserver()
|
||||||
|
if err != nil {
|
||||||
|
src.ShowError(err, 0)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||