pls pull #1
27 changed files with 791 additions and 627 deletions
17
.gitignore
vendored
17
.gitignore
vendored
|
@ -1 +1,16 @@
|
||||||
node_modules/
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.bat
|
||||||
|
|
||||||
|
# Test binary, build with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Custom Blacklist
|
||||||
|
config.json
|
71
README.md
71
README.md
|
@ -1,2 +1,71 @@
|
||||||
# tsviewer
|
# go-tsviewer **[WIP]**
|
||||||
|
## **WARNING** This API is not usable ATM
|
||||||
|
|
||||||
|
A REST API made for TS3 Viewer.
|
||||||
|
|
||||||
|
# Features
|
||||||
|
## Config
|
||||||
|
The config file is generated automatically on first startup.
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ip": "127.0.0.1", // Server IP
|
||||||
|
"port": 10011, // Dataquery Port
|
||||||
|
"user": {
|
||||||
|
"name": "serveradmin", // Username
|
||||||
|
"password": "" // Password
|
||||||
|
},
|
||||||
|
"server": {
|
||||||
|
"port": 9987 // Port of the target server
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
## URL-Parameter
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---------- | -------- | ------------------------ |
|
||||||
|
| `pretty` | `bool` | pretty-prints JSON |
|
||||||
|
| `envelope` | `bool` | wraps JSON in data array |
|
||||||
|
## Channels
|
||||||
|
- **`GET`** `/v1/channels/:id`
|
||||||
|
- **`GET`** `/v1/channels`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"databaseId": 1,
|
||||||
|
"channelId": 1,
|
||||||
|
"nickname": "serveradmin from 127.0.0.1:58359",
|
||||||
|
"type": 1,
|
||||||
|
"away": false,
|
||||||
|
"awayMessage": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
## Clients
|
||||||
|
- **`GET`** `/v1/clients/:id`
|
||||||
|
- **`GET`** `/v1/clients/`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"subchannels": [
|
||||||
|
... (contains all subchannel)
|
||||||
|
],
|
||||||
|
"name": "main1",
|
||||||
|
"totalClients": 0,
|
||||||
|
"neededSubscribePower": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
## Server
|
||||||
|
- **`GET`** `/v1/server/info`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "TeamSpeak ]I[ Server",
|
||||||
|
"status": "online",
|
||||||
|
"version": "3.5.1 [Build: 1545076855]",
|
||||||
|
"welcomeMessage": "Welcome to TeamSpeak, check [URL]www.teamspeak.com[/URL] for latest information",
|
||||||
|
"maxClients": 32,
|
||||||
|
"clientsOnline": 2,
|
||||||
|
"reservedSlots": 0,
|
||||||
|
"uptime": 5976,
|
||||||
|
"totalPing": 0,
|
||||||
|
"minAndroidVersion": 1502275280,
|
||||||
|
"minClientVersion": 1513163251,
|
||||||
|
"miniOSVersion": 1502275280
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
43
app.js
43
app.js
|
@ -1,43 +0,0 @@
|
||||||
var express = require('express');
|
|
||||||
var exphbs = require('express-handlebars');
|
|
||||||
|
|
||||||
var app = express();
|
|
||||||
|
|
||||||
app.engine('handlebars', exphbs({defaultLayout: 'main'}));
|
|
||||||
app.set('view engine', 'handlebars');
|
|
||||||
|
|
||||||
app.get('/', function (req, res) {
|
|
||||||
res.render('home', {foo: 'bar'});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.listen(3000);
|
|
||||||
|
|
||||||
const TeamSpeak3 = require("ts3-nodejs-library")
|
|
||||||
|
|
||||||
var ts3conn = new TeamSpeak3({
|
|
||||||
host: "localhost",
|
|
||||||
queryport: 10011,
|
|
||||||
serverport: 9987,
|
|
||||||
username: "serveradmin",
|
|
||||||
password: "R0cHL6tb",
|
|
||||||
nickname: "NodeJS Query Framework",
|
|
||||||
})
|
|
||||||
|
|
||||||
ts3conn.on("ready", () => {
|
|
||||||
ts3conn.clientList({client_type:0}).then(clients => {
|
|
||||||
clients.forEach(client => {
|
|
||||||
console.log("Online: ", client.getCache().client_nickname)
|
|
||||||
})
|
|
||||||
}).catch(e => console.log("CATCHED", e.message))
|
|
||||||
|
|
||||||
ts3conn.channelList({}).then(channel => {
|
|
||||||
channel.forEach(channel => {
|
|
||||||
console.log("Channel: ", channel.getCache().channel_name)
|
|
||||||
})
|
|
||||||
}).catch(e => console.log("CATCHED", e.message))
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ts3conn.on("error", e => console.log("Error", e.message))
|
|
||||||
ts3conn.on("close", e => console.log("Connection has been closed!", e))
|
|
69
config/config.go
Normal file
69
config/config.go
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
IP string `json:"ip"`
|
||||||
|
Port uint16 `json:"port"`
|
||||||
|
User User `json:"user"`
|
||||||
|
Server Server `json:"server"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
Port uint16 `json:"port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const FileName = "config.json"
|
||||||
|
|
||||||
|
func New() (*Config, error) {
|
||||||
|
config := defaults()
|
||||||
|
|
||||||
|
configFile, err := os.Open(FileName)
|
||||||
|
if err != nil {
|
||||||
|
if err := config.createFile(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println(config)
|
||||||
|
return &config, nil
|
||||||
|
}
|
||||||
|
defer configFile.Close()
|
||||||
|
|
||||||
|
jsonParser := json.NewDecoder(configFile)
|
||||||
|
jsonParser.Decode(&config)
|
||||||
|
|
||||||
|
return &config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config Config) createFile() error {
|
||||||
|
configJSON, err := json.MarshalIndent(config, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ioutil.WriteFile(FileName, configJSON, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaults() Config {
|
||||||
|
return Config{
|
||||||
|
IP: "127.0.0.1",
|
||||||
|
Port: 10011,
|
||||||
|
User: User{
|
||||||
|
Name: "serveradmin",
|
||||||
|
Password: "",
|
||||||
|
},
|
||||||
|
Server: Server{
|
||||||
|
Port: 9987,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
14
features/channel/channel.go
Normal file
14
features/channel/channel.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package channel
|
||||||
|
|
||||||
|
type Service interface {
|
||||||
|
Channel(id int) (*Channel, error)
|
||||||
|
Channels() ([]*Channel, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Channel struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Subchannels []Channel `json:"subchannels,omitempty"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
TotalClients int `json:"totalClients"`
|
||||||
|
NeededSubscribePower int `json:"neededSubscribePower"`
|
||||||
|
}
|
40
features/channel/handler.go
Normal file
40
features/channel/handler.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package channel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.cliffbreak.de/haveachin/go-tsviewer/response"
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ChannelAPIHandler(s Service) http.HandlerFunc {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
response.Handler(w, response.HandlerFunc(func() (int, error) {
|
||||||
|
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusBadRequest, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := s.Channel(int(id))
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusNotFound, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.New(c, r).Send(w, http.StatusOK)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChannelsAPIHandler(s Service) http.HandlerFunc {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
response.Handler(w, response.HandlerFunc(func() (int, error) {
|
||||||
|
cc, err := s.Channels()
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusBadRequest, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.New(cc, r).Send(w, http.StatusOK)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
14
features/channel/routes.go
Normal file
14
features/channel/routes.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package channel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
)
|
||||||
|
|
||||||
|
func APIRoutes(s Service) *chi.Mux {
|
||||||
|
router := chi.NewRouter()
|
||||||
|
|
||||||
|
router.Get("/{id}", ChannelAPIHandler(s))
|
||||||
|
router.Get("/", ChannelsAPIHandler(s))
|
||||||
|
|
||||||
|
return router
|
||||||
|
}
|
15
features/client/client.go
Normal file
15
features/client/client.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
type Service interface {
|
||||||
|
Client(id int) (*Client, error)
|
||||||
|
Clients() ([]*Client, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
DatabaseID int `json:"databaseId"`
|
||||||
|
ChannelID int `json:"channelId"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
Away bool `json:"away"`
|
||||||
|
AwayMessage string `json:"awayMessage"`
|
||||||
|
}
|
40
features/client/handler.go
Normal file
40
features/client/handler.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.cliffbreak.de/haveachin/go-tsviewer/response"
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ClientAPIHandler(s Service) http.HandlerFunc {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
response.Handler(w, response.HandlerFunc(func() (int, error) {
|
||||||
|
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusBadRequest, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := s.Client(int(id))
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusNotFound, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.New(c, r).Send(w, http.StatusOK)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClientsAPIHandler(s Service) http.HandlerFunc {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
response.Handler(w, response.HandlerFunc(func() (int, error) {
|
||||||
|
cc, err := s.Clients()
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusBadRequest, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.New(cc, r).Send(w, http.StatusOK)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
14
features/client/routes.go
Normal file
14
features/client/routes.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
)
|
||||||
|
|
||||||
|
func APIRoutes(s Service) *chi.Mux {
|
||||||
|
router := chi.NewRouter()
|
||||||
|
|
||||||
|
router.Get("/{id}", ClientAPIHandler(s))
|
||||||
|
router.Get("/", ClientsAPIHandler(s))
|
||||||
|
|
||||||
|
return router
|
||||||
|
}
|
20
features/server/handler.go
Normal file
20
features/server/handler.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.cliffbreak.de/haveachin/go-tsviewer/response"
|
||||||
|
)
|
||||||
|
|
||||||
|
func InfoAPIHandler(s Service) http.HandlerFunc {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
response.Handler(w, response.HandlerFunc(func() (int, error) {
|
||||||
|
s, err := s.Info()
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusNotFound, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.New(s, r).Send(w, http.StatusOK)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
13
features/server/routes.go
Normal file
13
features/server/routes.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
)
|
||||||
|
|
||||||
|
func APIRoutes(s Service) *chi.Mux {
|
||||||
|
router := chi.NewRouter()
|
||||||
|
|
||||||
|
router.Get("/info", InfoAPIHandler(s))
|
||||||
|
|
||||||
|
return router
|
||||||
|
}
|
22
features/server/server.go
Normal file
22
features/server/server.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Service interface {
|
||||||
|
Info() (*Server, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
WelcomeMessage string `json:"welcomeMessage"`
|
||||||
|
MaxClients int `json:"maxClients"`
|
||||||
|
ClientsOnline int `json:"clientsOnline"`
|
||||||
|
ReservedSlots int `json:"reservedSlots"`
|
||||||
|
Uptime time.Duration `json:"uptime"`
|
||||||
|
TotalPing float32 `json:"totalPing"`
|
||||||
|
MinAndroidVersion int `json:"minAndroidVersion"`
|
||||||
|
MinClientVersion int `json:"minClientVersion"`
|
||||||
|
MiniOSVersion int `json:"miniOSVersion"`
|
||||||
|
}
|
51
main.go
Normal file
51
main.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.cliffbreak.de/haveachin/go-tsviewer/config"
|
||||||
|
"git.cliffbreak.de/haveachin/go-tsviewer/features/channel"
|
||||||
|
"git.cliffbreak.de/haveachin/go-tsviewer/features/client"
|
||||||
|
"git.cliffbreak.de/haveachin/go-tsviewer/features/server"
|
||||||
|
"git.cliffbreak.de/haveachin/go-tsviewer/service"
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
"github.com/go-chi/chi/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Routes(s service.Service) *chi.Mux {
|
||||||
|
router := chi.NewRouter()
|
||||||
|
router.Use(
|
||||||
|
middleware.Logger,
|
||||||
|
middleware.Timeout(5*time.Second),
|
||||||
|
middleware.DefaultCompress,
|
||||||
|
middleware.RedirectSlashes,
|
||||||
|
middleware.Recoverer,
|
||||||
|
)
|
||||||
|
|
||||||
|
router.Route("/v1", func(r chi.Router) {
|
||||||
|
r.Mount("/channels", channel.APIRoutes(s))
|
||||||
|
r.Mount("/clients", client.APIRoutes(s))
|
||||||
|
r.Mount("/server", server.APIRoutes(s))
|
||||||
|
})
|
||||||
|
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
config, err := config.New()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
service, err := service.New(*config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer service.TSClient.Close()
|
||||||
|
|
||||||
|
router := Routes(*service)
|
||||||
|
|
||||||
|
log.Fatal("Handler: ", http.ListenAndServe(":8080", router))
|
||||||
|
}
|
552
package-lock.json
generated
552
package-lock.json
generated
|
@ -1,552 +0,0 @@
|
||||||
{
|
|
||||||
"name": "TSViewer",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"lockfileVersion": 1,
|
|
||||||
"requires": true,
|
|
||||||
"dependencies": {
|
|
||||||
"accepts": {
|
|
||||||
"version": "1.3.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
|
|
||||||
"integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
|
|
||||||
"requires": {
|
|
||||||
"mime-types": "2.1.21",
|
|
||||||
"negotiator": "0.6.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"array-flatten": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
|
||||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
|
||||||
},
|
|
||||||
"asap": {
|
|
||||||
"version": "2.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
|
||||||
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
|
|
||||||
},
|
|
||||||
"async": {
|
|
||||||
"version": "2.6.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz",
|
|
||||||
"integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==",
|
|
||||||
"requires": {
|
|
||||||
"lodash": "4.17.11"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"balanced-match": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
|
||||||
},
|
|
||||||
"body-parser": {
|
|
||||||
"version": "1.18.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
|
|
||||||
"integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
|
|
||||||
"requires": {
|
|
||||||
"bytes": "3.0.0",
|
|
||||||
"content-type": "1.0.4",
|
|
||||||
"debug": "2.6.9",
|
|
||||||
"depd": "1.1.2",
|
|
||||||
"http-errors": "1.6.3",
|
|
||||||
"iconv-lite": "0.4.23",
|
|
||||||
"on-finished": "2.3.0",
|
|
||||||
"qs": "6.5.2",
|
|
||||||
"raw-body": "2.3.3",
|
|
||||||
"type-is": "1.6.16"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"brace-expansion": {
|
|
||||||
"version": "1.1.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
|
||||||
"requires": {
|
|
||||||
"balanced-match": "1.0.0",
|
|
||||||
"concat-map": "0.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"bytes": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
|
||||||
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
|
|
||||||
},
|
|
||||||
"commander": {
|
|
||||||
"version": "2.17.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
|
|
||||||
"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"concat-map": {
|
|
||||||
"version": "0.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
|
||||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
|
||||||
},
|
|
||||||
"content-disposition": {
|
|
||||||
"version": "0.5.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
|
|
||||||
"integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
|
|
||||||
},
|
|
||||||
"content-type": {
|
|
||||||
"version": "1.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
|
|
||||||
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
|
|
||||||
},
|
|
||||||
"cookie": {
|
|
||||||
"version": "0.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
|
|
||||||
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
|
|
||||||
},
|
|
||||||
"cookie-signature": {
|
|
||||||
"version": "1.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
|
||||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
|
||||||
},
|
|
||||||
"debug": {
|
|
||||||
"version": "2.6.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
|
||||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
|
||||||
"requires": {
|
|
||||||
"ms": "2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"define-properties": {
|
|
||||||
"version": "1.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
|
|
||||||
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
|
|
||||||
"requires": {
|
|
||||||
"object-keys": "1.0.12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"depd": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
|
||||||
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
|
|
||||||
},
|
|
||||||
"destroy": {
|
|
||||||
"version": "1.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
|
||||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
|
||||||
},
|
|
||||||
"ee-first": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
|
||||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
|
||||||
},
|
|
||||||
"encodeurl": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
|
||||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
|
||||||
},
|
|
||||||
"escape-html": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
|
||||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
|
||||||
},
|
|
||||||
"etag": {
|
|
||||||
"version": "1.8.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
|
||||||
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
|
|
||||||
},
|
|
||||||
"express": {
|
|
||||||
"version": "4.16.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
|
|
||||||
"integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==",
|
|
||||||
"requires": {
|
|
||||||
"accepts": "1.3.5",
|
|
||||||
"array-flatten": "1.1.1",
|
|
||||||
"body-parser": "1.18.3",
|
|
||||||
"content-disposition": "0.5.2",
|
|
||||||
"content-type": "1.0.4",
|
|
||||||
"cookie": "0.3.1",
|
|
||||||
"cookie-signature": "1.0.6",
|
|
||||||
"debug": "2.6.9",
|
|
||||||
"depd": "1.1.2",
|
|
||||||
"encodeurl": "1.0.2",
|
|
||||||
"escape-html": "1.0.3",
|
|
||||||
"etag": "1.8.1",
|
|
||||||
"finalhandler": "1.1.1",
|
|
||||||
"fresh": "0.5.2",
|
|
||||||
"merge-descriptors": "1.0.1",
|
|
||||||
"methods": "1.1.2",
|
|
||||||
"on-finished": "2.3.0",
|
|
||||||
"parseurl": "1.3.2",
|
|
||||||
"path-to-regexp": "0.1.7",
|
|
||||||
"proxy-addr": "2.0.4",
|
|
||||||
"qs": "6.5.2",
|
|
||||||
"range-parser": "1.2.0",
|
|
||||||
"safe-buffer": "5.1.2",
|
|
||||||
"send": "0.16.2",
|
|
||||||
"serve-static": "1.13.2",
|
|
||||||
"setprototypeof": "1.1.0",
|
|
||||||
"statuses": "1.4.0",
|
|
||||||
"type-is": "1.6.16",
|
|
||||||
"utils-merge": "1.0.1",
|
|
||||||
"vary": "1.1.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"express-handlebars": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-3.0.0.tgz",
|
|
||||||
"integrity": "sha1-gKBwu4GbCeSvLKbQeA91zgXnXC8=",
|
|
||||||
"requires": {
|
|
||||||
"glob": "6.0.4",
|
|
||||||
"graceful-fs": "4.1.15",
|
|
||||||
"handlebars": "4.0.12",
|
|
||||||
"object.assign": "4.1.0",
|
|
||||||
"promise": "7.3.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"finalhandler": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
|
|
||||||
"requires": {
|
|
||||||
"debug": "2.6.9",
|
|
||||||
"encodeurl": "1.0.2",
|
|
||||||
"escape-html": "1.0.3",
|
|
||||||
"on-finished": "2.3.0",
|
|
||||||
"parseurl": "1.3.2",
|
|
||||||
"statuses": "1.4.0",
|
|
||||||
"unpipe": "1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"forwarded": {
|
|
||||||
"version": "0.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
|
|
||||||
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
|
|
||||||
},
|
|
||||||
"fresh": {
|
|
||||||
"version": "0.5.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
|
||||||
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
|
|
||||||
},
|
|
||||||
"function-bind": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
|
||||||
},
|
|
||||||
"glob": {
|
|
||||||
"version": "6.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
|
|
||||||
"integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
|
|
||||||
"requires": {
|
|
||||||
"inflight": "1.0.6",
|
|
||||||
"inherits": "2.0.3",
|
|
||||||
"minimatch": "3.0.4",
|
|
||||||
"once": "1.4.0",
|
|
||||||
"path-is-absolute": "1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"graceful-fs": {
|
|
||||||
"version": "4.1.15",
|
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
|
|
||||||
"integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA=="
|
|
||||||
},
|
|
||||||
"handlebars": {
|
|
||||||
"version": "4.0.12",
|
|
||||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.12.tgz",
|
|
||||||
"integrity": "sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==",
|
|
||||||
"requires": {
|
|
||||||
"async": "2.6.1",
|
|
||||||
"optimist": "0.6.1",
|
|
||||||
"source-map": "0.6.1",
|
|
||||||
"uglify-js": "3.4.9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"has-symbols": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q="
|
|
||||||
},
|
|
||||||
"http-errors": {
|
|
||||||
"version": "1.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
|
|
||||||
"integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
|
|
||||||
"requires": {
|
|
||||||
"depd": "1.1.2",
|
|
||||||
"inherits": "2.0.3",
|
|
||||||
"setprototypeof": "1.1.0",
|
|
||||||
"statuses": "1.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"iconv-lite": {
|
|
||||||
"version": "0.4.23",
|
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
|
|
||||||
"integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
|
|
||||||
"requires": {
|
|
||||||
"safer-buffer": "2.1.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"inflight": {
|
|
||||||
"version": "1.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
|
||||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
|
||||||
"requires": {
|
|
||||||
"once": "1.4.0",
|
|
||||||
"wrappy": "1.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"inherits": {
|
|
||||||
"version": "2.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
|
||||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
|
||||||
},
|
|
||||||
"ipaddr.js": {
|
|
||||||
"version": "1.8.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz",
|
|
||||||
"integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4="
|
|
||||||
},
|
|
||||||
"lodash": {
|
|
||||||
"version": "4.17.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
|
||||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
|
|
||||||
},
|
|
||||||
"media-typer": {
|
|
||||||
"version": "0.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
|
||||||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
|
||||||
},
|
|
||||||
"merge-descriptors": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
|
||||||
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
|
|
||||||
},
|
|
||||||
"methods": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
|
||||||
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
|
|
||||||
},
|
|
||||||
"mime": {
|
|
||||||
"version": "1.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
|
|
||||||
"integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
|
|
||||||
},
|
|
||||||
"mime-db": {
|
|
||||||
"version": "1.37.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz",
|
|
||||||
"integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg=="
|
|
||||||
},
|
|
||||||
"mime-types": {
|
|
||||||
"version": "2.1.21",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz",
|
|
||||||
"integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==",
|
|
||||||
"requires": {
|
|
||||||
"mime-db": "1.37.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"minimatch": {
|
|
||||||
"version": "3.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
|
||||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
|
||||||
"requires": {
|
|
||||||
"brace-expansion": "1.1.11"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"minimist": {
|
|
||||||
"version": "0.0.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
|
|
||||||
"integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="
|
|
||||||
},
|
|
||||||
"ms": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
|
||||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
|
||||||
},
|
|
||||||
"negotiator": {
|
|
||||||
"version": "0.6.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
|
|
||||||
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
|
|
||||||
},
|
|
||||||
"object-keys": {
|
|
||||||
"version": "1.0.12",
|
|
||||||
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz",
|
|
||||||
"integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag=="
|
|
||||||
},
|
|
||||||
"object.assign": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
|
|
||||||
"requires": {
|
|
||||||
"define-properties": "1.1.3",
|
|
||||||
"function-bind": "1.1.1",
|
|
||||||
"has-symbols": "1.0.0",
|
|
||||||
"object-keys": "1.0.12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"on-finished": {
|
|
||||||
"version": "2.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
|
||||||
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
|
|
||||||
"requires": {
|
|
||||||
"ee-first": "1.1.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"once": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
|
||||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
|
||||||
"requires": {
|
|
||||||
"wrappy": "1.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"optimist": {
|
|
||||||
"version": "0.6.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
|
|
||||||
"integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
|
|
||||||
"requires": {
|
|
||||||
"minimist": "0.0.10",
|
|
||||||
"wordwrap": "0.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"parseurl": {
|
|
||||||
"version": "1.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
|
|
||||||
"integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M="
|
|
||||||
},
|
|
||||||
"path-is-absolute": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
|
||||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
|
||||||
},
|
|
||||||
"path-to-regexp": {
|
|
||||||
"version": "0.1.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
|
||||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
|
||||||
},
|
|
||||||
"promise": {
|
|
||||||
"version": "7.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
|
||||||
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
|
||||||
"requires": {
|
|
||||||
"asap": "2.0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"proxy-addr": {
|
|
||||||
"version": "2.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz",
|
|
||||||
"integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==",
|
|
||||||
"requires": {
|
|
||||||
"forwarded": "0.1.2",
|
|
||||||
"ipaddr.js": "1.8.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"qs": {
|
|
||||||
"version": "6.5.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
|
||||||
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
|
|
||||||
},
|
|
||||||
"range-parser": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
|
|
||||||
"integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4="
|
|
||||||
},
|
|
||||||
"raw-body": {
|
|
||||||
"version": "2.3.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
|
|
||||||
"integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
|
|
||||||
"requires": {
|
|
||||||
"bytes": "3.0.0",
|
|
||||||
"http-errors": "1.6.3",
|
|
||||||
"iconv-lite": "0.4.23",
|
|
||||||
"unpipe": "1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"safe-buffer": {
|
|
||||||
"version": "5.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
|
||||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
|
||||||
},
|
|
||||||
"safer-buffer": {
|
|
||||||
"version": "2.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
|
||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
|
||||||
},
|
|
||||||
"send": {
|
|
||||||
"version": "0.16.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
|
|
||||||
"integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
|
|
||||||
"requires": {
|
|
||||||
"debug": "2.6.9",
|
|
||||||
"depd": "1.1.2",
|
|
||||||
"destroy": "1.0.4",
|
|
||||||
"encodeurl": "1.0.2",
|
|
||||||
"escape-html": "1.0.3",
|
|
||||||
"etag": "1.8.1",
|
|
||||||
"fresh": "0.5.2",
|
|
||||||
"http-errors": "1.6.3",
|
|
||||||
"mime": "1.4.1",
|
|
||||||
"ms": "2.0.0",
|
|
||||||
"on-finished": "2.3.0",
|
|
||||||
"range-parser": "1.2.0",
|
|
||||||
"statuses": "1.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"serve-static": {
|
|
||||||
"version": "1.13.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
|
|
||||||
"integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
|
|
||||||
"requires": {
|
|
||||||
"encodeurl": "1.0.2",
|
|
||||||
"escape-html": "1.0.3",
|
|
||||||
"parseurl": "1.3.2",
|
|
||||||
"send": "0.16.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"setprototypeof": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
|
|
||||||
},
|
|
||||||
"source-map": {
|
|
||||||
"version": "0.6.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
|
||||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
|
||||||
},
|
|
||||||
"statuses": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
|
|
||||||
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
|
|
||||||
},
|
|
||||||
"type-is": {
|
|
||||||
"version": "1.6.16",
|
|
||||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
|
|
||||||
"integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
|
|
||||||
"requires": {
|
|
||||||
"media-typer": "0.3.0",
|
|
||||||
"mime-types": "2.1.21"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"uglify-js": {
|
|
||||||
"version": "3.4.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz",
|
|
||||||
"integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==",
|
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
|
||||||
"commander": "2.17.1",
|
|
||||||
"source-map": "0.6.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"unpipe": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
|
||||||
},
|
|
||||||
"utils-merge": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
|
||||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
|
||||||
},
|
|
||||||
"vary": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
|
||||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
|
||||||
},
|
|
||||||
"wordwrap": {
|
|
||||||
"version": "0.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
|
|
||||||
"integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc="
|
|
||||||
},
|
|
||||||
"wrappy": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
|
||||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
17
package.json
17
package.json
|
@ -1,17 +0,0 @@
|
||||||
{
|
|
||||||
"name": "TSViewer",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"description": "",
|
|
||||||
"private": true,
|
|
||||||
"main": "app.js",
|
|
||||||
"scripts": {
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
|
||||||
"start": "node app.js"
|
|
||||||
},
|
|
||||||
"author": "Marc Kemper",
|
|
||||||
"license": "",
|
|
||||||
"dependencies": {
|
|
||||||
"express": "^4.16.4",
|
|
||||||
"express-handlebars": "^3.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
36
request/meta/meta.go
Normal file
36
request/meta/meta.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package meta
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Meta struct {
|
||||||
|
Pretty bool `json:"pretty"`
|
||||||
|
Envelope bool `json:"envelope"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFromRequest(r *http.Request) *Meta {
|
||||||
|
meta := Meta{
|
||||||
|
Pretty: false,
|
||||||
|
Envelope: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
pretty, ok := r.URL.Query()["pretty"]
|
||||||
|
if ok && len(pretty) > 0 {
|
||||||
|
prettyBool, err := strconv.ParseBool(pretty[0])
|
||||||
|
if err == nil {
|
||||||
|
meta.Pretty = prettyBool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
envelope, ok := r.URL.Query()["envelope"]
|
||||||
|
if ok && len(envelope) > 0 {
|
||||||
|
envelopeBool, err := strconv.ParseBool(envelope[0])
|
||||||
|
if err == nil {
|
||||||
|
meta.Envelope = envelopeBool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &meta
|
||||||
|
}
|
10
request/routes.go
Normal file
10
request/routes.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package request
|
||||||
|
|
||||||
|
const (
|
||||||
|
GET = "GET"
|
||||||
|
PUT = "PUT"
|
||||||
|
POST = "POST"
|
||||||
|
DELETE = "DELETE"
|
||||||
|
LINK = "LINK"
|
||||||
|
UNLINK = "UNLINK"
|
||||||
|
)
|
21
response/error.go
Normal file
21
response/error.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package response
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Error data type
|
||||||
|
type Error struct {
|
||||||
|
Status int `json:"status"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
Timestamp string `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewError creates a new Response fill with an error
|
||||||
|
func NewError(status int, err error) *Response {
|
||||||
|
return &Response{
|
||||||
|
Content: Error{
|
||||||
|
Status: status,
|
||||||
|
Error: err.Error(),
|
||||||
|
Timestamp: time.Now().Format("2006-01-02T15:04:05Z"), //.Format("02 Jan 2006, 15:04:05 MST"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
87
response/response.go
Normal file
87
response/response.go
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
package response
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.cliffbreak.de/haveachin/go-tsviewer/request/meta"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HandlerFunc func() (int, error)
|
||||||
|
|
||||||
|
// Responder is a service for responses
|
||||||
|
type Responder interface {
|
||||||
|
Send(http.ResponseWriter, int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response data type
|
||||||
|
type Response struct {
|
||||||
|
Content interface{}
|
||||||
|
Pretty bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Envelope struct {
|
||||||
|
Data interface{} `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Response
|
||||||
|
func New(content interface{}, r *http.Request) *Response {
|
||||||
|
var resp Response
|
||||||
|
|
||||||
|
meta := meta.NewFromRequest(r)
|
||||||
|
|
||||||
|
if meta.Envelope {
|
||||||
|
resp = Response{
|
||||||
|
Content: Envelope{
|
||||||
|
Data: content,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resp = Response{
|
||||||
|
Content: content,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Pretty = meta.Pretty
|
||||||
|
|
||||||
|
return &resp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send sends the response
|
||||||
|
func (resp Response) Send(w http.ResponseWriter, status int) (int, error) {
|
||||||
|
var (
|
||||||
|
rawJSON []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if resp.Pretty {
|
||||||
|
rawJSON, err = json.MarshalIndent(resp.Content, "", " ")
|
||||||
|
} else {
|
||||||
|
rawJSON, err = json.Marshal(resp.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
} else if string(rawJSON) == "null" {
|
||||||
|
rawJSON = make([]byte, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(status)
|
||||||
|
w.Write(rawJSON)
|
||||||
|
|
||||||
|
return status, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Handler(w http.ResponseWriter, hf HandlerFunc) {
|
||||||
|
status, err := hf()
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("HTTP %d: %q", status, err)
|
||||||
|
if status, err = NewError(status, err).Send(w, status); err != nil {
|
||||||
|
http.Error(w, http.StatusText(status), status)
|
||||||
|
}
|
||||||
|
}
|
64
service/channel.go
Normal file
64
service/channel.go
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"git.cliffbreak.de/haveachin/go-tsviewer/features/channel"
|
||||||
|
"github.com/multiplay/go-ts3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s Service) Channel(id int) (*channel.Channel, error) {
|
||||||
|
channels, err := s.TSClient.Server.ChannelList()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var c *channel.Channel
|
||||||
|
|
||||||
|
for _, channel := range channels {
|
||||||
|
if channel.ID == id {
|
||||||
|
c = convertChannel(channel)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c == nil {
|
||||||
|
return nil, errors.New("channel does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Service) Channels() ([]*channel.Channel, error) {
|
||||||
|
channels, err := s.TSClient.Server.ChannelList()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cc []*channel.Channel
|
||||||
|
|
||||||
|
for _, channel := range channels {
|
||||||
|
if channel.ParentID == 0 {
|
||||||
|
cc = append(cc, convertChannel(channel))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cc {
|
||||||
|
if c.ID == channel.ParentID {
|
||||||
|
c.Subchannels = append(c.Subchannels, *convertChannel(channel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertChannel(c *ts3.Channel) *channel.Channel {
|
||||||
|
return &channel.Channel{
|
||||||
|
ID: c.ID,
|
||||||
|
Subchannels: []channel.Channel{},
|
||||||
|
Name: c.ChannelName,
|
||||||
|
TotalClients: c.TotalClients,
|
||||||
|
NeededSubscribePower: c.NeededSubscribePower,
|
||||||
|
}
|
||||||
|
}
|
60
service/client.go
Normal file
60
service/client.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"git.cliffbreak.de/haveachin/go-tsviewer/features/client"
|
||||||
|
"github.com/multiplay/go-ts3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s Service) Client(id int) (*client.Client, error) {
|
||||||
|
clients, err := s.TSClient.Server.ClientList()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var c *client.Client
|
||||||
|
|
||||||
|
for _, client := range clients {
|
||||||
|
if client.DatabaseID == id {
|
||||||
|
c = convertClient(client)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c == nil {
|
||||||
|
return nil, errors.New("client does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if _, err := s.TSClient.Server.ExecCmd(ts3.NewCmd(fmt.Sprintf("clientinfo clid=%d", c.ID)).WithResponse(&c)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} */
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Service) Clients() ([]*client.Client, error) {
|
||||||
|
clients, err := s.TSClient.Server.ClientList()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cc []*client.Client
|
||||||
|
|
||||||
|
for _, client := range clients {
|
||||||
|
cc = append(cc, convertClient(client))
|
||||||
|
}
|
||||||
|
|
||||||
|
return cc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertClient(c *ts3.OnlineClient) *client.Client {
|
||||||
|
return &client.Client{
|
||||||
|
DatabaseID: c.DatabaseID,
|
||||||
|
ChannelID: c.ID,
|
||||||
|
Nickname: c.Nickname,
|
||||||
|
Type: c.Type,
|
||||||
|
Away: c.Away,
|
||||||
|
AwayMessage: c.AwayMessage,
|
||||||
|
}
|
||||||
|
}
|
29
service/server.go
Normal file
29
service/server.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.cliffbreak.de/haveachin/go-tsviewer/features/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s Service) Info() (*server.Server, error) {
|
||||||
|
serverInfo, err := s.TSClient.Server.Info()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &server.Server{
|
||||||
|
Name: serverInfo.Name,
|
||||||
|
Status: serverInfo.Status,
|
||||||
|
Version: serverInfo.Version,
|
||||||
|
WelcomeMessage: serverInfo.WelcomeMessage,
|
||||||
|
MaxClients: serverInfo.MaxClients,
|
||||||
|
ClientsOnline: serverInfo.ClientsOnline,
|
||||||
|
ReservedSlots: serverInfo.ReservedSlots,
|
||||||
|
Uptime: time.Duration(serverInfo.Uptime) * time.Nanosecond,
|
||||||
|
TotalPing: serverInfo.TotalPing,
|
||||||
|
MinAndroidVersion: serverInfo.MinAndroidVersion,
|
||||||
|
MinClientVersion: serverInfo.MinClientVersion,
|
||||||
|
MiniOSVersion: serverInfo.MiniOSVersion,
|
||||||
|
}, nil
|
||||||
|
}
|
37
service/service.go
Normal file
37
service/service.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.cliffbreak.de/haveachin/go-tsviewer/config"
|
||||||
|
"git.cliffbreak.de/haveachin/go-tsviewer/stringer"
|
||||||
|
ts3 "github.com/multiplay/go-ts3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
TSClient *ts3.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(config config.Config) (*Service, error) {
|
||||||
|
addr, err := stringer.Build(config.IP, ":", strconv.Itoa(int(config.Port)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := ts3.NewClient(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := client.Login(config.User.Name, config.User.Password); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := client.UsePort(int(config.Server.Port)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Service{
|
||||||
|
TSClient: client,
|
||||||
|
}, nil
|
||||||
|
}
|
49
stringer/stringer.go
Normal file
49
stringer/stringer.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package stringer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Build(ss ...string) (string, error) {
|
||||||
|
var builder strings.Builder
|
||||||
|
|
||||||
|
for _, s := range ss {
|
||||||
|
if _, err := builder.WriteString(s); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToCamel(str string) string {
|
||||||
|
if len(str) < 1 {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range str[1:] {
|
||||||
|
if c >= 'A' && c <= 'Z' {
|
||||||
|
str = fmt.Sprintf("%s_%c%s", str[:i+1], unicode.ToLower(c), str[i+2:])
|
||||||
|
return ToCamel(str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToSnake(str string) string {
|
||||||
|
if len(str) < 1 {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range str[1:] {
|
||||||
|
if c == '_' {
|
||||||
|
str = fmt.Sprintf("%s%c%s", str[:i+1], unicode.ToUpper(rune(str[i+2])), str[i+3:])
|
||||||
|
return ToSnake(str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
<h1>TSViewer: Home</h1>
|
|
|
@ -1,12 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>TSViewer</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
Test 123
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Loading…
Reference in a new issue