Implemented a music bot control panel
parent
7fbc84128e
commit
78410c9fdf
|
@ -1,4 +1,7 @@
|
|||
# Changelog:
|
||||
* **22.02.20**
|
||||
- Added a music bot control panel
|
||||
|
||||
* **16.02.20**
|
||||
- Updated the `setup_windows.md` tutorial
|
||||
- Correct redirecting to `index.php` when using the serve mode
|
||||
|
|
4
file.ts
4
file.ts
|
@ -31,7 +31,7 @@ type ProjectResource = {
|
|||
const APP_FILE_LIST_SHARED_SOURCE: ProjectResource[] = [
|
||||
{ /* shared html and php files */
|
||||
"type": "html",
|
||||
"search-pattern": /^([a-zA-Z]+)\.(html|php|json)$/,
|
||||
"search-pattern": /^.*([a-zA-Z]+)\.(html|php|json)$/,
|
||||
"build-target": "dev|rel",
|
||||
|
||||
"path": "./",
|
||||
|
@ -1151,7 +1151,7 @@ async function main(args: string[]) {
|
|||
}
|
||||
|
||||
/* proxy log for better format */
|
||||
const wrap_log = (original, prefix: string) => (message, ...args) => original(prefix + message.replace(/\n/g, "\n" + prefix), ...args.map(e => typeof(e) === "string" ? e.replace(/\n/g, "\n" + prefix) : e));
|
||||
const wrap_log = (original, prefix: string) => (message, ...args) => original(prefix + (message ? message + "" : "").replace(/\n/g, "\n" + prefix), ...args.map(e => typeof(e) === "string" ? e.replace(/\n/g, "\n" + prefix) : e));
|
||||
console.log = wrap_log(console.log, "[INFO ] ");
|
||||
console.debug = wrap_log(console.debug, "[DEBUG] ");
|
||||
console.warn = wrap_log(console.warn, "[WARNING] ");
|
||||
|
|
|
@ -149,3 +149,32 @@
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@mixin tooltip($base_with, $icon_size) {
|
||||
.container-tooltip {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
position: relative;
|
||||
width: $base_with;
|
||||
margin-left: .5em;
|
||||
margin-right: .5em;
|
||||
font-size: .9em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
img, .icon_em {
|
||||
height: $icon_size;
|
||||
width: $icon_size;
|
||||
|
||||
align-self: center;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,697 @@
|
|||
@import "mixin";
|
||||
@import "properties";
|
||||
|
||||
.modal-body.modal-music-manage {
|
||||
padding: 0 !important;
|
||||
|
||||
display: flex !important;;
|
||||
flex-direction: column !important;;
|
||||
justify-content: stretch !important;;
|
||||
|
||||
width: 80em;
|
||||
min-height: 20em; /* Set it here, so we dont have a inner modal scroll */
|
||||
|
||||
@include user-select(none);
|
||||
|
||||
> .header {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
height: 4em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
.category {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
min-width: 0;
|
||||
width: 50%;
|
||||
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
cursor: pointer;
|
||||
padding-bottom: 2px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
a {
|
||||
text-align: center;
|
||||
color: #e1e1e1;
|
||||
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border: none;
|
||||
border-bottom: 2px solid #4e4e4e;
|
||||
|
||||
padding-bottom: 0;
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
|
||||
margin-right: -10em;
|
||||
margin-left: -10em;
|
||||
margin-bottom: -.2em;
|
||||
bottom: 0;
|
||||
|
||||
height: 100%;
|
||||
width: calc(100% + 20em);
|
||||
|
||||
box-shadow: inset 0px -1.2em 3em -20px #424242;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border: none;
|
||||
border-bottom: 2px solid #0073d4;
|
||||
|
||||
padding-bottom: 0;
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
|
||||
margin-right: -10em;
|
||||
margin-left: -10em;
|
||||
margin-bottom: -.2em;
|
||||
bottom: 0;
|
||||
|
||||
height: 100%;
|
||||
width: calc(100% + 20em);
|
||||
|
||||
box-shadow: inset 0px -1.2em 3em -20px #0073d4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
|
||||
min-height: 20em;
|
||||
|
||||
background-color: #303036;
|
||||
|
||||
@include tooltip(1.6em, 1em);
|
||||
.container {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
|
||||
min-height: 20em;
|
||||
height: 40.5em;
|
||||
|
||||
.input-boxed, .btn {
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
$border_color: #1e2025;
|
||||
&.category-permissions {
|
||||
.column {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
&.column-permission {
|
||||
flex-shrink: 1000;
|
||||
flex-grow: 1;
|
||||
min-width: 6em;
|
||||
|
||||
a {
|
||||
@include text-dotdotdot();
|
||||
}
|
||||
|
||||
.master {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.slave {
|
||||
padding-left: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
&.column-required {
|
||||
flex-shrink: 1;
|
||||
flex-grow: 0;
|
||||
|
||||
a {
|
||||
@include text-dotdotdot();
|
||||
}
|
||||
|
||||
min-width: 6em;
|
||||
width: 10em;
|
||||
}
|
||||
|
||||
&.column-client-specific {
|
||||
flex-shrink: 1;
|
||||
flex-grow: 0;
|
||||
|
||||
min-width: 20em;
|
||||
width: 30em;
|
||||
}
|
||||
}
|
||||
|
||||
.table-head {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
.column {
|
||||
height: 5.5em;
|
||||
padding: .5em;
|
||||
|
||||
justify-content: flex-end!important;
|
||||
|
||||
&.column-permission, &.column-required {
|
||||
color: #e1e1e1;
|
||||
font-weight: bold;
|
||||
|
||||
border-right: 1px solid $border_color;
|
||||
}
|
||||
}
|
||||
|
||||
.select-client {
|
||||
padding-top: .5em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
input {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
min-width: 1em;
|
||||
}
|
||||
|
||||
button {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
width: 5em;
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.client-select, .client-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
button {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
margin-left: 1em;
|
||||
|
||||
@include text-dotdotdot();
|
||||
|
||||
&.button-search {
|
||||
width: 6em;
|
||||
}
|
||||
|
||||
&.button-list-clients, &.button-client-deselect {
|
||||
width: 8em;
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
height: 2em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
&:not(:first-of-type) {
|
||||
margin-top: .5em;
|
||||
}
|
||||
|
||||
a {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
min-width: 0;
|
||||
|
||||
align-self: center;
|
||||
|
||||
@include text-dotdotdot();
|
||||
}
|
||||
|
||||
input {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
min-width: 2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.client-info {
|
||||
color: #e1e1e1;
|
||||
font-weight: bold;
|
||||
|
||||
a {
|
||||
flex-shrink: 0;
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
.htmltag-client {
|
||||
color: #e1e1e1;
|
||||
align-self: center;
|
||||
|
||||
@include text-dotdotdot();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-body {
|
||||
flex-shrink: 1;
|
||||
flex-grow: 1;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
||||
min-height: 6em;
|
||||
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
@include chat-scrollbar-vertical();
|
||||
|
||||
.entry {
|
||||
height: 2.6em; /* input box + 2 * .5em */
|
||||
padding: .5em;
|
||||
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
background-color: unset;
|
||||
|
||||
&:nth-of-type(2n) {
|
||||
background-color: #25252a;
|
||||
}
|
||||
|
||||
border-top: 1px solid $border_color;
|
||||
border-right: 1px solid $border_color;
|
||||
|
||||
.container-input {
|
||||
color: #e1e1e1;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input {
|
||||
text-align: right;
|
||||
|
||||
flex-shrink: 1;
|
||||
flex-grow: 1;
|
||||
|
||||
min-width: 2em;
|
||||
|
||||
outline: none;
|
||||
background: transparent;
|
||||
border: none;
|
||||
|
||||
height: 1.6em;
|
||||
|
||||
/* fix the column padding */
|
||||
padding-left: 1em;
|
||||
margin-left: -.5em; /* have a bit of space on both sides */
|
||||
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.container-tooltip {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
border-bottom: 2px solid transparent;
|
||||
|
||||
&:focus-within {
|
||||
border-bottom-color: #3f7dbf;
|
||||
|
||||
input {
|
||||
color: #e1e1e1;
|
||||
}
|
||||
}
|
||||
@include transition(border-bottom-color $button_hover_animation_time ease-in-out);
|
||||
}
|
||||
}
|
||||
|
||||
.column-client-specific {
|
||||
position: relative;
|
||||
|
||||
.entry {
|
||||
border-right: unset;
|
||||
}
|
||||
|
||||
.overlay-client-list {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
|
||||
border-top: 1px solid $border_color;
|
||||
background-color: #303036;
|
||||
padding: .5em;
|
||||
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
|
||||
@include transition(all .25s ease-in-out);
|
||||
|
||||
&.hidden {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.container-client-list {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
border: 1px #161616 solid;
|
||||
border-radius: 0.2em;
|
||||
background-color: #28292b;
|
||||
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
padding-top: 0.25em;
|
||||
padding-bottom: 0.25em;
|
||||
|
||||
margin-top: .5em;
|
||||
margin-bottom: .5em;
|
||||
|
||||
@include chat-scrollbar-vertical();
|
||||
|
||||
.overlay {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
background-color: #28292b;
|
||||
color: #676468;
|
||||
|
||||
text-align: center;
|
||||
font-size: 1.3em;
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.client {
|
||||
padding-left: .5em;
|
||||
padding-right: .5em;
|
||||
|
||||
.htmltag-client {
|
||||
@include text-dotdotdot();
|
||||
color: #999;
|
||||
font-weight: unset;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #2c2d2f;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container-buttons {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container-buttons {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
|
||||
margin-top: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
&.category-settings {
|
||||
.container-settings {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
min-height: 10em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
.settings {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
width: 50%;
|
||||
min-width: 15em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
|
||||
padding: .5em;
|
||||
|
||||
&.settings-bot {
|
||||
border-right: 1px solid $border_color;
|
||||
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
&.settings-playlist {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
> a {
|
||||
color: #e1e1e1;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
> label, > div {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
flex-direction: row;
|
||||
|
||||
margin-top: .5em;
|
||||
|
||||
* {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
@include text-dotdotdot();
|
||||
}
|
||||
|
||||
.container-name-country {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
.option-bot-name {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
min-width: 6em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.container-country {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
width: 5em;
|
||||
|
||||
input {
|
||||
flex-shrink: 1;
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.country {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
margin: .5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
.container-replay-mode, .container-max-playlist-size {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
a {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
select, .input-boxed {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
width: 10em;
|
||||
|
||||
margin-left: .5em;
|
||||
|
||||
input {
|
||||
flex-shrink: 1;
|
||||
flex-grow: 1;
|
||||
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container-buttons {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip-music-permission-overview {
|
||||
padding-left: .25em;
|
||||
padding-right: .25em;
|
||||
text-align: left;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
max-height: 8em;
|
||||
|
||||
.container-title {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.container-groups {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
|
||||
min-height: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
@include chat-scrollbar-horizontal();
|
||||
}
|
||||
|
||||
.container-status {
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,574 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Template Music manage</title>
|
||||
</head>
|
||||
<body>
|
||||
<script class="jsrender-template" id="tmpl_music_manage" type="text/html">
|
||||
<div>
|
||||
<div class="header">
|
||||
<div class="category category-settings">
|
||||
<a>{{tr "Settings" /}}</a>
|
||||
</div>
|
||||
<div class="category category-permissions">
|
||||
<a>{{tr "Permissions" /}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="container category-settings">
|
||||
<div class="container-settings">
|
||||
<div class="settings settings-bot">
|
||||
<a>{{tr "Music Bot" /}}</a>
|
||||
<div class="body">
|
||||
<div class="container-name-country">
|
||||
<input class="input-boxed option-bot-name" placeholder="Bot name" max="30">
|
||||
<div class="input-boxed container-country">
|
||||
<input class="option-bot-country" maxlength="2">
|
||||
<div class="country flag-de"></div>
|
||||
</div>
|
||||
</div>
|
||||
<label>
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" class="option-channel-commander">
|
||||
<div class="mark"></div>
|
||||
</div>
|
||||
<a>{{tr "Music bot is channel commander" /}}</a>
|
||||
</label>
|
||||
<label>
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" class="option-priority-speaker">
|
||||
<div class="mark"></div>
|
||||
</div>
|
||||
<a>{{tr "Music bot is priority speaker" /}}</a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings settings-playlist">
|
||||
<a>{{tr "Replay" /}}</a>
|
||||
<div class="body">
|
||||
<div class="container-replay-mode">
|
||||
<a>{{tr "Replay mode" /}}</a>
|
||||
<select class="input-boxed option-replay-mode">
|
||||
<option value="-1" style="display: none;">{{tr "Unknown" /}}</option>
|
||||
<option value="0">{{tr "Normal" /}}</option>
|
||||
<option value="1">{{tr "Looped" /}}</option>
|
||||
<option value="2">{{tr "Single-Looped" /}}</option>
|
||||
<option value="3">{{tr "Shuffle" /}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="container-max-playlist-size">
|
||||
<a>{{tr "Max playlist size" /}}</a>
|
||||
<div class="input-boxed">
|
||||
<input type="number" min="0" value="60" maxlength="6">
|
||||
<div class="container-tooltip">
|
||||
<img src="img/icon_tooltip_music_permission.svg">
|
||||
<div class="tooltip">
|
||||
<a>You could set this value up to X.</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label>
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" class="option-delete-played-songs">
|
||||
<div class="mark"></div>
|
||||
</div>
|
||||
<a>{{tr "Delete played songs from playlist" /}}</a>
|
||||
</label>
|
||||
<label>
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" class="option-notify-songs-change">
|
||||
<div class="mark"></div>
|
||||
</div>
|
||||
<a>{{tr "Send a channel message when the song changes" /}}</a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-buttons">
|
||||
<button class="btn btn-blue button-reload">{{tr "Reload" /}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container category-permissions">
|
||||
<div class="table-head">
|
||||
<div class="column column-permission">
|
||||
<a>{{tr "Permission" /}}</a>
|
||||
</div>
|
||||
<div class="column column-required">
|
||||
<a>{{tr "Required value" /}}</a>
|
||||
</div>
|
||||
<div class="column column-client-specific">
|
||||
<div class="client-select">
|
||||
<div class="row">
|
||||
<a>{{tr "Search for an client:" /}}</a>
|
||||
<button class="btn btn-blue button-list-clients">error: client list</button>
|
||||
</div>
|
||||
<div class="row">
|
||||
<input type="text" class="input-boxed input-search" placeholder="error: placeholder" maxlength="40">
|
||||
<button class="btn btn-green button-search">{{tr "Search" /}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="client-info hidden">
|
||||
<div class="row">
|
||||
<a></a>
|
||||
<button class="btn btn-blue button-client-deselect">{{tr "Unselect" /}}</button>
|
||||
</div>
|
||||
<div class="row container-selected-client">
|
||||
<a>{{tr "Showing permissions for:" /}}</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-body">
|
||||
<div class="column column-permission">
|
||||
<div class="entry master">
|
||||
<a>{{tr "Bot permissions" /}}</a>
|
||||
</div>
|
||||
<div class="entry slave">
|
||||
<a>{{tr "Power to rename the music bot" /}}</a>
|
||||
</div>
|
||||
<div class="entry slave">
|
||||
<a>{{tr "Power to modify the music bot permissions" /}}</a>
|
||||
</div>
|
||||
<div class="entry slave">
|
||||
<a>{{tr "Power to delete the music bot" /}}</a>
|
||||
</div>
|
||||
<div class="entry master">
|
||||
<a>{{tr "Playlist permissions" /}}</a>
|
||||
</div>
|
||||
<div class="entry slave">
|
||||
<a>{{tr "Power to see the playlist songs" /}}</a>
|
||||
</div>
|
||||
<div class="entry slave">
|
||||
<a>{{tr "Power for editing playlist settings" /}}</a>
|
||||
</div>
|
||||
<div class="entry slave">
|
||||
<a>{{tr "Power for viewing playlist permissions" /}}</a>
|
||||
</div>
|
||||
<div class="entry slave">
|
||||
<a>{{tr "Power for editing playlist permissions" /}}</a>
|
||||
</div>
|
||||
<div class="entry slave">
|
||||
<a>{{tr "Power for adding a song to the playlist" /}}</a>
|
||||
</div>
|
||||
<div class="entry slave">
|
||||
<a>{{tr "Power to reorder a song within the playlist" /}}</a>
|
||||
</div>
|
||||
<div class="entry slave">
|
||||
<a>{{tr "Power to delete a song from the playlist" /}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column column-required">
|
||||
<div class="entry"></div> <!-- bot permissions -->
|
||||
<div class="entry">
|
||||
<div class="container-input general-permission" x-permission="i_client_music_needed_rename_power">
|
||||
<input type="number" min="0" value="60">
|
||||
<div class="container-tooltip">
|
||||
<img src="img/icon_tooltip_music_permission.svg">
|
||||
<div class="tooltip">
|
||||
<a class="tooltip-music-permission-overview">
|
||||
<div class="container-title">{{tr "These groups could rename the bot:" /}}</div>
|
||||
<div class="container-status status-no-groups">
|
||||
{{tr "No group could do that." /}}
|
||||
</div>
|
||||
<div class="container-status status-loading">
|
||||
{{tr "loading..." /}}
|
||||
</div>
|
||||
<div class="container-status status-error-permission">
|
||||
{{tr "failed on permission" /}}
|
||||
</div>
|
||||
<div class="container-status status-error">
|
||||
error: error
|
||||
</div>
|
||||
<div class="container-groups"> </div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry">
|
||||
<div class="container-input general-permission" x-permission="i_client_music_needed_modify_power">
|
||||
<input type="number" min="0" value="60">
|
||||
<div class="container-tooltip">
|
||||
<img src="img/icon_tooltip_music_permission.svg">
|
||||
<div class="tooltip">
|
||||
<a class="tooltip-music-permission-overview">
|
||||
<div class="container-title">{{tr "These groups could modify the bot:" /}}</div>
|
||||
<div class="container-status status-no-groups">
|
||||
{{tr "No group could do that." /}}
|
||||
</div>
|
||||
<div class="container-status status-loading">
|
||||
{{tr "loading..." /}}
|
||||
</div>
|
||||
<div class="container-status status-error-permission">
|
||||
{{tr "failed on permission" /}}<br>
|
||||
b_virtualserver_permission_find
|
||||
</div>
|
||||
<div class="container-status status-error">
|
||||
error: error
|
||||
</div>
|
||||
<div class="container-groups"> </div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry">
|
||||
<div class="container-input general-permission" x-permission="i_client_music_needed_delete_power">
|
||||
<input type="number" min="0" value="60">
|
||||
<div class="container-tooltip">
|
||||
<img src="img/icon_tooltip_music_permission.svg">
|
||||
<div class="tooltip">
|
||||
<a class="tooltip-music-permission-overview">
|
||||
<div class="container-title">{{tr "These groups could delete the bot:" /}}</div>
|
||||
<div class="container-status status-no-groups">
|
||||
{{tr "No group could do that." /}}
|
||||
</div>
|
||||
<div class="container-status status-loading">
|
||||
{{tr "loading..." /}}
|
||||
</div>
|
||||
<div class="container-status status-error-permission">
|
||||
{{tr "failed on permission" /}}
|
||||
</div>
|
||||
<div class="container-status status-error">
|
||||
error: error
|
||||
</div>
|
||||
<div class="container-groups"> </div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry"></div> <!-- play permissions -->
|
||||
<div class="entry">
|
||||
<div class="container-input general-permission" x-permission="i_playlist_needed_view_power">
|
||||
<input type="number" min="0" value="60">
|
||||
<div class="container-tooltip">
|
||||
<img src="img/icon_tooltip_music_permission.svg">
|
||||
<div class="tooltip">
|
||||
<a class="tooltip-music-permission-overview">
|
||||
<div class="container-title">{{tr "These groups could view the playlist:" /}}</div>
|
||||
<div class="container-status status-no-groups">
|
||||
{{tr "No group could do that." /}}
|
||||
</div>
|
||||
<div class="container-status status-loading">
|
||||
{{tr "loading..." /}}
|
||||
</div>
|
||||
<div class="container-status status-error-permission">
|
||||
{{tr "failed on permission" /}}
|
||||
</div>
|
||||
<div class="container-status status-error">
|
||||
error: error
|
||||
</div>
|
||||
<div class="container-groups"> </div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry">
|
||||
<div class="container-input general-permission" x-permission="i_playlist_needed_modify_power">
|
||||
<input type="number" min="0" value="60">
|
||||
<div class="container-tooltip">
|
||||
<img src="img/icon_tooltip_music_permission.svg">
|
||||
<div class="tooltip">
|
||||
<a class="tooltip-music-permission-overview">
|
||||
<div class="container-title">{{tr "These groups could edit the playlist settings:" /}}</div>
|
||||
<div class="container-status status-no-groups">
|
||||
{{tr "No group could do that." /}}
|
||||
</div>
|
||||
<div class="container-status status-loading">
|
||||
{{tr "loading..." /}}
|
||||
</div>
|
||||
<div class="container-status status-error-permission">
|
||||
{{tr "failed on permission" /}}
|
||||
</div>
|
||||
<div class="container-status status-error">
|
||||
error: error
|
||||
</div>
|
||||
<div class="container-groups"> </div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry">
|
||||
<div class="container-input general-permission" x-permission="b_virtualserver_playlist_permission_list">
|
||||
<div style="flex-grow: 1; flex-shrink: 1; min-width: 0"></div>
|
||||
<div class="container-tooltip">
|
||||
<img src="img/icon_tooltip_music_permission.svg">
|
||||
<div class="tooltip">
|
||||
<a class="tooltip-music-permission-overview">
|
||||
<div class="container-title">{{tr "These groups could view the playlist permissions:" /}}</div>
|
||||
<div class="container-status status-no-groups">
|
||||
{{tr "No group could do that." /}}
|
||||
</div>
|
||||
<div class="container-status status-loading">
|
||||
{{tr "loading..." /}}
|
||||
</div>
|
||||
<div class="container-status status-error-permission">
|
||||
{{tr "failed on permission" /}}
|
||||
</div>
|
||||
<div class="container-status status-error">
|
||||
error: error
|
||||
</div>
|
||||
<div class="container-groups"> </div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry">
|
||||
<div class="container-input general-permission" x-permission="i_playlist_needed_permission_modify_power">
|
||||
<input type="number" min="0" value="60">
|
||||
<div class="container-tooltip">
|
||||
<img src="img/icon_tooltip_music_permission.svg">
|
||||
<div class="tooltip">
|
||||
<a class="tooltip-music-permission-overview">
|
||||
<div class="container-title">{{tr "These groups could edit the playlist permissions:" /}}</div>
|
||||
<div class="container-status status-no-groups">
|
||||
{{tr "No group could do that." /}}
|
||||
</div>
|
||||
<div class="container-status status-loading">
|
||||
{{tr "loading..." /}}
|
||||
</div>
|
||||
<div class="container-status status-error-permission">
|
||||
{{tr "failed on permission" /}}
|
||||
</div>
|
||||
<div class="container-status status-error">
|
||||
error: error
|
||||
</div>
|
||||
<div class="container-groups"> </div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry">
|
||||
<div class="container-input general-permission" x-permission="i_playlist_song_needed_add_power">
|
||||
<input type="number" min="0" value="60">
|
||||
<div class="container-tooltip">
|
||||
<img src="img/icon_tooltip_music_permission.svg">
|
||||
<div class="tooltip">
|
||||
<a class="tooltip-music-permission-overview">
|
||||
<div class="container-title">{{tr "These groups could add a song:" /}}</div>
|
||||
<div class="container-status status-no-groups">
|
||||
{{tr "No group could do that." /}}
|
||||
</div>
|
||||
<div class="container-status status-loading">
|
||||
{{tr "loading..." /}}
|
||||
</div>
|
||||
<div class="container-status status-error-permission">
|
||||
{{tr "failed on permission" /}}
|
||||
</div>
|
||||
<div class="container-status status-error">
|
||||
error: error
|
||||
</div>
|
||||
<div class="container-groups"> </div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry">
|
||||
<div class="container-input general-permission" x-permission="i_playlist_song_needed_move_power">
|
||||
<input type="number" min="0" value="60">
|
||||
<div class="container-tooltip">
|
||||
<img src="img/icon_tooltip_music_permission.svg">
|
||||
<div class="tooltip">
|
||||
<a class="tooltip-music-permission-overview">
|
||||
<div class="container-title">{{tr "These groups could reorder the songs:" /}}</div>
|
||||
<div class="container-status status-no-groups">
|
||||
{{tr "No group could do that." /}}
|
||||
</div>
|
||||
<div class="container-status status-loading">
|
||||
{{tr "loading..." /}}
|
||||
</div>
|
||||
<div class="container-status status-error-permission">
|
||||
{{tr "failed on permission" /}}
|
||||
</div>
|
||||
<div class="container-status status-error">
|
||||
error: error
|
||||
</div>
|
||||
<div class="container-groups"> </div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry">
|
||||
<div class="container-input general-permission" x-permission="i_playlist_song_needed_remove_power">
|
||||
<input type="number" min="0" value="60">
|
||||
<div class="container-tooltip">
|
||||
<img src="img/icon_tooltip_music_permission.svg">
|
||||
<div class="tooltip">
|
||||
<a class="tooltip-music-permission-overview">
|
||||
<div class="container-title">{{tr "These groups could delete a song:" /}}</div>
|
||||
<div class="container-status status-no-groups">
|
||||
{{tr "No group could do that." /}}
|
||||
</div>
|
||||
<div class="container-status status-loading">
|
||||
{{tr "loading..." /}}
|
||||
</div>
|
||||
<div class="container-status status-error-permission">
|
||||
{{tr "failed on permission" /}}
|
||||
</div>
|
||||
<div class="container-status status-error">
|
||||
error: error
|
||||
</div>
|
||||
<div class="container-groups"> </div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column column-client-specific">
|
||||
<div class="entry"></div> <!-- bot permissions -->
|
||||
<div class="entry">
|
||||
<div class="container-input client-permission" x-permission="i_client_music_rename_power">
|
||||
<input type="number" min="0" value="60">
|
||||
<div class="container-tooltip">
|
||||
<div class="icon_em client-apply"></div>
|
||||
<div class="tooltip">
|
||||
<a>{{tr "Client could perform this action" /}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry">
|
||||
<div class="container-input client-permission" x-permission="i_client_music_modify_power">
|
||||
<input type="number" min="0" value="60">
|
||||
<div class="container-tooltip">
|
||||
<div class="icon_em client-delete"></div>
|
||||
<div class="tooltip">
|
||||
<a>{{tr "Client could perform this action" /}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry">
|
||||
<div class="container-input client-permission" x-permission="i_client_music_delete_power">
|
||||
<input type="number" min="0" value="60">
|
||||
<div class="container-tooltip">
|
||||
<div class="icon_em client-delete"></div>
|
||||
<div class="tooltip">
|
||||
<a>{{tr "Client could perform this action" /}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry"></div> <!-- play permissions -->
|
||||
<div class="entry">
|
||||
<div class="container-input client-permission" x-permission="i_playlist_view_power">
|
||||
<input type="number" min="0" value="60">
|
||||
<div class="container-tooltip">
|
||||
<div class="icon_em client-delete"></div>
|
||||
<div class="tooltip">
|
||||
<a>{{tr "Client could perform this action" /}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry">
|
||||
<div class="container-input client-permission" x-permission="i_playlist_modify_power">
|
||||
<input type="number" min="0" value="60">
|
||||
<div class="container-tooltip">
|
||||
<div class="icon_em client-delete"></div>
|
||||
<div class="tooltip">
|
||||
<a>{{tr "Client could perform this action" /}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry">
|
||||
<div class="container-input client-permission" x-permission="b_virtualserver_playlist_permission_list">
|
||||
<input type="number" min="0" value="60">
|
||||
<div class="container-tooltip">
|
||||
<div class="icon_em client-delete"></div>
|
||||
<div class="tooltip">
|
||||
<a>{{tr "Client could no see the permissions" /}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry">
|
||||
<div class="container-input client-permission" x-permission="i_playlist_permission_modify_power">
|
||||
<input type="number" min="0" value="60">
|
||||
<div class="container-tooltip">
|
||||
<div class="icon_em client-delete"></div>
|
||||
<div class="tooltip">
|
||||
<a>{{tr "Client could perform this action" /}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry">
|
||||
<div class="container-input client-permission" x-permission="i_playlist_song_add_power">
|
||||
<input type="number" min="0" value="60">
|
||||
<div class="container-tooltip">
|
||||
<div class="icon_em client-delete"></div>
|
||||
<div class="tooltip">
|
||||
<a>{{tr "Client could perform this action" /}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry">
|
||||
<div class="container-input client-permission" x-permission="i_playlist_song_move_power">
|
||||
<input type="number" min="0" value="60">
|
||||
<div class="container-tooltip">
|
||||
<div class="icon_em client-delete"></div>
|
||||
<div class="tooltip">
|
||||
<a>{{tr "Client could perform this action" /}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="entry">
|
||||
<div class="container-input client-permission" x-permission="i_playlist_song_remove_power">
|
||||
<input type="number" min="0" value="60">
|
||||
<div class="container-tooltip">
|
||||
<div class="icon_em client-delete"></div>
|
||||
<div class="tooltip">
|
||||
<a>{{tr "Client could perform this action" /}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overlay-client-list">
|
||||
<div class="title">{{tr "Clients which have special permissions:" /}}</div>
|
||||
<div class="container-client-list">
|
||||
<div class="overlay overlay-filter-no-result">
|
||||
<a>{{tr "No clients match the filter" /}}</a>
|
||||
</div>
|
||||
<div class="overlay overlay-empty-list">
|
||||
<a>{{tr "No clients available" /}}</a>
|
||||
</div>
|
||||
<div class="overlay overlay-query-error">
|
||||
<a>error: error text</a>
|
||||
</div>
|
||||
<div class="overlay overlay-query-error-permissions">
|
||||
<a>error: permission error text</a>
|
||||
</div>
|
||||
<div class="overlay overlay-refreshing">
|
||||
<a>{{tr "Loading..." /}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-buttons">
|
||||
<button class="btn btn-blue button-clientlist-refresh">{{tr "Refresh client list" /}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-buttons">
|
||||
<button class="btn btn-blue button-permission-refresh">{{tr "Refresh" /}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,11 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400" viewBox="0 0 400 400">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #999;
|
||||
fill-rule: evenodd;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path id="icon" class="cls-1" d="M200.012,0c110.457,0,200,89.545,200,200s-89.54,200-200,200S0.015,310.456.015,200,89.557,0,200.012,0Zm0.01,38.636A160.629,160.629,0,1,1,39.395,199.268,160.632,160.632,0,0,1,200.022,38.639ZM159.407,153.478s42.867-5.468,19.8,68.014c-24.208,77.113-66.566,140.45,63.467,81.447,0,0-64.556,21.6-42.619-37.407,12.79-34.406,28.292-81.421,27.092-93.4C225.387,154.546,207.6,141.025,159.407,153.478Zm65.564-79a24.685,24.685,0,1,1-24.687,24.684A24.685,24.685,0,0,1,224.971,74.479Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 757 B |
|
@ -704,21 +704,25 @@ class ConnectionHandler {
|
|||
if(vconnection && vconnection.voice_recorder() && vconnection.voice_recorder().record_supported) {
|
||||
const active = !this.client_status.input_muted && !this.client_status.output_muted;
|
||||
/* No need to start the microphone when we're not even connected */
|
||||
if(active && this.serverConnection.connected()) {
|
||||
if(vconnection.voice_recorder().input.current_state() === audio.recorder.InputState.PAUSED) {
|
||||
vconnection.voice_recorder().input.start().then(result => {
|
||||
if(result != audio.recorder.InputStartResult.EOK)
|
||||
throw result;
|
||||
}).catch(error => {
|
||||
log.warn(LogCategory.VOICE, tr("Failed to start microphone input (%s)."), error);
|
||||
if(Date.now() - (this._last_record_error_popup || 0) > 10 * 1000) {
|
||||
this._last_record_error_popup = Date.now();
|
||||
createErrorModal(tr("Failed to start recording"), MessageHelper.formatMessage(tr("Microphone start failed.{:br:}Error: {}"), error)).open();
|
||||
}
|
||||
});
|
||||
|
||||
const input = vconnection.voice_recorder().input;
|
||||
if(input) {
|
||||
if(active && this.serverConnection.connected()) {
|
||||
if(input.current_state() === audio.recorder.InputState.PAUSED) {
|
||||
input.start().then(result => {
|
||||
if(result != audio.recorder.InputStartResult.EOK)
|
||||
throw result;
|
||||
}).catch(error => {
|
||||
log.warn(LogCategory.VOICE, tr("Failed to start microphone input (%s)."), error);
|
||||
if(Date.now() - (this._last_record_error_popup || 0) > 10 * 1000) {
|
||||
this._last_record_error_popup = Date.now();
|
||||
createErrorModal(tr("Failed to start recording"), MessageHelper.formatMessage(tr("Microphone start failed.{:br:}Error: {}"), error)).open();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
input.stop();
|
||||
}
|
||||
} else {
|
||||
vconnection.voice_recorder().input.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ namespace connection {
|
|||
export class CommandHelper extends AbstractCommandHandler {
|
||||
private _who_am_i: any;
|
||||
private _awaiters_unique_ids: {[unique_id: string]:((resolved: ClientNameInfo) => any)[]} = {};
|
||||
private _awaiters_unique_dbid: {[database_id: number]:((resolved: ClientNameInfo) => any)[]} = {};
|
||||
|
||||
constructor(connection) {
|
||||
super(connection);
|
||||
|
@ -25,6 +26,8 @@ namespace connection {
|
|||
handle_command(command: connection.ServerCommand): boolean {
|
||||
if(command.command == "notifyclientnamefromuid")
|
||||
this.handle_notifyclientnamefromuid(command.arguments);
|
||||
if(command.command == "notifyclientgetnamefromdbid")
|
||||
this.handle_notifyclientgetnamefromdbid(command.arguments);
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
|
@ -57,6 +60,8 @@ namespace connection {
|
|||
const response: ClientNameInfo[] = [];
|
||||
const request = [];
|
||||
const unique_ids = new Set(_unique_ids);
|
||||
if(!unique_ids.size) return [];
|
||||
|
||||
const unique_id_resolvers: {[unique_id: string]: (resolved: ClientNameInfo) => any} = {};
|
||||
|
||||
|
||||
|
@ -83,6 +88,54 @@ namespace connection {
|
|||
return response;
|
||||
}
|
||||
|
||||
private handle_notifyclientgetnamefromdbid(json: any[]) {
|
||||
for(const entry of json) {
|
||||
const info: ClientNameInfo = {
|
||||
client_unique_id: entry["cluid"],
|
||||
client_nickname: entry["clname"],
|
||||
client_database_id: parseInt(entry["cldbid"])
|
||||
};
|
||||
|
||||
const functions = this._awaiters_unique_dbid[info.client_database_id] || [];
|
||||
delete this._awaiters_unique_dbid[info.client_database_id];
|
||||
|
||||
for(const fn of functions)
|
||||
fn(info);
|
||||
}
|
||||
}
|
||||
|
||||
async info_from_cldbid(..._cldbid: number[]) : Promise<ClientNameInfo[]> {
|
||||
const response: ClientNameInfo[] = [];
|
||||
const request = [];
|
||||
const unique_cldbid = new Set(_cldbid);
|
||||
if(!unique_cldbid.size) return [];
|
||||
|
||||
const unique_cldbid_resolvers: {[dbid: number]: (resolved: ClientNameInfo) => any} = {};
|
||||
|
||||
|
||||
for(const cldbid of unique_cldbid) {
|
||||
request.push({'cldbid': cldbid});
|
||||
(this._awaiters_unique_dbid[cldbid] || (this._awaiters_unique_dbid[cldbid] = []))
|
||||
.push(unique_cldbid_resolvers[cldbid] = info => response.push(info));
|
||||
}
|
||||
|
||||
try {
|
||||
await this.connection.send_command("clientgetnamefromdbid", request);
|
||||
} catch(error) {
|
||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) {
|
||||
/* nothing */
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
} finally {
|
||||
/* cleanup */
|
||||
for(const cldbid of Object.keys(unique_cldbid_resolvers))
|
||||
(this._awaiters_unique_dbid[cldbid] || []).remove(unique_cldbid_resolvers[cldbid]);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private handle_notifyclientnamefromuid(json: any[]) {
|
||||
for(const entry of json) {
|
||||
const info: ClientNameInfo = {
|
||||
|
@ -245,6 +298,40 @@ namespace connection {
|
|||
});
|
||||
}
|
||||
|
||||
request_playlist_client_list(playlist_id: number) : Promise<number[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const single_handler: SingleCommandHandler = {
|
||||
command: "notifyplaylistclientlist",
|
||||
function: command => {
|
||||
const json = command.arguments;
|
||||
|
||||
if(json[0]["playlist_id"] != playlist_id) {
|
||||
log.error(LogCategory.NETWORKING, tr("Received invalid notification for playlist clients"));
|
||||
return false;
|
||||
}
|
||||
|
||||
const result: number[] = [];
|
||||
|
||||
for(const entry of json)
|
||||
result.push(parseInt(entry["cldbid"]));
|
||||
|
||||
resolve(result.filter(e => !isNaN(e)));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
this.handler_boss.register_single_handler(single_handler);
|
||||
|
||||
this.connection.send_command("playlistclientlist", {playlist_id: playlist_id}).catch(error => {
|
||||
this.handler_boss.remove_single_handler(single_handler);
|
||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) {
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
reject(error);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async request_clients_by_server_group(group_id: number) : Promise<ServerGroupClient[]> {
|
||||
//servergroupclientlist sgid=2
|
||||
//notifyservergroupclientlist sgid=6 cldbid=2 client_nickname=WolverinDEV client_unique_identifier=xxjnc14LmvTk+Lyrm8OOeo4tOqw=
|
||||
|
@ -309,6 +396,8 @@ namespace connection {
|
|||
playlist_flag_finished: json["playlist_flag_finished"] == true || json["playlist_flag_finished"] == "1",
|
||||
playlist_replay_mode: parseInt(json["playlist_replay_mode"]),
|
||||
playlist_current_song_id: parseInt(json["playlist_current_song_id"]),
|
||||
|
||||
playlist_max_songs: parseInt(json["playlist_max_songs"])
|
||||
});
|
||||
} catch (error) {
|
||||
log.error(LogCategory.NETWORKING, tr("Failed to parse playlist info: %o"), error);
|
||||
|
|
|
@ -96,6 +96,8 @@ interface PlaylistInfo {
|
|||
playlist_flag_finished: boolean,
|
||||
playlist_replay_mode: number,
|
||||
playlist_current_song_id: number,
|
||||
|
||||
playlist_max_songs: number
|
||||
}
|
||||
|
||||
interface PlaylistSong {
|
||||
|
|
|
@ -160,6 +160,160 @@ namespace events {
|
|||
}
|
||||
}
|
||||
|
||||
export namespace modal {
|
||||
export type BotStatusType = "name" | "description" | "volume" | "country_code" | "channel_commander" | "priority_speaker";
|
||||
export type PlaylistStatusType = "replay_mode" | "finished" | "delete_played" | "max_size" | "notify_song_change";
|
||||
export interface music_manage {
|
||||
show_container: { container: "settings" | "permissions"; };
|
||||
|
||||
/* setting relevant */
|
||||
query_bot_status: {},
|
||||
bot_status: {
|
||||
status: "success" | "error";
|
||||
error_msg?: string;
|
||||
data?: {
|
||||
name: string,
|
||||
description: string,
|
||||
volume: number,
|
||||
|
||||
country_code: string,
|
||||
default_country_code: string,
|
||||
|
||||
channel_commander: boolean,
|
||||
priority_speaker: boolean,
|
||||
|
||||
client_version: string,
|
||||
client_platform: string,
|
||||
|
||||
uptime_mode: number,
|
||||
bot_type: number
|
||||
}
|
||||
},
|
||||
set_bot_status: {
|
||||
key: BotStatusType,
|
||||
value: any
|
||||
},
|
||||
set_bot_status_result: {
|
||||
key: BotStatusType,
|
||||
status: "success" | "error" | "timeout",
|
||||
error_msg?: string,
|
||||
value?: any
|
||||
}
|
||||
|
||||
query_playlist_status: {},
|
||||
playlist_status: {
|
||||
status: "success" | "error",
|
||||
error_msg?: string,
|
||||
data?: {
|
||||
replay_mode: number,
|
||||
finished: boolean,
|
||||
delete_played: boolean,
|
||||
max_size: number,
|
||||
notify_song_change: boolean
|
||||
}
|
||||
},
|
||||
set_playlist_status: {
|
||||
key: PlaylistStatusType,
|
||||
value: any
|
||||
},
|
||||
set_playlist_status_result: {
|
||||
key: PlaylistStatusType,
|
||||
status: "success" | "error" | "timeout",
|
||||
error_msg?: string,
|
||||
value?: any
|
||||
}
|
||||
|
||||
/* permission relevant */
|
||||
show_client_list: {},
|
||||
hide_client_list: {},
|
||||
|
||||
filter_client_list: { filter: string | undefined },
|
||||
|
||||
"refresh_permissions": {},
|
||||
|
||||
query_special_clients: {},
|
||||
special_client_list: {
|
||||
status: "success" | "error" | "error-permission",
|
||||
error_msg?: string,
|
||||
clients?: {
|
||||
name: string,
|
||||
unique_id: string,
|
||||
database_id: number
|
||||
}[]
|
||||
},
|
||||
|
||||
search_client: { text: string },
|
||||
search_client_result: {
|
||||
status: "error" | "timeout" | "empty" | "success",
|
||||
error_msg?: string,
|
||||
client?: {
|
||||
name: string,
|
||||
unique_id: string,
|
||||
database_id: number
|
||||
}
|
||||
},
|
||||
|
||||
/* sets a client to set the permission for */
|
||||
special_client_set: {
|
||||
client?: {
|
||||
name: string,
|
||||
unique_id: string,
|
||||
database_id: number
|
||||
}
|
||||
},
|
||||
|
||||
"query_general_permissions": {},
|
||||
"general_permissions": {
|
||||
status: "error" | "timeout" | "success",
|
||||
error_msg?: string,
|
||||
permissions?: {[key: string]:number}
|
||||
},
|
||||
"set_general_permission_result": {
|
||||
status: "error" | "success",
|
||||
key: string,
|
||||
value?: number,
|
||||
error_msg?: string
|
||||
},
|
||||
"set_general_permission": { /* try to change a permission for the server */
|
||||
key: string,
|
||||
value: number
|
||||
},
|
||||
|
||||
|
||||
"query_client_permissions": { client_database_id: number },
|
||||
"client_permissions": {
|
||||
status: "error" | "timeout" | "success",
|
||||
client_database_id: number,
|
||||
error_msg?: string,
|
||||
permissions?: {[key: string]:number}
|
||||
},
|
||||
"set_client_permission_result": {
|
||||
status: "error" | "success",
|
||||
client_database_id: number,
|
||||
key: string,
|
||||
value?: number,
|
||||
error_msg?: string
|
||||
},
|
||||
"set_client_permission": { /* try to change a permission for the server */
|
||||
client_database_id: number,
|
||||
key: string,
|
||||
value: number
|
||||
},
|
||||
|
||||
"query_group_permissions": { permission_name: string },
|
||||
"group_permissions": {
|
||||
permission_name: string;
|
||||
status: "error" | "timeout" | "success"
|
||||
groups?: {
|
||||
name: string,
|
||||
value: number,
|
||||
id: number
|
||||
}[],
|
||||
error_msg?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const eclient = new events.Registry<events.channel_tree.client>();
|
||||
|
|
|
@ -1492,7 +1492,7 @@ namespace i18n {
|
|||
alpha_3: "ZWE",
|
||||
un_code: 716
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export function country_name(alpha_code: string, fallback?: string) {
|
||||
return (alpha_2_map[alpha_code.toUpperCase()] || {name: fallback || tr("unknown country")}).name;
|
||||
|
|
|
@ -416,20 +416,68 @@ class NeededPermissionValue extends PermissionValue {
|
|||
}
|
||||
}
|
||||
|
||||
class ChannelPermissionRequest {
|
||||
requested: number;
|
||||
channel_id: number;
|
||||
callback_success: ((_: PermissionValue[]) => any)[] = [];
|
||||
callback_error: ((_: any) => any)[] = [];
|
||||
}
|
||||
|
||||
class TeaPermissionRequest {
|
||||
client_id?: number;
|
||||
channel_id?: number;
|
||||
playlist_id?: number;
|
||||
promise: LaterPromise<PermissionValue[]>;
|
||||
namespace permissions {
|
||||
export type PermissionRequestKeys = {
|
||||
client_id?: number;
|
||||
channel_id?: number;
|
||||
playlist_id?: number;
|
||||
}
|
||||
|
||||
export type PermissionRequest = PermissionRequestKeys & {
|
||||
timeout_id: any;
|
||||
promise: LaterPromise<PermissionValue[]>;
|
||||
};
|
||||
|
||||
export namespace find {
|
||||
export type Entry = {
|
||||
type: "server" | "channel" | "client" | "client_channel" | "channel_group" | "server_group";
|
||||
value: number;
|
||||
id: number;
|
||||
}
|
||||
|
||||
export type Client = Entry & {
|
||||
type: "client",
|
||||
|
||||
client_id: number;
|
||||
}
|
||||
|
||||
export type Channel = Entry & {
|
||||
type: "channel",
|
||||
|
||||
channel_id: number;
|
||||
}
|
||||
|
||||
export type Server = Entry & {
|
||||
type: "server"
|
||||
}
|
||||
|
||||
export type ClientChannel = Entry & {
|
||||
type: "client_channel",
|
||||
|
||||
client_id: number;
|
||||
channel_id: number;
|
||||
}
|
||||
|
||||
export type ChannelGroup = Entry & {
|
||||
type: "channel_group",
|
||||
|
||||
group_id: number;
|
||||
}
|
||||
|
||||
export type ServerGroup = Entry & {
|
||||
type: "server_group",
|
||||
|
||||
group_id: number;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type RequestLists =
|
||||
"requests_channel_permissions" |
|
||||
"requests_client_permissions" |
|
||||
"requests_client_channel_permissions" |
|
||||
"requests_playlist_permissions" |
|
||||
"requests_playlist_client_permissions";
|
||||
class PermissionManager extends connection.AbstractCommandHandler {
|
||||
readonly handle: ConnectionHandler;
|
||||
|
||||
|
@ -439,44 +487,50 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
|||
|
||||
needed_permission_change_listener: {[permission: string]:(() => any)[]} = {};
|
||||
|
||||
requests_channel_permissions: ChannelPermissionRequest[] = [];
|
||||
requests_client_permissions: TeaPermissionRequest[] = [];
|
||||
requests_client_channel_permissions: TeaPermissionRequest[] = [];
|
||||
requests_playlist_permissions: TeaPermissionRequest[] = [];
|
||||
requests_channel_permissions: permissions.PermissionRequest[] = [];
|
||||
requests_client_permissions: permissions.PermissionRequest[] = [];
|
||||
requests_client_channel_permissions: permissions.PermissionRequest[] = [];
|
||||
requests_playlist_permissions: permissions.PermissionRequest[] = [];
|
||||
requests_playlist_client_permissions: permissions.PermissionRequest[] = [];
|
||||
|
||||
requests_permfind: {
|
||||
timeout_id: number,
|
||||
permission: string,
|
||||
callback: (status: "success" | "error", data: any) => void
|
||||
}[] = [];
|
||||
|
||||
initializedListener: ((initialized: boolean) => void)[] = [];
|
||||
private _cacheNeededPermissions: any;
|
||||
|
||||
/* Static info mapping until TeaSpeak implements a detailed info */
|
||||
//TODO tr
|
||||
static readonly group_mapping: {name: string, deep: number}[] = [
|
||||
{name: "Global", deep: 0},
|
||||
{name: "Information", deep: 1},
|
||||
{name: "Virtual server management", deep: 1},
|
||||
{name: "Administration", deep: 1},
|
||||
{name: "Settings", deep: 1},
|
||||
{name: "Virtual Server", deep: 0},
|
||||
{name: "Information", deep: 1},
|
||||
{name: "Administration", deep: 1},
|
||||
{name: "Settings", deep: 1},
|
||||
{name: "Channel", deep: 0},
|
||||
{name: "Information", deep: 1},
|
||||
{name: "Create", deep: 1},
|
||||
{name: "Modify", deep: 1},
|
||||
{name: "Delete", deep: 1},
|
||||
{name: "Access", deep: 1},
|
||||
{name: "Group", deep: 0},
|
||||
{name: "Information", deep: 1},
|
||||
{name: "Create", deep: 1},
|
||||
{name: "Modify", deep: 1},
|
||||
{name: "Delete", deep: 1},
|
||||
{name: "Client", deep: 0},
|
||||
{name: "Information", deep: 1},
|
||||
{name: "Admin", deep: 1},
|
||||
{name: "Basics", deep: 1},
|
||||
{name: "Modify", deep: 1},
|
||||
{name: tr("Global"), deep: 0},
|
||||
{name: tr("Information"), deep: 1},
|
||||
{name: tr("Virtual server management"), deep: 1},
|
||||
{name: tr("Administration"), deep: 1},
|
||||
{name: tr("Settings"), deep: 1},
|
||||
{name: tr("Virtual Server"), deep: 0},
|
||||
{name: tr("Information"), deep: 1},
|
||||
{name: tr("Administration"), deep: 1},
|
||||
{name: tr("Settings"), deep: 1},
|
||||
{name: tr("Channel"), deep: 0},
|
||||
{name: tr("Information"), deep: 1},
|
||||
{name: tr("Create"), deep: 1},
|
||||
{name: tr("Modify"), deep: 1},
|
||||
{name: tr("Delete"), deep: 1},
|
||||
{name: tr("Access"), deep: 1},
|
||||
{name: tr("Group"), deep: 0},
|
||||
{name: tr("Information"), deep: 1},
|
||||
{name: tr("Create"), deep: 1},
|
||||
{name: tr("Modify"), deep: 1},
|
||||
{name: tr("Delete"), deep: 1},
|
||||
{name: tr("Client"), deep: 0},
|
||||
{name: tr("Information"), deep: 1},
|
||||
{name: tr("Admin"), deep: 1},
|
||||
{name: tr("Basics"), deep: 1},
|
||||
{name: tr("Modify"), deep: 1},
|
||||
//TODO Music bot
|
||||
{name: "File Transfer", deep: 0},
|
||||
{name: tr("File Transfer"), deep: 0},
|
||||
];
|
||||
private _group_mapping;
|
||||
|
||||
|
@ -539,10 +593,10 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
|||
|
||||
this.neededPermissions = undefined;
|
||||
|
||||
this.requests_channel_permissions = undefined;
|
||||
this.requests_client_permissions = undefined;
|
||||
this.requests_client_channel_permissions = undefined;
|
||||
this.requests_playlist_permissions = undefined;
|
||||
/* delete all requests */
|
||||
for(const key of Object.keys(this))
|
||||
if(key.startsWith("requests"))
|
||||
delete this[key];
|
||||
|
||||
this.initializedListener = undefined;
|
||||
this._cacheNeededPermissions = undefined;
|
||||
|
@ -568,6 +622,9 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
|||
case "notifyplaylistpermlist":
|
||||
this.onPlaylistPermList(command.arguments);
|
||||
return true;
|
||||
case "notifyplaylistclientpermlist":
|
||||
this.onPlaylistClientPermList(command.arguments);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -708,172 +765,252 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
requestChannelPermissions(channelId: number) : Promise<PermissionValue[]> {
|
||||
return new Promise<PermissionValue[]>((resolve, reject) => {
|
||||
let request: ChannelPermissionRequest;
|
||||
for(let element of this.requests_channel_permissions)
|
||||
if(element.requested + 1000 < Date.now() && element.channel_id == channelId) {
|
||||
request = element;
|
||||
break;
|
||||
}
|
||||
if(!request) {
|
||||
request = new ChannelPermissionRequest();
|
||||
request.requested = Date.now();
|
||||
request.channel_id = channelId;
|
||||
this.handle.serverConnection.send_command("channelpermlist", {"cid": channelId}).catch(error => {
|
||||
this.requests_channel_permissions.remove(request);
|
||||
|
||||
if(error instanceof CommandResult) {
|
||||
if(error.id == ErrorID.EMPTY_RESULT) {
|
||||
request.callback_success.forEach(e => e([]));
|
||||
return;
|
||||
}
|
||||
}
|
||||
request.callback_error.forEach(e => e(error));
|
||||
}).then(() => {
|
||||
//Error handler if we've not received an notify
|
||||
setTimeout(() => {
|
||||
if(this.requests_channel_permissions.remove(request)) {
|
||||
request.callback_error.forEach(e => e(tr("missing notify")));
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
this.requests_channel_permissions.push(request);
|
||||
}
|
||||
request.callback_error.push(reject);
|
||||
request.callback_success.push(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
/* channel permission request */
|
||||
private onChannelPermList(json) {
|
||||
let channelId: number = parseInt(json[0]["cid"]);
|
||||
|
||||
let permissions = PermissionManager.parse_permission_bulk(json, this.handle.permissions);
|
||||
log.debug(LogCategory.PERMISSIONS, tr("Got channel permissions for channel %o"), channelId);
|
||||
for(let element of this.requests_channel_permissions) {
|
||||
if(element.channel_id == channelId) {
|
||||
for(let l of element.callback_success)
|
||||
l(permissions);
|
||||
this.requests_channel_permissions.remove(element);
|
||||
return;
|
||||
}
|
||||
}
|
||||
log.debug(LogCategory.PERMISSIONS, tr("Missing channel permission handle for requested channel id %o"), channelId);
|
||||
this.fullfill_permission_request("requests_channel_permissions", {
|
||||
channel_id: channelId
|
||||
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
||||
}
|
||||
|
||||
private execute_channel_permission_request(request: permissions.PermissionRequestKeys) {
|
||||
this.handle.serverConnection.send_command("channelpermlist", {"cid": request.channel_id}).catch(error => {
|
||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
||||
this.fullfill_permission_request("requests_channel_permissions", request, "success", []);
|
||||
else
|
||||
this.fullfill_permission_request("requests_channel_permissions", request, "error", error);
|
||||
});
|
||||
}
|
||||
|
||||
requestChannelPermissions(channelId: number) : Promise<PermissionValue[]> {
|
||||
const keys: permissions.PermissionRequestKeys = {
|
||||
channel_id: channelId
|
||||
};
|
||||
return this.execute_permission_request("requests_channel_permissions", keys, this.execute_channel_permission_request.bind(this));
|
||||
}
|
||||
|
||||
/* client permission request */
|
||||
private onClientPermList(json: any[]) {
|
||||
let client = parseInt(json[0]["cldbid"]);
|
||||
this.fullfill_permission_request("requests_client_permissions", {
|
||||
client_id: client
|
||||
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
||||
}
|
||||
|
||||
private execute_client_permission_request(request: permissions.PermissionRequestKeys) {
|
||||
this.handle.serverConnection.send_command("clientpermlist", {cldbid: request.client_id}).catch(error => {
|
||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
||||
this.fullfill_permission_request("requests_client_permissions", request, "success", []);
|
||||
else
|
||||
this.fullfill_permission_request("requests_client_permissions", request, "error", error);
|
||||
});
|
||||
}
|
||||
|
||||
requestClientPermissions(client_id: number) : Promise<PermissionValue[]> {
|
||||
for(let request of this.requests_client_permissions)
|
||||
if(request.client_id == client_id && request.promise.time() + 1000 > Date.now())
|
||||
return request.promise;
|
||||
|
||||
let request: TeaPermissionRequest = {} as any;
|
||||
request.client_id = client_id;
|
||||
request.promise = new LaterPromise<PermissionValue[]>();
|
||||
|
||||
this.handle.serverConnection.send_command("clientpermlist", {cldbid: client_id}).catch(error => {
|
||||
this.requests_client_permissions.remove(request);
|
||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
||||
request.promise.resolved([]);
|
||||
else
|
||||
request.promise.rejected(error);
|
||||
}).then(() => {
|
||||
//Error handler if we've not received an notify
|
||||
setTimeout(() => {
|
||||
if(this.requests_client_permissions.remove(request)) {
|
||||
request.promise.rejected(tr("missing notify"));
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
this.requests_client_permissions.push(request);
|
||||
return request.promise;
|
||||
}
|
||||
|
||||
private onClientPermList(json: any[]) {
|
||||
let client = parseInt(json[0]["cldbid"]);
|
||||
let permissions = PermissionManager.parse_permission_bulk(json, this);
|
||||
for(let req of this.requests_client_permissions.slice(0)) {
|
||||
if(req.client_id == client) {
|
||||
this.requests_client_permissions.remove(req);
|
||||
req.promise.resolved(permissions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
requestClientChannelPermissions(client_id: number, channel_id: number) : Promise<PermissionValue[]> {
|
||||
for(let request of this.requests_client_channel_permissions)
|
||||
if(request.client_id == client_id && request.channel_id == channel_id && request.promise.time() + 1000 > Date.now())
|
||||
return request.promise;
|
||||
|
||||
let request: TeaPermissionRequest = {} as any;
|
||||
request.client_id = client_id;
|
||||
request.channel_id = channel_id;
|
||||
request.promise = new LaterPromise<PermissionValue[]>();
|
||||
|
||||
this.handle.serverConnection.send_command("channelclientpermlist", {cldbid: client_id, cid: channel_id}).catch(error => {
|
||||
this.requests_client_channel_permissions.remove(request);
|
||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
||||
request.promise.resolved([]);
|
||||
else
|
||||
request.promise.rejected(error);
|
||||
}).then(() => {
|
||||
//Error handler if we've not received an notify
|
||||
setTimeout(() => {
|
||||
if(this.requests_client_channel_permissions.remove(request)) {
|
||||
request.promise.rejected(tr("missing notify"));
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
this.requests_client_channel_permissions.push(request);
|
||||
return request.promise;
|
||||
const keys: permissions.PermissionRequestKeys = {
|
||||
client_id: client_id
|
||||
};
|
||||
return this.execute_permission_request("requests_client_permissions", keys, this.execute_client_permission_request.bind(this));
|
||||
}
|
||||
|
||||
/* client channel permission request */
|
||||
private onChannelClientPermList(json: any[]) {
|
||||
let client_id = parseInt(json[0]["cldbid"]);
|
||||
let channel_id = parseInt(json[0]["cid"]);
|
||||
|
||||
let permissions = PermissionManager.parse_permission_bulk(json, this);
|
||||
for(let req of this.requests_client_channel_permissions.slice(0)) {
|
||||
if(req.client_id == client_id && req.channel_id == channel_id) {
|
||||
this.requests_client_channel_permissions.remove(req);
|
||||
req.promise.resolved(permissions);
|
||||
}
|
||||
}
|
||||
this.fullfill_permission_request("requests_client_channel_permissions", {
|
||||
client_id: client_id,
|
||||
channel_id: channel_id
|
||||
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
||||
}
|
||||
|
||||
private execute_client_channel_permission_request(request: permissions.PermissionRequestKeys) {
|
||||
this.handle.serverConnection.send_command("channelclientpermlist", {cldbid: request.client_id, cid: request.channel_id})
|
||||
.catch(error => {
|
||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
||||
this.fullfill_permission_request("requests_client_channel_permissions", request, "success", []);
|
||||
else
|
||||
this.fullfill_permission_request("requests_client_channel_permissions", request, "error", error);
|
||||
});
|
||||
}
|
||||
|
||||
requestClientChannelPermissions(client_id: number, channel_id: number) : Promise<PermissionValue[]> {
|
||||
const keys: permissions.PermissionRequestKeys = {
|
||||
client_id: client_id
|
||||
};
|
||||
return this.execute_permission_request("requests_client_channel_permissions", keys, this.execute_client_channel_permission_request.bind(this));
|
||||
}
|
||||
|
||||
/* playlist permissions */
|
||||
private onPlaylistPermList(json: any[]) {
|
||||
let playlist_id = parseInt(json[0]["playlist_id"]);
|
||||
let permissions = PermissionManager.parse_permission_bulk(json, this);
|
||||
for(let req of this.requests_playlist_permissions.slice(0)) {
|
||||
if(req.playlist_id == playlist_id) {
|
||||
this.requests_playlist_permissions.remove(req);
|
||||
req.promise.resolved(permissions);
|
||||
}
|
||||
}
|
||||
|
||||
this.fullfill_permission_request("requests_playlist_permissions", {
|
||||
playlist_id: playlist_id
|
||||
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
||||
}
|
||||
|
||||
private execute_playlist_permission_request(request: permissions.PermissionRequestKeys) {
|
||||
this.handle.serverConnection.send_command("playlistpermlist", {playlist_id: request.playlist_id})
|
||||
.catch(error => {
|
||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
||||
this.fullfill_permission_request("requests_playlist_permissions", request, "success", []);
|
||||
else
|
||||
this.fullfill_permission_request("requests_playlist_permissions", request, "error", error);
|
||||
});
|
||||
}
|
||||
|
||||
requestPlaylistPermissions(playlist_id: number) : Promise<PermissionValue[]> {
|
||||
for(let request of this.requests_playlist_permissions)
|
||||
if(request.playlist_id == playlist_id && request.promise.time() + 1000 > Date.now())
|
||||
const keys: permissions.PermissionRequestKeys = {
|
||||
playlist_id: playlist_id
|
||||
};
|
||||
return this.execute_permission_request("requests_playlist_permissions", keys, this.execute_playlist_permission_request.bind(this));
|
||||
}
|
||||
|
||||
/* playlist client permissions */
|
||||
private onPlaylistClientPermList(json: any[]) {
|
||||
let playlist_id = parseInt(json[0]["playlist_id"]);
|
||||
let client_id = parseInt(json[0]["cldbid"]);
|
||||
|
||||
this.fullfill_permission_request("requests_playlist_client_permissions", {
|
||||
playlist_id: playlist_id,
|
||||
client_id: client_id
|
||||
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
||||
}
|
||||
|
||||
private execute_playlist_client_permission_request(request: permissions.PermissionRequestKeys) {
|
||||
this.handle.serverConnection.send_command("playlistclientpermlist", {playlist_id: request.playlist_id, cldbid: request.client_id})
|
||||
.catch(error => {
|
||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
||||
this.fullfill_permission_request("requests_playlist_client_permissions", request, "success", []);
|
||||
else
|
||||
this.fullfill_permission_request("requests_playlist_client_permissions", request, "error", error);
|
||||
});
|
||||
}
|
||||
|
||||
requestPlaylistClientPermissions(playlist_id: number, client_database_id: number) : Promise<PermissionValue[]> {
|
||||
const keys: permissions.PermissionRequestKeys = {
|
||||
playlist_id: playlist_id,
|
||||
client_id: client_database_id
|
||||
};
|
||||
return this.execute_permission_request("requests_playlist_client_permissions", keys, this.execute_playlist_client_permission_request.bind(this));
|
||||
}
|
||||
|
||||
private readonly criteria_equal = (a, b) => {
|
||||
for(const criteria of ["client_id", "channel_id", "playlist_id"]) {
|
||||
if((typeof a[criteria] === "undefined") !== (typeof b[criteria] === "undefined")) return false;
|
||||
if(a[criteria] != b[criteria]) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
private execute_permission_request(list: RequestLists,
|
||||
criteria: permissions.PermissionRequestKeys,
|
||||
execute: (criteria: permissions.PermissionRequestKeys) => any) : Promise<PermissionValue[]> {
|
||||
for(const request of this[list])
|
||||
if(this.criteria_equal(request, criteria) && request.promise.time() + 1000 < Date.now())
|
||||
return request.promise;
|
||||
|
||||
let request: TeaPermissionRequest = {} as any;
|
||||
request.playlist_id = playlist_id;
|
||||
request.promise = new LaterPromise<PermissionValue[]>();
|
||||
const result = Object.assign({
|
||||
timeout_id: setTimeout(() => this.fullfill_permission_request(list, criteria, "error", tr("timeout")), 5000),
|
||||
promise: new LaterPromise<PermissionValue[]>()
|
||||
}, criteria);
|
||||
this[list].push(result);
|
||||
execute(criteria);
|
||||
return result.promise;
|
||||
};
|
||||
|
||||
this.handle.serverConnection.send_command("playlistpermlist", {playlist_id: playlist_id}).catch(error => {
|
||||
this.requests_playlist_permissions.remove(request);
|
||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
||||
request.promise.resolved([]);
|
||||
else
|
||||
request.promise.rejected(error);
|
||||
private fullfill_permission_request(list: RequestLists, criteria: permissions.PermissionRequestKeys, status: "success" | "error", result: any) {
|
||||
for(const request of this[list]) {
|
||||
if(this.criteria_equal(request, criteria)) {
|
||||
this[list].remove(request);
|
||||
clearTimeout(request.timeout_id);
|
||||
status === "error" ? request.promise.rejected(result) : request.promise.resolved(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
find_permission(...permissions: string[]) : Promise<permissions.find.Entry[]> {
|
||||
const permission_ids = [];
|
||||
for(const permission of permissions) {
|
||||
const info = this.resolveInfo(permission);
|
||||
if(!info) continue;
|
||||
|
||||
permission_ids.push(info.id);
|
||||
}
|
||||
if(!permission_ids.length) return Promise.resolve([]);
|
||||
|
||||
return new Promise<permissions.find.Entry[]>((resolve, reject) => {
|
||||
const single_handler = {
|
||||
command: "notifypermfind",
|
||||
function: command => {
|
||||
const result: permissions.find.Entry[] = [];
|
||||
for(const entry of command.arguments) {
|
||||
const perm_id = parseInt(entry["p"]);
|
||||
if(permission_ids.indexOf(perm_id) === -1) return; /* not our permfind result */
|
||||
|
||||
const value = parseInt(entry["v"]);
|
||||
const type = parseInt(entry["t"]);
|
||||
|
||||
let data;
|
||||
switch (type) {
|
||||
case 0:
|
||||
data = {
|
||||
type: "server_group",
|
||||
group_id: parseInt(entry["id1"]),
|
||||
} as permissions.find.ServerGroup;
|
||||
break;
|
||||
case 1:
|
||||
data = {
|
||||
type: "client",
|
||||
client_id: parseInt(entry["id2"]),
|
||||
} as permissions.find.Client;
|
||||
break;
|
||||
case 2:
|
||||
data = {
|
||||
type: "channel",
|
||||
channel_id: parseInt(entry["id2"]),
|
||||
} as permissions.find.Channel;
|
||||
break;
|
||||
case 3:
|
||||
data = {
|
||||
type: "channel_group",
|
||||
group_id: parseInt(entry["id1"]),
|
||||
} as permissions.find.ChannelGroup;
|
||||
break;
|
||||
case 4:
|
||||
data = {
|
||||
type: "client_channel",
|
||||
client_id: parseInt(entry["id1"]),
|
||||
channel_id: parseInt(entry["id1"]),
|
||||
} as permissions.find.ClientChannel;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
data.id = perm_id;
|
||||
data.value = value;
|
||||
result.push(data);
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
this.handler_boss.register_single_handler(single_handler);
|
||||
|
||||
this.connection.send_command("permfind", permission_ids.map(e => { return {permid: e }})).catch(error => {
|
||||
this.handler_boss.remove_single_handler(single_handler);
|
||||
|
||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) {
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
this.requests_playlist_permissions.push(request);
|
||||
return request.promise;
|
||||
}
|
||||
|
||||
neededPermission(key: number | string | PermissionType | PermissionInfo) : NeededPermissionValue {
|
||||
|
|
|
@ -54,6 +54,7 @@ class ClientProperties {
|
|||
client_total_bytes_downloaded: number = 0;
|
||||
|
||||
client_talk_power: number = 0;
|
||||
client_is_priority_speaker: boolean = false;
|
||||
}
|
||||
|
||||
class ClientConnectionInfo {
|
||||
|
@ -1237,6 +1238,10 @@ class MusicClientProperties extends ClientProperties {
|
|||
|
||||
client_playlist_id: number = 0;
|
||||
client_disabled: boolean = false;
|
||||
|
||||
client_flag_notify_song_change: boolean = false;
|
||||
client_bot_type: number = 0;
|
||||
client_uptime_mode: number = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -10,7 +10,12 @@ namespace tooltip {
|
|||
hide();
|
||||
update();
|
||||
}
|
||||
export function initialize(entry: JQuery) : Handle {
|
||||
export function initialize(entry: JQuery, callbacks?: {
|
||||
on_show?(tag: JQuery),
|
||||
on_hide?(tag: JQuery)
|
||||
}) : Handle {
|
||||
if(!callbacks) callbacks = {};
|
||||
|
||||
let _show;
|
||||
let _hide;
|
||||
let _shown;
|
||||
|
@ -34,12 +39,14 @@ namespace tooltip {
|
|||
_global_tooltip[0].classList.add("shown");
|
||||
|
||||
_global_tooltip[0].innerHTML = node_content[0].innerHTML;
|
||||
callbacks.on_show && callbacks.on_show(_global_tooltip);
|
||||
_flag_shown = _flag_shown || !!event; /* if event is undefined then it has been triggered by hand */
|
||||
};
|
||||
|
||||
const mouseexit = () => {
|
||||
if(_global_tooltip) {
|
||||
if(!_force_show) {
|
||||
callbacks.on_hide && callbacks.on_hide(_global_tooltip);
|
||||
_global_tooltip[0].classList.remove("shown");
|
||||
}
|
||||
_flag_shown = false;
|
||||
|
|
|
@ -431,6 +431,7 @@ namespace top_menu {
|
|||
{
|
||||
const menu = driver.append_item(tr("Tools"));
|
||||
|
||||
/*
|
||||
item = menu.append_item(tr("Manage Playlists"));
|
||||
item.icon('client-music');
|
||||
item.click(() => {
|
||||
|
@ -442,6 +443,7 @@ namespace top_menu {
|
|||
}
|
||||
});
|
||||
_state_updater["tools.pl"] = { item: item, conditions: [condition_connected]};
|
||||
*/
|
||||
|
||||
item = menu.append_item(tr("Ban List"));
|
||||
item.icon('client-ban_list');
|
||||
|
|
|
@ -65,7 +65,12 @@ namespace chat {
|
|||
this.handle.show_private_conversations();
|
||||
})[0];
|
||||
|
||||
this._button_bot_manage = this._html_tag.find(".bot-manage");
|
||||
this._button_bot_manage = this._html_tag.find(".bot-manage").on('click', event => {
|
||||
const bot = this.handle.music_info().current_bot();
|
||||
if(!bot) return;
|
||||
|
||||
Modals.openMusicManage(this.handle.handle, bot);
|
||||
});
|
||||
this._button_song_add = this._html_tag.find(".bot-add-song").on('click', event => {
|
||||
this.handle.music_info().events.fire("action_song_add");
|
||||
});
|
||||
|
|
|
@ -656,6 +656,10 @@ namespace chat {
|
|||
});
|
||||
}
|
||||
|
||||
current_bot() : MusicClientEntry | undefined {
|
||||
return this._current_bot;
|
||||
}
|
||||
|
||||
private sort_songs(data: PlaylistSong[]) {
|
||||
const result = [];
|
||||
|
||||
|
|
|
@ -13,7 +13,8 @@ namespace htmltags {
|
|||
client_id: number,
|
||||
client_unique_id: string,
|
||||
client_name: string,
|
||||
add_braces?: boolean
|
||||
add_braces?: boolean,
|
||||
client_database_id?: number; /* not yet used */
|
||||
}
|
||||
|
||||
export interface ChannelProperties {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,21 +0,0 @@
|
|||
/// <reference path="../../ui/elements/modal.ts" />
|
||||
/// <reference path="../../ConnectionHandler.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export function openPlaylistManage(client: ConnectionHandler, playlist: Playlist) {
|
||||
let modal = createModal({
|
||||
header: tr(tr("Playlist Manage")),
|
||||
body: () => $("#tmpl_playlist_manage").renderTag().children(),
|
||||
footer: null,
|
||||
|
||||
width: "",
|
||||
closeable: false
|
||||
});
|
||||
|
||||
//TODO!
|
||||
|
||||
modal.open();
|
||||
|
||||
}
|
||||
}
|
|
@ -196,6 +196,7 @@ const loader_javascript = {
|
|||
"js/ui/modal/ModalInvite.js",
|
||||
"js/ui/modal/ModalIdentity.js",
|
||||
"js/ui/modal/ModalBanList.js",
|
||||
"js/ui/modal/ModalMusicManage.js",
|
||||
"js/ui/modal/ModalYesNo.js",
|
||||
"js/ui/modal/ModalPoke.js",
|
||||
"js/ui/modal/ModalKeySelect.js",
|
||||
|
@ -362,6 +363,7 @@ const loader_style = {
|
|||
"css/static/modal-channelinfo.css",
|
||||
"css/static/modal-clientinfo.css",
|
||||
"css/static/modal-serverinfo.css",
|
||||
"css/static/modal-musicmanage.css",
|
||||
"css/static/modal-serverinfobandwidth.css",
|
||||
"css/static/modal-identity.css",
|
||||
"css/static/modal-settings.css",
|
||||
|
@ -391,34 +393,6 @@ const loader_style = {
|
|||
}
|
||||
};
|
||||
|
||||
async function load_templates() {
|
||||
try {
|
||||
const response = await $.ajax("templates.html" + loader.get_cache_version());
|
||||
|
||||
let node = document.createElement("html");
|
||||
node.innerHTML = response;
|
||||
let tags: HTMLCollection;
|
||||
if(node.getElementsByTagName("body").length > 0)
|
||||
tags = node.getElementsByTagName("body")[0].children;
|
||||
else
|
||||
tags = node.children;
|
||||
|
||||
let root = document.getElementById("templates");
|
||||
if(!root) {
|
||||
loader.critical_error("Failed to find template tag!");
|
||||
return;
|
||||
}
|
||||
while(tags.length > 0){
|
||||
let tag = tags.item(0);
|
||||
root.appendChild(tag);
|
||||
|
||||
}
|
||||
} catch(error) {
|
||||
loader.critical_error("Failed to find template tag!");
|
||||
throw "template error";
|
||||
}
|
||||
}
|
||||
|
||||
/* register tasks */
|
||||
loader.register_task(loader.Stage.INITIALIZING, {
|
||||
name: "safari fix",
|
||||
|
@ -502,7 +476,12 @@ loader.register_task(loader.Stage.STYLE, {
|
|||
|
||||
loader.register_task(loader.Stage.TEMPLATES, {
|
||||
name: "templates",
|
||||
function: load_templates,
|
||||
function: async () => {
|
||||
await loader.load_templates([
|
||||
"templates.html",
|
||||
"templates/music/manage.html"
|
||||
]);
|
||||
},
|
||||
priority: 10
|
||||
});
|
||||
|
||||
|
|
|
@ -473,6 +473,70 @@ namespace loader {
|
|||
}
|
||||
}
|
||||
|
||||
export async function load_template(path: SourcePath) : Promise<void> {
|
||||
try {
|
||||
const response = await $.ajax(path + (cache_tag || ""));
|
||||
|
||||
let node = document.createElement("html");
|
||||
node.innerHTML = response;
|
||||
let tags: HTMLCollection;
|
||||
if(node.getElementsByTagName("body").length > 0)
|
||||
tags = node.getElementsByTagName("body")[0].children;
|
||||
else
|
||||
tags = node.children;
|
||||
|
||||
let root = document.getElementById("templates");
|
||||
if(!root) {
|
||||
loader.critical_error("Failed to find template tag!");
|
||||
throw "Failed to find template tag";
|
||||
}
|
||||
while(tags.length > 0){
|
||||
let tag = tags.item(0);
|
||||
root.appendChild(tag);
|
||||
|
||||
}
|
||||
} catch(error) {
|
||||
let msg;
|
||||
if('responseText' in error)
|
||||
msg = error.responseText;
|
||||
else if(error instanceof Error)
|
||||
msg = error.message;
|
||||
|
||||
loader.critical_error("failed to load template " + script_name(path), msg);
|
||||
throw "template error";
|
||||
}
|
||||
}
|
||||
|
||||
export async function load_templates(paths: SourcePath[]) : Promise<void> {
|
||||
const promises: Promise<void>[] = [];
|
||||
const errors: {
|
||||
template: SourcePath,
|
||||
error: any
|
||||
}[] = [];
|
||||
|
||||
for(const template of paths)
|
||||
promises.push(load_template(template).catch(error => {
|
||||
errors.push({
|
||||
template: template,
|
||||
error: error
|
||||
});
|
||||
return Promise.resolve();
|
||||
}));
|
||||
|
||||
await Promise.all([...promises]);
|
||||
|
||||
if(errors.length > 0) {
|
||||
if (loader.config.error) {
|
||||
console.error("Failed to load the following templates:");
|
||||
for (const sheet of errors)
|
||||
console.log(" - %o: %o", sheet.template, sheet.error);
|
||||
}
|
||||
|
||||
loader.critical_error("Failed to load template " + script_name(errors[0].template) + " <br>" + "View the browser console for more information!");
|
||||
throw "failed to load template " + script_name(errors[0].template);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export type ErrorHandler = (message: string, detail: string) => void;
|
||||
|
||||
|
@ -578,7 +642,7 @@ namespace loader {
|
|||
const display_detect = /./;
|
||||
display_detect.toString = function() { print_security(); return ""; };
|
||||
|
||||
clog("%cLovely to see you using and debugging the TeaSpeak Web client.", css);
|
||||
clog("%cLovely to see you using and debugging the TeaSpeak-Web client.", css);
|
||||
clog("%cIf you have some good ideas or already done some incredible changes,", css);
|
||||
clog("%cyou'll be may interested to share them here: %chttps://github.com/TeaSpeak/TeaWeb", css, css_2);
|
||||
clog("%c ", display_detect);
|
||||
|
|
Loading…
Reference in New Issue