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
|
||||
|
||||
Copyright (c) 2019 xteve-project
|
||||
Copyright (c) 2019 marmei@xteve-project
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
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
|
||||
128
README.md
@@ -1 +1,127 @@
|
||||
# xTeVe
|
||||
<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
|
||||
## 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)
|
||||
}
|
||||
|
||||
}
|
||||